added physical memory manager
This commit is contained in:
+25
-9
@@ -32,10 +32,11 @@ Defined in `src/root.zig`, the shared loader↔kernel contract:
|
|||||||
```zig
|
```zig
|
||||||
pub const MemoryKind = enum(u32) {
|
pub const MemoryKind = enum(u32) {
|
||||||
usable, // free RAM the kernel may allocate
|
usable, // free RAM the kernel may allocate
|
||||||
reserved, // firmware / MMIO / kernel image — never hand out
|
reserved, // firmware / kernel image — real RAM, but never hand out
|
||||||
reclaimable, // usable once boot-time structures are done with
|
reclaimable, // usable once boot-time structures are done with
|
||||||
acpi_tables, // parse, then reclaim
|
acpi_tables, // parse, then reclaim
|
||||||
acpi_nvs, // preserve across sleep
|
acpi_nvs, // preserve across sleep
|
||||||
|
mmio, // device registers / reserved address space — not RAM at all
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const MemoryRegion = extern struct {
|
pub const MemoryRegion = extern struct {
|
||||||
@@ -70,12 +71,20 @@ pub const BootInfo = extern struct {
|
|||||||
|
|
||||||
Two functions in `src/efi.zig`, called from `exitBootServices`:
|
Two functions in `src/efi.zig`, called from `exitBootServices`:
|
||||||
|
|
||||||
- **`classify`** maps each UEFI memory type to a `MemoryKind`:
|
- **`classify`** maps each UEFI descriptor to a `MemoryKind`:
|
||||||
`conventional_memory → usable`; `boot_services_code`/`boot_services_data →
|
`conventional_memory → usable`; `boot_services_code`/`boot_services_data →
|
||||||
reclaimable` (free once we've exited); `acpi_reclaim_memory → acpi_tables`;
|
reclaimable` (free once we've exited); `acpi_reclaim_memory → acpi_tables`;
|
||||||
`acpi_memory_nvs → acpi_nvs`; **everything else → reserved** (the safe default).
|
`acpi_memory_nvs → acpi_nvs`; **everything else → reserved** (the safe default).
|
||||||
Our own `loader_data` — the kernel image and these buffers — falls into
|
Our own `loader_data` — the kernel image and these buffers — falls into
|
||||||
`reserved`, so it won't be handed out until the kernel deliberately reclaims it.
|
`reserved`, so it won't be handed out until the kernel deliberately reclaims it.
|
||||||
|
|
||||||
|
One subtlety: **a region that isn't writeback-cacheable (the descriptor's `wb`
|
||||||
|
attribute) is classified `mmio` regardless of type.** UEFI overloads
|
||||||
|
`reserved_memory_type` for both reserved RAM *and* reserved address-space windows
|
||||||
|
(PCIe config space, device BARs); the cache attribute is what actually tells them
|
||||||
|
apart, since only real RAM is writeback-cacheable. Without this, a QEMU q35 guest
|
||||||
|
reports ~12 GiB of "reserved" that is really a PCIe address hole near the 1 TB
|
||||||
|
mark — not memory at all.
|
||||||
- **`convertMemoryMap`** walks the UEFI descriptors (striding by
|
- **`convertMemoryMap`** walks the UEFI descriptors (striding by
|
||||||
`descriptor_size`, *not* `@sizeOf`), classifies each, and writes danos
|
`descriptor_size`, *not* `@sizeOf`), classifies each, and writes danos
|
||||||
`MemoryRegion`s into an output buffer, coalescing adjacent same-kind regions.
|
`MemoryRegion`s into an output buffer, coalescing adjacent same-kind regions.
|
||||||
@@ -111,17 +120,24 @@ for (regions) |r| {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Today `kmain` just prints the region count and total usable RAM — enough to prove
|
`kmain` summarises the map to prove the handoff works. Booted in QEMU with
|
||||||
the handoff works. Booted in QEMU with 128 MiB, it reports something like:
|
128 MiB, it reports:
|
||||||
|
|
||||||
```
|
```
|
||||||
mem regions: 35
|
danos: physical memory
|
||||||
usable RAM : 77 MiB
|
total RAM : 0.12 GiB (127 MiB) - RAM the firmware reported
|
||||||
|
usable : 77 MiB - free now; owned by the frame allocator
|
||||||
|
reclaimable: 44 MiB - UEFI boot-services memory, free after exit
|
||||||
|
reserved : 6 MiB - kernel image, ACPI, runtime services
|
||||||
|
regions : 35 - entries in the firmware memory map
|
||||||
```
|
```
|
||||||
|
|
||||||
with the balance being `reclaimable` boot-services memory (~44 MiB) and a large
|
The `usable` figure is only ~77 of ~127 MiB because most of the rest is
|
||||||
`reserved` span that is mostly MMIO address space, not RAM. Those figures summing
|
`reclaimable` boot-services memory — real RAM we'll take back once we implement
|
||||||
back to ~128 MiB is the sanity check that nothing was dropped.
|
reclaiming, not memory that's gone. `total` counts only writeback-cacheable RAM,
|
||||||
|
so the ~12 GiB PCIe address hole is excluded (it's `mmio`), and the three RAM
|
||||||
|
categories summing back to the firmware's total is the sanity check that nothing
|
||||||
|
was dropped.
|
||||||
|
|
||||||
## How Raspberry Pi will fit
|
## How Raspberry Pi will fit
|
||||||
|
|
||||||
|
|||||||
+13
-7
@@ -262,7 +262,7 @@ fn convertMemoryMap(map: MemoryMapSlice, out: []u8) danos.MemoryMap {
|
|||||||
const d: *const uefi.tables.MemoryDescriptor =
|
const d: *const uefi.tables.MemoryDescriptor =
|
||||||
@ptrCast(@alignCast(map.ptr + i * map.info.descriptor_size));
|
@ptrCast(@alignCast(map.ptr + i * map.info.descriptor_size));
|
||||||
if (d.number_of_pages == 0) continue;
|
if (d.number_of_pages == 0) continue;
|
||||||
const kind = classify(d.@"type");
|
const kind = classify(d);
|
||||||
|
|
||||||
// Coalesce with the previous region if it's the same kind and contiguous.
|
// Coalesce with the previous region if it's the same kind and contiguous.
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
@@ -284,16 +284,22 @@ fn convertMemoryMap(map: MemoryMapSlice, out: []u8) danos.MemoryMap {
|
|||||||
return .{ .regions = @intFromPtr(regions), .len = count };
|
return .{ .regions = @intFromPtr(regions), .len = count };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map a UEFI memory type to danos's neutral kind. Anything we don't explicitly
|
/// Map a UEFI descriptor to danos's neutral kind. A region that isn't
|
||||||
/// recognise is treated as `reserved` — the safe default. Our own loader data
|
/// writeback-cacheable (`wb`) isn't backed by real RAM — it's device registers or
|
||||||
/// (the kernel image, these buffers) is LoaderData, which falls here too and so
|
/// a reserved address-space window (e.g. PCIe config space) — so it's `mmio`
|
||||||
/// stays reserved until the kernel decides to reclaim it.
|
/// regardless of type. UEFI overloads `reserved_memory_type` for both reserved RAM
|
||||||
fn classify(t: uefi.tables.MemoryType) danos.MemoryKind {
|
/// and such holes, and the cache attribute is what actually tells them apart.
|
||||||
return switch (t) {
|
/// Among RAM regions, anything we don't recognise is `reserved` — the safe
|
||||||
|
/// default; our own LoaderData (kernel image, these buffers) lands there too and
|
||||||
|
/// stays reserved until the kernel reclaims it.
|
||||||
|
fn classify(d: *const uefi.tables.MemoryDescriptor) danos.MemoryKind {
|
||||||
|
if (!d.attribute.wb) return .mmio;
|
||||||
|
return switch (d.@"type") {
|
||||||
.conventional_memory => .usable,
|
.conventional_memory => .usable,
|
||||||
.boot_services_code, .boot_services_data => .reclaimable,
|
.boot_services_code, .boot_services_data => .reclaimable,
|
||||||
.acpi_reclaim_memory => .acpi_tables,
|
.acpi_reclaim_memory => .acpi_tables,
|
||||||
.acpi_memory_nvs => .acpi_nvs,
|
.acpi_memory_nvs => .acpi_nvs,
|
||||||
|
.memory_mapped_io, .memory_mapped_io_port_space => .mmio,
|
||||||
else => .reserved,
|
else => .reserved,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-3
@@ -40,11 +40,41 @@ fn kmain(boot_info: *const BootInfo) noreturn {
|
|||||||
// own MemoryRegion, so this is a plain slice — no firmware layout in sight.
|
// 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];
|
const regions = @as([*]const danos.MemoryRegion, @ptrFromInt(boot_info.memory_map.regions))[0..boot_info.memory_map.len];
|
||||||
var usable_pages: u64 = 0;
|
var usable_pages: u64 = 0;
|
||||||
|
var reclaim_pages: u64 = 0;
|
||||||
|
var reserved_pages: u64 = 0; // reserved RAM only — MMIO is device space, not RAM
|
||||||
for (regions) |r| {
|
for (regions) |r| {
|
||||||
if (r.kind == .usable) usable_pages += r.pages;
|
switch (r.kind) {
|
||||||
|
.usable => usable_pages += r.pages,
|
||||||
|
.reclaimable => reclaim_pages += r.pages,
|
||||||
|
.reserved, .acpi_tables, .acpi_nvs => reserved_pages += r.pages,
|
||||||
|
.mmio => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
con.print(" mem regions: {d}\n", .{regions.len});
|
const total_pages = usable_pages + reclaim_pages + reserved_pages;
|
||||||
con.print(" usable RAM : {d} MiB\n", .{usable_pages * danos.page_size / (1024 * 1024)});
|
const total_bytes = total_pages * danos.page_size;
|
||||||
|
const gib = 1 << 30;
|
||||||
|
|
||||||
|
con.write("\ndanos: physical memory\n");
|
||||||
|
con.print(" total RAM : {d}.{d:0>2} GiB ({d} MiB) - RAM the firmware reported\n", .{ total_bytes / gib, (total_bytes % gib) * 100 / gib, mib(total_pages) });
|
||||||
|
con.print(" usable : {d} MiB - free now; owned by the frame allocator\n", .{mib(usable_pages)});
|
||||||
|
con.print(" reclaimable: {d} MiB - UEFI boot-services memory, free after exit\n", .{mib(reclaim_pages)});
|
||||||
|
con.print(" reserved : {d} MiB - kernel image, ACPI, runtime services\n", .{mib(reserved_pages)});
|
||||||
|
con.print(" regions : {d} - entries in the firmware memory map\n", .{regions.len});
|
||||||
|
|
||||||
|
// Bring up the physical frame allocator over that map, and prove it works:
|
||||||
|
// allocate three frames, then hand them back.
|
||||||
|
pmm.init(boot_info.memory_map);
|
||||||
|
const s = pmm.stats();
|
||||||
|
con.print("\ndanos: frame allocator online\n", .{});
|
||||||
|
con.print(" free frames: {d} ({d} MiB)\n", .{ s.free_frames, mib(s.free_frames) });
|
||||||
|
const f0 = pmm.alloc();
|
||||||
|
const f1 = pmm.alloc();
|
||||||
|
const f2 = pmm.alloc();
|
||||||
|
con.print(" alloc x3 : 0x{x} 0x{x} 0x{x}\n", .{ f0 orelse 0, f1 orelse 0, f2 orelse 0 });
|
||||||
|
if (f0) |p| pmm.free(p);
|
||||||
|
if (f1) |p| pmm.free(p);
|
||||||
|
if (f2) |p| pmm.free(p);
|
||||||
|
con.print(" after free : {d} frames free\n", .{pmm.stats().free_frames});
|
||||||
|
|
||||||
// Bring up the physical frame allocator over that map, and prove it works:
|
// Bring up the physical frame allocator over that map, and prove it works:
|
||||||
// allocate three frames, then hand them back.
|
// allocate three frames, then hand them back.
|
||||||
@@ -66,6 +96,11 @@ fn kmain(boot_info: *const BootInfo) noreturn {
|
|||||||
arch.halt();
|
arch.halt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Frames (4 KiB pages) to whole MiB.
|
||||||
|
fn mib(pages: u64) u64 {
|
||||||
|
return pages * danos.page_size / (1024 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
/// Freestanding has no OS to receive a panic. Print it to the console (if it is
|
/// Freestanding has no OS to receive a panic. Print it to the console (if it is
|
||||||
/// up yet) in red, then halt.
|
/// up yet) in red, then halt.
|
||||||
pub const panic = std.debug.FullPanic(struct {
|
pub const panic = std.debug.FullPanic(struct {
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ pub const MemoryKind = enum(u32) {
|
|||||||
acpi_tables,
|
acpi_tables,
|
||||||
/// ACPI non-volatile storage: preserve across sleep, do not allocate.
|
/// ACPI non-volatile storage: preserve across sleep, do not allocate.
|
||||||
acpi_nvs,
|
acpi_nvs,
|
||||||
|
/// Not backed by RAM: memory-mapped device registers or a reserved
|
||||||
|
/// address-space window (e.g. PCIe config space). Kept distinct from
|
||||||
|
/// `reserved` so RAM accounting doesn't count device address space.
|
||||||
|
mmio,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// One contiguous span of physical memory. Because danos defines this layout
|
/// One contiguous span of physical memory. Because danos defines this layout
|
||||||
|
|||||||
Reference in New Issue
Block a user