uefi, vm: 2 MB pages where the CPU has no 1 GB ones
Every page table SchOS built -- the loader's, which the kernel starts on and the other CPUs start through, and the kernel's own direct map and device window -- mapped memory in 1 GB pages. Intel's desktop CPUs before Haswell do not have them, and there the PS bit in a PDPT entry is reserved: the first access after the switch is a page fault that cannot be handled either. An HP all-in-one with an Ivy Bridge i3 showed "Placing and starting the kernel" and reset. Both now check CPUID 0x80000001 EDX bit 26. Without it the loader maps the low 4 GB through four page directories of 2 MB pages, placed below the kernel where nothing reuses them, and keeps the staged kernel segments below 4 GB; the kernel gives each gigabyte of its direct map and of ioremap() a page directory of its own. Neither KVM nor QEMU's emulator enforces the reserved bit, so the fault itself could not be reproduced here. With the fallback forced on, the kernel boots at 2 and 8 GB, both CPUs start, and evtest passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
8c426b2077
commit
132a2981b1
2 changed files with 114 additions and 9 deletions
|
|
@ -45,6 +45,49 @@ static int ioremap_next = 4;
|
|||
|
||||
static pdpe_t *kpdpt;
|
||||
|
||||
// Whether a PDPT entry can map a gigabyte (CPUID 0x80000001, EDX bit 26).
|
||||
// Intel's desktop CPUs before Haswell cannot: the bit is reserved there, and
|
||||
// a 1 GB entry is a page fault on first use. The direct map and the device
|
||||
// window then use a page directory of 2 MB pages per gigabyte instead -- the
|
||||
// same reach, one page of table per gigabyte. See gb_pages() in the loader,
|
||||
// which has the same fallback for the tables this kernel starts on.
|
||||
static int gbpages;
|
||||
|
||||
static int
|
||||
cpu_gb_pages(void)
|
||||
{
|
||||
uint32 max, d;
|
||||
|
||||
cpuid_leaf(0x80000000, &max, 0, 0, 0);
|
||||
if (max < 0x80000001)
|
||||
return 0;
|
||||
cpuid_leaf(0x80000001, 0, 0, 0, &d);
|
||||
return (d >> 26) & 1;
|
||||
}
|
||||
|
||||
// One gigabyte at `pa` with `flags`, in kpdpt[slot]: a single entry if the
|
||||
// CPU allows, otherwise a page directory of 2 MB pages. 0 if that page
|
||||
// directory could not be had.
|
||||
static int
|
||||
map_gigabyte(int slot, uint64 pa, uint64 flags)
|
||||
{
|
||||
if (gbpages) {
|
||||
kpdpt[slot] = pa | flags | PTE_PS;
|
||||
return 1;
|
||||
}
|
||||
pde_t *pd = (pde_t *)kalloc();
|
||||
if (pd == 0)
|
||||
return 0;
|
||||
for (int i = 0; i < 512; i++)
|
||||
pd[i] = (pa + ((uint64)i << 21)) | flags | PTE_PS;
|
||||
kpdpt[slot] = v2p(pd) | PTE_P | PTE_W;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// What each device-window slot maps, since kpdpt[] holds a page directory
|
||||
// rather than the frame when there are no 1 GB pages.
|
||||
static uint64 window_base[512];
|
||||
|
||||
struct tassdesc {
|
||||
uint lim_15_0 : 16;
|
||||
uint base_15_0 : 16;
|
||||
|
|
@ -258,12 +301,17 @@ kvmalloc(void)
|
|||
if (gigs > IOREMAP_LAST_PDPT - 8)
|
||||
gigs = IOREMAP_LAST_PDPT - 8; // leave room for device windows
|
||||
|
||||
gbpages = cpu_gb_pages();
|
||||
if (!gbpages)
|
||||
kinfo("vm: no 1 GB pages; the direct map uses 2 MB ones\n");
|
||||
|
||||
for(uint64 i = 0; i < gigs; i++) {
|
||||
uint64 flags = PTE_PS | PTE_P | PTE_W;
|
||||
uint64 flags = PTE_P | PTE_W;
|
||||
if(i == 3) {
|
||||
flags |= PTE_PCD | PTE_PWT;
|
||||
}
|
||||
kpdpt[i] = (i << 30) | flags;
|
||||
if (!map_gigabyte((int)i, i << 30, flags))
|
||||
panic("kvmalloc: no memory for the direct map");
|
||||
}
|
||||
|
||||
// ioremap takes what is left. It used to start at a constant 4, which was
|
||||
|
|
@ -312,8 +360,7 @@ ioremap(uint64 pa, uint64 size)
|
|||
// Already mapped? A second BAR in the same gigabyte should not consume a
|
||||
// second window, and drivers do re-map the same device.
|
||||
for (int i = 4; i < ioremap_next; i++) {
|
||||
// Clearing the low 30 bits drops every flag as well, leaving the frame.
|
||||
if ((kpdpt[i] & PTE_P) && (kpdpt[i] & ~(GIGABYTE - 1)) == base)
|
||||
if ((kpdpt[i] & PTE_P) && window_base[i] == base)
|
||||
return (void *)(KERNBASE + ((uint64)i << 30) + (pa - base));
|
||||
}
|
||||
|
||||
|
|
@ -324,8 +371,12 @@ ioremap(uint64 pa, uint64 size)
|
|||
|
||||
int first = ioremap_next;
|
||||
for (int i = 0; i < span; i++) {
|
||||
kpdpt[ioremap_next] = (base + (uint64)i * GIGABYTE)
|
||||
| PTE_PS | PTE_P | PTE_W | PTE_PCD | PTE_PWT;
|
||||
if (!map_gigabyte(ioremap_next, base + (uint64)i * GIGABYTE,
|
||||
PTE_P | PTE_W | PTE_PCD | PTE_PWT)) {
|
||||
cprintf("ioremap: no memory to map %p\n", (void *)pa);
|
||||
return 0;
|
||||
}
|
||||
window_base[ioremap_next] = base + (uint64)i * GIGABYTE;
|
||||
ioremap_next++;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue