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
103 changes: 102 additions & 1 deletion src/kernel/arch/x86/arch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,28 @@ const rtc = @import("rtc.zig");
const paging = @import("paging.zig");
const syscalls = @import("syscalls.zig");
const mem = @import("../../mem.zig");
const multiboot = @import("../../multiboot.zig");
const multiboot = @import("multiboot.zig");
const pmm = @import("pmm.zig");
const vmm = @import("../../vmm.zig");
const log = @import("../../log.zig");
const tty = @import("../../tty.zig");
const MemProfile = mem.MemProfile;

/// The virtual end of the kernel code
extern var KERNEL_VADDR_END: *u32;

/// The virtual start of the kernel code
extern var KERNEL_VADDR_START: *u32;

/// The physical end of the kernel code
extern var KERNEL_PHYSADDR_END: *u32;

/// The physical start of the kernel code
extern var KERNEL_PHYSADDR_START: *u32;

/// The boot-time offset that the virtual addresses are from the physical addresses
extern var KERNEL_ADDR_OFFSET: *u32;

/// The interrupt context that is given to a interrupt handler. It contains most of the registers
/// and the interrupt number and error code (if there is one).
pub const InterruptContext = struct {
Expand Down Expand Up @@ -49,6 +66,9 @@ pub const InterruptContext = struct {
ss: u32,
};

/// x86's boot payload is the multiboot info passed by grub
pub const BootPayload = *multiboot.multiboot_info_t;

/// 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;
Expand Down Expand Up @@ -231,6 +251,87 @@ pub fn haltNoInterrupts() noreturn {
}
}

///
/// Initialise the system's memory. Populates a memory profile with boot modules from grub, the amount of available memory, the reserved regions of virtual and physical memory as well as the start and end of the kernel code
///
/// Arguments:
/// IN mb_info: *multiboot.multiboot_info_t - The multiboot info passed by grub
///
/// Return: mem.MemProfile
/// The constructed memory profile
///
/// Error: std.mem.Allocator.Error
/// std.mem.Allocator.Error.OutOfMemory - There wasn't enough memory in the allocated created to populate the memory profile, consider increasing mem.FIXED_ALLOC_SIZE
///
pub fn initMem(mb_info: BootPayload) std.mem.Allocator.Error!MemProfile {
log.logInfo("Init mem\n", .{});
defer log.logInfo("Done mem\n", .{});

const mods_count = mb_info.mods_count;
mem.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 vaddr_end = @ptrCast([*]u8, &KERNEL_VADDR_END);

var allocator = std.heap.FixedBufferAllocator.init(vaddr_end[0..mem.FIXED_ALLOC_SIZE]);
var reserved_physical_mem = std.ArrayList(mem.Range).init(&allocator.allocator);
var reserved_virtual_mem = std.ArrayList(mem.Map).init(&allocator.allocator);
const mem_map = @intToPtr([*]multiboot.multiboot_memory_map_t, mmap_addr)[0..num_mmap_entries];

// Reserve the unavailable sections from the multiboot memory map
for (mem_map) |entry| {
if (entry.@"type" != multiboot.MULTIBOOT_MEMORY_AVAILABLE) {
// If addr + len is greater than maxInt(usize) just ignore whatever comes after maxInt(usize) since it can't be addressed anyway
const end: usize = if (entry.addr > std.math.maxInt(usize) - entry.len) std.math.maxInt(usize) else @intCast(usize, entry.addr + entry.len);
try reserved_physical_mem.append(.{ .start = @intCast(usize, entry.addr), .end = end });
}
}
// Map the multiboot info struct itself
const mb_region = mem.Range{
.start = @ptrToInt(mb_info),
.end = @ptrToInt(mb_info) + @sizeOf(multiboot.multiboot_info_t),
};
const mb_physical = mem.Range{ .start = mem.virtToPhys(mb_region.start), .end = mem.virtToPhys(mb_region.end) };
try reserved_virtual_mem.append(.{ .virtual = mb_region, .physical = mb_physical });

// Map the tty buffer
const tty_addr = mem.virtToPhys(tty.getVideoBufferAddress());
const tty_region = mem.Range{
.start = tty_addr,
.end = tty_addr + 32 * 1024,
};
try reserved_virtual_mem.append(.{
.physical = tty_region,
.virtual = .{
.start = mem.physToVirt(tty_region.start),
.end = mem.physToVirt(tty_region.end),
},
});

// Map the boot modules
const boot_modules = @intToPtr([*]multiboot.multiboot_mod_list, mem.physToVirt(mb_info.mods_addr))[0..mods_count];
var modules = std.ArrayList(mem.Module).init(&allocator.allocator);
for (boot_modules) |module| {
const virtual = mem.Range{ .start = mem.physToVirt(module.mod_start), .end = mem.physToVirt(module.mod_end) };
const physical = mem.Range{ .start = module.mod_start, .end = module.mod_end };
try modules.append(.{ .region = virtual, .name = std.mem.span(mem.physToVirt(@intToPtr([*:0]u8, module.cmdline))) });
try reserved_virtual_mem.append(.{ .physical = physical, .virtual = virtual });
}

return MemProfile{
.vaddr_end = vaddr_end,
.vaddr_start = @ptrCast([*]u8, &KERNEL_VADDR_START),
.physaddr_end = @ptrCast([*]u8, &KERNEL_PHYSADDR_END),
.physaddr_start = @ptrCast([*]u8, &KERNEL_PHYSADDR_START),
// Total memory available including the initial 1MiB that grub doesn't include
.mem_kb = mb_info.mem_upper + mb_info.mem_lower + 1024,
.modules = modules.items,
.physical_reserved = reserved_physical_mem.items,
.virtual_reserved = reserved_virtual_mem.items,
.fixed_allocator = allocator,
};
}

///
/// Initialise the architecture
///
Expand Down
3 changes: 1 addition & 2 deletions src/kernel/arch/x86/boot.zig
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,9 @@ export fn start_higher_half() callconv(.Naked) noreturn {
\\xor %%ebp, %%ebp
);

// Push the bootloader magic number and multiboot header address with virtual offset
// Push the multiboot header address with virtual offset
asm volatile (
\\.extern KERNEL_ADDR_OFFSET
\\push %%eax
\\add $KERNEL_ADDR_OFFSET, %%ebx
\\push %%ebx
);
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions src/kernel/arch/x86/paging.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const tty = @import("../../tty.zig");
const log = @import("../../log.zig");
const mem = @import("../../mem.zig");
const vmm = @import("../../vmm.zig");
const multiboot = @import("../../multiboot.zig");
const multiboot = @import("multiboot.zig");
const options = @import("build_options");
const testing = std.testing;

Expand Down Expand Up @@ -404,7 +404,7 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile
:
: [addr] "{eax}" (dir_physaddr)
);
const v_end = std.mem.alignForward(@ptrToInt(mem_profile.vaddr_end) + mem_profile.fixed_alloc_size, PAGE_SIZE_4KB);
const v_end = std.mem.alignForward(@ptrToInt(mem_profile.vaddr_end) + mem.FIXED_ALLOC_SIZE, PAGE_SIZE_4KB);
if (options.rt_test) runtimeTests(v_end);
}

Expand Down
65 changes: 30 additions & 35 deletions src/kernel/kmain.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const is_test = builtin.is_test;
const build_options = @import("build_options");
const mock_path = build_options.mock_path;
const arch = @import("arch.zig").internals;
const multiboot = @import("multiboot.zig");
const tty = @import("tty.zig");
const vga = @import("vga.zig");
const log = @import("log.zig");
Expand Down Expand Up @@ -38,46 +37,42 @@ pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn
panic_root.panic(error_return_trace, "{}", .{msg});
}

export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void {
if (mb_magic == multiboot.MULTIBOOT_BOOTLOADER_MAGIC) {
// Booted with compatible bootloader
serial.init(serial.DEFAULT_BAUDRATE, serial.Port.COM1) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise serial: {}", .{e});
};
export fn kmain(boot_payload: arch.BootPayload) void {
serial.init(serial.DEFAULT_BAUDRATE, serial.Port.COM1) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise serial: {}", .{e});
};

if (build_options.rt_test) log.runtimeTests();
if (build_options.rt_test) log.runtimeTests();

const mem_profile = mem.init(mb_info);
var buffer = mem_profile.vaddr_end[0..mem_profile.fixed_alloc_size];
var fixed_allocator = std.heap.FixedBufferAllocator.init(buffer);
const mem_profile = arch.initMem(boot_payload) catch |e| panic_root.panic(@errorReturnTrace(), "Failed to initialise memory profile: {}", .{e});
var fixed_allocator = mem_profile.fixed_allocator;

panic_root.init(&mem_profile, &fixed_allocator.allocator) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise panic: {}", .{e});
};
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});
pmm.init(&mem_profile, &fixed_allocator.allocator);
kernel_vmm = vmm.init(&mem_profile, &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", .{});
log.logInfo("Init arch " ++ @tagName(builtin.arch) ++ "\n", .{});
arch.init(boot_payload, &mem_profile, &fixed_allocator.allocator);
log.logInfo("Arch init done\n", .{});

vga.init();
tty.init();
// Give the kernel heap 10% of the available memory. This can be fine-tuned as time goes on.
var heap_size = mem_profile.mem_kb / 10 * 1024;
// The heap size must be a power of two so find the power of two smaller than or equal to the heap_size
if (!std.math.isPowerOfTwo(heap_size)) {
heap_size = std.math.floorPowerOfTwo(usize, heap_size);
}
var kernel_heap = heap.init(arch.VmmPayload, &kernel_vmm, vmm.Attributes{ .kernel = true, .writable = true, .cachable = true }, heap_size, &fixed_allocator.allocator) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise kernel heap: {}\n", .{e});
};
log.logInfo("Init done\n", .{});
vga.init();
tty.init();
// Give the kernel heap 10% of the available memory. This can be fine-tuned as time goes on.
var heap_size = mem_profile.mem_kb / 10 * 1024;
// The heap size must be a power of two so find the power of two smaller than or equal to the heap_size
if (!std.math.isPowerOfTwo(heap_size)) {
heap_size = std.math.floorPowerOfTwo(usize, heap_size);
}
var kernel_heap = heap.init(arch.VmmPayload, &kernel_vmm, vmm.Attributes{ .kernel = true, .writable = true, .cachable = true }, heap_size, &fixed_allocator.allocator) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise kernel heap: {}\n", .{e});
};
log.logInfo("Init done\n", .{});

tty.print("Hello Pluto from kernel :)\n", .{});
tty.print("Hello Pluto from kernel :)\n", .{});

// The panic runtime tests must run last as they never return
if (options.rt_test) panic_root.runtimeTests();
}
// The panic runtime tests must run last as they never return
if (options.rt_test) panic_root.runtimeTests();
}
88 changes: 33 additions & 55 deletions src/kernel/mem.zig
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
const multiboot = @import("multiboot.zig");
const std = @import("std");
const expectEqual = std.testing.expectEqual;
const log = @import("log.zig");

pub const Module = struct {
/// The region of memory occupied by the module
region: Range,
/// The module's name
name: []const u8,
};

pub const Map = struct {
/// The virtual range to reserve
virtual: Range,
/// The physical range to map to, if any
physical: ?Range,
};

/// A range of memory
pub const Range = struct {
/// The start of the range, inclusive
start: usize,
/// The end of the range, exclusive
end: usize,
};

pub const MemProfile = struct {
/// The virtual end address of the kernel code.
vaddr_end: [*]u8,
Expand All @@ -19,39 +40,26 @@ pub const MemProfile = struct {
/// The amount of memory in the system, in kilobytes.
mem_kb: usize,

/// The size of the fixed buffer allocator used as the first memory allocator.
fixed_alloc_size: usize,

/// 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
extern var KERNEL_VADDR_END: *u32;
/// The modules loaded into memory at boot.
modules: []Module,

/// The virtual start of the kernel code
extern var KERNEL_VADDR_START: *u32;
/// The virtual regions of reserved memory. Should not include what is tracked by the vaddr_* fields but should include the regions occupied by the modules. These are reserved and mapped by the VMM
virtual_reserved: []Map,

/// The physical end of the kernel code
extern var KERNEL_PHYSADDR_END: *u32;
/// The phsyical regions of reserved memory. Should not include what is tracked by the physaddr_* fields but should include the regions occupied by the modules. These are reserved by the PMM
physical_reserved: []Range,

/// The physical start of the kernel code
extern var KERNEL_PHYSADDR_START: *u32;

/// The boot-time offset that the virtual addresses are from the physical addresses
extern var KERNEL_ADDR_OFFSET: *u32;
/// The allocator to use before a heap can be set up.
fixed_allocator: std.heap.FixedBufferAllocator,
};

/// The size of the fixed allocator used before the heap is set up. Set to 1MiB.
const FIXED_ALLOC_SIZE: usize = 1024 * 1024;
pub const FIXED_ALLOC_SIZE: usize = 1024 * 1024;

/// The kernel's virtual address offset. It's assigned in the init function and this file's tests.
/// We can't just use KERNEL_ADDR_OFFSET since using externs in the virtToPhys test is broken in
/// release-safe. This is a workaround until that is fixed.
var ADDR_OFFSET: usize = undefined;
pub var ADDR_OFFSET: usize = undefined;

///
/// Convert a virtual address to its physical counterpart by subtracting the kernel virtual offset from the virtual address.
Expand Down Expand Up @@ -89,36 +97,6 @@ pub fn physToVirt(phys: var) @TypeOf(phys) {
};
}

///
/// Initialise the system's memory profile based on linker symbols and the multiboot info struct.
///
/// Arguments:
/// IN mb_info: *multiboot.multiboot_info_t - The multiboot info passed by the bootloader.
///
/// Return: MemProfile
/// The memory profile constructed from the exported linker symbols and the relevant multiboot info.
///
pub fn init(mb_info: *multiboot.multiboot_info_t) MemProfile {
log.logInfo("Init mem\n", .{});
defer log.logInfo("Done mem\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);
return .{
.vaddr_end = @ptrCast([*]u8, &KERNEL_VADDR_END),
.vaddr_start = @ptrCast([*]u8, &KERNEL_VADDR_START),
.physaddr_end = @ptrCast([*]u8, &KERNEL_PHYSADDR_END),
.physaddr_start = @ptrCast([*]u8, &KERNEL_PHYSADDR_START),
// 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,
.boot_modules = @intToPtr([*]multiboot.multiboot_mod_list, physToVirt(@intCast(usize, mb_info.mods_addr)))[0..mods_count],
.mem_map = @intToPtr([*]multiboot.multiboot_memory_map_t, mmap_addr)[0..num_mmap_entries],
};
}

test "physToVirt" {
ADDR_OFFSET = 0xC0000000;
const offset: usize = ADDR_OFFSET;
Expand Down
12 changes: 5 additions & 7 deletions src/kernel/panic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -281,15 +281,14 @@ pub fn init(mem_profile: *const mem.MemProfile, allocator: *std.mem.Allocator) !
defer log.logInfo("Done panic\n", .{});

// Exit if we haven't loaded all debug modules
if (mem_profile.boot_modules.len < 1)
if (mem_profile.modules.len < 1)
return;
var kmap_start: usize = 0;
var kmap_end: usize = 0;
for (mem_profile.boot_modules) |module| {
const mod_start = mem.physToVirt(@intCast(usize, module.mod_start));
const mod_end = mem.physToVirt(@intCast(usize, module.mod_end) - 1);
const mod_str_ptr = mem.physToVirt(@intToPtr([*:0]u8, module.cmdline));
if (std.mem.eql(u8, std.mem.span(mod_str_ptr), "kernel.map")) {
for (mem_profile.modules) |module| {
const mod_start = module.region.start;
const mod_end = module.region.end - 1;
if (std.mem.eql(u8, module.name, "kernel.map")) {
kmap_start = mod_start;
kmap_end = mod_end;
break;
Expand All @@ -302,7 +301,6 @@ pub fn init(mem_profile: *const mem.MemProfile, allocator: *std.mem.Allocator) !

var syms = SymbolMap.init(allocator);
errdefer syms.deinit();
var file_index = kmap_start;
var kmap_ptr = @intToPtr([*]u8, kmap_start);
while (@ptrToInt(kmap_ptr) < kmap_end - 1) {
const entry = try parseMapEntry(&kmap_ptr, @intToPtr(*const u8, kmap_end));
Expand Down
Loading