Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/kernel/arch/x86/arch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const syscalls = @import("syscalls.zig");
const mem = @import("../../mem.zig");
const multiboot = @import("../../multiboot.zig");
const pmm = @import("pmm.zig");
const vmm = @import("../../vmm.zig");
const MemProfile = mem.MemProfile;

/// The interrupt context that is given to a interrupt handler. It contains most of the registers
Expand Down Expand Up @@ -48,6 +49,18 @@ pub const InterruptContext = struct {
ss: u32,
};

/// The type of the payload passed to a virtual memory mapper.
/// For x86 it's the page directory that should be mapped.
pub const VmmPayload = *paging.Directory;

/// The payload used in the kernel virtual memory manager.
/// For x86 it's the kernel's page directory.
pub const KERNEL_VMM_PAYLOAD = &paging.kernel_directory;

/// The architecture's virtual memory mapper.
/// For x86, it simply forwards the calls to the paging subsystem.
pub const VMM_MAPPER: vmm.Mapper(VmmPayload) = vmm.Mapper(VmmPayload){ .mapFn = paging.map, .unmapFn = paging.unmap };

/// The size of each allocatable block of memory, normally set to the page size.
pub const MEMORY_BLOCK_SIZE = paging.PAGE_SIZE_4KB;

Expand Down
341 changes: 203 additions & 138 deletions src/kernel/arch/x86/paging.zig

Large diffs are not rendered by default.

96 changes: 92 additions & 4 deletions src/kernel/bitmap.zig
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,82 @@ pub fn Bitmap(comptime BitmapType: type) type {
/// 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 self: *const 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 {
fn indexToBit(self: *const Self, idx: u32) BitmapType {
return @as(BitmapType, 1) << @intCast(IndexType, idx % ENTRIES_PER_BITMAP);
}

///
/// Find a number of contiguous free entries and set them.
///
/// Arguments:
/// INOUT self: *Self - The bitmap to modify.
/// IN num: u32 - The number of entries to set.
///
/// Return: ?u32
/// The first entry set or null if there weren't enough contiguous entries.
///
pub fn setContiguous(self: *Self, num: u32) ?u32 {
if (num > self.num_free_entries) {
return null;
}

var count: u32 = 0;
var start: ?u32 = null;
for (self.bitmaps) |bmp, i| {
var bit: IndexType = 0;
while (true) {
const entry = bit + @intCast(u32, i * ENTRIES_PER_BITMAP);
if (entry >= self.num_entries) {
return null;
}
if ((bmp & @as(u32, 1) << bit) != 0) {
// This is a one so clear the progress
count = 0;
start = null;
} else {
// It's a zero so increment the count
count += 1;
if (start == null) {
// Start of the contiguous zeroes
start = entry;
}
if (count == num) {
// Reached the desired number
break;
}
}
// Avoiding overflow by checking if bit is less than the max - 1
if (bit < ENTRIES_PER_BITMAP - 1) {
bit += 1;
} else {
// Reached the end of the bitmap
break;
}
}
if (count == num) {
break;
}
}

if (count == num) {
if (start) |start_entry| {
var i: u32 = 0;
while (i < num) : (i += 1) {
// Can't fail as the entry was found to be free
self.setEntry(start_entry + i) catch unreachable;
}
return start_entry;
}
}
return null;
}

///
/// Set the first free entry within the bitmaps as occupied.
///
Expand All @@ -140,7 +206,7 @@ pub fn Bitmap(comptime BitmapType: type) type {
/// Check if an entry is set.
///
/// Arguments:
/// IN self: *Bitmap - The bitmap to check.
/// IN self: *const Self - The bitmap to check.
/// IN idx: u32 - The entry to check.
///
/// Return: bool.
Expand All @@ -149,7 +215,7 @@ pub fn Bitmap(comptime BitmapType: type) type {
/// Error: BitmapError.
/// OutOfBounds: The index given is out of bounds.
///
pub fn isSet(self: *Self, idx: u32) BitmapError!bool {
pub fn isSet(self: *const Self, idx: u32) BitmapError!bool {
if (idx >= self.num_entries) return BitmapError.OutOfBounds;
return (self.bitmaps[idx / ENTRIES_PER_BITMAP] & self.indexToBit(idx)) != 0;
}
Expand Down Expand Up @@ -304,3 +370,25 @@ test "indexToBit" {
testing.expectEqual(bmp.indexToBit(8), 1);
testing.expectEqual(bmp.indexToBit(9), 2);
}

test "setContiguous" {
var bmp = try Bitmap(u4).init(15, std.heap.page_allocator);
// Test trying to set more entries than the bitmap has
testing.expectEqual(bmp.setContiguous(bmp.num_entries + 1), null);
// All entries should still be free
testing.expectEqual(bmp.num_free_entries, bmp.num_entries);

testing.expectEqual(bmp.setContiguous(3) orelse unreachable, 0);
testing.expectEqual(bmp.setContiguous(4) orelse unreachable, 3);
// 0b0000.0000.0111.1111
bmp.bitmaps[2] |= 2;
// 0b0000.0010.0111.1111
testing.expectEqual(bmp.setContiguous(3) orelse unreachable, 10);
// 0b0001.1110.0111.1111
testing.expectEqual(bmp.setContiguous(5), null);
testing.expectEqual(bmp.setContiguous(2), 7);
// 0b001.1111.1111.1111
// Test trying to set beyond the end of the bitmaps
testing.expectEqual(bmp.setContiguous(3), null);
testing.expectEqual(bmp.setContiguous(2), 13);
}
14 changes: 11 additions & 3 deletions src/kernel/kmain.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const vga = @import("vga.zig");
const log = @import("log.zig");
const serial = @import("serial.zig");
const pmm = @import("pmm.zig");
const vmm = if (is_test) @import(mock_path ++ "vmm_mock.zig") else @import("vmm.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");
Expand All @@ -23,6 +24,9 @@ comptime {
}
}

/// The virtual memory manager associated with the kernel address space
var kernel_vmm: vmm.VirtualMemoryManager(arch.VmmPayload) = undefined;

// This is for unit testing as we need to export KERNEL_ADDR_OFFSET as it is no longer available
// from the linker script
export var KERNEL_ADDR_OFFSET: u32 = if (builtin.is_test) 0xC0000000 else undefined;
Expand All @@ -46,13 +50,17 @@ 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);

panic_root.init(&mem_profile, &fixed_allocator.allocator) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise panic: {}", .{e});
};

pmm.init(&mem_profile, &fixed_allocator.allocator);
kernel_vmm = vmm.init(&mem_profile, mb_info, &fixed_allocator.allocator) catch |e| panic_root.panic(@errorReturnTrace(), "Failed to initialise kernel VMM: {}", .{e});

log.logInfo("Init arch " ++ @tagName(builtin.arch) ++ "\n", .{});
arch.init(mb_info, &mem_profile, &fixed_allocator.allocator);
log.logInfo("Arch init done\n", .{});
panic_root.init(&mem_profile, &fixed_allocator.allocator) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise panic: {}", .{e});
};

vga.init();
tty.init();

Expand Down
4 changes: 2 additions & 2 deletions src/kernel/mem.zig
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ var ADDR_OFFSET: usize = undefined;
/// Return: @TypeOf(virt)
/// The physical address.
///
pub inline fn virtToPhys(virt: var) @TypeOf(virt) {
pub fn virtToPhys(virt: var) @TypeOf(virt) {
const T = @TypeOf(virt);
return switch (@typeInfo(T)) {
.Pointer => @intToPtr(T, @ptrToInt(virt) - ADDR_OFFSET),
Expand All @@ -80,7 +80,7 @@ pub inline fn virtToPhys(virt: var) @TypeOf(virt) {
/// Return: @TypeOf(virt)
/// The virtual address.
///
pub inline fn physToVirt(phys: var) @TypeOf(phys) {
pub fn physToVirt(phys: var) @TypeOf(phys) {
const T = @TypeOf(phys);
return switch (@typeInfo(T)) {
.Pointer => @intToPtr(T, @ptrToInt(phys) + ADDR_OFFSET),
Expand Down
47 changes: 30 additions & 17 deletions src/kernel/pmm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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 log = if (is_test) @import(mock_path ++ "log_mock.zig") else @import("log.zig");
const MEMORY_AVAILABLE = @import("multiboot.zig").MULTIBOOT_MEMORY_AVAILABLE;
const Bitmap = @import("bitmap.zig").Bitmap;

Expand All @@ -19,7 +19,7 @@ const PmmError = error{
};

/// The size of memory associated with each bitmap entry
const BLOCK_SIZE = arch.MEMORY_BLOCK_SIZE;
pub const BLOCK_SIZE = arch.MEMORY_BLOCK_SIZE;

var bitmap: PmmBitmap = undefined;

Expand All @@ -32,7 +32,7 @@ var bitmap: PmmBitmap = undefined;
/// Error: PmmBitmap.BitmapError.
/// *: See PmmBitmap.setEntry. Could occur if the address is out of bounds.
///
fn setAddr(addr: usize) PmmBitmap.BitmapError!void {
pub fn setAddr(addr: usize) PmmBitmap.BitmapError!void {
try bitmap.setEntry(@intCast(u32, addr / BLOCK_SIZE));
}

Expand All @@ -47,7 +47,7 @@ fn setAddr(addr: usize) PmmBitmap.BitmapError!void {
/// Error: PmmBitmap.BitmapError.
/// *: See PmmBitmap.setEntry. Could occur if the address is out of bounds.
///
fn isSet(addr: usize) PmmBitmap.BitmapError!bool {
pub fn isSet(addr: usize) PmmBitmap.BitmapError!bool {
return bitmap.isSet(@intCast(u32, addr / BLOCK_SIZE));
}

Expand Down Expand Up @@ -83,6 +83,15 @@ pub fn free(addr: usize) (PmmBitmap.BitmapError || PmmError)!void {
}

///
/// Get the number of unallocated blocks of memory.
///
/// Return: u32.
/// The number of unallocated blocks of memory
///
pub fn blocksFree() u32 {
return bitmap.num_free_entries;
}

/// Intiialise the physical memory manager and set all unavailable regions as occupied (those from the memory map and those from the linker symbols).
///
/// Arguments:
Expand Down Expand Up @@ -112,27 +121,24 @@ pub fn init(mem: *const MemProfile, allocator: *std.mem.Allocator) void {
}
}
}
// 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 }),
};
}

if (build_options.rt_test) runtimeTests(mem);
if (build_options.rt_test) {
runtimeTests(mem, allocator);
}
}

///
/// 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.
/// INOUT allocator: *std.mem.Allocator - The allocator to use when needing to create intermediate structures used for testing
///
fn runtimeTests(mem: *const MemProfile) void {
fn runtimeTests(mem: *const MemProfile, allocator: *std.mem.Allocator) void {
// Make sure that occupied memory can't be allocated
var prev_alloc: usize = std.math.maxInt(usize);
var alloc_list = std.ArrayList(usize).init(allocator);
defer alloc_list.deinit();
while (alloc()) |alloced| {
if (prev_alloc == alloced) {
panic(null, "PMM allocated the same address twice: 0x{x}", .{alloced});
Expand All @@ -146,9 +152,11 @@ fn runtimeTests(mem: *const MemProfile) void {
}
}
}
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});
}
alloc_list.append(alloced) catch |e| panic(@errorReturnTrace(), "Failed to add PMM allocation to list: {}", .{e});
}
// Clean up
for (alloc_list.items) |alloced| {
free(alloced) catch |e| panic(@errorReturnTrace(), "Failed freeing allocation in PMM rt test: {}", .{e});
}
log.logInfo("PMM: Tested allocation\n", .{});
}
Expand All @@ -165,6 +173,7 @@ test "alloc" {
testing.expect(!(try isSet(addr)));
testing.expect(alloc().? == addr);
testing.expect(try isSet(addr));
testing.expectEqual(blocksFree(), 31 - i);
}
// Allocation should now fail
testing.expect(alloc() == null);
Expand All @@ -177,7 +186,9 @@ test "free" {
inline while (i < 32) : (i += 1) {
const addr = alloc().?;
testing.expect(try isSet(addr));
testing.expectEqual(blocksFree(), 31);
try free(addr);
testing.expectEqual(blocksFree(), 32);
testing.expect(!(try isSet(addr)));
// Double frees should be caught
testing.expectError(PmmError.NotAllocated, free(addr));
Expand All @@ -203,9 +214,11 @@ test "setAddr and isSet" {
testing.expect(try isSet(addr2));
}

testing.expectEqual(blocksFree(), num_entries - i);
// Set the current block
try setAddr(addr);
testing.expect(try isSet(addr));
testing.expectEqual(blocksFree(), num_entries - i - 1);

// Ensure all successive entries are not set
var j: u32 = i + 1;
Expand Down
Loading