diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index 2a9300c..a19f187 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -11,6 +11,7 @@ const paging = @import("paging.zig"); const syscalls = @import("syscalls.zig"); const mem = @import("../../mem.zig"); const multiboot = @import("../../multiboot.zig"); +const pmm = @import("pmm.zig"); const MemProfile = mem.MemProfile; /// The interrupt context that is given to a interrupt handler. It contains most of the registers @@ -46,6 +47,9 @@ pub const InterruptContext = struct { ss: u32, }; +/// The size of each allocatable block of memory, normally set to the page size. +pub const MEMORY_BLOCK_SIZE = paging.PAGE_SIZE_4KB; + /// /// Assembly to write to a given port with a byte of data. /// diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig index c5fb89b..70cf1ce 100644 --- a/src/kernel/arch/x86/paging.zig +++ b/src/kernel/arch/x86/paging.zig @@ -83,12 +83,6 @@ const ENTRIES_PER_DIRECTORY: u32 = 1024; /// Each table has 1024 entries const ENTRIES_PER_TABLE: u32 = 1024; -/// The number of bytes in 4MB -const PAGE_SIZE_4MB: u32 = 0x400000; - -/// The number of bytes in 4KB -const PAGE_SIZE_4KB: u32 = PAGE_SIZE_4MB / 1024; - /// There are 1024 entries per directory with each one covering 4KB const PAGES_PER_DIR_ENTRY: u32 = 1024; @@ -121,6 +115,12 @@ 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: u32 = 0x400000; + +/// The number of bytes in 4KB +pub const PAGE_SIZE_4KB: u32 = PAGE_SIZE_4MB / 1024; + /// /// Convert a virtual address to an index within an array of directory entries. /// diff --git a/src/kernel/bitmap.zig b/src/kernel/bitmap.zig new file mode 100644 index 0000000..480637d --- /dev/null +++ b/src/kernel/bitmap.zig @@ -0,0 +1,306 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const testing = std.testing; + +/// +/// A bitmap that uses a specific type to store the entries. +/// +/// Arguments: +/// IN BitmapType: type - The integer type to use to store entries. +/// +/// Return: type. +/// The bitmap type created. +/// +pub fn Bitmap(comptime BitmapType: type) type { + return struct { + /// The possible errors thrown by bitmap functions + pub const BitmapError = error{ + /// The address given was outside the region covered by a bitmap + OutOfBounds, + }; + + const Self = @This(); + + /// The number of entries that one bitmap type can hold. Evaluates to the number of bits the type has + pub const ENTRIES_PER_BITMAP: u32 = std.meta.bitCount(BitmapType); + + /// The value that a full bitmap will have + pub const BITMAP_FULL = std.math.maxInt(BitmapType); + + /// The type of an index into a bitmap entry. The smallest integer needed to represent all bit positions in the bitmap entry type + pub const IndexType = @Type(builtin.TypeInfo{ .Int = builtin.TypeInfo.Int{ .is_signed = false, .bits = std.math.log2(std.meta.bitCount(BitmapType)) } }); + + num_bitmaps: u32, + num_entries: u32, + bitmaps: []BitmapType, + num_free_entries: u32, + + /// + /// Create an instance of this bitmap type. + /// + /// Arguments: + /// IN num_entries: u32 - The number of entries that the bitmap created will have. + /// The number of BitmapType required to store this many entries will be allocated and each will be zeroed. + /// IN allocator: *std.mem.Allocator - The allocator to use when allocating the BitmapTypes required. + /// + /// Return: Self. + /// The bitmap instance. + /// + /// Error: std.mem.Allocator.Error + /// OutOfMemory: There isn't enough memory available to allocate the required number of BitmapType. + /// + pub fn init(num_entries: u32, allocator: *std.mem.Allocator) !Self { + const num = @floatToInt(u32, @ceil(@intToFloat(f32, num_entries) / @intToFloat(f32, ENTRIES_PER_BITMAP))); + const self = Self{ + .num_bitmaps = num, + .num_entries = num_entries, + .bitmaps = try allocator.alloc(BitmapType, num), + .num_free_entries = num_entries, + }; + for (self.bitmaps) |*bmp| { + bmp.* = 0; + } + return self; + } + + /// + /// Set an entry within a bitmap as occupied. + /// + /// Arguments: + /// INOUT self: *Self - The bitmap to modify. + /// IN idx: BitmapIndex - The index within the bitmap to set. + /// + /// Error: BitmapError. + /// OutOfBounds: The index given is out of bounds. + /// + pub fn setEntry(self: *Self, idx: u32) BitmapError!void { + if (idx >= self.num_entries) return BitmapError.OutOfBounds; + if (!try self.isSet(idx)) { + const bit = self.indexToBit(idx); + self.bitmaps[idx / ENTRIES_PER_BITMAP] |= bit; + self.num_free_entries -= 1; + } + } + + /// + /// Set an entry within a bitmap as unoccupied. + /// + /// Arguments: + /// INOUT self: *Self - The bitmap to modify. + /// IN idx: BitmapIndex - The index within the bitmap to clear. + /// + /// Error: BitmapError. + /// OutOfBounds: The index given is out of bounds. + /// + pub fn clearEntry(self: *Self, idx: u32) BitmapError!void { + if (idx >= self.num_entries) return BitmapError.OutOfBounds; + if (try self.isSet(idx)) { + const bit = self.indexToBit(idx); + self.bitmaps[idx / ENTRIES_PER_BITMAP] &= ~bit; + self.num_free_entries += 1; + } + } + + /// + /// Convert a global bitmap index into the bit corresponding to an entry within a single BitmapType. + /// + /// Arguments: + /// IN self: *Self - The bitmap to use. + /// IN idx: u32 - The index into all of the bitmap's entries. + /// + /// Return: BitmapType. + /// The bit corresponding to that index but within a single BitmapType. + /// + fn indexToBit(self: *Self, idx: u32) BitmapType { + return @as(BitmapType, 1) << @intCast(IndexType, idx % ENTRIES_PER_BITMAP); + } + + /// + /// Set the first free entry within the bitmaps as occupied. + /// + /// Return: ?u32. + /// The index within all bitmaps that was set or null if there wasn't one free. + /// 0 .. ENTRIES_PER_BITMAP - 1 if in the first bitmap, ENTRIES_PER_BITMAP .. ENTRIES_PER_BITMAP * 2 - 1 if in the second etc. + /// + pub fn setFirstFree(self: *Self) ?u32 { + if (self.num_free_entries == 0) return null; + for (self.bitmaps) |*bmp, i| { + if (bmp.* == BITMAP_FULL) + continue; + const bit = @truncate(IndexType, @ctz(BitmapType, ~bmp.*)); + const idx = bit + @intCast(u32, i) * ENTRIES_PER_BITMAP; + // Failing here means that the index is outside of the bitmap, so there are no free entries + self.setEntry(idx) catch return null; + return idx; + } + return null; + } + + /// + /// Check if an entry is set. + /// + /// Arguments: + /// IN self: *Bitmap - The bitmap to check. + /// IN idx: u32 - The entry to check. + /// + /// Return: bool. + /// True if the entry is set, else false. + /// + /// Error: BitmapError. + /// OutOfBounds: The index given is out of bounds. + /// + pub fn isSet(self: *Self, idx: u32) BitmapError!bool { + if (idx >= self.num_entries) return BitmapError.OutOfBounds; + return (self.bitmaps[idx / ENTRIES_PER_BITMAP] & self.indexToBit(idx)) != 0; + } + }; +} + +test "setEntry" { + var bmp = try Bitmap(u32).init(31, std.heap.direct_allocator); + testing.expectEqual(@as(u32, 31), bmp.num_free_entries); + + try bmp.setEntry(0); + testing.expectEqual(@as(u32, 1), bmp.bitmaps[0]); + testing.expectEqual(@as(u32, 30), bmp.num_free_entries); + + try bmp.setEntry(1); + testing.expectEqual(@as(u32, 3), bmp.bitmaps[0]); + testing.expectEqual(@as(u32, 29), bmp.num_free_entries); + + // Repeat setting entry 1 to make sure state doesn't change + try bmp.setEntry(1); + testing.expectEqual(@as(u32, 3), bmp.bitmaps[0]); + testing.expectEqual(@as(u32, 29), bmp.num_free_entries); + + testing.expectError(Bitmap(u32).BitmapError.OutOfBounds, bmp.setEntry(31)); + testing.expectEqual(@as(u32, 29), bmp.num_free_entries); +} + +test "clearEntry" { + var bmp = try Bitmap(u32).init(32, std.heap.direct_allocator); + testing.expectEqual(@as(u32, 32), bmp.num_free_entries); + + try bmp.setEntry(0); + testing.expectEqual(@as(u32, 31), bmp.num_free_entries); + try bmp.setEntry(1); + testing.expectEqual(@as(u32, 30), bmp.num_free_entries); + testing.expectEqual(@as(u32, 3), bmp.bitmaps[0]); + try bmp.clearEntry(0); + testing.expectEqual(@as(u32, 31), bmp.num_free_entries); + testing.expectEqual(@as(u32, 2), bmp.bitmaps[0]); + + // Repeat to make sure state doesn't change + try bmp.clearEntry(0); + testing.expectEqual(@as(u32, 31), bmp.num_free_entries); + testing.expectEqual(@as(u32, 2), bmp.bitmaps[0]); + + // Try clearing an unset entry to make sure state doesn't change + try bmp.clearEntry(2); + testing.expectEqual(@as(u32, 31), bmp.num_free_entries); + testing.expectEqual(@as(u32, 2), bmp.bitmaps[0]); + + testing.expectError(Bitmap(u32).BitmapError.OutOfBounds, bmp.clearEntry(32)); +} + +test "setFirstFree multiple bitmaps" { + var bmp = try Bitmap(u8).init(9, std.heap.direct_allocator); + + // Allocate the first entry + testing.expectEqual(bmp.setFirstFree() orelse unreachable, 0); + testing.expectEqual(bmp.bitmaps[0], 1); + + // Allocate the second entry + testing.expectEqual(bmp.setFirstFree() orelse unreachable, 1); + testing.expectEqual(bmp.bitmaps[0], 3); + + // Allocate the entirety of the first bitmap + var entry: u32 = 2; + var expected: u8 = 7; + while (entry < Bitmap(u8).ENTRIES_PER_BITMAP) { + testing.expectEqual(bmp.setFirstFree() orelse unreachable, entry); + testing.expectEqual(bmp.bitmaps[0], expected); + if (entry + 1 < Bitmap(u8).ENTRIES_PER_BITMAP) { + entry += 1; + expected = expected * 2 + 1; + } else { + break; + } + } + + // Try allocating an entry in the next bitmap + testing.expectEqual(bmp.setFirstFree() orelse unreachable, Bitmap(u8).ENTRIES_PER_BITMAP); + testing.expectEqual(bmp.bitmaps[0], Bitmap(u8).BITMAP_FULL); + testing.expectEqual(bmp.bitmaps[1], 1); + + // We should no longer be able to allocate any entries + testing.expectEqual(bmp.setFirstFree(), null); + testing.expectEqual(bmp.bitmaps[0], Bitmap(u8).BITMAP_FULL); + testing.expectEqual(bmp.bitmaps[1], 1); +} +test "setFirstFree" { + var bmp = try Bitmap(u32).init(32, std.heap.direct_allocator); + + // Allocate the first entry + testing.expectEqual(bmp.setFirstFree() orelse unreachable, 0); + testing.expectEqual(bmp.bitmaps[0], 1); + + // Allocate the second entry + testing.expectEqual(bmp.setFirstFree() orelse unreachable, 1); + testing.expectEqual(bmp.bitmaps[0], 3); + + // Make all but the MSB occupied and try to allocate it + bmp.bitmaps[0] = Bitmap(u32).BITMAP_FULL & ~@as(u32, 1 << (Bitmap(u32).ENTRIES_PER_BITMAP - 1)); + testing.expectEqual(bmp.setFirstFree() orelse unreachable, Bitmap(u32).ENTRIES_PER_BITMAP - 1); + testing.expectEqual(bmp.bitmaps[0], Bitmap(u32).BITMAP_FULL); + + // We should no longer be able to allocate any entries + testing.expectEqual(bmp.setFirstFree(), null); + testing.expectEqual(bmp.bitmaps[0], Bitmap(u32).BITMAP_FULL); +} + +test "isSet" { + var bmp = try Bitmap(u32).init(32, std.heap.direct_allocator); + + bmp.bitmaps[0] = 1; + // Make sure that only the set entry is considered set + testing.expect(try bmp.isSet(0)); + var i: u32 = 1; + while (i < bmp.num_entries) : (i += 1) { + testing.expect(!try bmp.isSet(i)); + } + + bmp.bitmaps[0] = 3; + testing.expect(try bmp.isSet(0)); + testing.expect(try bmp.isSet(1)); + i = 2; + while (i < bmp.num_entries) : (i += 1) { + testing.expect(!try bmp.isSet(i)); + } + + bmp.bitmaps[0] = 11; + testing.expect(try bmp.isSet(0)); + testing.expect(try bmp.isSet(1)); + testing.expect(!try bmp.isSet(2)); + testing.expect(try bmp.isSet(3)); + i = 4; + while (i < bmp.num_entries) : (i += 1) { + testing.expect(!try bmp.isSet(i)); + } + + testing.expectError(Bitmap(u32).BitmapError.OutOfBounds, bmp.isSet(33)); +} + +test "indexToBit" { + var bmp = try Bitmap(u8).init(10, std.heap.direct_allocator); + testing.expectEqual(bmp.indexToBit(0), 1); + testing.expectEqual(bmp.indexToBit(1), 2); + testing.expectEqual(bmp.indexToBit(2), 4); + testing.expectEqual(bmp.indexToBit(3), 8); + testing.expectEqual(bmp.indexToBit(4), 16); + testing.expectEqual(bmp.indexToBit(5), 32); + testing.expectEqual(bmp.indexToBit(6), 64); + testing.expectEqual(bmp.indexToBit(7), 128); + testing.expectEqual(bmp.indexToBit(8), 1); + testing.expectEqual(bmp.indexToBit(9), 2); +} diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 1fc5046..aaf6bf1 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -9,6 +9,7 @@ const tty = @import("tty.zig"); const vga = @import("vga.zig"); const log = @import("log.zig"); const serial = @import("serial.zig"); +const pmm = @import("pmm.zig"); const mem = if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig"); const panic_root = if (is_test) @import(mock_path ++ "panic_mock.zig") else @import("panic.zig"); const options = @import("build_options"); @@ -42,6 +43,7 @@ export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void { var buffer = mem_profile.vaddr_end[0..mem_profile.fixed_alloc_size]; var fixed_allocator = std.heap.FixedBufferAllocator.init(buffer); + pmm.init(&mem_profile, &fixed_allocator.allocator); log.logInfo("Init arch " ++ @tagName(builtin.arch) ++ "\n", .{}); arch.init(mb_info, &mem_profile, &fixed_allocator.allocator); log.logInfo("Arch init done\n", .{}); diff --git a/src/kernel/mem.zig b/src/kernel/mem.zig index 43b42f5..0734326 100644 --- a/src/kernel/mem.zig +++ b/src/kernel/mem.zig @@ -4,13 +4,30 @@ const expectEqual = std.testing.expectEqual; const log = @import("log.zig"); pub const MemProfile = struct { + /// The virtual end address of the kernel code. vaddr_end: [*]u8, + + /// The virtual end address of the kernel code. vaddr_start: [*]u8, + + /// The physical end address of the kernel code. physaddr_end: [*]u8, + + /// The physical start address of the kernel code. physaddr_start: [*]u8, + + /// The amount of memory in the system, in kilobytes. mem_kb: u32, + + /// The size of the fixed buffer allocator used as the first memory allocator. fixed_alloc_size: u32, + + /// The boot modules provided by the bootloader. boot_modules: []multiboot.multiboot_module_t, + + /// The memory map provided by the bootloader. Desribes which areas of memory are available and + /// which are reserved. + mem_map: []multiboot.multiboot_memory_map_t, }; /// The virtual end of the kernel code @@ -47,8 +64,11 @@ var ADDR_OFFSET: usize = undefined; /// pub fn init(mb_info: *multiboot.multiboot_info_t) MemProfile { log.logInfo("Init mem\n", .{}); + defer log.logInfo("Done\n", .{}); const mods_count = mb_info.mods_count; ADDR_OFFSET = @ptrToInt(&KERNEL_ADDR_OFFSET); + const mmap_addr = mb_info.mmap_addr; + const num_mmap_entries = mb_info.mmap_length / @sizeOf(multiboot.multiboot_memory_map_t); const mem_profile = MemProfile{ .vaddr_end = @ptrCast([*]u8, &KERNEL_VADDR_END), .vaddr_start = @ptrCast([*]u8, &KERNEL_VADDR_START), @@ -58,8 +78,8 @@ pub fn init(mb_info: *multiboot.multiboot_info_t) MemProfile { .mem_kb = mb_info.mem_upper + mb_info.mem_lower + 1024, .fixed_alloc_size = FIXED_ALLOC_SIZE, .boot_modules = @intToPtr([*]multiboot.multiboot_mod_list, physToVirt(mb_info.mods_addr))[0..mods_count], + .mem_map = @intToPtr([*]multiboot.multiboot_memory_map_t, mmap_addr)[0..num_mmap_entries], }; - log.logInfo("Done\n", .{}); return mem_profile; } diff --git a/src/kernel/pmm.zig b/src/kernel/pmm.zig new file mode 100644 index 0000000..02a74dd --- /dev/null +++ b/src/kernel/pmm.zig @@ -0,0 +1,220 @@ +const is_test = @import("builtin").is_test; +const std = @import("std"); +const build_options = @import("build_options"); +const mock_path = build_options.mock_path; +const arch = @import("arch.zig").internals; +const MemProfile = (if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig")).MemProfile; +const testing = std.testing; +const panic = @import("panic.zig").panic; +const log = @import("log.zig"); +const MEMORY_AVAILABLE = @import("multiboot.zig").MULTIBOOT_MEMORY_AVAILABLE; +const Bitmap = @import("bitmap.zig").Bitmap; + +const PmmBitmap = Bitmap(u32); + +/// The possible errors thrown by bitmap functions +const PmmError = error{ + /// The address given hasn't been allocated + NotAllocated, +}; + +/// The size of memory associated with each bitmap entry +const BLOCK_SIZE = arch.MEMORY_BLOCK_SIZE; + +var bitmap: PmmBitmap = undefined; + +/// +/// Set the bitmap entry for an address as occupied +/// +/// Arguments: +/// IN addr: usize - The address. +/// +/// Error: PmmBitmap.BitmapError. +/// *: See PmmBitmap.setEntry. Could occur if the address is out of bounds. +/// +fn setAddr(addr: usize) PmmBitmap.BitmapError!void { + try bitmap.setEntry(@intCast(u32, addr / BLOCK_SIZE)); +} + +/// +/// Check if an address is set as occupied. +/// +/// Arguments: +/// IN addr: usize - The address to check. +/// +/// Return: True if occupied, else false. +/// +/// Error: PmmBitmap.BitmapError. +/// *: See PmmBitmap.setEntry. Could occur if the address is out of bounds. +/// +fn isSet(addr: usize) PmmBitmap.BitmapError!bool { + return bitmap.isSet(@intCast(u32, addr / BLOCK_SIZE)); +} + +/// +/// Find the next free memory block, set it as occupied and return it. The region allocated will be of size BLOCK_SIZE. +/// +/// Return: The address that was allocated. +/// +pub fn alloc() ?usize { + if (bitmap.setFirstFree()) |entry| { + return entry * BLOCK_SIZE; + } + return null; +} + +/// +/// Set the address as free so it can be allocated in the future. This will free a block of size BLOCK_SIZE. +/// +/// Arguments: +/// IN addr: usize - The previously allocated address to free. Will be aligned down to the nearest multiple of BLOCK_SIZE. +/// +/// Error: PmmError || PmmBitmap.BitmapError. +/// PmmError.NotAllocated: The address wasn't allocated. +/// PmmBitmap.BitmapError.OutOfBounds: The address given was out of bounds. +/// +pub fn free(addr: usize) (PmmBitmap.BitmapError || PmmError)!void { + const idx = @intCast(u32, addr / BLOCK_SIZE); + if (try bitmap.isSet(idx)) { + try bitmap.clearEntry(idx); + } else { + return PmmError.NotAllocated; + } +} + +/// +/// Intiialise the physical memory manager and set all unavailable regions as occupied (those from the memory map and those from the linker symbols). +/// +/// Arguments: +/// IN mem: *const MemProfile - The system's memory profile. +/// IN allocator: *std.mem.Allocator - The allocator to use to allocate the bitmaps. +/// +pub fn init(mem: *const MemProfile, allocator: *std.mem.Allocator) void { + log.logInfo("Init pmm\n", .{}); + bitmap = PmmBitmap.init(mem.mem_kb * 1024 / BLOCK_SIZE, allocator) catch @panic("Bitmap allocation failed"); + + // Occupy the regions of memory that the memory map describes as reserved + for (mem.mem_map) |entry| { + if (entry.@"type" != MEMORY_AVAILABLE) { + var addr = std.mem.alignBackward(@intCast(usize, entry.addr), BLOCK_SIZE); + var end = @intCast(usize, entry.addr + (entry.len - 1)); + // If the end address can be aligned without overflowing then align it + if (end <= std.math.maxInt(usize) - BLOCK_SIZE) + end = std.mem.alignForward(end, BLOCK_SIZE); + while (addr < end) : (addr += BLOCK_SIZE) { + setAddr(addr) catch |e| switch (e) { + // We can ignore out of bounds errors as the memory won't be available anyway + PmmBitmap.BitmapError.OutOfBounds => break, + else => panic(@errorReturnTrace(), "Failed setting address 0x{x} from memory map as occupied: {}", .{ addr, e }), + }; + } + } + } + // Occupy kernel memory + var addr = std.mem.alignBackward(@ptrToInt(mem.physaddr_start), BLOCK_SIZE); + while (addr < @ptrToInt(mem.physaddr_end)) : (addr += BLOCK_SIZE) { + setAddr(addr) catch |e| switch (e) { + error.OutOfBounds => panic(@errorReturnTrace(), "Failed setting kernel code address 0x{x} as occupied. The amount of system memory seems to be too low for the kernel image: {}", .{ addr, e }), + else => panic(@errorReturnTrace(), "Failed setting kernel code address 0x{x} as occupied: {}", .{ addr, e }), + }; + } + log.logInfo("Done\n", .{}); + if (build_options.rt_test) { + runtimeTests(mem); + } +} + +/// +/// Allocate all blocks and make sure they don't overlap with any reserved addresses. +/// +/// Arguments: +/// IN mem: *const MemProfile - The memory profile to check for reserved memory regions. +/// +fn runtimeTests(mem: *const MemProfile) void { + // Make sure that occupied memory can't be allocated + var prev_alloc: usize = std.math.maxInt(usize); + while (alloc()) |alloced| { + if (prev_alloc == alloced) { + panic(null, "PMM allocated the same address twice: 0x{x}", .{alloced}); + } + prev_alloc = alloced; + for (mem.mem_map) |entry| { + if (entry.@"type" != MEMORY_AVAILABLE) { + var addr = std.mem.alignBackward(@intCast(usize, entry.addr), BLOCK_SIZE); + if (addr == alloced) { + panic(null, "PMM allocated an address that should be reserved by the memory map: 0x{x}", .{addr}); + } + } + } + if (alloced >= std.mem.alignBackward(@ptrToInt(mem.physaddr_start), BLOCK_SIZE) and alloced < std.mem.alignForward(@ptrToInt(mem.physaddr_end), BLOCK_SIZE)) { + panic(null, "PMM allocated an address that should be reserved by kernel code: 0x{x}", .{alloced}); + } + } + log.logInfo("PMM: Tested allocation\n", .{}); +} + +test "alloc" { + bitmap = try Bitmap(u32).init(32, std.heap.direct_allocator); + comptime var addr = 0; + comptime var i = 0; + // Allocate all entries, making sure they succeed and return the correct addresses + inline while (i < 32) : ({ + i += 1; + addr += BLOCK_SIZE; + }) { + testing.expect(!(try isSet(addr))); + testing.expect(alloc().? == addr); + testing.expect(try isSet(addr)); + } + // Allocation should now fail + testing.expect(alloc() == null); +} + +test "free" { + bitmap = try Bitmap(u32).init(32, std.heap.direct_allocator); + comptime var i = 0; + // Allocate and free all entries + inline while (i < 32) : (i += 1) { + const addr = alloc().?; + testing.expect(try isSet(addr)); + try free(addr); + testing.expect(!(try isSet(addr))); + // Double frees should be caught + testing.expectError(PmmError.NotAllocated, free(addr)); + } +} + +test "setAddr and isSet" { + const num_entries: u32 = 32; + bitmap = try Bitmap(u32).init(num_entries, std.heap.direct_allocator); + var addr: u32 = 0; + var i: u32 = 0; + while (i < num_entries) : ({ + i += 1; + addr += BLOCK_SIZE; + }) { + // Ensure all previous blocks are still set + var h: u32 = 0; + var addr2: u32 = 0; + while (h < i) : ({ + h += 1; + addr2 += BLOCK_SIZE; + }) { + testing.expect(try isSet(addr2)); + } + + // Set the current block + try setAddr(addr); + testing.expect(try isSet(addr)); + + // Ensure all successive entries are not set + var j: u32 = i + 1; + var addr3: u32 = addr + BLOCK_SIZE; + while (j < num_entries) : ({ + j += 1; + addr3 += BLOCK_SIZE; + }) { + testing.expect(!try isSet(addr3)); + } + } +} diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index 1d043e8..47a31d3 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -5,6 +5,7 @@ const MemProfile = mem.MemProfile; const gdt = @import("gdt_mock.zig"); const idt = @import("idt_mock.zig"); const multiboot = @import("../../../src/kernel/multiboot.zig"); +const paging = @import("paging_mock.zig"); const mock_framework = @import("mock_framework.zig"); pub const initTest = mock_framework.initTest; @@ -35,6 +36,8 @@ pub const InterruptContext = struct { ss: u32, }; +pub const MEMORY_BLOCK_SIZE = paging.PAGE_SIZE_4KB; + pub fn outb(port: u16, data: u8) void { return mock_framework.performAction("outb", void, .{ port, data }); } diff --git a/test/mock/kernel/mem_mock.zig b/test/mock/kernel/mem_mock.zig index 62abeff..c9bbd0a 100644 --- a/test/mock/kernel/mem_mock.zig +++ b/test/mock/kernel/mem_mock.zig @@ -7,6 +7,7 @@ pub const MemProfile = struct { physaddr_start: [*]u8, mem_kb: u32, fixed_alloc_size: u32, + mem_map: []multiboot.multiboot_memory_map_t, }; // The virtual/physical start/end of the kernel code @@ -28,5 +29,6 @@ pub fn init(mb_info: *multiboot.multiboot_info_t) MemProfile { // Total memory available including the initial 1MiB that grub doesn't include .mem_kb = mb_info.mem_upper + mb_info.mem_lower + 1024, .fixed_alloc_size = FIXED_ALLOC_SIZE, + .mem_map = undefined, }; } diff --git a/test/mock/kernel/paging_mock.zig b/test/mock/kernel/paging_mock.zig new file mode 100644 index 0000000..002f9a8 --- /dev/null +++ b/test/mock/kernel/paging_mock.zig @@ -0,0 +1 @@ +pub const PAGE_SIZE_4KB = 4096; diff --git a/test/rt-test.py b/test/rt-test.py index 30aea24..49d7a54 100644 --- a/test/rt-test.py +++ b/test/rt-test.py @@ -36,6 +36,8 @@ def get_pre_archinit_cases(): TestCase("Log warning tests", [r"Test WARNING level", r"Test WARNING level with args a, 1", r"Test WARNING function", r"Test WARNING function with args a, 1"], "\[WARNING\] "), TestCase("Log error tests", [r"Test ERROR level", r"Test ERROR level with args a, 1", r"Test ERROR function", r"Test ERROR function with args a, 1"], "\[ERROR\] "), TestCase("Mem init", [r"Init mem", r"Done"]), + TestCase("PMM init", [r"Init pmm", r"Done"]), + TestCase("PMM tests", [r"PMM: Tested allocation"]), TestCase("Arch init starts", [r"Init arch \w+"]) ]