const std = @import("std"); const easyboot = @cImport(@cInclude("easyboot.h")); const mmap = @import("../../mmap.zig"); const pmm = @import("../../pmm.zig"); const USER_ADDRESS_RANGE_END = 0x0000_7fff_ffff_ffff; const PHYSICAL_MAPPING_BASE = 0xffff_8000_0000_0000; const HUGE_PAGE_SIZE = 0x200000; // 2 MiB pub const PageTableEntry = packed struct { present: u1, read_write: u1, user: u1, write_through: u1, cache_disabled: u1, accessed: u1, ignore0: u1, larger_pages: u1, global: u1, available: u3, address: u48, available2: u3, no_execute: u1, pub fn set_address(self: *PageTableEntry, address: u64) void { self.address = @intCast(address >> 12); } pub fn get_address(self: *PageTableEntry) u64 { return self.address << 12; } pub fn clear(self: *PageTableEntry) void { self = std.mem.zeroes(@TypeOf(self)); } }; pub const PageDirectory = struct { entries: [512]PageTableEntry, }; const Flags = enum(u32) { None = 0, ReadWrite = 1, User = 2, NoExecute = 4, WriteThrough = 8, CacheDisable = 16, Global = 32, }; const PageTableIndexes = struct { level4: u24, level3: u24, level2: u24, level1: u24, }; fn calculatePageTableIndexes(address: usize) PageTableIndexes { return .{ .level4 = @intCast((address >> 39) & 0o777), .level3 = @intCast((address >> 30) & 0o777), .level2 = @intCast((address >> 21) & 0o777), .level1 = @intCast((address >> 12) & 0o777) }; } fn hasFlag(flags: u32, flag: Flags) u1 { return switch ((flags & @intFromEnum(flag)) > 0) { true => 1, false => 0, }; } fn updatePageTableEntry(entry: *PageTableEntry, phys: pmm.PhysFrame, flags: u32) void { entry.present = 1; entry.read_write = hasFlag(flags, Flags.ReadWrite); entry.user = hasFlag(flags, Flags.User); entry.write_through = hasFlag(flags, Flags.WriteThrough); entry.cache_disabled = hasFlag(flags, Flags.CacheDisable); entry.no_execute = hasFlag(flags, Flags.NoExecute); entry.global = hasFlag(flags, Flags.Global); entry.set_address(phys.address); } fn setUpParentPageTableEntry(allocator: *pmm.FrameAllocator, pte: *PageTableEntry, flags: u32, base: usize) !void { if (pte.present == 0) { const frame = try pmm.allocFrame(allocator); pte.present = 1; pte.set_address(frame.address); getTable(pte, base).* = std.mem.zeroes(PageDirectory); } if (hasFlag(flags, Flags.ReadWrite) == 1) pte.read_write = 1; if (hasFlag(flags, Flags.User) == 1) pte.user = 1; } fn getTable(pte: *PageTableEntry, base: usize) *allowzero PageDirectory { const frame = pmm.PhysFrame{ .address = pte.get_address() }; return @ptrFromInt(frame.virtualAddress(base)); } pub fn map(allocator: *pmm.FrameAllocator, directory: *PageDirectory, base: usize, virt_address: u64, phys: pmm.PhysFrame, flags: u32, use_huge_pages: bool) !void { const indexes = calculatePageTableIndexes(virt_address); const l4 = &directory.entries[indexes.level4]; try setUpParentPageTableEntry(allocator, l4, flags, base); const l3 = &getTable(l4, base).entries[indexes.level3]; if (l3.larger_pages == 1) return error.MemoryAlreadyInUse; try setUpParentPageTableEntry(allocator, l3, flags, base); const l2 = &getTable(l3, base).entries[indexes.level2]; if (l2.larger_pages == 1) return error.MemoryAlreadyInUse; if (use_huge_pages) { l2.larger_pages = 1; updatePageTableEntry(l2, phys, flags); return; } try setUpParentPageTableEntry(allocator, l2, flags, base); const l1 = &getTable(l2, base).entries[indexes.level1]; if (l1.present == 1) return error.MemoryAlreadyInUse; updatePageTableEntry(l1, phys, flags); } fn mapPhysicalMemory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, directory: *PageDirectory, base: usize, flags: u32) !void { const address_space_size = mmap.getAddressSpaceSize(tag) orelse return error.InvalidMemoryMap; const address_space_pages = address_space_size / HUGE_PAGE_SIZE; var index: usize = 0; while (index < address_space_pages) : (index += 1) { try map(allocator, directory, 0, base + index * HUGE_PAGE_SIZE, pmm.PhysFrame{ .address = index * HUGE_PAGE_SIZE }, flags, true); } } fn lockPageDirectoryFrames(allocator: *pmm.FrameAllocator, directory: *PageDirectory, index: u8) !void { if (index > 1) { var i: u64 = 0; while (i < 512) : (i += 1) { const pte = &directory.entries[i]; if (pte.present == 0) continue; if ((index < 4) and (pte.larger_pages == 1)) continue; try pmm.lockFrame(allocator, pte.get_address()); const child_table: *PageDirectory = @ptrFromInt(pte.get_address()); try lockPageDirectoryFrames(allocator, child_table, index - 1); } } } fn lockPageDirectory(allocator: *pmm.FrameAllocator, directory: *PageDirectory) !void { try pmm.lockFrame(allocator, @intFromPtr(directory)); try lockPageDirectoryFrames(allocator, directory, 4); } fn setUpKernelPageDirectory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t) !*PageDirectory { const directory = readPageDirectory(); try lockPageDirectory(allocator, directory); try mapPhysicalMemory(allocator, tag, directory, PHYSICAL_MAPPING_BASE, @intFromEnum(Flags.ReadWrite) | @intFromEnum(Flags.NoExecute) | @intFromEnum(Flags.Global)); return directory; } fn setUpInitialUserPageDirectory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, kernel_directory: *PageDirectory, user_directory: *PageDirectory) !usize { const physical_address_space_size = mmap.getAddressSpaceSize(tag) orelse return error.InvalidMemoryMap; user_directory.* = std.mem.zeroes(PageDirectory); const directory_upper_half: *[256]PageTableEntry = kernel_directory.entries[256..]; const user_directory_upper_half: *[256]PageTableEntry = user_directory.entries[256..]; @memcpy(user_directory_upper_half, directory_upper_half); const user_physical_address_base = (USER_ADDRESS_RANGE_END + 1) - physical_address_space_size; try mapPhysicalMemory(allocator, tag, user_directory, user_physical_address_base, @intFromEnum(Flags.ReadWrite) | @intFromEnum(Flags.NoExecute) | @intFromEnum(Flags.User)); return user_physical_address_base; } pub fn createInitialMappings(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, user_directory: *PageDirectory) !usize { const directory = try setUpKernelPageDirectory(allocator, tag); const base = try setUpInitialUserPageDirectory(allocator, tag, directory, user_directory); setPageDirectory(directory); allocator.bitmap.location = @ptrFromInt(@as(usize, PHYSICAL_MAPPING_BASE) + @intFromPtr(allocator.bitmap.location)); return base; } pub fn readPageDirectory() *PageDirectory { var directory: *PageDirectory = undefined; asm volatile ("mov %%cr3, %[dir]" : [dir] "=r" (directory), ); return directory; } pub fn setPageDirectory(directory: *PageDirectory) void { asm volatile ("mov %[dir], %%cr3" : : [dir] "{rdi}" (directory), ); }