Move to multi arch support and added memory map
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
//! x86_64 CPU operations. This is the "arch" module: the generic kernel imports
|
||||
//! it as `@import("arch")` and never names x86_64 directly, so a second
|
||||
//! architecture is added by pointing that module at a different directory in
|
||||
//! build.zig — no change to the generic code. Keep everything CPU-specific here
|
||||
//! (halt now; GDT, IDT and paging will join it), and nothing generic.
|
||||
|
||||
/// Park the core forever. `hlt` drops it into a low-power idle until the next
|
||||
/// interrupt; the loop re-halts on every wake so the stop is permanent. See
|
||||
/// docs/halting.md for the full reasoning.
|
||||
pub fn halt() noreturn {
|
||||
while (true) asm volatile ("hlt");
|
||||
}
|
||||
+70
-10
@@ -5,6 +5,7 @@ const danos = @import("danos");
|
||||
const BootInfo = danos.BootInfo;
|
||||
const GraphicsOutput = uefi.protocol.GraphicsOutput;
|
||||
const EdidActive = uefi.protocol.edid.Active;
|
||||
const MemoryMapSlice = uefi.tables.MemoryMapSlice;
|
||||
|
||||
/// Name of the kernel ELF on the boot volume (installed to the ESP root by
|
||||
/// build.zig). UEFI wants a UTF-16, null-terminated path.
|
||||
@@ -34,12 +35,13 @@ fn boot() !noreturn {
|
||||
// services, since afterwards none of these calls are usable.
|
||||
var boot_info: BootInfo = .{
|
||||
.framebuffer = try queryFramebuffer(bs),
|
||||
.memory_map = undefined, // filled by exitBootServices, just below
|
||||
};
|
||||
|
||||
const entry = try loadKernel(bs);
|
||||
|
||||
log("danos: kernel loaded, exiting boot services\r\n");
|
||||
try exitBootServices(bs);
|
||||
boot_info.memory_map = try exitBootServices(bs);
|
||||
|
||||
// Hand control to the kernel. `danos.kernel_abi` is SysV, so the pointer is
|
||||
// passed in RDI as the kernel expects — not RCX, which this UEFI binary's
|
||||
@@ -217,27 +219,85 @@ fn loadElf(bs: *uefi.tables.BootServices, image: []u8) !usize {
|
||||
return @intCast(ehdr.e_entry);
|
||||
}
|
||||
|
||||
/// Fetch the memory map and exit boot services. Allocating the map buffer can
|
||||
/// itself change the map (invalidating the key), so retry until it takes.
|
||||
fn exitBootServices(bs: *uefi.tables.BootServices) !void {
|
||||
/// Fetch the memory map, exit boot services, and hand back the map in danos's
|
||||
/// neutral form. Allocating the buffers can itself change the map (invalidating
|
||||
/// the key), so retry until it takes. Both buffers are LoaderData, which survives
|
||||
/// the exit, so the returned map stays valid for the kernel.
|
||||
fn exitBootServices(bs: *uefi.tables.BootServices) !danos.MemoryMap {
|
||||
var attempts: usize = 0;
|
||||
while (attempts < 8) : (attempts += 1) {
|
||||
const info = try bs.getMemoryMapInfo();
|
||||
// Spare descriptors to absorb the growth from the allocatePool below.
|
||||
const buf = try bs.allocatePool(.loader_data, (info.len + 8) * info.descriptor_size);
|
||||
const map = bs.getMemoryMap(buf) catch {
|
||||
_ = bs.freePool(buf.ptr) catch {};
|
||||
// Spare descriptors to absorb the growth from the allocations below.
|
||||
const cap = info.len + 8;
|
||||
const map_buf = try bs.allocatePool(.loader_data, cap * info.descriptor_size);
|
||||
const regions_buf = try bs.allocatePool(.loader_data, cap * @sizeOf(danos.MemoryRegion));
|
||||
const map = bs.getMemoryMap(map_buf) catch {
|
||||
_ = bs.freePool(map_buf.ptr) catch {};
|
||||
_ = bs.freePool(regions_buf.ptr) catch {};
|
||||
continue;
|
||||
};
|
||||
bs.exitBootServices(uefi.handle, map.info.key) catch {
|
||||
_ = bs.freePool(buf.ptr) catch {};
|
||||
_ = bs.freePool(map_buf.ptr) catch {};
|
||||
_ = bs.freePool(regions_buf.ptr) catch {};
|
||||
continue;
|
||||
};
|
||||
return; // Boot services are gone; do not touch `bs` again.
|
||||
// Boot services are gone; do not touch `bs` again. Converting the map is
|
||||
// pure computation on memory we already hold, so it's safe here.
|
||||
return convertMemoryMap(map, regions_buf);
|
||||
}
|
||||
return error.ExitBootServicesFailed;
|
||||
}
|
||||
|
||||
/// Translate UEFI's memory map into danos's neutral `MemoryRegion` array, written
|
||||
/// into `out` (sized for at least `map.info.len` regions). Adjacent regions of
|
||||
/// the same kind are coalesced. This is the loader's job precisely so the kernel
|
||||
/// never sees UEFI's vocabulary — the same seam the framebuffer already uses.
|
||||
fn convertMemoryMap(map: MemoryMapSlice, out: []u8) danos.MemoryMap {
|
||||
const regions: [*]danos.MemoryRegion = @ptrCast(@alignCast(out.ptr));
|
||||
var count: usize = 0;
|
||||
var i: usize = 0;
|
||||
while (i < map.info.len) : (i += 1) {
|
||||
// Stride by descriptor_size, NOT @sizeOf — firmware descriptors may be
|
||||
// larger than the struct.
|
||||
const d: *const uefi.tables.MemoryDescriptor =
|
||||
@ptrCast(@alignCast(map.ptr + i * map.info.descriptor_size));
|
||||
if (d.number_of_pages == 0) continue;
|
||||
const kind = classify(d.@"type");
|
||||
|
||||
// Coalesce with the previous region if it's the same kind and contiguous.
|
||||
if (count > 0) {
|
||||
const prev = ®ions[count - 1];
|
||||
if (prev.kind == kind and
|
||||
prev.base + prev.pages * danos.page_size == d.physical_start)
|
||||
{
|
||||
prev.pages += d.number_of_pages;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
regions[count] = .{
|
||||
.base = d.physical_start,
|
||||
.pages = d.number_of_pages,
|
||||
.kind = kind,
|
||||
};
|
||||
count += 1;
|
||||
}
|
||||
return .{ .regions = @intFromPtr(regions), .len = count };
|
||||
}
|
||||
|
||||
/// Map a UEFI memory type to danos's neutral kind. Anything we don't explicitly
|
||||
/// recognise is treated as `reserved` — the safe default. Our own loader data
|
||||
/// (the kernel image, these buffers) is LoaderData, which falls here too and so
|
||||
/// stays reserved until the kernel decides to reclaim it.
|
||||
fn classify(t: uefi.tables.MemoryType) danos.MemoryKind {
|
||||
return switch (t) {
|
||||
.conventional_memory => .usable,
|
||||
.boot_services_code, .boot_services_data => .reclaimable,
|
||||
.acpi_reclaim_memory => .acpi_tables,
|
||||
.acpi_memory_nvs => .acpi_nvs,
|
||||
else => .reserved,
|
||||
};
|
||||
}
|
||||
|
||||
/// Write a compile-time string to the console (best effort).
|
||||
fn log(comptime msg: []const u8) void {
|
||||
const out = uefi.system_table.con_out orelse return;
|
||||
|
||||
+14
-8
@@ -1,5 +1,6 @@
|
||||
const std = @import("std");
|
||||
const danos = @import("danos");
|
||||
const arch = @import("arch");
|
||||
const console = @import("console.zig");
|
||||
const BootInfo = danos.BootInfo;
|
||||
|
||||
@@ -33,15 +34,20 @@ fn kmain(boot_info: *const BootInfo) noreturn {
|
||||
con.print(" pitch : {d} bytes\n", .{fb.pitch});
|
||||
con.print(" format : {s}\n", .{@tagName(fb.format)});
|
||||
con.print(" framebuffer: 0x{x:0>16}\n", .{fb.base});
|
||||
|
||||
// Summarise the physical memory the loader handed us. The array is danos's
|
||||
// own MemoryRegion, so this is a plain slice — no firmware layout in sight.
|
||||
const regions = @as([*]const danos.MemoryRegion, @ptrFromInt(boot_info.memory_map.regions))[0..boot_info.memory_map.len];
|
||||
var usable_pages: u64 = 0;
|
||||
for (regions) |r| {
|
||||
if (r.kind == .usable) usable_pages += r.pages;
|
||||
}
|
||||
con.print(" mem regions: {d}\n", .{regions.len});
|
||||
con.print(" usable RAM : {d} MiB\n", .{usable_pages * danos.page_size / (1024 * 1024)});
|
||||
|
||||
con.write("\nkernel initialised; nothing left to do, halting.\n");
|
||||
|
||||
hang();
|
||||
}
|
||||
|
||||
/// Stop the CPU. `hlt` in a loop parks the core at near-zero power until the
|
||||
/// next interrupt; we loop because `hlt` returns when one arrives.
|
||||
fn hang() noreturn {
|
||||
while (true) asm volatile ("hlt");
|
||||
arch.halt();
|
||||
}
|
||||
|
||||
/// Freestanding has no OS to receive a panic. Print it to the console (if it is
|
||||
@@ -55,6 +61,6 @@ pub const panic = std.debug.FullPanic(struct {
|
||||
con.write(msg);
|
||||
con.write("\n");
|
||||
}
|
||||
hang();
|
||||
arch.halt();
|
||||
}
|
||||
}.panic);
|
||||
|
||||
@@ -32,8 +32,49 @@ pub const Framebuffer = extern struct {
|
||||
format: PixelFormat,
|
||||
};
|
||||
|
||||
/// Page size the memory map is measured in. 4 KiB on every architecture danos
|
||||
/// targets so far.
|
||||
pub const page_size = 4096;
|
||||
|
||||
/// danos's own classification of a span of physical memory — deliberately not
|
||||
/// UEFI's vocabulary. Each boot path (UEFI now, device tree later) translates its
|
||||
/// native memory description into these kinds, so the kernel never learns what
|
||||
/// booted it. [[arch]] keeps the same discipline for CPU code.
|
||||
pub const MemoryKind = enum(u32) {
|
||||
/// Free RAM the kernel may allocate.
|
||||
usable,
|
||||
/// Firmware, MMIO, the kernel image, our own boot buffers — never hand out.
|
||||
reserved,
|
||||
/// Usable once the kernel is done with boot-time structures (e.g. UEFI boot
|
||||
/// services memory, which is free after ExitBootServices).
|
||||
reclaimable,
|
||||
/// ACPI tables: parse, then reclaim.
|
||||
acpi_tables,
|
||||
/// ACPI non-volatile storage: preserve across sleep, do not allocate.
|
||||
acpi_nvs,
|
||||
};
|
||||
|
||||
/// One contiguous span of physical memory. Because danos defines this layout
|
||||
/// itself (unlike the UEFI descriptor it's built from), `@sizeOf` is
|
||||
/// authoritative — the kernel walks a plain `[]MemoryRegion`, with none of the
|
||||
/// firmware's variable descriptor-stride to worry about.
|
||||
pub const MemoryRegion = extern struct {
|
||||
base: u64, // physical start address
|
||||
pages: u64, // length in `page_size` units
|
||||
kind: MemoryKind,
|
||||
_pad: u32 = 0,
|
||||
};
|
||||
|
||||
/// The physical memory layout handed to the kernel: a pointer to an array of
|
||||
/// `len` `MemoryRegion`s, in a buffer that outlives the loader.
|
||||
pub const MemoryMap = extern struct {
|
||||
regions: usize, // address of a `[len]MemoryRegion`
|
||||
len: usize,
|
||||
};
|
||||
|
||||
/// Handoff structure the bootloader fills in and passes to the kernel's
|
||||
/// `_start` in RDI (the first argument under the SysV AMD64 C ABI).
|
||||
pub const BootInfo = extern struct {
|
||||
framebuffer: Framebuffer,
|
||||
memory_map: MemoryMap,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user