const std = @import("std"); const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; const builtin = @import("builtin"); const is_test = builtin.is_test; const panic = @import("../../panic.zig").panic; const build_options = @import("build_options"); const mock_path = build_options.arch_mock_path; const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig"); const isr = @import("isr.zig"); const MemProfile = @import("../../mem.zig").MemProfile; const tty = @import("../../tty.zig"); const mem = @import("../../mem.zig"); const vmm = @import("../../vmm.zig"); const multiboot = @import("multiboot.zig"); const Allocator = std.mem.Allocator; /// An array of directory entries and page tables. Forms the first level of paging and covers the entire 4GB memory space. pub const Directory = packed struct { /// The directory entries. entries: [ENTRIES_PER_DIRECTORY]DirectoryEntry, /// The tables allocated for the directory. This is ignored by the CPU. tables: [ENTRIES_PER_DIRECTORY]?*Table, }; /// An array of table entries. Forms the second level of paging and covers a 4MB memory space. const Table = packed struct { /// The table entries. entries: [ENTRIES_PER_TABLE]TableEntry, }; /// An entry within a directory. References a single page table. /// Bit 0: Present. Set if present in physical memory. /// When not set, all remaining 31 bits are ignored and available for use. /// Bit 1: Writable. Set if writable. /// Bit 2: User. Set if accessible by user mode. /// Bit 3: Write through. Set if write-through caching is enabled. /// Bit 4: Cache disabled. Set if caching is disabled for this table. /// Bit 5: Accessed. Set by the CPU when the table is accessed. Not cleared by CPU. /// Bit 6: Zero. /// Bit 7: Page size. Set if this entry covers a single 4MB page rather than 1024 4KB pages. /// Bit 8: Ignored. /// Bits 9-11: Ignored and available for use by kernel. /// Bits 12-31: The 4KB aligned physical address of the corresponding page table. /// Must be 4MB aligned if the page size bit is set. const DirectoryEntry = u32; /// An entry within a page table. References a single page. /// Bit 0: Present. Set if present in physical memory. /// When not set, all remaining 31 bits are ignored and available for use. /// Bit 1: Writable. Set if writable. /// Bit 2: User. Set if accessible by user mode. /// Bit 3: Write through. Set if write-through caching is enabled. /// Bit 4: Cache disabled. Set if caching is disabled for this page. /// Bit 5: Accessed. Set by the CPU when the page is accessed. Not cleared by CPU. /// Bit 6: Dirty. Set by the CPU when the page has been written to. Not cleared by the CPU. /// Bit 7: Zero. /// Bit 8: Global. Set if the cached address for this page shouldn't be updated when cr3 is changed. /// Bits 9-11: Ignored and available for use by the kernel. /// Bits 12-31: The 4KB aligned physical address mapped to this page. const TableEntry = u32; /// Each directory has 1024 entries const ENTRIES_PER_DIRECTORY: u32 = 1024; /// Each table has 1024 entries const ENTRIES_PER_TABLE: u32 = 1024; /// There are 1024 entries per directory with each one covering 4KB const PAGES_PER_DIR_ENTRY: u32 = 1024; /// There are 1 million pages per directory const PAGES_PER_DIR: u32 = ENTRIES_PER_DIRECTORY * PAGES_PER_DIR_ENTRY; /// The bitmasks for the bits in a DirectoryEntry const DENTRY_PRESENT: u32 = 0x1; const DENTRY_WRITABLE: u32 = 0x2; const DENTRY_USER: u32 = 0x4; const DENTRY_WRITE_THROUGH: u32 = 0x8; const DENTRY_CACHE_DISABLED: u32 = 0x10; const DENTRY_ACCESSED: u32 = 0x20; const DENTRY_ZERO: u32 = 0x40; const DENTRY_4MB_PAGES: u32 = 0x80; const DENTRY_IGNORED: u32 = 0x100; const DENTRY_AVAILABLE: u32 = 0xE00; const DENTRY_PAGE_ADDR: u32 = 0xFFFFF000; /// The bitmasks for the bits in a TableEntry const TENTRY_PRESENT: u32 = 0x1; const TENTRY_WRITABLE: u32 = 0x2; const TENTRY_USER: u32 = 0x4; const TENTRY_WRITE_THROUGH: u32 = 0x8; const TENTRY_CACHE_DISABLED: u32 = 0x10; const TENTRY_ACCESSED: u32 = 0x20; const TENTRY_DIRTY: u32 = 0x40; const TENTRY_ZERO: u32 = 0x80; const TENTRY_GLOBAL: u32 = 0x100; const TENTRY_AVAILABLE: u32 = 0xE00; const TENTRY_PAGE_ADDR: u32 = 0xFFFFF000; /// The number of bytes in 4MB pub const PAGE_SIZE_4MB: usize = 0x400000; /// The number of bytes in 4KB pub const PAGE_SIZE_4KB: usize = PAGE_SIZE_4MB / 1024; /// The kernel's page directory. Should only be used to map kernel-owned code and data pub var kernel_directory: Directory align(@truncate(u29, PAGE_SIZE_4KB)) = Directory{ .entries = [_]DirectoryEntry{0} ** ENTRIES_PER_DIRECTORY, .tables = [_]?*Table{null} ** ENTRIES_PER_DIRECTORY }; /// /// Convert a virtual address to an index within an array of directory entries. /// /// Arguments: /// IN virt: usize - The virtual address to convert. /// /// Return: usize /// The index into an array of directory entries. /// inline fn virtToDirEntryIdx(virt: usize) usize { return virt / PAGE_SIZE_4MB; } /// /// Convert a virtual address to an index within an array of table entries. /// /// Arguments: /// IN virt: usize - The virtual address to convert. /// /// Return: usize /// The index into an array of table entries. /// inline fn virtToTableEntryIdx(virt: usize) usize { return (virt / PAGE_SIZE_4KB) % ENTRIES_PER_TABLE; } /// /// Set the bit(s) associated with an attribute of a table or directory entry. /// /// Arguments: /// val: *align(1) u32 - The entry to modify /// attr: u32 - The bits corresponding to the attribute to set /// inline fn setAttribute(val: *align(1) u32, attr: u32) void { val.* |= attr; } /// /// Clear the bit(s) associated with an attribute of a table or directory entry. /// /// Arguments: /// val: *align(1) u32 - The entry to modify /// attr: u32 - The bits corresponding to the attribute to clear /// inline fn clearAttribute(val: *align(1) u32, attr: u32) void { val.* &= ~attr; } /// /// Map a page directory entry, setting the present, size, writable, write-through and physical address bits. /// Clears the user and cache disabled bits. Entry should be zeroed. /// /// Arguments: /// IN virt_addr: usize - The start of the virtual space to map /// IN virt_end: usize - The end of the virtual space to map /// IN phys_addr: usize - The start of the physical space to map /// IN phys_end: usize - The end of the physical space to map /// IN attrs: vmm.Attributes - The attributes to apply to this mapping /// IN allocator: *Allocator - The allocator to use to map any tables needed /// OUT dir: *Directory - The directory that this entry is in /// /// Error: vmm.MapperError || Allocator.Error /// vmm.MapperError.InvalidPhysicalAddress - The physical start address is greater than the end /// vmm.MapperError.InvalidVirtualAddress - The virtual start address is greater than the end or is larger than 4GB /// vmm.MapperError.AddressMismatch - The differences between the virtual addresses and the physical addresses aren't the same /// vmm.MapperError.MisalignedPhysicalAddress - One or both of the physical addresses aren't page size aligned /// vmm.MapperError.MisalignedVirtualAddress - One or both of the virtual addresses aren't page size aligned /// Allocator.Error.* - See Allocator.alignedAlloc /// fn mapDirEntry(dir: *Directory, virt_start: usize, virt_end: usize, phys_start: usize, phys_end: usize, attrs: vmm.Attributes, allocator: *Allocator) (vmm.MapperError || Allocator.Error)!void { if (phys_start > phys_end) { return vmm.MapperError.InvalidPhysicalAddress; } if (virt_start > virt_end) { return vmm.MapperError.InvalidVirtualAddress; } if (phys_end - phys_start != virt_end - virt_start) { return vmm.MapperError.AddressMismatch; } if (!std.mem.isAligned(phys_start, PAGE_SIZE_4KB) or !std.mem.isAligned(phys_end, PAGE_SIZE_4KB)) { return vmm.MapperError.MisalignedPhysicalAddress; } if (!std.mem.isAligned(virt_start, PAGE_SIZE_4KB) or !std.mem.isAligned(virt_end, PAGE_SIZE_4KB)) { return vmm.MapperError.MisalignedVirtualAddress; } const entry = virtToDirEntryIdx(virt_start); var dir_entry = &dir.entries[entry]; setAttribute(dir_entry, DENTRY_PRESENT); setAttribute(dir_entry, DENTRY_WRITE_THROUGH); clearAttribute(dir_entry, DENTRY_4MB_PAGES); if (attrs.writable) { setAttribute(dir_entry, DENTRY_WRITABLE); } else { clearAttribute(dir_entry, DENTRY_WRITABLE); } if (attrs.kernel) { clearAttribute(dir_entry, DENTRY_USER); } else { setAttribute(dir_entry, DENTRY_USER); } if (attrs.cachable) { clearAttribute(dir_entry, DENTRY_CACHE_DISABLED); } else { setAttribute(dir_entry, DENTRY_CACHE_DISABLED); } // Only create a new table if one hasn't already been created for this dir entry. // Prevents us from overriding previous mappings. var table: *Table = undefined; if (dir.tables[entry]) |tbl| { table = tbl; } else { // Create a table and put the physical address in the dir entry table = &(try allocator.alignedAlloc(Table, @truncate(u29, PAGE_SIZE_4KB), 1))[0]; @memset(@ptrCast([*]u8, table), 0, @sizeOf(Table)); const table_phys_addr = @ptrToInt(mem.virtToPhys(table)); dir_entry.* |= DENTRY_PAGE_ADDR & table_phys_addr; dir.tables[entry] = table; } // Map the table entries within the requested space var virt = virt_start; var phys = phys_start; var tentry = virtToTableEntryIdx(virt); while (virt < virt_end) : ({ virt += PAGE_SIZE_4KB; phys += PAGE_SIZE_4KB; tentry += 1; }) { try mapTableEntry(&table.entries[tentry], phys, attrs); } } /// /// Unmap a page directory entry, clearing the present bits. /// /// Arguments: /// IN virt_addr: usize - The start of the virtual space to map /// IN virt_end: usize - The end of the virtual space to map /// OUT dir: *Directory - The directory that this entry is in /// /// Error: vmm.MapperError /// vmm.MapperError.NotMapped - If the region being unmapped wasn't mapped in the first place /// fn unmapDirEntry(dir: *Directory, virt_start: usize, virt_end: usize) vmm.MapperError!void { const entry = virtToDirEntryIdx(virt_start); var dir_entry = &dir.entries[entry]; const table = dir.tables[entry] orelse return vmm.MapperError.NotMapped; var addr = virt_start; while (addr < virt_end) : (addr += PAGE_SIZE_4KB) { var table_entry = &table.entries[virtToTableEntryIdx(addr)]; if (table_entry.* & TENTRY_PRESENT != 0) { clearAttribute(table_entry, TENTRY_PRESENT); } else { return vmm.MapperError.NotMapped; } } } /// /// Map a table entry by setting its bits to the appropriate values. /// Sets the entry to be present, writable, kernel access, write through, cache enabled, non-global and the page address bits. /// /// Arguments: /// OUT entry: *align(1) TableEntry - The entry to map. 1 byte aligned. /// IN phys_addr: usize - The physical address to map the table entry to. /// /// Error: PagingError /// PagingError.UnalignedPhysAddresses - If the physical address isn't page size aligned. /// fn mapTableEntry(entry: *align(1) TableEntry, phys_addr: usize, attrs: vmm.Attributes) vmm.MapperError!void { if (!std.mem.isAligned(phys_addr, PAGE_SIZE_4KB)) { return vmm.MapperError.MisalignedPhysicalAddress; } setAttribute(entry, TENTRY_PRESENT); if (attrs.writable) { setAttribute(entry, TENTRY_WRITABLE); } else { clearAttribute(entry, TENTRY_WRITABLE); } if (attrs.kernel) { clearAttribute(entry, TENTRY_USER); } else { setAttribute(entry, TENTRY_USER); } if (attrs.writable) { setAttribute(entry, TENTRY_WRITE_THROUGH); } else { clearAttribute(entry, TENTRY_WRITE_THROUGH); } if (attrs.cachable) { clearAttribute(entry, TENTRY_CACHE_DISABLED); } else { setAttribute(entry, TENTRY_CACHE_DISABLED); } clearAttribute(entry, TENTRY_GLOBAL); setAttribute(entry, TENTRY_PAGE_ADDR & phys_addr); } /// /// Map a virtual region of memory to a physical region with a set of attributes within a directory. /// If this call is made to a directory that has been loaded by the CPU, the virtual memory will immediately be accessible (given the proper attributes) /// and will be mirrored to the physical region given. Otherwise it will be accessible once the given directory is loaded by the CPU. /// /// This call will panic if mapDir returns an error when called with any of the arguments given. /// /// Arguments: /// IN virtual_start: usize - The start of the virtual region to map /// IN virtual_end: usize - The end (exclusive) of the virtual region to map /// IN physical_start: usize - The start of the physical region to map to /// IN physical_end: usize - The end (exclusive) of the physical region to map to /// IN attrs: vmm.Attributes - The attributes to apply to this mapping /// IN/OUT allocator: *Allocator - The allocator to use to allocate any intermediate data structures required to map this region /// IN/OUT dir: *Directory - The page directory to map within /// /// Error: vmm.MapperError || Allocator.Error /// * - See mapDirEntry /// pub fn map(virtual_start: usize, virtual_end: usize, phys_start: usize, phys_end: usize, attrs: vmm.Attributes, allocator: *Allocator, dir: *Directory) (Allocator.Error || vmm.MapperError)!void { var virt_addr = virtual_start; var phys_addr = phys_start; var virt_next = std.math.min(virtual_end, std.mem.alignBackward(virt_addr, PAGE_SIZE_4MB) + PAGE_SIZE_4MB); var phys_next = std.math.min(phys_end, std.mem.alignBackward(phys_addr, PAGE_SIZE_4MB) + PAGE_SIZE_4MB); var entry_idx = virtToDirEntryIdx(virt_addr); while (entry_idx < ENTRIES_PER_DIRECTORY and virt_addr < virtual_end) : ({ virt_addr = virt_next; phys_addr = phys_next; virt_next = std.math.min(virtual_end, virt_next + PAGE_SIZE_4MB); phys_next = std.math.min(phys_end, phys_next + PAGE_SIZE_4MB); entry_idx += 1; }) { try mapDirEntry(dir, virt_addr, virt_next, phys_addr, phys_next, attrs, allocator); } } /// /// Unmap a virtual region of memory within a directory so that it is no longer accessible. /// /// Arguments: /// IN virtual_start: usize - The start of the virtual region to unmap /// IN virtual_end: usize - The end (exclusive) of the virtual region to unmap /// IN/OUT dir: *Directory - The page directory to unmap within /// /// Error: Allocator.Error || vmm.MapperError /// vmm.MapperError.NotMapped - If the region being unmapped wasn't mapped in the first place /// pub fn unmap(virtual_start: usize, virtual_end: usize, dir: *Directory) (Allocator.Error || vmm.MapperError)!void { var virt_addr = virtual_start; var virt_next = std.math.min(virtual_end, std.mem.alignBackward(virt_addr, PAGE_SIZE_4MB) + PAGE_SIZE_4MB); var entry_idx = virtToDirEntryIdx(virt_addr); while (entry_idx < ENTRIES_PER_DIRECTORY and virt_addr < virtual_end) : ({ virt_addr = virt_next; virt_next = std.math.min(virtual_end, virt_next + PAGE_SIZE_4MB); entry_idx += 1; }) { try unmapDirEntry(dir, virt_addr, virt_next); if (virt_next - virt_addr >= PAGE_SIZE_4MB) { clearAttribute(&dir.entries[entry_idx], DENTRY_PRESENT); } } } /// /// Called when a page fault occurs. This will log the CPU state and control registers. /// /// Arguments: /// IN state: *arch.CpuState - The CPU's state when the fault occurred. /// fn pageFault(state: *arch.CpuState) u32 { std.log.info(.paging, "State: {X}\n", .{state}); var cr0 = asm volatile ("mov %%cr0, %[cr0]" : [cr0] "=r" (-> u32) ); var cr2 = asm volatile ("mov %%cr2, %[cr2]" : [cr2] "=r" (-> u32) ); var cr3 = asm volatile ("mov %%cr3, %[cr3]" : [cr3] "=r" (-> u32) ); var cr4 = asm volatile ("mov %%cr4, %[cr4]" : [cr4] "=r" (-> u32) ); std.log.info(.paging, "CR0: 0x{X}, CR2: 0x{X}, CR3: 0x{X}, CR4: 0x{X}\n", .{ cr0, cr2, cr3, cr4 }); @panic("Page fault"); } /// /// Initialise x86 paging, overwriting any previous paging set up. /// /// Arguments: /// IN mem_profile: *const MemProfile - The memory profile of the system and kernel /// pub fn init(mem_profile: *const MemProfile) void { std.log.info(.paging, "Init\n", .{}); defer std.log.info(.paging, "Done\n", .{}); isr.registerIsr(isr.PAGE_FAULT, if (build_options.test_mode == .Initialisation) rt_pageFault else pageFault) catch |e| { panic(@errorReturnTrace(), "Failed to register page fault ISR: {}\n", .{e}); }; const dir_physaddr = @ptrToInt(mem.virtToPhys(&kernel_directory)); asm volatile ("mov %[addr], %%cr3" : : [addr] "{eax}" (dir_physaddr) ); const v_end = std.mem.alignForward(@ptrToInt(mem_profile.vaddr_end), PAGE_SIZE_4KB); switch (build_options.test_mode) { .Initialisation => runtimeTests(v_end), else => {}, } } fn checkDirEntry(entry: DirectoryEntry, virt_start: usize, virt_end: usize, phys_start: usize, attrs: vmm.Attributes, table: *Table, present: bool) void { expectEqual(entry & DENTRY_PRESENT, if (present) DENTRY_PRESENT else 0); expectEqual(entry & DENTRY_WRITABLE, if (attrs.writable) DENTRY_WRITABLE else 0); expectEqual(entry & DENTRY_USER, if (attrs.kernel) 0 else DENTRY_USER); expectEqual(entry & DENTRY_WRITE_THROUGH, DENTRY_WRITE_THROUGH); expectEqual(entry & DENTRY_CACHE_DISABLED, if (attrs.cachable) 0 else DENTRY_CACHE_DISABLED); expectEqual(entry & DENTRY_4MB_PAGES, 0); expectEqual(entry & DENTRY_ZERO, 0); var tentry_idx = virtToTableEntryIdx(virt_start); var tentry_idx_end = virtToTableEntryIdx(virt_end); var phys = phys_start; while (tentry_idx < tentry_idx_end) : ({ tentry_idx += 1; phys += PAGE_SIZE_4KB; }) { const tentry = table.entries[tentry_idx]; checkTableEntry(tentry, phys, attrs, present); } } fn checkTableEntry(entry: TableEntry, page_phys: usize, attrs: vmm.Attributes, present: bool) void { expectEqual(entry & TENTRY_PRESENT, if (present) TENTRY_PRESENT else 0); expectEqual(entry & TENTRY_WRITABLE, if (attrs.writable) TENTRY_WRITABLE else 0); expectEqual(entry & TENTRY_USER, if (attrs.kernel) 0 else TENTRY_USER); expectEqual(entry & TENTRY_WRITE_THROUGH, TENTRY_WRITE_THROUGH); expectEqual(entry & TENTRY_CACHE_DISABLED, if (attrs.cachable) 0 else TENTRY_CACHE_DISABLED); expectEqual(entry & TENTRY_ZERO, 0); expectEqual(entry & TENTRY_GLOBAL, 0); expectEqual(entry & TENTRY_PAGE_ADDR, page_phys); } test "setAttribute and clearAttribute" { var val: u32 = 0; const attrs = [_]u32{ DENTRY_PRESENT, DENTRY_WRITABLE, DENTRY_USER, DENTRY_WRITE_THROUGH, DENTRY_CACHE_DISABLED, DENTRY_ACCESSED, DENTRY_ZERO, DENTRY_4MB_PAGES, DENTRY_IGNORED, DENTRY_AVAILABLE, DENTRY_PAGE_ADDR }; for (attrs) |attr| { const old_val = val; setAttribute(&val, attr); std.testing.expectEqual(val, old_val | attr); } for (attrs) |attr| { const old_val = val; clearAttribute(&val, attr); std.testing.expectEqual(val, old_val & ~attr); } } test "virtToDirEntryIdx" { expectEqual(virtToDirEntryIdx(0), 0); expectEqual(virtToDirEntryIdx(123), 0); expectEqual(virtToDirEntryIdx(PAGE_SIZE_4MB - 1), 0); expectEqual(virtToDirEntryIdx(PAGE_SIZE_4MB), 1); expectEqual(virtToDirEntryIdx(PAGE_SIZE_4MB + 1), 1); expectEqual(virtToDirEntryIdx(PAGE_SIZE_4MB * 2), 2); expectEqual(virtToDirEntryIdx(PAGE_SIZE_4MB * (ENTRIES_PER_DIRECTORY - 1)), ENTRIES_PER_DIRECTORY - 1); } test "virtToTableEntryIdx" { expectEqual(virtToTableEntryIdx(0), 0); expectEqual(virtToTableEntryIdx(123), 0); expectEqual(virtToTableEntryIdx(PAGE_SIZE_4KB - 1), 0); expectEqual(virtToTableEntryIdx(PAGE_SIZE_4KB), 1); expectEqual(virtToTableEntryIdx(PAGE_SIZE_4KB + 1), 1); expectEqual(virtToTableEntryIdx(PAGE_SIZE_4KB * 2), 2); expectEqual(virtToTableEntryIdx(PAGE_SIZE_4KB * (ENTRIES_PER_TABLE - 1)), ENTRIES_PER_TABLE - 1); expectEqual(virtToTableEntryIdx(PAGE_SIZE_4KB * (ENTRIES_PER_TABLE)), 0); } test "mapDirEntry" { var allocator = std.heap.page_allocator; var dir: Directory = Directory{ .entries = [_]DirectoryEntry{0} ** ENTRIES_PER_DIRECTORY, .tables = [_]?*Table{null} ** ENTRIES_PER_DIRECTORY }; var phys: usize = 0 * PAGE_SIZE_4MB; const phys_end: usize = phys + PAGE_SIZE_4MB; const virt: usize = 1 * PAGE_SIZE_4MB; const virt_end: usize = virt + PAGE_SIZE_4MB; try mapDirEntry(&dir, virt, virt_end, phys, phys_end, .{ .kernel = true, .writable = true, .cachable = true }, allocator); const entry_idx = virtToDirEntryIdx(virt); const entry = dir.entries[entry_idx]; const table = dir.tables[entry_idx] orelse unreachable; checkDirEntry(entry, virt, virt_end, phys, .{ .kernel = true, .writable = true, .cachable = true }, table, true); } test "mapDirEntry returns errors correctly" { var allocator = std.heap.page_allocator; var dir = Directory{ .entries = [_]DirectoryEntry{0} ** ENTRIES_PER_DIRECTORY, .tables = undefined }; const attrs = vmm.Attributes{ .kernel = true, .writable = true, .cachable = true }; testing.expectError(vmm.MapperError.MisalignedVirtualAddress, mapDirEntry(&dir, 1, PAGE_SIZE_4KB + 1, 0, PAGE_SIZE_4KB, attrs, allocator)); testing.expectError(vmm.MapperError.MisalignedPhysicalAddress, mapDirEntry(&dir, 0, PAGE_SIZE_4KB, 1, PAGE_SIZE_4KB + 1, attrs, allocator)); testing.expectError(vmm.MapperError.AddressMismatch, mapDirEntry(&dir, 0, PAGE_SIZE_4KB, 1, PAGE_SIZE_4KB, attrs, allocator)); testing.expectError(vmm.MapperError.InvalidVirtualAddress, mapDirEntry(&dir, 1, 0, 0, PAGE_SIZE_4KB, attrs, allocator)); testing.expectError(vmm.MapperError.InvalidPhysicalAddress, mapDirEntry(&dir, 0, PAGE_SIZE_4KB, 1, 0, attrs, allocator)); } test "map and unmap" { var allocator = std.heap.page_allocator; var dir = Directory{ .entries = [_]DirectoryEntry{0} ** ENTRIES_PER_DIRECTORY, .tables = [_]?*Table{null} ** ENTRIES_PER_DIRECTORY }; const phys_start: usize = PAGE_SIZE_4MB * 2; const virt_start: usize = PAGE_SIZE_4MB * 4; const phys_end: usize = PAGE_SIZE_4MB * 4; const virt_end: usize = PAGE_SIZE_4MB * 6; const attrs = vmm.Attributes{ .kernel = true, .writable = true, .cachable = true }; map(virt_start, virt_end, phys_start, phys_end, attrs, allocator, &dir) catch unreachable; var virt = virt_start; var phys = phys_start; while (virt < virt_end) : ({ virt += PAGE_SIZE_4MB; phys += PAGE_SIZE_4MB; }) { const entry_idx = virtToDirEntryIdx(virt); const entry = dir.entries[entry_idx]; const table = dir.tables[entry_idx] orelse unreachable; checkDirEntry(entry, virt, virt + PAGE_SIZE_4MB, phys, attrs, table, true); } unmap(virt_start, virt_end, &dir) catch unreachable; virt = virt_start; phys = phys_start; while (virt < virt_end) : ({ virt += PAGE_SIZE_4MB; phys += PAGE_SIZE_4MB; }) { const entry_idx = virtToDirEntryIdx(virt); const entry = dir.entries[entry_idx]; const table = dir.tables[entry_idx] orelse unreachable; checkDirEntry(entry, virt, virt + PAGE_SIZE_4MB, phys, attrs, table, false); } } // The labels to jump to after attempting to cause a page fault. This is needed as we don't want to cause an // infinite loop by jumping to the same instruction that caused the fault. extern var rt_fault_callback: *u32; extern var rt_fault_callback2: *u32; var faulted = false; var use_callback2 = false; fn rt_pageFault(ctx: *arch.CpuState) u32 { faulted = true; // Return to the fault callback ctx.eip = @ptrToInt(&if (use_callback2) rt_fault_callback2 else rt_fault_callback); return @ptrToInt(ctx); } fn rt_accessUnmappedMem(v_end: u32) void { use_callback2 = false; faulted = false; // Accessing unmapped mem causes a page fault var ptr = @intToPtr(*u8, v_end); var value = ptr.*; // Need this as in release builds the above is optimised out so it needs to be use std.log.emerg(.paging, "FAILURE: Value: {}\n", .{value}); // This is the label that we return to after processing the page fault asm volatile ( \\.global rt_fault_callback \\rt_fault_callback: ); if (!faulted) { panic(@errorReturnTrace(), "FAILURE: Paging should have faulted\n", .{}); } std.log.info(.paging, "Tested accessing unmapped memory\n", .{}); } fn rt_accessMappedMem(v_end: u32) void { use_callback2 = true; faulted = false; // Accessing mapped memory doesn't cause a page fault var ptr = @intToPtr(*u8, v_end - PAGE_SIZE_4KB); var value = ptr.*; asm volatile ( \\.global rt_fault_callback2 \\rt_fault_callback2: ); if (faulted) { panic(@errorReturnTrace(), "FAILURE: Paging shouldn't have faulted\n", .{}); } std.log.info(.paging, "Tested accessing mapped memory\n", .{}); } pub fn runtimeTests(v_end: u32) void { rt_accessUnmappedMem(v_end); rt_accessMappedMem(v_end); }