Changeset View
Standalone View
sys/arm64/arm64/pmap.c
Show First 20 Lines • Show All 115 Lines • ▼ Show 20 Lines | |||||
#include <sys/limits.h> | #include <sys/limits.h> | ||||
#include <sys/lock.h> | #include <sys/lock.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/mman.h> | #include <sys/mman.h> | ||||
#include <sys/msgbuf.h> | #include <sys/msgbuf.h> | ||||
#include <sys/mutex.h> | #include <sys/mutex.h> | ||||
#include <sys/proc.h> | #include <sys/proc.h> | ||||
#include <sys/rwlock.h> | #include <sys/rwlock.h> | ||||
#include <sys/sbuf.h> | |||||
#include <sys/sx.h> | #include <sys/sx.h> | ||||
#include <sys/vmem.h> | #include <sys/vmem.h> | ||||
#include <sys/vmmeter.h> | #include <sys/vmmeter.h> | ||||
#include <sys/sched.h> | #include <sys/sched.h> | ||||
#include <sys/sysctl.h> | #include <sys/sysctl.h> | ||||
#include <sys/_unrhdr.h> | #include <sys/_unrhdr.h> | ||||
#include <sys/smp.h> | #include <sys/smp.h> | ||||
▲ Show 20 Lines • Show All 6,018 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
boolean_t | boolean_t | ||||
pmap_is_valid_memattr(pmap_t pmap __unused, vm_memattr_t mode) | pmap_is_valid_memattr(pmap_t pmap __unused, vm_memattr_t mode) | ||||
{ | { | ||||
return (mode >= VM_MEMATTR_DEVICE && mode <= VM_MEMATTR_WRITE_THROUGH); | return (mode >= VM_MEMATTR_DEVICE && mode <= VM_MEMATTR_WRITE_THROUGH); | ||||
} | } | ||||
/* | |||||
* Track a range of the kernel's virtual address space that is contiguous | |||||
* in various mapping attributes. | |||||
*/ | |||||
struct pmap_kernel_map_range { | |||||
vm_offset_t sva; | |||||
pt_entry_t attrs; | |||||
int l3pages; | |||||
int l3contig; | |||||
int l2blocks; | |||||
int l1blocks; | |||||
}; | |||||
static void | |||||
sysctl_kmaps_dump(struct sbuf *sb, struct pmap_kernel_map_range *range, | |||||
vm_offset_t eva) | |||||
{ | |||||
const char *mode; | |||||
int index; | |||||
if (eva <= range->sva) | |||||
return; | |||||
index = range->attrs & ATTR_IDX_MASK; | |||||
switch (index) { | |||||
andrew: Could you use `ATTR_IDX(VM_MEMATTR_DEVICE)` here? It would make it easier to find them later if… | |||||
case ATTR_IDX(VM_MEMATTR_DEVICE): | |||||
mode = "DEV"; | |||||
break; | |||||
case ATTR_IDX(VM_MEMATTR_UNCACHEABLE): | |||||
mode = "UC"; | |||||
break; | |||||
case ATTR_IDX(VM_MEMATTR_WRITE_BACK): | |||||
mode = "WB"; | |||||
break; | |||||
case ATTR_IDX(VM_MEMATTR_WRITE_THROUGH): | |||||
mode = "WT"; | |||||
break; | |||||
default: | |||||
printf( | |||||
"%s: unknown memory type %x for range 0x%016lx-0x%016lx\n", | |||||
__func__, index, range->sva, eva); | |||||
mode = "??"; | |||||
break; | |||||
} | |||||
Not Done Inline ActionsPlease consider adding another column to represent l3pages with ATTR_CONTIGUOUS set. I think that you can simply do the following: If range->attrs has ATTR_CONTIGUOUS set, then (1) divide range->l3pages by 16 and display the quotient in this new column and (2) display zero in the current l3pages column. alc: Please consider adding another column to represent l3pages with ATTR_CONTIGUOUS set. I think… | |||||
Done Inline ActionsIs there any reason to limit ourselves to L3 pages? From what I can see ATTR_CONTIGUOUS can also be applied to runs of block descriptors. Then, rather than adding an extra column or three, we might format the results as: <# L1 blocks>(<# contiguous ranges>) <# L2 blocks>(<# contiguous ranges>) <# L3 pages>(<# contiguous ranges>) or something like that. markj: Is there any reason to limit ourselves to L3 pages? From what I can see ATTR_CONTIGUOUS can… | |||||
Not Done Inline ActionsIn principle, no. You are correct that the architectural specification defines the contiguous bit on L1 and L2 block descriptors when using a 4KB base page size. So, what you propose seems fine. That said, I can't see how any of the cores that we currently have access to will do anything with a contiguous bit setting on L1 and L2 block descriptors. Cortex-A75's second-level TLB supports 32 MB mappings, and so it could hypothetically do something for contiguous L2 block descriptors. alc: In principle, no. You are correct that the architectural specification defines the contiguous… | |||||
Done Inline ActionsOk. We can simply add new columns later if need be. markj: Ok. We can simply add new columns later if need be. | |||||
Done Inline ActionsI implemented this using a different algorithm than what you suggested. In particular, with my change, ATTR_CONTIGUOUS is not recorded as an attribute of the range. This is so that we do not emit multiple lines for a range of addresses that is only partially backed by contiguous blocks. For instance, if we have a 4MB range consisting of one L2 block followed by 512/16 contiguous L3 blocks, I would like the whole range to be printed as a single line. markj: I implemented this using a different algorithm than what you suggested. In particular, with my… | |||||
sbuf_printf(sb, "0x%016lx-0x%016lx r%c%c%c %3s %d %d %d %d\n", | |||||
range->sva, eva, | |||||
(range->attrs & ATTR_AP_RW_BIT) == ATTR_AP_RW ? 'w' : '-', | |||||
(range->attrs & ATTR_PXN) != 0 ? '-' : 'x', | |||||
(range->attrs & ATTR_AP_USER) != 0 ? 'u' : 's', | |||||
mode, range->l1blocks, range->l2blocks, range->l3contig, | |||||
range->l3pages); | |||||
/* Reset to sentinel value. */ | |||||
range->sva = 0xfffffffffffffffful; | |||||
} | |||||
/* | |||||
* Determine whether the attributes specified by a page table entry match those | |||||
* being tracked by the current range. | |||||
*/ | |||||
static bool | |||||
sysctl_kmaps_match(struct pmap_kernel_map_range *range, pt_entry_t attrs) | |||||
{ | |||||
return (range->attrs == attrs); | |||||
} | |||||
static void | |||||
sysctl_kmaps_reinit(struct pmap_kernel_map_range *range, vm_offset_t va, | |||||
pt_entry_t attrs) | |||||
{ | |||||
memset(range, 0, sizeof(*range)); | |||||
range->sva = va; | |||||
range->attrs = attrs; | |||||
} | |||||
/* | |||||
* Given a leaf PTE, derive the mapping's attributes. If they do not match | |||||
* those of the current run, dump the address range and its attributes, and | |||||
* begin a new run. | |||||
*/ | |||||
static void | |||||
sysctl_kmaps_check(struct sbuf *sb, struct pmap_kernel_map_range *range, | |||||
vm_offset_t va, pd_entry_t l0e, pd_entry_t l1e, pd_entry_t l2e, | |||||
pt_entry_t l3e) | |||||
{ | |||||
pt_entry_t attrs; | |||||
attrs = l0e & (ATTR_AP_MASK | ATTR_XN); | |||||
attrs |= l1e & (ATTR_AP_MASK | ATTR_XN); | |||||
if ((l1e & ATTR_DESCR_MASK) == L1_BLOCK) | |||||
attrs |= l1e & ATTR_IDX_MASK; | |||||
attrs |= l2e & (ATTR_AP_MASK | ATTR_XN); | |||||
if ((l2e & ATTR_DESCR_MASK) == L2_BLOCK) | |||||
Not Done Inline ActionsI suspect that you'll need to include ATTR_CONTIGUOUS here. alc: I suspect that you'll need to include ATTR_CONTIGUOUS here. | |||||
attrs |= l2e & ATTR_IDX_MASK; | |||||
attrs |= l3e & (ATTR_AP_MASK | ATTR_XN | ATTR_IDX_MASK); | |||||
if (range->sva > va || !sysctl_kmaps_match(range, attrs)) { | |||||
sysctl_kmaps_dump(sb, range, va); | |||||
sysctl_kmaps_reinit(range, va, attrs); | |||||
} | |||||
} | |||||
static int | |||||
sysctl_kmaps(SYSCTL_HANDLER_ARGS) | |||||
{ | |||||
struct pmap_kernel_map_range range; | |||||
struct sbuf sbuf, *sb; | |||||
pd_entry_t l0e, *l1, l1e, *l2, l2e; | |||||
pt_entry_t *l3, l3e; | |||||
vm_offset_t sva; | |||||
vm_paddr_t pa; | |||||
int error, i, j, k, l; | |||||
error = sysctl_wire_old_buffer(req, 0); | |||||
if (error != 0) | |||||
return (error); | |||||
sb = &sbuf; | |||||
sbuf_new_for_sysctl(sb, NULL, PAGE_SIZE, req); | |||||
/* Sentinel value. */ | |||||
range.sva = 0xfffffffffffffffful; | |||||
/* | |||||
* Iterate over the kernel page tables without holding the kernel pmap | |||||
* lock. Kernel page table pages are never freed, so at worst we will | |||||
* observe inconsistencies in the output. | |||||
Not Done Inline ActionsIsn't this referring to an amd64 feature, i.e., the "large map", that we don't have on arm64? alc: Isn't this referring to an amd64 feature, i.e., the "large map", that we don't have on arm64? | |||||
Not Done Inline ActionsWhy sva = 0? andrew: Why `sva = 0`? | |||||
*/ | |||||
for (sva = 0xffff000000000000ul, i = pmap_l0_index(sva); i < Ln_ENTRIES; | |||||
i++) { | |||||
if (i == pmap_l0_index(DMAP_MIN_ADDRESS)) | |||||
sbuf_printf(sb, "\nDirect map:\n"); | |||||
else if (i == pmap_l0_index(VM_MIN_KERNEL_ADDRESS)) | |||||
sbuf_printf(sb, "\nKernel map:\n"); | |||||
l0e = kernel_pmap->pm_l0[i]; | |||||
if ((l0e & ATTR_DESCR_VALID) == 0) { | |||||
sysctl_kmaps_dump(sb, &range, sva); | |||||
sva += L0_SIZE; | |||||
continue; | |||||
} | |||||
pa = l0e & ~ATTR_MASK; | |||||
l1 = (pd_entry_t *)PHYS_TO_DMAP(pa); | |||||
for (j = pmap_l1_index(sva); j < Ln_ENTRIES; j++) { | |||||
l1e = l1[j]; | |||||
if ((l1e & ATTR_DESCR_VALID) == 0) { | |||||
sysctl_kmaps_dump(sb, &range, sva); | |||||
sva += L1_SIZE; | |||||
continue; | |||||
Done Inline ActionsI think that this can move down, past the subsequent "if" statement. alc: I think that this can move down, past the subsequent "if" statement. | |||||
} | |||||
if ((l1e & ATTR_DESCR_MASK) == L1_BLOCK) { | |||||
sysctl_kmaps_check(sb, &range, sva, l0e, l1e, | |||||
0, 0); | |||||
range.l1blocks++; | |||||
sva += L1_SIZE; | |||||
continue; | |||||
} | |||||
pa = l1e & ~ATTR_MASK; | |||||
l2 = (pd_entry_t *)PHYS_TO_DMAP(pa); | |||||
for (k = pmap_l2_index(sva); k < Ln_ENTRIES; k++) { | |||||
l2e = l2[k]; | |||||
if ((l2e & ATTR_DESCR_VALID) == 0) { | |||||
sysctl_kmaps_dump(sb, &range, sva); | |||||
sva += L2_SIZE; | |||||
continue; | |||||
Done Inline ActionsI think that this can move down, past the subsequent "if" statement. alc: I think that this can move down, past the subsequent "if" statement. | |||||
} | |||||
if ((l2e & ATTR_DESCR_MASK) == L2_BLOCK) { | |||||
sysctl_kmaps_check(sb, &range, sva, | |||||
l0e, l1e, l2e, 0); | |||||
range.l2blocks++; | |||||
sva += L2_SIZE; | |||||
continue; | |||||
} | |||||
pa = l2e & ~ATTR_MASK; | |||||
l3 = (pt_entry_t *)PHYS_TO_DMAP(pa); | |||||
for (l = pmap_l3_index(sva); l < Ln_ENTRIES; | |||||
l++, sva += L3_SIZE) { | |||||
l3e = l3[l]; | |||||
if ((l3e & ATTR_DESCR_VALID) == 0) { | |||||
sysctl_kmaps_dump(sb, &range, | |||||
sva); | |||||
continue; | |||||
} | |||||
sysctl_kmaps_check(sb, &range, sva, | |||||
l0e, l1e, l2e, l3e); | |||||
if ((l3e & ATTR_CONTIGUOUS) != 0) | |||||
range.l3contig += l % 16 == 0 ? | |||||
1 : 0; | |||||
else | |||||
range.l3pages++; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
error = sbuf_finish(sb); | |||||
sbuf_delete(sb); | |||||
return (error); | |||||
} | |||||
SYSCTL_OID(_vm_pmap, OID_AUTO, kernel_maps, | |||||
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, | |||||
NULL, 0, sysctl_kmaps, "A", | |||||
"Dump kernel address layout"); |
Could you use ATTR_IDX(VM_MEMATTR_DEVICE) here? It would make it easier to find them later if we add a new memory type.