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
4 changes: 4 additions & 0 deletions src/kernel/arch/x86/arch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
///
Expand Down
12 changes: 6 additions & 6 deletions src/kernel/arch/x86/paging.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
///
Expand Down
306 changes: 306 additions & 0 deletions src/kernel/bitmap.zig
Original file line number Diff line number Diff line change
@@ -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| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bmp doesn't need to be a reference because you don't updates its value. Unless I'm wrong

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);
}
2 changes: 2 additions & 0 deletions src/kernel/kmain.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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", .{});
Expand Down
Loading