I implemented my own MMU code in both “efi” and “riscv” platforms, there are no machine mode and page table setup code in kernel anymore. There are some tricky things such as arch_enter_kernel()
function code should be identity mapped, otherwise it will crash after setting SATP register. Also boot loader maps MMIO registers of some devices such as UART, PLIC, CLINT.
Page table initialization code looks very nice:
static void
SetupPageTable()
{
sPageTable = AllocPhysPage();
PreallocKernelRange();
// Physical memory mapping
gKernelArgs.arch_args.physMap.size
= gKernelArgs.physical_memory_range[0].size;
gKernelArgs.arch_args.physMap.start = KERNEL_TOP + 1
- gKernelArgs.arch_args.physMap.size;
MapRange(gKernelArgs.arch_args.physMap.start,
gKernelArgs.physical_memory_range[0].start,
gKernelArgs.arch_args.physMap.size,
(1 << pteRead) | (1 << pteWrite));
// Boot loader
MapRangeIdentity((addr_t)gMemBase, &gStackEnd - gMemBase,
(1 << pteRead) | (1 << pteWrite) | (1 << pteExec));
// Memory regions
MemoryRegion* region;
for (region = sRegions; region != NULL; region = region->next) {
uint64 flags = 0;
if ((region->protection & B_READ_AREA) != 0)
flags |= (1 << pteRead);
if ((region->protection & B_WRITE_AREA) != 0)
flags |= (1 << pteWrite);
if ((region->protection & B_EXECUTE_AREA) != 0)
flags |= (1 << pteExec);
MapRange(region->virtAdr, region->physAdr, region->size, flags);
}
// Devices
MapAddrRange(gKernelArgs.arch_args.clint, (1 << pteRead) | (1 << pteWrite));
MapAddrRange(gKernelArgs.arch_args.htif, (1 << pteRead) | (1 << pteWrite));
MapAddrRange(gKernelArgs.arch_args.plic, (1 << pteRead) | (1 << pteWrite));
if (gKernelArgs.arch_args.uart.kind != kUartKindNone) {
MapAddrRange(gKernelArgs.arch_args.uart.regs,
(1 << pteRead) | (1 << pteWrite));
}
}
EFI version:
uint64
arch_mmu_generate_post_efi_page_tables(size_t memory_map_size,
efi_memory_descriptor *memory_map, size_t descriptor_size,
uint32_t descriptor_version)
{
sPageTable = mmu_allocate_page();
memset(VirtFromPhys(sPageTable), 0, B_PAGE_SIZE);
PreallocKernelRange();
gKernelArgs.arch_args.num_virtual_ranges_to_keep = 0;
FillPhysicalMemoryMap(memory_map_size, memory_map, descriptor_size, descriptor_version);
addr_range physMemRange;
GetPhysMemRange(physMemRange);
printf("physMemRange: 0x%" B_PRIxADDR ", 0x%" B_PRIxSIZE "\n", physMemRange.start, physMemRange.size);
// Physical memory mapping
gKernelArgs.arch_args.physMap.start = KERNEL_TOP + 1 - physMemRange.size;
gKernelArgs.arch_args.physMap.size = physMemRange.size;
MapRange(gKernelArgs.arch_args.physMap.start, physMemRange.start, physMemRange.size, (1 << pteRead) | (1 << pteWrite));
// Devices
MapAddrRange(gKernelArgs.arch_args.clint, (1 << pteRead) | (1 << pteWrite));
MapAddrRange(gKernelArgs.arch_args.htif, (1 << pteRead) | (1 << pteWrite));
MapAddrRange(gKernelArgs.arch_args.plic, (1 << pteRead) | (1 << pteWrite));
if (gKernelArgs.arch_args.uart.kind != kUartKindNone) {
MapAddrRange(gKernelArgs.arch_args.uart.regs,
(1 << pteRead) | (1 << pteWrite));
}
for (size_t i = 0; i < memory_map_size / descriptor_size; ++i) {
efi_memory_descriptor* entry = &memory_map[i];
switch (entry->Type) {
case EfiLoaderCode:
case EfiLoaderData:
MapRange(entry->VirtualStart, entry->PhysicalStart, entry->NumberOfPages * B_PAGE_SIZE, (1 << pteRead) | (1 << pteWrite) | (1 << pteExec));
break;
default:
;
}
}
return GetSatp();
}