diff --git a/src/arch.zig b/src/arch.zig index 6ef99a4..c0618c5 100644 --- a/src/arch.zig +++ b/src/arch.zig @@ -10,6 +10,8 @@ pub const impl = switch (builtin.cpu.arch) { else => @compileError("Unsupported architecture"), }; +pub const vmm = impl.vmm; + /// Halts the CPU execution indefinitely, without ever returning. pub inline fn halt() noreturn { impl.halt(); diff --git a/src/arch/aarch64.zig b/src/arch/aarch64.zig index e6da0ed..29f50ff 100644 --- a/src/arch/aarch64.zig +++ b/src/arch/aarch64.zig @@ -3,6 +3,8 @@ const std = @import("std"); const boot = @import("aarch64/boot.zig"); const regs = @import("aarch64/regs.zig"); +pub const vmm = @import("aarch64/vmm.zig"); + export const _ = boot.aa64_bsp_lower_entry; pub const Context = @import("aarch64/context.zig").Context; diff --git a/src/arch/aarch64/vmm.zig b/src/arch/aarch64/vmm.zig index f66c557..0cb93c1 100644 --- a/src/arch/aarch64/vmm.zig +++ b/src/arch/aarch64/vmm.zig @@ -8,7 +8,7 @@ pub const KERNEL_L1_INDEX: usize = L1.index(KERNEL_VIRTUAL_BASE); pub const L1 = mem.TranslationLevel(30); pub const L2 = mem.TranslationLevel(21); -pub const L3 = mem.TranslationLevel(12); +pub const L3 = mem.vmm.L3; pub const RawEntry = packed struct(u64) { // 0 diff --git a/src/arch/riscv64.zig b/src/arch/riscv64.zig index 4ff443a..deef238 100644 --- a/src/arch/riscv64.zig +++ b/src/arch/riscv64.zig @@ -5,6 +5,8 @@ const regs = @import("riscv64/regs.zig"); const std = @import("std"); const builtin = @import("builtin"); +pub const vmm = @import("riscv64/vmm.zig"); + export const _ = boot.rv64_bsp_lower_entry; /// This CPU's HART (HARdware Thread) ID. diff --git a/src/arch/riscv64/vmm.zig b/src/arch/riscv64/vmm.zig index 59a34bb..43f4ed8 100644 --- a/src/arch/riscv64/vmm.zig +++ b/src/arch/riscv64/vmm.zig @@ -12,7 +12,7 @@ const EARLY_MAPPING_SIZE: usize = 16; pub const L1 = mem.TranslationLevel(30); pub const L2 = mem.TranslationLevel(21); -pub const L3 = mem.TranslationLevel(12); +pub const L3 = mem.vmm.L3; pub const RawEntry = packed struct(u64) { // 0: Valid diff --git a/src/mem/phys.zig b/src/mem/phys.zig index 9287fa5..a9b3a0a 100644 --- a/src/mem/phys.zig +++ b/src/mem/phys.zig @@ -15,12 +15,12 @@ const Spinlock = sync.Spinlock; pub const MemoryRegion = struct { /// Name string, used to represent where the memory comes from. name: []const u8, - /// Byte range of the memory region. + /// Page frame number range of the region. range: Range(u64), }; /// Represents information about a single managed physical memory page. -pub const Page = packed struct (u128) { +pub const Page = packed struct(u128) { unused: @Vector(4, u32), }; @@ -52,28 +52,28 @@ const Bitmap = struct { }; const PhysicalMemoryManager = struct { - page_array: []Page, memory_start: u64, last_free: usize, + len: usize, /// Each bit represents a page, there can be more u64s than needed usage_bitmap: Bitmap, page_refcounters: []u32, const empty: @This() = .{ - .page_array = &.{}, .memory_start = 0, .last_free = 0, + .len = 0, .usage_bitmap = .empty, .page_refcounters = &.{}, }; fn alloc_page(self: *@This()) ?mem.PhysicalAddress { - for (self.last_free..self.page_array.len) |i| { + for (self.last_free..self.len) |i| { if (!self.is_page_used(i)) { self.page_refcounters[i] += 1; self.set_page_used(i); - self.last_free = (i + 1) % self.page_array.len; + self.last_free = (i + 1) % self.len; return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE }; } } @@ -81,7 +81,7 @@ const PhysicalMemoryManager = struct { if (!self.is_page_used(i)) { self.page_refcounters[i] += 1; self.set_page_used(i); - self.last_free = (i + 1) % self.page_array.len; + self.last_free = (i + 1) % self.len; return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE }; } } @@ -89,8 +89,8 @@ const PhysicalMemoryManager = struct { } fn alloc_pages(self: *@This(), count: usize) ?mem.PhysicalAddress { - if (self.last_free + count < self.page_array.len) { - if (self.alloc_from(self.last_free, self.page_array.len, count)) |p| { + if (self.last_free + count < self.len) { + if (self.alloc_from(self.last_free, self.len, count)) |p| { return p; } } @@ -123,7 +123,7 @@ const PhysicalMemoryManager = struct { log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw}); } const index = (page.raw - self.memory_start) / vmm.PAGE_SIZE; - if (index >= self.page_array.len) { + if (index >= self.len) { log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw}); } return index; @@ -141,11 +141,6 @@ const PhysicalMemoryManager = struct { } } - fn get_page(self: *@This(), page: mem.PhysicalAddress) *Page { - const index = self.valid_index(page); - return &self.page_array[index]; - } - fn is_page_used(self: *@This(), index: usize) bool { return self.usage_bitmap.get_bit(index) == 1; } @@ -166,30 +161,45 @@ var g_physical_memory = PhysicalMemoryManager.empty; /// Adds an available memory region to the list. /// +/// `base` and `size` are in bytes. Regions are page-aligned "inwards", meaning the function will +/// only add the range of full pages of the specified region. If a combination is provided that +/// does not yield any full 4KiB pages (e.g. `base=0x1234, size=0x123`), it is ignored. +/// /// # Note /// /// Only meaningful to call before calling `init()`. pub fn add_memory_region(name: []const u8, base: u64, size: u64) void { log.info("Memory: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size }); - g_memory_regions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) // - catch @panic("memory regions overflow"); + const start = vmm.L3.align_up(base) / vmm.L3.SIZE; + const len = vmm.L3.align_down(base + size) / vmm.L3.SIZE - start; + if (len > 0) { + g_memory_regions.append(.{ .name = name, .range = .{ .start = start, .len = len } }) // + catch @panic("memory regions overflow"); + } } /// Adds an reserved memory region to the list. /// +/// `base` and `size` are in bytes. Regions are page-aligned "outwards", meaning that the +/// reservation extends to any pages affected by the specified region. +/// /// # Note /// /// Only meaningful to call before calling `init()`. pub fn add_reserved_region(name: []const u8, base: u64, size: u64) void { log.info("Reserved: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size }); - g_reserved_regions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) // - catch @panic("reserved regions overflow"); + const start = base / vmm.L3.SIZE; + const len = vmm.L3.align_up(base + size) / vmm.L3.SIZE - start; + if (len > 0) { + g_reserved_regions.append(.{ .name = name, .range = .{ .start = start, .len = len } }) // + catch @panic("reserved regions overflow"); + } } -fn is_reserved_in(page: u64) ?*const MemoryRegion { +fn is_reserved_in(page_index: u64) ?*const MemoryRegion { for (0..g_reserved_regions.len) |i| { const region = &g_reserved_regions.buffer[i]; - if (page >= region.range.start and page < region.range.end()) { + if (page_index >= region.range.start and page_index < region.range.end()) { return region; } } @@ -201,7 +211,7 @@ fn alloc_from_region(region: *const MemoryRegion, reason: []const u8, page_count while (offset < region.range.len) { var taken: ?*const MemoryRegion = null; for (0..page_count) |i| { - if (is_reserved_in(region.range.start + offset + i * vmm.PAGE_SIZE)) |resv| { + if (is_reserved_in(region.range.start + offset + i)) |resv| { taken = resv; break; } @@ -212,7 +222,7 @@ fn alloc_from_region(region: *const MemoryRegion, reason: []const u8, page_count continue; } - const base = region.range.start + offset; + const base = (region.range.start + offset) * vmm.L3.SIZE; add_reserved_region(reason, base, page_count * vmm.PAGE_SIZE); return base; } @@ -224,8 +234,7 @@ fn alloc_slice_pages(comptime T: type, reason: []const u8, page_count: usize) [] for (g_memory_regions.constSlice()) |region| { if (alloc_from_region(®ion, reason, page_count)) |physAddress| { const vaddr = (mem.PhysicalAddress{ .raw = physAddress }).virtualize(); - const items_per_page = vmm.PAGE_SIZE / @sizeOf(T); - const len = page_count * items_per_page; + const len = (page_count * vmm.PAGE_SIZE) / @sizeOf(T); const ptr: [*]T = @ptrFromInt(vaddr); const slice: []T = ptr[0..len]; return slice; @@ -239,7 +248,7 @@ fn alloc_slice_pages(comptime T: type, reason: []const u8, page_count: usize) [] fn alloc_slice(comptime T: type, reason: []const u8, min_len: usize) []T { const min_alloc_bytes = min_len * @sizeOf(T); // Round up to make sure we have enough space for the data - const needed_pages = (min_alloc_bytes + vmm.PAGE_SIZE - 1) / vmm.PAGE_SIZE; + const needed_pages = vmm.L3.page_count(min_alloc_bytes); const slice = alloc_slice_pages(T, reason, needed_pages); const slice_as_bytes = std.mem.sliceAsBytes(slice); @memset(slice_as_bytes, 0); @@ -272,6 +281,7 @@ pub fn init() void { var memory_end: u64 = std.math.minInt(u64); for (g_memory_regions.constSlice()) |region| { + log.info("Region: {}..{}", .{ region.range.start, region.range.end() }); if (region.range.start < memory_start) { memory_start = region.range.start; } @@ -280,24 +290,23 @@ pub fn init() void { } } - const memory_pages = (memory_end - memory_start) / vmm.PAGE_SIZE; // == bitmap bits required + const memory_pages = memory_end - memory_start; // == bitmap bits required var bitmap = alloc_bitmap(memory_pages); - const page_array = alloc_slice(Page, "page-array", memory_pages); const refcounters = alloc_refcounters(memory_pages); var available_pages: usize = 0; for (g_memory_regions.constSlice()) |region| { - const offset = (region.range.start - memory_start) / vmm.PAGE_SIZE; - for (0..region.range.len / vmm.PAGE_SIZE) |i| { + const offset = region.range.start - memory_start; + for (0..region.range.len) |i| { refcounters[offset + i] = 0; available_pages += 1; } } for (g_reserved_regions.constSlice()) |region| { - const offset = (region.range.start - memory_start) / vmm.PAGE_SIZE; - for (0..region.range.len / vmm.PAGE_SIZE) |i| { - if (offset + i >= page_array.len) { + const offset = region.range.start - memory_start; + for (0..region.range.len) |i| { + if (offset + i >= memory_pages) { break; } refcounters[offset + i] = std.math.maxInt(u32); @@ -309,10 +318,13 @@ pub fn init() void { var size_fmt: [64]u8 = undefined; const size_fmt_str = mem.format_size(&size_fmt, available_pages * vmm.PAGE_SIZE); - log.info("Available memory: {s}, page array {*}", .{ size_fmt_str, page_array }); + log.info( + "Available memory: {s}, bitmap {*}, refcounts {*}", + .{ size_fmt_str, bitmap.data, refcounters }, + ); - g_physical_memory.page_array = page_array; - g_physical_memory.memory_start = memory_start; + g_physical_memory.len = memory_pages; + g_physical_memory.memory_start = memory_start * vmm.L3.SIZE; g_physical_memory.usage_bitmap = bitmap; g_physical_memory.page_refcounters = refcounters; } @@ -366,16 +378,3 @@ pub fn free_page(page: mem.PhysicalAddress) void { defer guard.release(); g_physical_memory.free_page(page); } - -/// Returns a `Page` struct representing the given `page`. -/// -/// # Invariants -/// -/// The physical memory lock must be held. -/// -/// # Panics -/// -/// Will panic if the `page` does not represent a valid managed page. -pub fn get_page(page: mem.PhysicalAddress) *Page { - return g_physical_memory.get_page(page); -} diff --git a/src/mem/vmm.zig b/src/mem/vmm.zig index e94be63..1f7f483 100644 --- a/src/mem/vmm.zig +++ b/src/mem/vmm.zig @@ -1,7 +1,12 @@ //! Platform-independent virtual memory management definitions. +const mem = @import("../mem.zig"); + +/// Last virtual memory translation level. Always 4KiB on all platforms. +pub const L3 = mem.TranslationLevel(12); + /// Page size is 4KiB on all platforms. -pub const PAGE_SIZE: usize = 0x1000; +pub const PAGE_SIZE: usize = L3.SIZE; /// Helper function to construct a "Translation Level" struct type from a bit shift. pub fn TranslationLevel(comptime shift: usize) type { @@ -28,5 +33,13 @@ pub fn TranslationLevel(comptime shift: usize) type { pub inline fn align_up(addr: usize) usize { return (addr + SIZE - 1) & ~(SIZE - 1); } + + pub inline fn page_number(addr: usize) usize { + return addr >> shift; + } + + pub inline fn page_count(size: usize) usize { + return (size + SIZE - 1) / SIZE; + } }; }