Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c8dbfbd0f | |||
| 1effc9e76f | |||
| 1bc326de6d | |||
| bc91b5c07c | |||
| 23bb7bb63e | |||
| 0785c424b9 | |||
| 0a89436d86 | |||
| a97d79d8ca | |||
| 734cd7eb0e | |||
| 1a8d842479 | |||
| d3e44e5067 | |||
| c0df9d712d | |||
| f85d04d715 |
@@ -1,2 +1,5 @@
|
||||
/zig-out
|
||||
/.zig-cache
|
||||
/*.dtb
|
||||
/*.dts
|
||||
/*.log
|
||||
|
||||
@@ -6,7 +6,7 @@ const SupportedArch = enum {
|
||||
aarch64,
|
||||
riscv64,
|
||||
|
||||
fn makeTarget(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget {
|
||||
fn make_target(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget {
|
||||
switch (self) {
|
||||
.riscv64 => {
|
||||
return b.resolveTargetQuery(.{
|
||||
@@ -18,11 +18,11 @@ const SupportedArch = enum {
|
||||
.aarch64 => {
|
||||
const T = std.Target.aarch64;
|
||||
|
||||
const addFeatures = T.featureSet(&.{
|
||||
const add_features = T.featureSet(&.{
|
||||
T.Feature.v8a,
|
||||
T.Feature.strict_align,
|
||||
});
|
||||
const subFeatures = T.featureSet(&.{
|
||||
const sub_features = T.featureSet(&.{
|
||||
T.Feature.neon,
|
||||
T.Feature.fp_armv8,
|
||||
});
|
||||
@@ -31,14 +31,14 @@ const SupportedArch = enum {
|
||||
.cpu_arch = .aarch64,
|
||||
.os_tag = .freestanding,
|
||||
.abi = .none,
|
||||
.cpu_features_add = addFeatures,
|
||||
.cpu_features_sub = subFeatures,
|
||||
.cpu_features_add = add_features,
|
||||
.cpu_features_sub = sub_features,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn addTargetSpecific(self: SupportedArch, b: *std.Build, kernel: *std.Build.Step.Compile) anyerror!*std.Build.Step {
|
||||
fn add_target_specific(self: SupportedArch, b: *std.Build, kernel: *std.Build.Step.Compile) anyerror!*std.Build.Step {
|
||||
switch (self) {
|
||||
.riscv64 => {
|
||||
kernel.entry = .{ .symbol_name = "__rv64_entry" };
|
||||
@@ -51,6 +51,7 @@ const SupportedArch = enum {
|
||||
},
|
||||
.aarch64 => {
|
||||
kernel.entry = .{ .symbol_name = "__aa64_entry" };
|
||||
kernel.link_z_max_page_size = 0x1000;
|
||||
|
||||
kernel.setLinkerScript(b.path("etc/aarch64-unknown-none.ld"));
|
||||
kernel.addCSourceFiles(.{
|
||||
@@ -63,12 +64,12 @@ const SupportedArch = enum {
|
||||
b.installArtifact(kernel);
|
||||
|
||||
if (self == .riscv64 or self == .aarch64) {
|
||||
const fakeLinuxHeader: *std.Build.Step = try b.allocator.create(std.Build.Step);
|
||||
fakeLinuxHeader.* = std.Build.Step.init(.{
|
||||
const fake_linux_header: *std.Build.Step = try b.allocator.create(std.Build.Step);
|
||||
fake_linux_header.* = std.Build.Step.init(.{
|
||||
.id = std.Build.Step.Id.custom,
|
||||
.name = "insert fake linux header",
|
||||
.owner = kernel.step.owner,
|
||||
.makeFn = insertFakeLinuxImageHeader,
|
||||
.makeFn = insert_fake_linux_image_header,
|
||||
});
|
||||
|
||||
const elf2bin = b.addSystemCommand(&.{
|
||||
@@ -79,8 +80,8 @@ const SupportedArch = enum {
|
||||
"zig-out/bin/kernel.bin",
|
||||
});
|
||||
|
||||
fakeLinuxHeader.dependOn(b.getInstallStep());
|
||||
elf2bin.step.dependOn(fakeLinuxHeader);
|
||||
fake_linux_header.dependOn(b.getInstallStep());
|
||||
elf2bin.step.dependOn(fake_linux_header);
|
||||
|
||||
return &elf2bin.step;
|
||||
} else {
|
||||
@@ -89,7 +90,7 @@ const SupportedArch = enum {
|
||||
}
|
||||
};
|
||||
|
||||
fn insertFakeLinuxImageHeader(step: *std.Build.Step, opts: std.Build.Step.MakeOptions) anyerror!void {
|
||||
fn insert_fake_linux_image_header(step: *std.Build.Step, opts: std.Build.Step.MakeOptions) anyerror!void {
|
||||
const RISCV_MAGIC1 = "RISCV\x00\x00\x00";
|
||||
const RISCV_MAGIC2 = "RSC\x05";
|
||||
|
||||
@@ -101,15 +102,15 @@ fn insertFakeLinuxImageHeader(step: *std.Build.Step, opts: std.Build.Step.MakeOp
|
||||
_ = try file.readAll(std.mem.asBytes(&ehdr));
|
||||
|
||||
// Figure out total image size
|
||||
var imageAddrMax: u64 = 0;
|
||||
var image_addr_max: u64 = 0;
|
||||
for (0..ehdr.e_phnum) |i| {
|
||||
var phdr: elf.Phdr = undefined;
|
||||
_ = try file.preadAll(std.mem.asBytes(&phdr), ehdr.e_phoff + i * ehdr.e_phentsize);
|
||||
|
||||
const end = (phdr.p_vaddr + phdr.p_memsz + 0xFFF) & ~@as(u64, 0xFFF);
|
||||
|
||||
if (phdr.p_type == elf.PT_LOAD and end > imageAddrMax) {
|
||||
imageAddrMax = end;
|
||||
if (phdr.p_type == elf.PT_LOAD and end > image_addr_max) {
|
||||
image_addr_max = end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +127,7 @@ fn insertFakeLinuxImageHeader(step: *std.Build.Step, opts: std.Build.Step.MakeOp
|
||||
_ = try file.preadAll(&data, phdr.p_offset);
|
||||
|
||||
if (std.mem.eql(u8, RISCV_MAGIC1, data[48..56]) and std.mem.eql(u8, RISCV_MAGIC2, data[56..60])) {
|
||||
try file.pwriteAll(std.mem.asBytes(&imageAddrMax), phdr.p_offset + 16);
|
||||
try file.pwriteAll(std.mem.asBytes(&image_addr_max), phdr.p_offset + 16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -140,41 +141,42 @@ fn build_riscv64(b: *std.Build) anyerror!void {
|
||||
}
|
||||
|
||||
pub fn build(b: *std.Build) anyerror!void {
|
||||
const maybeArchOption = b.option(SupportedArch, "arch", "Architecture to use");
|
||||
const maybe_arch_option = b.option(SupportedArch, "arch", "Architecture to use");
|
||||
|
||||
const arch = maybeArchOption orelse DEFAULT_ARCH;
|
||||
const target = arch.makeTarget(b);
|
||||
const arch = maybe_arch_option orelse DEFAULT_ARCH;
|
||||
const target = arch.make_target(b);
|
||||
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
||||
|
||||
const codeModel: std.builtin.CodeModel = switch (arch) {
|
||||
const code_model: std.builtin.CodeModel = switch (arch) {
|
||||
.riscv64 => .medium,
|
||||
.aarch64 => .small,
|
||||
};
|
||||
const kernelModule = b.addModule("kernel", .{
|
||||
const kernel_module = b.addModule("kernel", .{
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
.pic = true,
|
||||
.red_zone = false,
|
||||
.code_model = codeModel,
|
||||
.code_model = code_model,
|
||||
.root_source_file = b.path("src/kernel.zig"),
|
||||
});
|
||||
const kernel = b.addExecutable(.{
|
||||
.name = "kernel",
|
||||
.root_module = kernelModule,
|
||||
.root_module = kernel_module,
|
||||
.pic = true,
|
||||
.use_lld = true,
|
||||
});
|
||||
kernel.pie = true;
|
||||
|
||||
const installDocs = b.addInstallDirectory(.{
|
||||
const install_docs = b.addInstallDirectory(.{
|
||||
.source_dir = kernel.getEmittedDocs(),
|
||||
.install_dir = .prefix,
|
||||
.install_subdir = "docs",
|
||||
});
|
||||
|
||||
const docsStep = b.step("docs", "Install documentation");
|
||||
docsStep.dependOn(&installDocs.step);
|
||||
const docs_step = b.step("docs", "Install documentation");
|
||||
docs_step.dependOn(&install_docs.step);
|
||||
|
||||
const kernelStep = try arch.addTargetSpecific(b, kernel);
|
||||
const kernel_step = try arch.add_target_specific(b, kernel);
|
||||
|
||||
// TODO QEMU binary override
|
||||
const qemu_info = switch (target.result.cpu.arch) {
|
||||
@@ -203,7 +205,7 @@ pub fn build(b: *std.Build) anyerror!void {
|
||||
qemu_cmd.addArgs(&.{ "-bios", "etc/boot/rv64_fw_jump.bin" });
|
||||
}
|
||||
|
||||
qemu_cmd.step.dependOn(kernelStep);
|
||||
qemu_cmd.step.dependOn(kernel_step);
|
||||
if (b.args) |args| qemu_cmd.addArgs(args);
|
||||
const run_step = b.step("run", "Start the OS in qemu");
|
||||
run_step.dependOn(&qemu_cmd.step);
|
||||
|
||||
+42
-2
@@ -1,12 +1,52 @@
|
||||
//! Helper module to select architecture-specific modules depending on what platform is
|
||||
//! being targeted.
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const impl = switch (builtin.cpu.arch) {
|
||||
pub const impl = switch (builtin.cpu.arch) {
|
||||
.riscv64 => @import("arch/riscv64.zig"),
|
||||
.aarch64 => @import("arch/aarch64.zig"),
|
||||
else => @compileError("Unsupported architecture"),
|
||||
};
|
||||
|
||||
pub usingnamespace impl;
|
||||
pub const vmm = impl.vmm;
|
||||
|
||||
/// Halts the CPU execution indefinitely, without ever returning.
|
||||
pub inline fn halt() noreturn {
|
||||
impl.halt();
|
||||
}
|
||||
|
||||
/// Returns the current state of IRQ masking.
|
||||
pub inline fn interrupt_mask() bool {
|
||||
return impl.interrupt_mask();
|
||||
}
|
||||
|
||||
/// Modifies the interrupt mask to either allow or block IRQs from being delivered to the CPU.
|
||||
/// Returns the old IRQ mask.
|
||||
pub inline fn set_interrupt_mask(masked: bool) bool {
|
||||
return impl.set_interrupt_mask(masked);
|
||||
}
|
||||
|
||||
/// Suspends the CPU until an interrupt is signalled.
|
||||
pub inline fn wait_for_interrupt() void {
|
||||
impl.wait_for_interrupt();
|
||||
}
|
||||
|
||||
/// Hint to the CPU that the code is executing a "busy-wait" or a "spin-wait" loop.
|
||||
pub inline fn spin_hint() void {
|
||||
impl.spin_hint();
|
||||
}
|
||||
|
||||
/// Set the CPU's thread pointer to some value.
|
||||
pub inline fn set_thread_pointer(value: usize) void {
|
||||
impl.set_thread_pointer(value);
|
||||
}
|
||||
|
||||
/// Combined memory/compiler fence to ensure specific ordering of instructions and memory accesses.
|
||||
pub inline fn barrier(ordering: std.builtin.AtomicOrder) void {
|
||||
impl.barrier(ordering);
|
||||
}
|
||||
|
||||
/// Platform-specific task context implementation
|
||||
pub const Context = impl.Context;
|
||||
|
||||
+35
-27
@@ -1,37 +1,45 @@
|
||||
const std = @import("std");
|
||||
|
||||
const boot = @import("aarch64/boot.zig");
|
||||
const regs = @import("aarch64/regs.zig");
|
||||
|
||||
export const _ = boot.aa64BspLowerEntry;
|
||||
pub const vmm = @import("aarch64/vmm.zig");
|
||||
|
||||
pub const Context = struct {
|
||||
pub fn idle() Context {
|
||||
@panic("TODO");
|
||||
export const _ = boot.aa64_bsp_lower_entry;
|
||||
|
||||
pub const Context = @import("aarch64/context.zig").Context;
|
||||
|
||||
pub fn set_interrupt_mask(masked: bool) bool {
|
||||
const old = interrupt_mask();
|
||||
if (masked) {
|
||||
regs.DAIF.modify(.{ .I = true }, .{});
|
||||
} else {
|
||||
regs.DAIF.modify(.{}, .{ .I = true });
|
||||
}
|
||||
|
||||
pub fn kernel(pc: usize, arg: usize) Context {
|
||||
_ = pc;
|
||||
_ = arg;
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
pub fn enter(self: *Context) noreturn {
|
||||
_ = self;
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
pub fn switchFrom(self: *Context, from: *Context) void {
|
||||
_ = self;
|
||||
_ = from;
|
||||
@panic("TODO");
|
||||
}
|
||||
};
|
||||
|
||||
pub fn halt() noreturn {
|
||||
while (true) {}
|
||||
return old;
|
||||
}
|
||||
|
||||
pub fn spinHint() void {
|
||||
// TODO
|
||||
pub inline fn interrupt_mask() bool {
|
||||
return regs.DAIF.read().I;
|
||||
}
|
||||
|
||||
pub inline fn wait_for_interrupt() void {
|
||||
asm volatile ("wfi");
|
||||
}
|
||||
|
||||
pub fn halt() noreturn {
|
||||
while (true) {
|
||||
_ = set_interrupt_mask(true);
|
||||
wait_for_interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spin_hint() void {
|
||||
asm volatile ("isb sy" ::: "memory");
|
||||
}
|
||||
|
||||
pub inline fn set_thread_pointer(tp: usize) void {
|
||||
regs.TPIDR_EL0.set(tp);
|
||||
}
|
||||
|
||||
pub inline fn barrier(comptime kind: std.builtin.AtomicOrder) void {
|
||||
|
||||
+48
-45
@@ -2,22 +2,23 @@ const kernel = @import("../../kernel.zig");
|
||||
const vmm = @import("vmm.zig");
|
||||
const dtb = @import("../../util/dtb.zig");
|
||||
const exception = @import("exception.zig");
|
||||
const tls = @import("../../mem/tls.zig");
|
||||
|
||||
const arch = kernel.arch;
|
||||
const mem = kernel.mem;
|
||||
const log = kernel.debug.log;
|
||||
const physMemory = mem.phys;
|
||||
const phys_memory = mem.phys;
|
||||
|
||||
extern const __aa64_bsp_stack_top: u8;
|
||||
|
||||
var gDtbAddress: u64 = undefined;
|
||||
var g_dtb_address: u64 = undefined;
|
||||
|
||||
fn earlyDebugPrint(byte: u8) void {
|
||||
fn early_debug_print(byte: u8) void {
|
||||
const address = 0x9000000;
|
||||
@as(*volatile u32, @ptrFromInt(address)).* = byte;
|
||||
}
|
||||
|
||||
fn relocAddressToUpper(ptr: *const anyopaque) usize {
|
||||
fn reloc_address_to_upper(ptr: *const anyopaque) usize {
|
||||
const p = @intFromPtr(ptr);
|
||||
if (p >= vmm.KERNEL_VIRTUAL_BASE) {
|
||||
return p;
|
||||
@@ -26,7 +27,7 @@ fn relocAddressToUpper(ptr: *const anyopaque) usize {
|
||||
}
|
||||
}
|
||||
|
||||
fn relocAddressToLower(ptr: *const anyopaque) usize {
|
||||
fn reloc_address_to_lower(ptr: *const anyopaque) usize {
|
||||
const p = @intFromPtr(ptr);
|
||||
if (p >= vmm.KERNEL_VIRTUAL_BASE) {
|
||||
return p - vmm.KERNEL_VIRTUAL_BASE;
|
||||
@@ -35,44 +36,39 @@ fn relocAddressToLower(ptr: *const anyopaque) usize {
|
||||
}
|
||||
}
|
||||
|
||||
fn aa64BspUpperEntry(realAddress: u64) callconv(.C) noreturn {
|
||||
fn aa64_bsp_upper_entry(real_address: u64) callconv(.C) noreturn {
|
||||
// Relocate the kernel yet again
|
||||
const relaStart = relocAddressToUpper(&__rela_start);
|
||||
const relaEnd = relocAddressToUpper(&__rela_end);
|
||||
const relOffset = vmm.KERNEL_VIRTUAL_BASE + realAddress;
|
||||
const rela_start = reloc_address_to_upper(&__rela_start);
|
||||
const rela_end = reloc_address_to_upper(&__rela_end);
|
||||
const rel_offset = vmm.KERNEL_VIRTUAL_BASE + real_address;
|
||||
|
||||
arch.barrier(.acq_rel);
|
||||
aa64RelocateKernel(relOffset, relaStart, relaEnd);
|
||||
aa64_relocate_kernel(rel_offset, rela_start, rela_end);
|
||||
arch.barrier(.acq_rel);
|
||||
|
||||
log.setWriteFn(&earlyDebugPrint);
|
||||
log.set_write_fn(&early_debug_print);
|
||||
|
||||
exception.init();
|
||||
|
||||
mem.PhysicalAddress.gVirtualizeBase = 0;
|
||||
mem.PhysicalAddress.gVirtualizeSize = 16 << 30;
|
||||
mem.PhysicalAddress.g_virtualize_base = 0;
|
||||
mem.PhysicalAddress.g_virtualize_size = 16 << 30;
|
||||
|
||||
setupMemoryFromFdt(realAddress);
|
||||
setup_memory_from_fdt(real_address);
|
||||
|
||||
asm volatile ("" ::: "memory");
|
||||
setup_per_cpu();
|
||||
|
||||
// Test exception handling
|
||||
const p: *const u32 = @ptrFromInt(0x111122223338);
|
||||
const v: u32 = p.*;
|
||||
log.info("v = {}", .{v});
|
||||
|
||||
arch.halt();
|
||||
kernel.kernel_main();
|
||||
}
|
||||
|
||||
pub export fn aa64BspLowerEntry(realAddress: u64, dtbAddress: u64) callconv(.C) noreturn {
|
||||
gDtbAddress = dtbAddress;
|
||||
pub export fn aa64_bsp_lower_entry(real_address: u64, dtb_address: u64) callconv(.C) noreturn {
|
||||
g_dtb_address = dtb_address;
|
||||
|
||||
vmm.mapEarly(realAddress);
|
||||
vmm.map_early(real_address);
|
||||
|
||||
const pc = @intFromPtr(&aa64BspUpperEntry) + vmm.KERNEL_VIRTUAL_BASE;
|
||||
const pc = @intFromPtr(&aa64_bsp_upper_entry) + vmm.KERNEL_VIRTUAL_BASE;
|
||||
const sp = @intFromPtr(&__aa64_bsp_stack_top) + vmm.KERNEL_VIRTUAL_BASE;
|
||||
|
||||
longJump(pc, sp, realAddress);
|
||||
long_jump(pc, sp, real_address);
|
||||
}
|
||||
|
||||
// Functions used by the boot process
|
||||
@@ -82,18 +78,18 @@ extern const __rela_end: u8;
|
||||
extern var __kernel_start: u8;
|
||||
extern var __kernel_end: u8;
|
||||
|
||||
export fn aa64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: usize) void {
|
||||
export fn aa64_relocate_kernel(image_base: usize, rela_start: usize, rela_end: usize) void {
|
||||
const elf = @import("std").elf;
|
||||
|
||||
const relaTablePtr = @as([*]elf.Rela, @ptrFromInt(relaStart));
|
||||
const relaCount = (relaEnd - relaStart) / @sizeOf(elf.Rela);
|
||||
const relaTable = relaTablePtr[0..relaCount];
|
||||
for (relaTable) |entry| {
|
||||
const relaType: elf.R_AARCH64 = @enumFromInt(entry.r_type());
|
||||
switch (relaType) {
|
||||
const rela_table_ptr = @as([*]elf.Rela, @ptrFromInt(rela_start));
|
||||
const rela_count = (rela_end - rela_start) / @sizeOf(elf.Rela);
|
||||
const rela_table = rela_table_ptr[0..rela_count];
|
||||
for (rela_table) |entry| {
|
||||
const rela_type: elf.R_AARCH64 = @enumFromInt(entry.r_type());
|
||||
switch (rela_type) {
|
||||
.RELATIVE => {
|
||||
const value = @as(*isize, @ptrFromInt(imageBase + entry.r_offset));
|
||||
value.* = @as(isize, @bitCast(imageBase)) + entry.r_addend;
|
||||
const value = @as(*isize, @ptrFromInt(image_base + entry.r_offset));
|
||||
value.* = @as(isize, @bitCast(image_base)) + entry.r_addend;
|
||||
},
|
||||
else => {
|
||||
arch.halt();
|
||||
@@ -102,7 +98,7 @@ export fn aa64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: usize)
|
||||
}
|
||||
}
|
||||
|
||||
inline fn longJump(pc: usize, sp: usize, a0: usize) noreturn {
|
||||
inline fn long_jump(pc: usize, sp: usize, a0: usize) noreturn {
|
||||
asm volatile (
|
||||
\\ mov sp, %[sp]
|
||||
\\ br %[pc]
|
||||
@@ -115,19 +111,26 @@ inline fn longJump(pc: usize, sp: usize, a0: usize) noreturn {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
fn setupMemoryFromFdt(realAddress: usize) void {
|
||||
_ = realAddress;
|
||||
fn setup_memory_from_fdt(real_address: usize) void {
|
||||
_ = real_address;
|
||||
|
||||
const kernelStart = relocAddressToLower(&__kernel_start); // 0
|
||||
const kernelEnd = relocAddressToLower(&__kernel_end); // whatever
|
||||
const kernel_start = reloc_address_to_lower(&__kernel_start); // 0
|
||||
const kernel_end = reloc_address_to_lower(&__kernel_end); // whatever
|
||||
|
||||
const fdt = dtb.Fdt.fromPhysicalAddress(.{ .raw = gDtbAddress }) catch |err| {
|
||||
const fdt = dtb.Fdt.from_physical_address(.{ .raw = g_dtb_address }) catch |err| {
|
||||
log.panic("Cannot initialize raw DTB: {}", .{err});
|
||||
};
|
||||
fdt.addPhysicalMemoryToSystem();
|
||||
fdt.add_physical_memory_to_system();
|
||||
|
||||
physMemory.addReservedRegion("kernel", kernelStart, kernelEnd - kernelStart);
|
||||
physMemory.addReservedRegion("fdt", gDtbAddress, vmm.L3.align_up(fdt.bytes.len));
|
||||
phys_memory.add_reserved_region("kernel", kernel_start, kernel_end - kernel_start);
|
||||
phys_memory.add_reserved_region("fdt", g_dtb_address, vmm.L3.align_up(fdt.bytes.len));
|
||||
|
||||
physMemory.init();
|
||||
phys_memory.init();
|
||||
}
|
||||
|
||||
fn setup_per_cpu() void {
|
||||
const tls_data = tls.load_kernel_tls_image();
|
||||
const tp = @intFromPtr(tls_data.ptr);
|
||||
log.info("Set TP = 0x{x}", .{tp});
|
||||
arch.set_thread_pointer(tp);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// vi:set ft=asm:
|
||||
|
||||
.global __aa64_enter_task
|
||||
.global __aa64_switch_task
|
||||
.global __aa64_task_enter_kernel
|
||||
|
||||
.set CONTEXT_SIZE, (12 * 8)
|
||||
|
||||
.macro SAVE_TASK_CONTEXT
|
||||
sub sp, sp, #CONTEXT_SIZE
|
||||
stp x19, x20, [sp, #0 * 16]
|
||||
stp x21, x22, [sp, #1 * 16]
|
||||
stp x23, x24, [sp, #2 * 16]
|
||||
stp x25, x26, [sp, #3 * 16]
|
||||
stp x27, x28, [sp, #4 * 16]
|
||||
stp x29, x30, [sp, #5 * 16]
|
||||
.endm
|
||||
|
||||
.macro RESTORE_TASK_CONTEXT
|
||||
ldp x19, x20, [sp, #0 * 16]
|
||||
ldp x21, x22, [sp, #1 * 16]
|
||||
ldp x23, x24, [sp, #2 * 16]
|
||||
ldp x25, x26, [sp, #3 * 16]
|
||||
ldp x27, x28, [sp, #4 * 16]
|
||||
ldp x29, x30, [sp, #5 * 16]
|
||||
add sp, sp, #CONTEXT_SIZE
|
||||
.endm
|
||||
|
||||
.pushsection .text
|
||||
|
||||
__aa64_task_enter_kernel:
|
||||
// arg, entry
|
||||
ldp x0, lr, [sp]
|
||||
add sp, sp, #16
|
||||
|
||||
// TODO enter task via eret to EL1t
|
||||
|
||||
ret
|
||||
|
||||
__aa64_switch_task:
|
||||
// x0 -- "dst" context
|
||||
// x1 -- "src" context
|
||||
|
||||
SAVE_TASK_CONTEXT
|
||||
|
||||
mov x19, sp
|
||||
str x19, [x1]
|
||||
|
||||
__aa64_enter_task:
|
||||
// x0 -- "dst" context
|
||||
ldr x0, [x0]
|
||||
mov sp, x0
|
||||
|
||||
RESTORE_TASK_CONTEXT
|
||||
|
||||
ret
|
||||
|
||||
.popsection // .text
|
||||
@@ -0,0 +1,55 @@
|
||||
const thread = @import("../../thread.zig");
|
||||
|
||||
fn idle_function() callconv(.naked) noreturn {
|
||||
asm volatile ("b .");
|
||||
}
|
||||
|
||||
extern fn __aa64_enter_task(cx: *Context) callconv(.C) noreturn;
|
||||
extern fn __aa64_switch_task(dcx: *Context, scx: *Context) callconv(.C) void;
|
||||
extern fn __aa64_task_enter_kernel() callconv(.C) noreturn;
|
||||
|
||||
pub const Context = extern struct {
|
||||
const STACK_SIZE: usize = 16384;
|
||||
|
||||
kstack: thread.KStack(STACK_SIZE),
|
||||
|
||||
pub fn idle() Context {
|
||||
const entry = @intFromPtr(&idle_function);
|
||||
return Context.kernel(entry, 0);
|
||||
}
|
||||
|
||||
pub fn kernel(pc: usize, arg: usize) Context {
|
||||
var ks = thread.KStack(STACK_SIZE).create();
|
||||
const entry = @intFromPtr(&__aa64_task_enter_kernel);
|
||||
|
||||
ks.push(pc);
|
||||
ks.push(arg);
|
||||
|
||||
ks.push(entry); // x30/lr
|
||||
ks.push(0); // x29
|
||||
ks.push(0); // x28
|
||||
ks.push(0); // x27
|
||||
ks.push(0); // x26
|
||||
ks.push(0); // x25
|
||||
ks.push(0); // x24
|
||||
ks.push(0); // x23
|
||||
ks.push(0); // x22
|
||||
ks.push(0); // x21
|
||||
ks.push(0); // x20
|
||||
ks.push(0); // x19
|
||||
|
||||
return Context{ .kstack = ks };
|
||||
}
|
||||
|
||||
pub fn enter(self: *Context) noreturn {
|
||||
__aa64_enter_task(self);
|
||||
}
|
||||
|
||||
pub fn switch_from(self: *Context, from: *Context) void {
|
||||
__aa64_switch_task(self, from);
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
asm (@embedFile("context.S"));
|
||||
}
|
||||
@@ -41,11 +41,11 @@ __aa64_entry:
|
||||
mov x0, x19
|
||||
adr x1, __rela_start
|
||||
adr x2, __rela_end
|
||||
bl aa64RelocateKernel
|
||||
bl aa64_relocate_kernel
|
||||
|
||||
mov x0, x19
|
||||
mov x1, x20
|
||||
b aa64BspLowerEntry
|
||||
b aa64_bsp_lower_entry
|
||||
|
||||
.parking_lot:
|
||||
wfe
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn init() void {
|
||||
regs.VBAR_EL1.set(vbar_el1);
|
||||
}
|
||||
|
||||
fn commonIrqHandler(frame: *ExceptionFrame) void {
|
||||
fn common_irq_handler(frame: *ExceptionFrame) void {
|
||||
_ = frame;
|
||||
@panic("TODO: IRQ");
|
||||
}
|
||||
@@ -40,16 +40,16 @@ export fn __aa64_el1_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||
const elr = regs.ELR_EL1.get();
|
||||
|
||||
log.err("Exception in EL1:", .{});
|
||||
log.err(" EC = {s} (0b{b:06}) ISS = 0x{x}", .{ esr.EC.asStr(), @intFromEnum(esr.EC), esr.ISS });
|
||||
log.err(" EC = {s} (0b{b:06}) ISS = 0x{x}", .{ esr.EC.as_str(), @intFromEnum(esr.EC), esr.ISS });
|
||||
log.err(" ELR = 0x{x:016}", .{elr});
|
||||
|
||||
switch (esr.asEnum()) {
|
||||
switch (esr.as_enum()) {
|
||||
.data_abort => |abort| {
|
||||
const faultKindStr = abort.DFSC.asStr();
|
||||
const accessSizeStr = @tagName(abort.SAS);
|
||||
const accessTypeStr = if (abort.WnR) "write" else "read";
|
||||
const fault_kind_str = abort.DFSC.as_str();
|
||||
const access_size_str = @tagName(abort.SAS);
|
||||
const access_type_str = if (abort.WnR) "write" else "read";
|
||||
|
||||
log.err(" Illegal {s} of a {s}: {s}", .{ accessTypeStr, accessSizeStr, faultKindStr });
|
||||
log.err(" Illegal {s} of a {s}: {s}", .{ access_type_str, access_size_str, fault_kind_str });
|
||||
if (!abort.FnV) {
|
||||
log.err(" FAR = 0x{x:016}", .{far});
|
||||
} else {
|
||||
@@ -65,7 +65,7 @@ export fn __aa64_el1_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||
|
||||
// IRQ
|
||||
export fn __aa64_el1_irq_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||
commonIrqHandler(frame);
|
||||
common_irq_handler(frame);
|
||||
}
|
||||
|
||||
export fn __aa64_el1_fiq_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||
@@ -88,7 +88,7 @@ export fn __aa64_el0_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||
}
|
||||
|
||||
export fn __aa64_el0_irq_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||
commonIrqHandler(frame);
|
||||
common_irq_handler(frame);
|
||||
}
|
||||
|
||||
export fn __aa64_el0_fiq_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||
|
||||
@@ -36,6 +36,25 @@ fn Register(comptime name: []const u8, comptime bits: type) type {
|
||||
|
||||
pub const TTBR0_EL1 = Register("ttbr0_el1", u64);
|
||||
pub const TTBR1_EL1 = Register("ttbr1_el1", u64);
|
||||
|
||||
// NOTE: tpidr_el0 is used until codegen can emit TLS instructions against tpidr_el1
|
||||
pub const TPIDR_EL0 = Register("tpidr_el0", u64);
|
||||
|
||||
pub const DAIF = Register("daif", packed struct(u64) {
|
||||
// 0..6
|
||||
_0: u6 = 0,
|
||||
// 6
|
||||
F: bool = false,
|
||||
// 7
|
||||
I: bool = false,
|
||||
// 8
|
||||
A: bool = false,
|
||||
// 9
|
||||
D: bool = false,
|
||||
// 10..64
|
||||
_1: u54 = 0,
|
||||
});
|
||||
|
||||
pub const VBAR_EL1 = Register("vbar_el1", u64);
|
||||
pub const ELR_EL1 = Register("elr_el1", u64);
|
||||
pub const FAR_EL1 = Register("far_el1", u64);
|
||||
@@ -53,7 +72,7 @@ pub const ESR_EL1 = Register("esr_el1", packed struct(u64) {
|
||||
sp_align = 0b100110,
|
||||
_,
|
||||
|
||||
pub fn asStr(self: @This()) []const u8 {
|
||||
pub fn as_str(self: @This()) []const u8 {
|
||||
return std.enums.tagName(@This(), self) orelse "<unknown>";
|
||||
}
|
||||
} = .unknown,
|
||||
@@ -80,7 +99,7 @@ pub const ESR_EL1 = Register("esr_el1", packed struct(u64) {
|
||||
permission_fault_l3 = 0b001111,
|
||||
_,
|
||||
|
||||
pub fn asStr(self: @This()) []const u8 {
|
||||
pub fn as_str(self: @This()) []const u8 {
|
||||
return std.enums.tagName(@This(), self) orelse "<other>";
|
||||
}
|
||||
} = .address_size_fault_l0,
|
||||
@@ -120,7 +139,7 @@ pub const ESR_EL1 = Register("esr_el1", packed struct(u64) {
|
||||
other,
|
||||
};
|
||||
|
||||
pub fn asEnum(self: @This()) AsEnum {
|
||||
pub fn as_enum(self: @This()) AsEnum {
|
||||
switch (self.EC) {
|
||||
.data_abort_lower_el, .data_abort_same_el => return .{ .data_abort = @bitCast(self.ISS) },
|
||||
else => return .other,
|
||||
|
||||
+14
-14
@@ -8,7 +8,7 @@ pub const KERNEL_L1_INDEX: usize = L1.index(KERNEL_VIRTUAL_BASE);
|
||||
|
||||
pub const L1 = mem.TranslationLevel(30);
|
||||
pub const L2 = mem.TranslationLevel(21);
|
||||
pub const L3 = mem.TranslationLevel(12);
|
||||
pub const L3 = mem.vmm.L3;
|
||||
|
||||
pub const RawEntry = packed struct(u64) {
|
||||
// 0
|
||||
@@ -47,7 +47,7 @@ pub const RawEntry = packed struct(u64) {
|
||||
// 55..64
|
||||
_2: u9 = 0,
|
||||
|
||||
pub fn makeUnion(self: @This(), other: @This()) @This() {
|
||||
pub fn make_union(self: @This(), other: @This()) @This() {
|
||||
const lhs = @as(u64, @bitCast(self));
|
||||
const rhs = @as(u64, @bitCast(other));
|
||||
return @as(@This(), @bitCast(lhs | rhs));
|
||||
@@ -81,7 +81,7 @@ pub fn TableEntry(comptime Level: type) type {
|
||||
|
||||
pub fn normal_page(addr: PhysicalAddress, flags: RawEntry) @This() {
|
||||
return .{
|
||||
.raw = flags.makeUnion(RawEntry{
|
||||
.raw = flags.make_union(RawEntry{
|
||||
.PPN = @as(u36, @intCast(addr.raw >> 12)),
|
||||
.V = true,
|
||||
.P = true,
|
||||
@@ -94,7 +94,7 @@ pub fn TableEntry(comptime Level: type) type {
|
||||
|
||||
pub fn device_page(addr: PhysicalAddress, flags: RawEntry) @This() {
|
||||
return .{
|
||||
.raw = flags.makeUnion(RawEntry{
|
||||
.raw = flags.make_union(RawEntry{
|
||||
.PPN = @as(u36, @intCast(addr.raw >> 12)),
|
||||
.V = true,
|
||||
.P = true,
|
||||
@@ -109,7 +109,7 @@ pub fn TableEntry(comptime Level: type) type {
|
||||
|
||||
pub fn normal_block(addr: PhysicalAddress, flags: RawEntry) @This() {
|
||||
return .{
|
||||
.raw = flags.makeUnion(RawEntry{
|
||||
.raw = flags.make_union(RawEntry{
|
||||
.PPN = @as(u36, @intCast(addr.raw >> 12)),
|
||||
.V = true,
|
||||
.AF = true,
|
||||
@@ -121,7 +121,7 @@ pub fn TableEntry(comptime Level: type) type {
|
||||
|
||||
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
|
||||
return .{
|
||||
.raw = flags.makeUnion(.{
|
||||
.raw = flags.make_union(.{
|
||||
.PPN = @as(u36, @intCast(addr.raw >> 12)),
|
||||
.V = true,
|
||||
.P = true,
|
||||
@@ -144,16 +144,16 @@ pub fn Table(comptime Level: type) type {
|
||||
}
|
||||
|
||||
// 0x0000_0000_0000_0000 .. 0x0000_0080_0000_0000
|
||||
var gFixedLow = Table(L1){};
|
||||
var g_fixed_low = Table(L1){};
|
||||
// 0xFFFF_FF80_0000_0000 .. 0xFFFF_FFFF_FFFF_FFFF
|
||||
var gFixedHigh = Table(L1){};
|
||||
var g_fixed_high = Table(L1){};
|
||||
|
||||
pub fn mapEarly(realAddress: usize) void {
|
||||
_ = realAddress;
|
||||
pub fn map_early(real_address: usize) void {
|
||||
_ = real_address;
|
||||
|
||||
for (0..16) |i| {
|
||||
// Identity
|
||||
gFixedLow.entry(i).* = TableEntry(L1).normal_block(
|
||||
g_fixed_low.entry(i).* = TableEntry(L1).normal_block(
|
||||
.{ .raw = i << L1.SHIFT },
|
||||
.{},
|
||||
);
|
||||
@@ -161,14 +161,14 @@ pub fn mapEarly(realAddress: usize) void {
|
||||
|
||||
for (0..16) |i| {
|
||||
// Identity + KERNEL_VIRTUAL_BASE
|
||||
gFixedHigh.entry(i).* = TableEntry(L1).normal_block(
|
||||
g_fixed_high.entry(i).* = TableEntry(L1).normal_block(
|
||||
.{ .raw = i << L1.SHIFT },
|
||||
.{},
|
||||
);
|
||||
}
|
||||
|
||||
const ttbr0 = @intFromPtr(&gFixedLow);
|
||||
const ttbr1 = @intFromPtr(&gFixedHigh);
|
||||
const ttbr0 = @intFromPtr(&g_fixed_low);
|
||||
const ttbr1 = @intFromPtr(&g_fixed_high);
|
||||
|
||||
regs.TTBR0_EL1.set(ttbr0);
|
||||
regs.TTBR1_EL1.set(ttbr1);
|
||||
|
||||
+12
-76
@@ -2,87 +2,28 @@
|
||||
|
||||
const boot = @import("riscv64/boot.zig");
|
||||
const regs = @import("riscv64/regs.zig");
|
||||
const thread = @import("../thread.zig");
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Arena = @import("../arena.zig").Arena;
|
||||
pub const vmm = @import("riscv64/vmm.zig");
|
||||
|
||||
export const _ = boot.rv64BspLowerEntry;
|
||||
|
||||
extern fn __rv64_enter_task(cx: *Context) callconv(.C) noreturn;
|
||||
extern fn __rv64_switch_task(dcx: *Context, scx: *Context) callconv(.C) void;
|
||||
extern fn __rv64_task_enter_kernel() callconv(.C) noreturn;
|
||||
|
||||
fn idleFunction() callconv(.naked) noreturn {
|
||||
asm volatile ("j .");
|
||||
}
|
||||
export const _ = boot.rv64_bsp_lower_entry;
|
||||
|
||||
/// This CPU's HART (HARdware Thread) ID.
|
||||
pub threadlocal var tHartId: u32 = 0;
|
||||
pub threadlocal var t_hart_id: u32 = 0;
|
||||
|
||||
/// RISC-V task context
|
||||
pub const Context = extern struct {
|
||||
const STACK_SIZE: usize = 8192;
|
||||
pub const Context = @import("riscv64/context.zig").Context;
|
||||
|
||||
// Has to be exactly at offset 0x00, used in assembly.
|
||||
kstack: thread.KStack(STACK_SIZE),
|
||||
|
||||
/// Constructs an idle context struct.
|
||||
pub fn idle() @This() {
|
||||
const entry = @intFromPtr(&idleFunction);
|
||||
return Context.kernel(entry, 0);
|
||||
}
|
||||
|
||||
/// Constructs a kernel task context with entry point in `pc` and an `arg`ument.
|
||||
pub fn kernel(pc: usize, arg: usize) @This() {
|
||||
var ks = thread.KStack(STACK_SIZE).create();
|
||||
const entry = @intFromPtr(&__rv64_task_enter_kernel);
|
||||
|
||||
ks.push(pc);
|
||||
ks.push(arg);
|
||||
|
||||
ks.push(0); // x8/s0/fp
|
||||
ks.push(0); // x9/s1
|
||||
ks.push(0); // x18/s2
|
||||
ks.push(0); // x19/s3
|
||||
ks.push(0); // x20/s4
|
||||
ks.push(0); // x21/s5
|
||||
ks.push(0); // x22/s6
|
||||
ks.push(0); // x23/s7
|
||||
ks.push(0); // x24/s8
|
||||
ks.push(0); // x25/s9
|
||||
ks.push(0); // x26/s10
|
||||
ks.push(0); // x27/s11
|
||||
ks.push(0); // x4/gp
|
||||
ks.push(entry); // x1/ra return address
|
||||
|
||||
return .{ .kstack = ks };
|
||||
}
|
||||
|
||||
/// Low-level task context entry function.
|
||||
pub fn enter(self: *@This()) noreturn {
|
||||
__rv64_enter_task(self);
|
||||
}
|
||||
|
||||
/// Low-level task context switch function.
|
||||
pub fn switchFrom(self: *@This(), from: *@This()) void {
|
||||
__rv64_switch_task(self, from);
|
||||
}
|
||||
};
|
||||
|
||||
/// Halts the CPU execution indefinitely, without ever returning.
|
||||
pub inline fn halt() noreturn {
|
||||
while (true) {
|
||||
_ = setInterruptMask(true);
|
||||
waitForInterrupt();
|
||||
_ = set_interrupt_mask(true);
|
||||
wait_for_interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifies the interrupt mask to either allow or block IRQs from being delivered to the CPU.
|
||||
/// Returns the old IRQ mask.
|
||||
pub inline fn setInterruptMask(mask: bool) bool {
|
||||
const old = interruptMask();
|
||||
pub inline fn set_interrupt_mask(mask: bool) bool {
|
||||
const old = interrupt_mask();
|
||||
if (mask) {
|
||||
regs.SSTATUS.modify(.{}, .{ .SIE = true });
|
||||
} else {
|
||||
@@ -91,23 +32,19 @@ pub inline fn setInterruptMask(mask: bool) bool {
|
||||
return old;
|
||||
}
|
||||
|
||||
/// Returns the current state of IRQ masking.
|
||||
pub fn interruptMask() bool {
|
||||
pub fn interrupt_mask() bool {
|
||||
return regs.SSTATUS.read().SIE;
|
||||
}
|
||||
|
||||
/// Suspends the CPU until an interrupt is signalled.
|
||||
pub inline fn waitForInterrupt() void {
|
||||
pub inline fn wait_for_interrupt() void {
|
||||
asm volatile ("wfi");
|
||||
}
|
||||
|
||||
/// Hint to the CPU that the code is executing a "busy-wait" or a "spin-wait" loop.
|
||||
pub inline fn spinHint() void {
|
||||
pub inline fn spin_hint() void {
|
||||
// Don't want to explicitly enable Zihintpause ext, so just paste this as raw opcode
|
||||
asm volatile (".word 0x0100000f");
|
||||
}
|
||||
|
||||
/// Combined memory/compiler fence to ensure specific ordering of instructions and memory accesses.
|
||||
pub inline fn barrier(comptime ordering: std.builtin.AtomicOrder) void {
|
||||
switch (ordering) {
|
||||
.acquire => {
|
||||
@@ -124,8 +61,7 @@ pub inline fn barrier(comptime ordering: std.builtin.AtomicOrder) void {
|
||||
asm volatile ("" ::: "memory");
|
||||
}
|
||||
|
||||
/// Set the CPU's thread pointer to some value.
|
||||
pub inline fn setThreadPointer(tp: usize) void {
|
||||
pub inline fn set_thread_pointer(tp: usize) void {
|
||||
asm volatile ("mv tp, %[tp]"
|
||||
:
|
||||
: [tp] "r" (tp),
|
||||
|
||||
+55
-70
@@ -7,66 +7,68 @@ const dtb = @import("../../util/dtb.zig");
|
||||
const mem = @import("../../mem.zig");
|
||||
const arena = @import("../../arena.zig");
|
||||
const exception = @import("exception.zig");
|
||||
const tls = @import("../../mem/tls.zig");
|
||||
|
||||
const physMemory = mem.phys;
|
||||
const phys_memory = mem.phys;
|
||||
const PAGE_SIZE = mem.vmm.PAGE_SIZE;
|
||||
const log = debug.log;
|
||||
const arch = kernel.arch;
|
||||
|
||||
extern const __rv64_bsp_stack_top: u8;
|
||||
|
||||
var gDtbAddress: usize = 0;
|
||||
var gBspHartId: u32 = 0;
|
||||
var g_dtb_address: usize = 0;
|
||||
var g_bsp_hart_id: u32 = 0;
|
||||
|
||||
fn bspUpperEntry(realAddress: usize, unused: usize) callconv(.C) noreturn {
|
||||
fn bsp_upper_entry(real_address: usize, unused: usize) callconv(.C) noreturn {
|
||||
_ = unused;
|
||||
|
||||
arch.barrier(.acq_rel);
|
||||
|
||||
// Relocate the kernel yet again, this time to another base
|
||||
const relaStart = @intFromPtr(&__rela_start);
|
||||
const relaEnd = @intFromPtr(&__rela_end);
|
||||
const relOffset = vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(realAddress);
|
||||
const rela_start = @intFromPtr(&__rela_start);
|
||||
const rela_end = @intFromPtr(&__rela_end);
|
||||
const rel_offset = vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(real_address);
|
||||
|
||||
arch.barrier(.acq_rel);
|
||||
rv64RelocateKernel(relOffset, relaStart, relaEnd);
|
||||
vmm.unmapEarly();
|
||||
rv64_relocate_kernel(rel_offset, rela_start, rela_end);
|
||||
vmm.unmap_early();
|
||||
|
||||
// Setup exception handling
|
||||
exception.init();
|
||||
|
||||
debug.log.setWriteFn(&sbi.debugPrintByte);
|
||||
kernel.mem.PhysicalAddress.gVirtualizeBase = 0;
|
||||
kernel.mem.PhysicalAddress.gVirtualizeSize = vmm.virtualizeRange();
|
||||
debug.log.set_write_fn(&sbi.debug_print_byte);
|
||||
kernel.mem.PhysicalAddress.g_virtualize_base = 0;
|
||||
kernel.mem.PhysicalAddress.g_virtualize_size = vmm.virtualize_range();
|
||||
|
||||
// Setup physical memory management
|
||||
setupMemoryFromFdt(realAddress);
|
||||
setup_memory_from_fdt(real_address);
|
||||
|
||||
setupPerCpu();
|
||||
arch.tHartId = gBspHartId;
|
||||
setup_per_cpu();
|
||||
arch.impl.t_hart_id = g_bsp_hart_id;
|
||||
|
||||
kernel.kernel_main();
|
||||
}
|
||||
|
||||
pub export fn rv64BspLowerEntry(realAddress: usize, bspHartId: usize, dtbAddress: usize) callconv(.C) noreturn {
|
||||
debug.log.setWriteFn(&sbi.debugPrintByte);
|
||||
pub export fn rv64_bsp_lower_entry(real_address: usize, bsp_hart_id: usize, dtb_address: usize) callconv(.C) noreturn {
|
||||
debug.log.set_write_fn(&sbi.debug_print_byte);
|
||||
|
||||
gDtbAddress = dtbAddress;
|
||||
gBspHartId = @truncate(bspHartId);
|
||||
g_dtb_address = dtb_address;
|
||||
g_bsp_hart_id = @truncate(bsp_hart_id);
|
||||
|
||||
vmm.mapEarly(realAddress);
|
||||
vmm.map_early(real_address);
|
||||
|
||||
// &bspUpperEntry will yield a pointer like: X + P, where
|
||||
// * X is symbol's raw address,
|
||||
// * P is the physical load base of the image (0x80200000 on rv64 usually)
|
||||
//
|
||||
// Relocate the address to point to Y + P, where Y is the virtual load base
|
||||
// const kernelL1Offset = realAddress & ((1 << 30) - 1);
|
||||
const realAddressL1Offset = vmm.L1.offset(realAddress);
|
||||
const virtualEntry = @intFromPtr(&bspUpperEntry) + vmm.KERNEL_VIRTUAL_BASE - realAddress + realAddressL1Offset;
|
||||
const virtualSp = @intFromPtr(&__rv64_bsp_stack_top) + vmm.KERNEL_VIRTUAL_BASE - realAddress + realAddressL1Offset;
|
||||
const real_address_l1_offset = vmm.L1.offset(real_address);
|
||||
const virtual_entry = @intFromPtr(&bsp_upper_entry) + vmm.KERNEL_VIRTUAL_BASE //
|
||||
- real_address + real_address_l1_offset;
|
||||
const virtual_sp = @intFromPtr(&__rv64_bsp_stack_top) + vmm.KERNEL_VIRTUAL_BASE //
|
||||
- real_address + real_address_l1_offset;
|
||||
|
||||
longJump(virtualEntry, virtualSp, realAddress, 0);
|
||||
long_jump(virtual_entry, virtual_sp, real_address, 0);
|
||||
|
||||
arch.halt();
|
||||
}
|
||||
@@ -75,50 +77,28 @@ pub export fn rv64BspLowerEntry(realAddress: usize, bspHartId: usize, dtbAddress
|
||||
|
||||
extern const __rela_start: u8;
|
||||
extern const __rela_end: u8;
|
||||
extern var __tdata_start: u8;
|
||||
extern var __tdata_end: u8;
|
||||
extern var __tbss_start: u8;
|
||||
extern var __tbss_end: u8;
|
||||
extern var __kernel_start: u8;
|
||||
extern var __kernel_end: u8;
|
||||
|
||||
fn setupPerCpu() void {
|
||||
// Assume .tbss follows .tdata
|
||||
const tdataStart = @intFromPtr(&__tdata_start);
|
||||
const tdataEnd = @intFromPtr(&__tdata_end);
|
||||
const tdataSize = tdataEnd - tdataStart;
|
||||
const tbssStart = @intFromPtr(&__tbss_start);
|
||||
const tbssEnd = @intFromPtr(&__tbss_end);
|
||||
const tbssSize = tbssEnd - tbssStart;
|
||||
|
||||
const tdataData = @as([*]u8, @ptrFromInt(tdataStart))[0..tdataSize];
|
||||
|
||||
const tlsSize = tdataSize + tbssSize;
|
||||
const tlsPageCount = (tlsSize + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||
// Variant I: TLS block 0 follows TP after a certain displacement
|
||||
const tlsAddress = physMemory.alloc_pages(tlsPageCount).?.virtualize();
|
||||
const tlsData = @as([*]u8, @ptrFromInt(tlsAddress))[0..tlsSize];
|
||||
|
||||
log.info("Allocated TLS @ {*}", .{tlsData});
|
||||
|
||||
@memcpy(tlsData[0..tdataSize], tdataData);
|
||||
@memset(tlsData[tdataSize..], 0);
|
||||
|
||||
arch.setThreadPointer(tlsAddress);
|
||||
fn setup_per_cpu() void {
|
||||
const tls_data = tls.load_kernel_tls_image();
|
||||
const tp = @intFromPtr(tls_data.ptr);
|
||||
log.info("Set TP = 0x{x}", .{tp});
|
||||
arch.set_thread_pointer(tp);
|
||||
}
|
||||
|
||||
export fn rv64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: usize) void {
|
||||
export fn rv64_relocate_kernel(image_base: usize, rela_start: usize, rela_end: usize) void {
|
||||
const elf = @import("std").elf;
|
||||
|
||||
const relaTablePtr = @as([*]elf.Rela, @ptrFromInt(relaStart));
|
||||
const relaCount = (relaEnd - relaStart) / @sizeOf(elf.Rela);
|
||||
const relaTable = relaTablePtr[0..relaCount];
|
||||
for (relaTable) |entry| {
|
||||
const relaType: elf.R_RISCV = @enumFromInt(entry.r_type());
|
||||
switch (relaType) {
|
||||
const rela_table_ptr = @as([*]elf.Rela, @ptrFromInt(rela_start));
|
||||
const rela_count = (rela_end - rela_start) / @sizeOf(elf.Rela);
|
||||
const rela_table = rela_table_ptr[0..rela_count];
|
||||
for (rela_table) |entry| {
|
||||
const rela_type: elf.R_RISCV = @enumFromInt(entry.r_type());
|
||||
switch (rela_type) {
|
||||
.RELATIVE => {
|
||||
const value = @as(*isize, @ptrFromInt(imageBase + entry.r_offset));
|
||||
value.* = @as(isize, @bitCast(imageBase)) + entry.r_addend;
|
||||
const value = @as(*isize, @ptrFromInt(image_base + entry.r_offset));
|
||||
value.* = @as(isize, @bitCast(image_base)) + entry.r_addend;
|
||||
},
|
||||
else => {
|
||||
arch.halt();
|
||||
@@ -127,22 +107,27 @@ export fn rv64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn setupMemoryFromFdt(realAddress: usize) void {
|
||||
const kernelStart = @intFromPtr(&__kernel_start);
|
||||
const kernelEnd = @intFromPtr(&__kernel_end);
|
||||
fn setup_memory_from_fdt(real_address: usize) void {
|
||||
const kernel_start = @intFromPtr(&__kernel_start);
|
||||
const kernel_end = @intFromPtr(&__kernel_end);
|
||||
|
||||
const fdt = dtb.Fdt.fromPhysicalAddress(.{ .raw = gDtbAddress }) catch |err| {
|
||||
const fdt = dtb.Fdt.from_physical_address(.{ .raw = g_dtb_address }) catch |err| {
|
||||
log.panic("Cannot initialize raw DTB: {}", .{err});
|
||||
};
|
||||
fdt.addPhysicalMemoryToSystem();
|
||||
fdt.add_physical_memory_to_system();
|
||||
|
||||
physMemory.addReservedRegion("kernel", kernelStart - (vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(realAddress)) + realAddress, kernelEnd - kernelStart);
|
||||
physMemory.addReservedRegion("fdt", gDtbAddress, vmm.L3.align_up(fdt.bytes.len));
|
||||
phys_memory.add_reserved_region(
|
||||
"kernel",
|
||||
kernel_start -
|
||||
(vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(real_address)) + real_address,
|
||||
kernel_end - kernel_start,
|
||||
);
|
||||
phys_memory.add_reserved_region("fdt", g_dtb_address, vmm.L3.align_up(fdt.bytes.len));
|
||||
|
||||
physMemory.init();
|
||||
phys_memory.init();
|
||||
}
|
||||
|
||||
inline fn longJump(pc: usize, sp: usize, a0: usize, a1: usize) noreturn {
|
||||
inline fn long_jump(pc: usize, sp: usize, a0: usize, a1: usize) noreturn {
|
||||
asm volatile (
|
||||
\\ mv sp, %[sp]
|
||||
\\ jr %[pc]
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
.pushsection .text
|
||||
.option push
|
||||
.option norvc
|
||||
|
||||
.global __rv64_enter_task
|
||||
.global __rv64_switch_task
|
||||
.global __rv64_task_enter_kernel
|
||||
|
||||
.macro LOAD_TASK_STATE
|
||||
ld ra, 0 * 8(sp)
|
||||
ld gp, 1 * 8(sp)
|
||||
ld s11, 2 * 8(sp)
|
||||
ld s10, 3 * 8(sp)
|
||||
ld s9, 4 * 8(sp)
|
||||
ld s8, 5 * 8(sp)
|
||||
ld s7, 6 * 8(sp)
|
||||
ld s6, 7 * 8(sp)
|
||||
ld s5, 8 * 8(sp)
|
||||
ld s4, 9 * 8(sp)
|
||||
ld s3, 10 * 8(sp)
|
||||
ld s2, 11 * 8(sp)
|
||||
ld s1, 12 * 8(sp)
|
||||
ld s0, 13 * 8(sp)
|
||||
|
||||
addi sp, sp, 14 * 8
|
||||
.endm
|
||||
|
||||
.macro SAVE_TASK_STATE
|
||||
addi sp, sp, -(14 * 8)
|
||||
|
||||
sd ra, 0 * 8(sp)
|
||||
sd gp, 1 * 8(sp)
|
||||
sd s11, 2 * 8(sp)
|
||||
sd s10, 3 * 8(sp)
|
||||
sd s9, 4 * 8(sp)
|
||||
sd s8, 5 * 8(sp)
|
||||
sd s7, 6 * 8(sp)
|
||||
sd s6, 7 * 8(sp)
|
||||
sd s5, 8 * 8(sp)
|
||||
sd s4, 9 * 8(sp)
|
||||
sd s3, 10 * 8(sp)
|
||||
sd s2, 11 * 8(sp)
|
||||
sd s1, 12 * 8(sp)
|
||||
sd s0, 13 * 8(sp)
|
||||
.endm
|
||||
|
||||
__rv64_task_enter_kernel:
|
||||
ld a0, (sp) // argument
|
||||
ld ra, 8(sp) // entry
|
||||
addi sp, sp, 16
|
||||
|
||||
// TODO S-mode -> S-mode return via sret
|
||||
ret
|
||||
|
||||
__rv64_switch_task:
|
||||
// a0 - new context
|
||||
// a1 - old context
|
||||
SAVE_TASK_STATE
|
||||
sd sp, (a1)
|
||||
__rv64_enter_task:
|
||||
// a0 -- new context
|
||||
ld sp, (a0)
|
||||
LOAD_TASK_STATE
|
||||
ret
|
||||
|
||||
.option pop // norvc
|
||||
.popsection // .text
|
||||
@@ -0,0 +1,62 @@
|
||||
const thread = @import("../../thread.zig");
|
||||
|
||||
fn idle_function() callconv(.naked) noreturn {
|
||||
asm volatile ("j .");
|
||||
}
|
||||
|
||||
extern fn __rv64_enter_task(cx: *Context) callconv(.C) noreturn;
|
||||
extern fn __rv64_switch_task(dcx: *Context, scx: *Context) callconv(.C) void;
|
||||
extern fn __rv64_task_enter_kernel() callconv(.C) noreturn;
|
||||
|
||||
pub const Context = extern struct {
|
||||
const STACK_SIZE: usize = 8192;
|
||||
|
||||
// Has to be exactly at offset 0x00, used in assembly.
|
||||
kstack: thread.KStack(STACK_SIZE),
|
||||
|
||||
/// Constructs an idle context struct.
|
||||
pub fn idle() @This() {
|
||||
const entry = @intFromPtr(&idle_function);
|
||||
return Context.kernel(entry, 0);
|
||||
}
|
||||
|
||||
/// Constructs a kernel task context with entry point in `pc` and an `arg`ument.
|
||||
pub fn kernel(pc: usize, arg: usize) @This() {
|
||||
var ks = thread.KStack(STACK_SIZE).create();
|
||||
const entry = @intFromPtr(&__rv64_task_enter_kernel);
|
||||
|
||||
ks.push(pc);
|
||||
ks.push(arg);
|
||||
|
||||
ks.push(0); // x8/s0/fp
|
||||
ks.push(0); // x9/s1
|
||||
ks.push(0); // x18/s2
|
||||
ks.push(0); // x19/s3
|
||||
ks.push(0); // x20/s4
|
||||
ks.push(0); // x21/s5
|
||||
ks.push(0); // x22/s6
|
||||
ks.push(0); // x23/s7
|
||||
ks.push(0); // x24/s8
|
||||
ks.push(0); // x25/s9
|
||||
ks.push(0); // x26/s10
|
||||
ks.push(0); // x27/s11
|
||||
ks.push(0); // x4/gp
|
||||
ks.push(entry); // x1/ra return address
|
||||
|
||||
return .{ .kstack = ks };
|
||||
}
|
||||
|
||||
/// Low-level task context entry function.
|
||||
pub fn enter(self: *@This()) noreturn {
|
||||
__rv64_enter_task(self);
|
||||
}
|
||||
|
||||
/// Low-level task context switch function.
|
||||
pub fn switch_from(self: *@This(), from: *@This()) void {
|
||||
__rv64_switch_task(self, from);
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
asm (@embedFile("context.S"));
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
.set ENTRY_SYMBOL, rv64BspLowerEntry
|
||||
.set RELOC_SYMBOL, rv64RelocateKernel
|
||||
.set ENTRY_SYMBOL, rv64_bsp_lower_entry
|
||||
.set RELOC_SYMBOL, rv64_relocate_kernel
|
||||
|
||||
.global __rv64_entry
|
||||
.global __rv64_bsp_stack_top
|
||||
@@ -25,7 +25,7 @@ __rv64_entry:
|
||||
.ascii "RISCV\x00\x00\x00" // Magic 1
|
||||
.ascii "RSC\x05" // Magic 2
|
||||
.long 0
|
||||
.option pop
|
||||
.option pop // rvc
|
||||
|
||||
.option push
|
||||
.option norvc
|
||||
@@ -80,80 +80,12 @@ __rv64_real_entry:
|
||||
jr t0
|
||||
.size __rv64_entry, . - __rv64_entry
|
||||
|
||||
.option pop
|
||||
.popsection
|
||||
.option pop // norvc
|
||||
.popsection // .text.entry
|
||||
|
||||
.pushsection .bss
|
||||
.p2align 4
|
||||
__rv64_bsp_stack_bottom:
|
||||
.skip 65536
|
||||
__rv64_bsp_stack_top:
|
||||
.popsection
|
||||
|
||||
.pushsection .text
|
||||
.option push
|
||||
.option norvc
|
||||
|
||||
.global __rv64_enter_task
|
||||
.global __rv64_switch_task
|
||||
.global __rv64_task_enter_kernel
|
||||
|
||||
.macro LOAD_TASK_STATE
|
||||
ld ra, 0 * 8(sp)
|
||||
ld gp, 1 * 8(sp)
|
||||
ld s11, 2 * 8(sp)
|
||||
ld s10, 3 * 8(sp)
|
||||
ld s9, 4 * 8(sp)
|
||||
ld s8, 5 * 8(sp)
|
||||
ld s7, 6 * 8(sp)
|
||||
ld s6, 7 * 8(sp)
|
||||
ld s5, 8 * 8(sp)
|
||||
ld s4, 9 * 8(sp)
|
||||
ld s3, 10 * 8(sp)
|
||||
ld s2, 11 * 8(sp)
|
||||
ld s1, 12 * 8(sp)
|
||||
ld s0, 13 * 8(sp)
|
||||
|
||||
addi sp, sp, 14 * 8
|
||||
.endm
|
||||
|
||||
.macro SAVE_TASK_STATE
|
||||
addi sp, sp, -(14 * 8)
|
||||
|
||||
sd ra, 0 * 8(sp)
|
||||
sd gp, 1 * 8(sp)
|
||||
sd s11, 2 * 8(sp)
|
||||
sd s10, 3 * 8(sp)
|
||||
sd s9, 4 * 8(sp)
|
||||
sd s8, 5 * 8(sp)
|
||||
sd s7, 6 * 8(sp)
|
||||
sd s6, 7 * 8(sp)
|
||||
sd s5, 8 * 8(sp)
|
||||
sd s4, 9 * 8(sp)
|
||||
sd s3, 10 * 8(sp)
|
||||
sd s2, 11 * 8(sp)
|
||||
sd s1, 12 * 8(sp)
|
||||
sd s0, 13 * 8(sp)
|
||||
.endm
|
||||
|
||||
__rv64_task_enter_kernel:
|
||||
ld a0, (sp) // argument
|
||||
ld ra, 8(sp) // entry
|
||||
addi sp, sp, 16
|
||||
|
||||
// TODO S-mode -> S-mode return via sret
|
||||
ret
|
||||
|
||||
__rv64_switch_task:
|
||||
// a0 - new context
|
||||
// a1 - old context
|
||||
SAVE_TASK_STATE
|
||||
sd sp, (a1)
|
||||
__rv64_enter_task:
|
||||
// a0 -- new context
|
||||
ld sp, (a0)
|
||||
LOAD_TASK_STATE
|
||||
ret
|
||||
|
||||
.option pop
|
||||
.popsection
|
||||
.popsection // .bss
|
||||
|
||||
@@ -27,7 +27,7 @@ const SbiResult = union(enum) {
|
||||
ok: u64,
|
||||
err: SbiError,
|
||||
|
||||
fn fromSbi(a0: u64, a1: u64) SbiResult {
|
||||
fn from_sbi(a0: u64, a1: u64) SbiResult {
|
||||
if (a0 == 0) {
|
||||
return .{ .ok = a1 };
|
||||
} else {
|
||||
@@ -36,7 +36,7 @@ const SbiResult = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
fn sbiCall1(ext: SbiExtension, func: u64, arg0: u64) SbiResult {
|
||||
fn sbi_call1(ext: SbiExtension, func: u64, arg0: u64) SbiResult {
|
||||
var a0: u64 = undefined;
|
||||
var a1: u64 = undefined;
|
||||
asm volatile ("ecall"
|
||||
@@ -47,9 +47,9 @@ fn sbiCall1(ext: SbiExtension, func: u64, arg0: u64) SbiResult {
|
||||
[extn] "{a7}" (ext),
|
||||
: "a2", "a3", "a4", "a5"
|
||||
);
|
||||
return SbiResult.fromSbi(a0, a1);
|
||||
return SbiResult.from_sbi(a0, a1);
|
||||
}
|
||||
|
||||
pub fn debugPrintByte(byte: u8) void {
|
||||
_ = sbiCall1(.dbcn, 0x02, @as(u64, byte));
|
||||
pub fn debug_print_byte(byte: u8) void {
|
||||
_ = sbi_call1(.dbcn, 0x02, @as(u64, byte));
|
||||
}
|
||||
|
||||
+22
-16
@@ -12,7 +12,7 @@ const EARLY_MAPPING_SIZE: usize = 16;
|
||||
|
||||
pub const L1 = mem.TranslationLevel(30);
|
||||
pub const L2 = mem.TranslationLevel(21);
|
||||
pub const L3 = mem.TranslationLevel(12);
|
||||
pub const L3 = mem.vmm.L3;
|
||||
|
||||
pub const RawEntry = packed struct(u64) {
|
||||
// 0: Valid
|
||||
@@ -38,7 +38,7 @@ pub const RawEntry = packed struct(u64) {
|
||||
// 49..64: Unused bits
|
||||
_pad1: u15 = 0,
|
||||
|
||||
pub fn makeUnion(self: @This(), other: @This()) @This() {
|
||||
pub fn make_union(self: @This(), other: @This()) @This() {
|
||||
const lhs = @as(u64, @bitCast(self));
|
||||
const rhs = @as(u64, @bitCast(other));
|
||||
return @as(@This(), @bitCast(lhs | rhs));
|
||||
@@ -72,7 +72,7 @@ pub fn TableEntry(comptime Level: type) type {
|
||||
|
||||
pub fn page(addr: PhysicalAddress, flags: RawEntry) @This() {
|
||||
return .{
|
||||
.raw = flags.makeUnion(.{
|
||||
.raw = flags.make_union(.{
|
||||
.address = @as(u39, @intCast(addr.raw >> 12)),
|
||||
.r = true,
|
||||
.v = true,
|
||||
@@ -84,7 +84,7 @@ pub fn TableEntry(comptime Level: type) type {
|
||||
|
||||
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
|
||||
flags.clear(.{ .r = true, .w = true, .x = true });
|
||||
return .{ .raw = flags.makeUnion(.{
|
||||
return .{ .raw = flags.make_union(.{
|
||||
.address = @as(u39, @intCast(addr.raw >> 12)),
|
||||
.v = true,
|
||||
}) };
|
||||
@@ -108,37 +108,43 @@ pub fn Table(comptime Level: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
var gFixed = Table(L1).empty();
|
||||
var gFixedLock: sync.IrqSafeSpinlock = .{};
|
||||
var g_fixed = Table(L1).empty();
|
||||
var g_fixed_lock: sync.Spinlock = .{};
|
||||
|
||||
pub fn virtualizeRange() usize {
|
||||
pub fn virtualize_range() usize {
|
||||
return EARLY_MAPPING_SIZE * L1.SIZE;
|
||||
}
|
||||
|
||||
pub fn unmapEarly() void {
|
||||
pub fn unmap_early() void {
|
||||
// Make lower half mappings non-executable
|
||||
gFixedLock.lock();
|
||||
defer gFixedLock.release();
|
||||
const guard = g_fixed_lock.lock_irqsave();
|
||||
defer guard.release();
|
||||
for (0..EARLY_MAPPING_SIZE) |i| {
|
||||
gFixed.entry(i).* = .page(
|
||||
g_fixed.entry(i).* = .page(
|
||||
.{ .raw = L1.address(i) },
|
||||
.{ .r = true, .w = true },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mapEarly(realAddress: usize) void {
|
||||
const realL1 = L1.index(realAddress);
|
||||
pub fn map_early(real_address: usize) void {
|
||||
const real_l1 = L1.index(real_address);
|
||||
|
||||
// Identity map first 16GiB of memory
|
||||
for (0..EARLY_MAPPING_SIZE) |i| {
|
||||
gFixed.entry(i).* = .page(.{ .raw = L1.address(i) }, .{ .r = true, .w = true, .x = true });
|
||||
g_fixed.entry(i).* = .page(
|
||||
.{ .raw = L1.address(i) },
|
||||
.{ .r = true, .w = true, .x = true },
|
||||
);
|
||||
}
|
||||
|
||||
// Map 1GiB at KERNEL_VIRTUAL_BASE -> physical 1GiB where the kernel is loaded
|
||||
gFixed.entry(KERNEL_VIRTUAL_L1I).* = .page(.{ .raw = L1.address(realL1) }, .{ .r = true, .w = true, .x = true });
|
||||
g_fixed.entry(KERNEL_VIRTUAL_L1I).* = .page(
|
||||
.{ .raw = L1.address(real_l1) },
|
||||
.{ .r = true, .w = true, .x = true },
|
||||
);
|
||||
|
||||
const address = @as(usize, @intFromPtr(&gFixed));
|
||||
const address = @as(usize, @intFromPtr(&g_fixed));
|
||||
regs.SATP.write(.{ .PPN = @intCast(address >> 12), .MODE = .sv39 });
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
//! Simple bump allocator arena.
|
||||
|
||||
const physMemory = @import("mem/phys.zig");
|
||||
const phys_memory = @import("mem/phys.zig");
|
||||
const log = @import("debug.zig").log;
|
||||
const mem = @import("mem.zig");
|
||||
|
||||
/// Bump allocator implementation.
|
||||
pub const Arena = struct {
|
||||
physBase: mem.PhysicalAddress,
|
||||
phys_base: mem.PhysicalAddress,
|
||||
capacity: usize,
|
||||
len: usize,
|
||||
|
||||
@@ -14,8 +14,8 @@ pub const Arena = struct {
|
||||
///
|
||||
/// Requires initialized physical memory management.
|
||||
pub fn init(cap: usize) ?Arena {
|
||||
const physBase = physMemory.alloc_pages(cap / mem.vmm.PAGE_SIZE) orelse return null;
|
||||
return .{ .physBase = physBase, .capacity = cap, .len = 0 };
|
||||
const phys_base = phys_memory.alloc_pages(cap / mem.vmm.PAGE_SIZE) orelse return null;
|
||||
return .{ .phys_base = phys_base, .capacity = cap, .len = 0 };
|
||||
}
|
||||
|
||||
/// Allocates an object of type `T` within this arena.
|
||||
@@ -28,7 +28,7 @@ pub const Arena = struct {
|
||||
log.panic("Out of memory. Cannot allocate {} bytes", .{@sizeOf(T)});
|
||||
}
|
||||
|
||||
const v = self.physBase.add(self.len).virtualize();
|
||||
const v = self.phys_base.add(self.len).virtualize();
|
||||
const ptr = @as(*T, @ptrFromInt(v));
|
||||
self.len += @sizeOf(T);
|
||||
|
||||
|
||||
+13
-13
@@ -2,7 +2,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
fn dummyWrite(_: u8) void {}
|
||||
fn dummy_write(_: u8) void {}
|
||||
|
||||
/// The main method of kernel logging.
|
||||
pub const log = struct {
|
||||
@@ -18,20 +18,20 @@ pub const log = struct {
|
||||
err,
|
||||
};
|
||||
|
||||
var writeFn: *const fn (u8) void = dummyWrite;
|
||||
const writer: std.io.GenericWriter(u0, error{}, writeWrapperFn) = .{ .context = 0 };
|
||||
var write_fn: *const fn (u8) void = dummy_write;
|
||||
const writer: std.io.GenericWriter(u0, error{}, write_wrapper_fn) = .{ .context = 0 };
|
||||
|
||||
fn writeWrapperFn(context: u0, data: []const u8) error{}!usize {
|
||||
fn write_wrapper_fn(context: u0, data: []const u8) error{}!usize {
|
||||
_ = context;
|
||||
for (data) |byte| {
|
||||
writeFn(byte);
|
||||
write_fn(byte);
|
||||
}
|
||||
return data.len;
|
||||
}
|
||||
|
||||
/// Replaces the function to print debug bytes with a new one.
|
||||
pub fn setWriteFn(f: *const fn (u8) void) void {
|
||||
writeFn = f;
|
||||
pub fn set_write_fn(f: *const fn (u8) void) void {
|
||||
write_fn = f;
|
||||
}
|
||||
|
||||
/// Emit an `info`-level log record.
|
||||
@@ -55,8 +55,8 @@ pub const log = struct {
|
||||
}
|
||||
|
||||
/// Write raw byte data into the debugging output.
|
||||
pub fn writeRaw(data: []const u8) void {
|
||||
_ = writeWrapperFn(0, data) catch return;
|
||||
pub fn write_waw(data: []const u8) void {
|
||||
_ = write_wrapper_fn(0, data) catch return;
|
||||
}
|
||||
|
||||
/// Write a formatted string (without logging prefix/suffix/newline) into the debugging output.
|
||||
@@ -66,8 +66,8 @@ pub const log = struct {
|
||||
|
||||
/// Write a formatted log record into the debugging output.
|
||||
pub fn writeln(comptime level: Level, comptime format: []const u8, args: anytype) void {
|
||||
const prefix = comptime logPrefix(level);
|
||||
const suffix = comptime logSuffix(level);
|
||||
const prefix = comptime log_prefix(level);
|
||||
const suffix = comptime log_suffix(level);
|
||||
writer.print(prefix ++ format ++ suffix ++ "\r\n", args) catch return;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ pub const log = struct {
|
||||
@panic("Explicit kernel panic");
|
||||
}
|
||||
|
||||
fn logPrefix(comptime level: Level) []const u8 {
|
||||
fn log_prefix(comptime level: Level) []const u8 {
|
||||
switch (level) {
|
||||
.debug => return "",
|
||||
.info => return "\x1B[1;36m",
|
||||
@@ -91,7 +91,7 @@ pub const log = struct {
|
||||
.err => return "\x1B[1;31m",
|
||||
}
|
||||
}
|
||||
fn logSuffix(comptime level: Level) []const u8 {
|
||||
fn log_suffix(comptime level: Level) []const u8 {
|
||||
if (level == .debug) {
|
||||
return "";
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ noinline fn f1(arg: usize, c: usize) void {
|
||||
pub export fn kernel_main() callconv(.C) noreturn {
|
||||
log.write("\x1B[2J", .{});
|
||||
var a = arena.Arena.init(256 * 0x1000) orelse @panic("Could not setup kernel arena");
|
||||
thread.Queue.initThisCpu(&a);
|
||||
thread.Queue.init_this_cpu(&a);
|
||||
|
||||
const pc = @intFromPtr(&f0);
|
||||
for (0..4) |i| {
|
||||
|
||||
+14
-14
@@ -15,9 +15,9 @@ pub const PhysicalAddress = packed struct(u64) {
|
||||
pub const NULL: @This() = .{ .raw = 0 };
|
||||
|
||||
/// Base address to add to a given `PhysicalAddress` in order to "virtualize" it.
|
||||
pub var gVirtualizeBase: usize = 0;
|
||||
pub var g_virtualize_base: usize = 0;
|
||||
/// Maximum `PhysicalAddress` that can be represented as a virtual address.
|
||||
pub var gVirtualizeSize: usize = 0;
|
||||
pub var g_virtualize_size: usize = 0;
|
||||
|
||||
/// Adds an `offset` to this `PhysicalAddress`
|
||||
pub fn add(self: @This(), offset: usize) @This() {
|
||||
@@ -32,11 +32,11 @@ pub const PhysicalAddress = packed struct(u64) {
|
||||
/// Panics if the physical address points to a memory that cannot be represented by a virtual
|
||||
/// address.
|
||||
pub fn virtualize(self: @This()) usize {
|
||||
if (self.raw > gVirtualizeSize) {
|
||||
if (self.raw > g_virtualize_size) {
|
||||
@panic("Physical address out of virtualize bounds");
|
||||
}
|
||||
|
||||
return self.raw + gVirtualizeBase;
|
||||
return self.raw + g_virtualize_base;
|
||||
}
|
||||
|
||||
/// "De-virtualizes" a previously "virtualized" physical address by mapping it back into its
|
||||
@@ -46,17 +46,17 @@ pub const PhysicalAddress = packed struct(u64) {
|
||||
///
|
||||
/// Panics if the virtual address provided is outside of virtualizable memory range.
|
||||
pub fn from_virtualized(virt: usize) @This() {
|
||||
if ((virt < gVirtualizeBase) || (virt - gVirtualizeBase > gVirtualizeSize)) {
|
||||
if ((virt < g_virtualize_base) || (virt - g_virtualize_base > g_virtualize_size)) {
|
||||
@panic("Invalid virtualized physical address");
|
||||
}
|
||||
|
||||
return .{ .raw = virt - gVirtualizeBase };
|
||||
return .{ .raw = virt - g_virtualize_base };
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper function to format a byte quantity into a human-readable size.
|
||||
/// Writes its result to the `buffer` provided and returns a pointer to it.
|
||||
pub fn formatSize(buffer: []u8, size: u64) []const u8 {
|
||||
pub fn format_size(buffer: []u8, size: u64) []const u8 {
|
||||
const KIBI: u64 = 1024;
|
||||
const MIBI: u64 = KIBI * 1024;
|
||||
const GIBI: u64 = MIBI * 1024;
|
||||
@@ -73,20 +73,20 @@ pub fn formatSize(buffer: []u8, size: u64) []const u8 {
|
||||
const integer = size / div;
|
||||
const dot = size >= 1024;
|
||||
|
||||
const iLen = std.fmt.formatIntBuf(buffer, integer, 10, .lower, .{});
|
||||
var len = iLen;
|
||||
var fLen: usize = 0;
|
||||
const ilen = std.fmt.formatIntBuf(buffer, integer, 10, .lower, .{});
|
||||
var len = ilen;
|
||||
var flen: usize = 0;
|
||||
|
||||
if (dot and integer < 100) {
|
||||
const fractional = (((size * 1000) / div) % 1000) / 10;
|
||||
|
||||
if (iLen < buffer.len + 1) {
|
||||
buffer[iLen] = '.';
|
||||
fLen = 1 + std.fmt.formatIntBuf(buffer[iLen + 1 ..], fractional, 10, .lower, .{ .fill = '0', .width = 2 });
|
||||
if (ilen < buffer.len + 1) {
|
||||
buffer[ilen] = '.';
|
||||
flen = 1 + std.fmt.formatIntBuf(buffer[ilen + 1 ..], fractional, 10, .lower, .{ .fill = '0', .width = 2 });
|
||||
}
|
||||
}
|
||||
|
||||
len += fLen;
|
||||
len += flen;
|
||||
|
||||
if (len + suffix.len < buffer.len) {
|
||||
std.mem.copyForwards(u8, buffer[len..], suffix);
|
||||
|
||||
+189
-127
@@ -9,85 +9,104 @@ const vmm = @import("vmm.zig");
|
||||
const sync = @import("../sync.zig");
|
||||
|
||||
const Range = @import("../util/range.zig").Range;
|
||||
const Spinlock = sync.IrqSafeSpinlock;
|
||||
const Spinlock = sync.Spinlock;
|
||||
|
||||
/// Represents a single region of physical memory (reserved or available).
|
||||
pub const MemoryRegion = struct {
|
||||
/// Name string, used to represent where the memory comes from.
|
||||
name: []const u8,
|
||||
/// Byte range of the memory region.
|
||||
/// Page frame number range of the region.
|
||||
range: Range(u64),
|
||||
};
|
||||
|
||||
/// Represents information about a single managed physical memory page.
|
||||
pub const Page = extern struct {
|
||||
/// Reference count of the page. Zero means the page is not allocated.
|
||||
refcount: u32 = 0,
|
||||
unused: [3]u32 = undefined,
|
||||
const Bitmap = struct {
|
||||
data: []u64,
|
||||
|
||||
/// Returns `true` if the page is allocated/used.
|
||||
pub fn isUsed(self: *const @This()) bool {
|
||||
return self.refcount != 0;
|
||||
const Self = @This();
|
||||
|
||||
pub const empty: Self = .{ .data = &.{} };
|
||||
|
||||
fn get_bit(self: *Self, index: usize) u1 {
|
||||
const word_index = index / 64;
|
||||
const bit_index = index % 64;
|
||||
const masked = self.data[word_index] & (@as(u64, 1) << @intCast(bit_index));
|
||||
return if (masked == 0) 0 else 1;
|
||||
}
|
||||
|
||||
fn makeAvailable(self: *@This()) void {
|
||||
self.refcount = 0;
|
||||
fn set_bit(self: *Self, index: usize) void {
|
||||
const word_index = index / 64;
|
||||
const bit_index = index % 64;
|
||||
self.data[word_index] |= (@as(u64, 1) << @intCast(bit_index));
|
||||
}
|
||||
|
||||
fn makeReserved(self: *@This()) void {
|
||||
self.refcount = std.math.maxInt(u32);
|
||||
fn clear_bit(self: *Self, index: usize) void {
|
||||
const word_index = index / 64;
|
||||
const bit_index = index % 64;
|
||||
self.data[word_index] &= ~(@as(u64, 1) << @intCast(bit_index));
|
||||
}
|
||||
};
|
||||
|
||||
const PhysicalMemoryManager = struct {
|
||||
pageArray: []Page,
|
||||
offset: u64 = 0,
|
||||
lastFree: usize = 0,
|
||||
memory_start: u64,
|
||||
last_free: usize,
|
||||
len: usize,
|
||||
|
||||
const RECORDS_PER_PAGE: usize = vmm.PAGE_SIZE / @sizeOf(Page);
|
||||
/// Each bit represents a page, there can be more u64s than needed
|
||||
usage_bitmap: Bitmap,
|
||||
page_refcounters: []u32,
|
||||
|
||||
const empty: @This() = .{
|
||||
.memory_start = 0,
|
||||
.last_free = 0,
|
||||
.len = 0,
|
||||
.usage_bitmap = .empty,
|
||||
.page_refcounters = &.{},
|
||||
};
|
||||
|
||||
fn alloc_page(self: *@This()) ?mem.PhysicalAddress {
|
||||
for (self.lastFree..self.pageArray.len) |i| {
|
||||
if (self.pageArray[i].refcount == 0) {
|
||||
self.pageArray[i].refcount += 1;
|
||||
self.lastFree = (i + 1) % self.pageArray.len;
|
||||
return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
|
||||
for (self.last_free..self.len) |i| {
|
||||
if (!self.is_page_used(i)) {
|
||||
self.page_refcounters[i] += 1;
|
||||
self.set_page_used(i);
|
||||
self.last_free = (i + 1) % self.len;
|
||||
return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE };
|
||||
}
|
||||
}
|
||||
for (0..self.lastFree) |i| {
|
||||
if (self.pageArray[i].refcount == 0) {
|
||||
self.pageArray[i].refcount += 1;
|
||||
self.lastFree = (i + 1) % self.pageArray.len;
|
||||
return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
|
||||
for (0..self.last_free) |i| {
|
||||
if (!self.is_page_used(i)) {
|
||||
self.page_refcounters[i] += 1;
|
||||
self.set_page_used(i);
|
||||
self.last_free = (i + 1) % self.len;
|
||||
return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn alloc_pages(self: *@This(), count: usize) ?mem.PhysicalAddress {
|
||||
if (self.lastFree + count < self.pageArray.len) {
|
||||
if (self.alloc_from(self.lastFree, self.pageArray.len, count)) |p| {
|
||||
if (self.last_free + count < self.len) {
|
||||
if (self.alloc_from(self.last_free, self.len, count)) |p| {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return self.alloc_from(0, self.lastFree, count);
|
||||
return self.alloc_from(0, self.last_free, count);
|
||||
}
|
||||
|
||||
fn alloc_from(self: *@This(), start: usize, end: usize, count: usize) ?mem.PhysicalAddress {
|
||||
for (start..end) |i| {
|
||||
var taken = false;
|
||||
for (0..count) |j| {
|
||||
if (self.pageArray[i + j].isUsed()) {
|
||||
taken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const taken = taken: {
|
||||
for (0..count) |j|
|
||||
if (self.is_page_used(i + j))
|
||||
break :taken true;
|
||||
break :taken false;
|
||||
};
|
||||
|
||||
if (!taken) {
|
||||
for (0..count) |j| {
|
||||
self.pageArray[i + j].refcount = 1;
|
||||
self.page_refcounters[i + j] = 1;
|
||||
self.set_page_used(i + j);
|
||||
}
|
||||
return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
|
||||
return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,11 +114,11 @@ const PhysicalMemoryManager = struct {
|
||||
}
|
||||
|
||||
fn valid_index(self: *@This(), page: mem.PhysicalAddress) usize {
|
||||
if (page.raw < self.offset) {
|
||||
if (page.raw < self.memory_start) {
|
||||
log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw});
|
||||
}
|
||||
const index = (page.raw - self.offset) / vmm.PAGE_SIZE;
|
||||
if (index >= self.pageArray.len) {
|
||||
const index = (page.raw - self.memory_start) / vmm.PAGE_SIZE;
|
||||
if (index >= self.len) {
|
||||
log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw});
|
||||
}
|
||||
return index;
|
||||
@@ -107,62 +126,87 @@ const PhysicalMemoryManager = struct {
|
||||
|
||||
fn free_page(self: *@This(), page: mem.PhysicalAddress) void {
|
||||
const index = self.valid_index(page);
|
||||
if (self.pageArray[index].refcount == 0) {
|
||||
if (!self.is_page_used(index)) {
|
||||
log.panic("free_page: double free of page 0x{x} detected", .{page.raw});
|
||||
}
|
||||
self.pageArray[index].refcount -= 1;
|
||||
if (self.pageArray[index].refcount == 0) {
|
||||
self.lastFree = index;
|
||||
self.page_refcounters[index] -= 1;
|
||||
if (self.page_refcounters[index] == 0) {
|
||||
self.clear_page_used(index);
|
||||
self.last_free = index;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_page(self: *@This(), page: mem.PhysicalAddress) *Page {
|
||||
const index = self.valid_index(page);
|
||||
return &self.pageArray[index];
|
||||
fn is_page_used(self: *@This(), index: usize) bool {
|
||||
return self.usage_bitmap.get_bit(index) == 1;
|
||||
}
|
||||
|
||||
fn set_page_used(self: *@This(), index: usize) void {
|
||||
self.usage_bitmap.set_bit(index);
|
||||
}
|
||||
|
||||
fn clear_page_used(self: *@This(), index: usize) void {
|
||||
self.usage_bitmap.clear_bit(index);
|
||||
}
|
||||
};
|
||||
|
||||
var gMemoryRegions: std.BoundedArray(MemoryRegion, 16) = .{};
|
||||
var gReservedRegions: std.BoundedArray(MemoryRegion, 16) = .{};
|
||||
var gPhysicalMemoryLock = Spinlock{};
|
||||
var gPhysicalMemory = PhysicalMemoryManager{ .pageArray = undefined };
|
||||
var g_memory_regions: std.BoundedArray(MemoryRegion, 16) = .{};
|
||||
var g_reserved_regions: std.BoundedArray(MemoryRegion, 16) = .{};
|
||||
var g_physical_memory_lock = Spinlock{};
|
||||
var g_physical_memory = PhysicalMemoryManager.empty;
|
||||
|
||||
/// Adds an available memory region to the list.
|
||||
///
|
||||
/// `base` and `size` are in bytes. Regions are page-aligned "inwards", meaning the function will
|
||||
/// only add the range of full pages of the specified region. If a combination is provided that
|
||||
/// does not yield any full 4KiB pages (e.g. `base=0x1234, size=0x123`), it is ignored.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Only meaningful to call before calling `init()`.
|
||||
pub fn addMemoryRegion(name: []const u8, base: u64, size: u64) void {
|
||||
pub fn add_memory_region(name: []const u8, base: u64, size: u64) void {
|
||||
log.info("Memory: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
||||
gMemoryRegions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) catch @panic("memory regions overflow");
|
||||
const start = vmm.L3.align_up(base) / vmm.L3.SIZE;
|
||||
const len = vmm.L3.align_down(base + size) / vmm.L3.SIZE - start;
|
||||
if (len > 0) {
|
||||
g_memory_regions.append(.{ .name = name, .range = .{ .start = start, .len = len } }) //
|
||||
catch @panic("memory regions overflow");
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an reserved memory region to the list.
|
||||
///
|
||||
/// `base` and `size` are in bytes. Regions are page-aligned "outwards", meaning that the
|
||||
/// reservation extends to any pages affected by the specified region.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Only meaningful to call before calling `init()`.
|
||||
pub fn addReservedRegion(name: []const u8, base: u64, size: u64) void {
|
||||
pub fn add_reserved_region(name: []const u8, base: u64, size: u64) void {
|
||||
log.info("Reserved: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
||||
gReservedRegions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) catch @panic("reserved regions overflow");
|
||||
const start = base / vmm.L3.SIZE;
|
||||
const len = vmm.L3.align_up(base + size) / vmm.L3.SIZE - start;
|
||||
if (len > 0) {
|
||||
g_reserved_regions.append(.{ .name = name, .range = .{ .start = start, .len = len } }) //
|
||||
catch @panic("reserved regions overflow");
|
||||
}
|
||||
}
|
||||
|
||||
fn isReservedIn(page: u64) ?*const MemoryRegion {
|
||||
for (0..gReservedRegions.len) |i| {
|
||||
const region = &gReservedRegions.buffer[i];
|
||||
if (page >= region.range.start and page < region.range.end()) {
|
||||
fn is_reserved_in(page_index: u64) ?*const MemoryRegion {
|
||||
for (0..g_reserved_regions.len) |i| {
|
||||
const region = &g_reserved_regions.buffer[i];
|
||||
if (page_index >= region.range.start and page_index < region.range.end()) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn allocFromRegion(region: *const MemoryRegion, reason: []const u8, pageCount: usize) ?u64 {
|
||||
fn alloc_from_region(region: *const MemoryRegion, reason: []const u8, page_count: usize) ?u64 {
|
||||
var offset = @as(u64, 0);
|
||||
while (offset < region.range.len) {
|
||||
var taken: ?*const MemoryRegion = null;
|
||||
for (0..pageCount) |i| {
|
||||
if (isReservedIn(region.range.start + offset + i * vmm.PAGE_SIZE)) |resv| {
|
||||
for (0..page_count) |i| {
|
||||
if (is_reserved_in(region.range.start + offset + i)) |resv| {
|
||||
taken = resv;
|
||||
break;
|
||||
}
|
||||
@@ -173,27 +217,51 @@ fn allocFromRegion(region: *const MemoryRegion, reason: []const u8, pageCount: u
|
||||
continue;
|
||||
}
|
||||
|
||||
const base = region.range.start + offset;
|
||||
addReservedRegion(reason, base, pageCount * vmm.PAGE_SIZE);
|
||||
const base = (region.range.start + offset) * vmm.L3.SIZE;
|
||||
add_reserved_region(reason, base, page_count * vmm.PAGE_SIZE);
|
||||
return base;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn allocPageArray(pageCount: usize) []Page {
|
||||
for (gMemoryRegions.constSlice()) |region| {
|
||||
if (allocFromRegion(®ion, "page-array", pageCount)) |physAddress| {
|
||||
/// Allocates a slice of type `T` that spans the `page_count` pages.
|
||||
fn alloc_slice_pages(comptime T: type, reason: []const u8, page_count: usize) []T {
|
||||
for (g_memory_regions.constSlice()) |region| {
|
||||
if (alloc_from_region(®ion, reason, page_count)) |physAddress| {
|
||||
const vaddr = (mem.PhysicalAddress{ .raw = physAddress }).virtualize();
|
||||
const len = pageCount * PhysicalMemoryManager.RECORDS_PER_PAGE;
|
||||
const ptr: [*]Page = @ptrFromInt(vaddr);
|
||||
const slice: []Page = ptr[0..len];
|
||||
for (0..len) |i| {
|
||||
slice[i].refcount = std.math.maxInt(u32);
|
||||
}
|
||||
const len = (page_count * vmm.PAGE_SIZE) / @sizeOf(T);
|
||||
const ptr: [*]T = @ptrFromInt(vaddr);
|
||||
const slice: []T = ptr[0..len];
|
||||
return slice;
|
||||
}
|
||||
}
|
||||
@panic("TODO");
|
||||
@panic("Failed to allocate a slice");
|
||||
}
|
||||
|
||||
/// Allocates a slice of type `T` that has at least `min_len` items, allocates in 4KiB pages.
|
||||
/// The items are zeroed out.
|
||||
fn alloc_slice(comptime T: type, reason: []const u8, min_len: usize) []T {
|
||||
const min_alloc_bytes = min_len * @sizeOf(T);
|
||||
// Round up to make sure we have enough space for the data
|
||||
const needed_pages = vmm.L3.page_count(min_alloc_bytes);
|
||||
const slice = alloc_slice_pages(T, reason, needed_pages);
|
||||
const slice_as_bytes = std.mem.sliceAsBytes(slice);
|
||||
@memset(slice_as_bytes, 0);
|
||||
return slice;
|
||||
}
|
||||
|
||||
/// Allocates a bitmap that has at least `bits_required` total bits.
|
||||
/// It can have more since we allocate 4KiB pages and the backing type is []u64
|
||||
fn alloc_bitmap(bits_required: usize) Bitmap {
|
||||
// Round up to the upper u64 that has at least `pages` bits
|
||||
const bitmap_entries = (bits_required + 63) / 64;
|
||||
const bitmap = alloc_slice(u64, "bitmap", bitmap_entries);
|
||||
return .{ .data = bitmap };
|
||||
}
|
||||
fn alloc_refcounters(count: usize) []u32 {
|
||||
const refcounters = alloc_slice(u32, "refcounters", count);
|
||||
@memset(refcounters, std.math.maxInt(u32));
|
||||
return refcounters;
|
||||
}
|
||||
|
||||
/// Initializes the physical memory management.
|
||||
@@ -203,49 +271,56 @@ fn allocPageArray(pageCount: usize) []Page {
|
||||
/// Calls to `add***Region()` functions have no meaning past this point, so all the memory
|
||||
/// present in the system, along with memory reservations, should be added **prior** to this point.
|
||||
pub fn init() void {
|
||||
var memoryStart: u64 = std.math.maxInt(u64);
|
||||
var memoryEnd: u64 = std.math.minInt(u64);
|
||||
var memory_start: u64 = std.math.maxInt(u64);
|
||||
var memory_end: u64 = std.math.minInt(u64);
|
||||
|
||||
for (gMemoryRegions.constSlice()) |region| {
|
||||
if (region.range.start < memoryStart) {
|
||||
memoryStart = region.range.start;
|
||||
for (g_memory_regions.constSlice()) |region| {
|
||||
log.info("Region: {}..{}", .{ region.range.start, region.range.end() });
|
||||
if (region.range.start < memory_start) {
|
||||
memory_start = region.range.start;
|
||||
}
|
||||
if (region.range.end() > memoryEnd) {
|
||||
memoryEnd = region.range.end();
|
||||
if (region.range.end() > memory_end) {
|
||||
memory_end = region.range.end();
|
||||
}
|
||||
}
|
||||
|
||||
const memoryPages = (memoryEnd - memoryStart) / vmm.PAGE_SIZE; // == bitmap bits required
|
||||
const pageArrayPages = (memoryPages + PhysicalMemoryManager.RECORDS_PER_PAGE - 1) //
|
||||
/ PhysicalMemoryManager.RECORDS_PER_PAGE;
|
||||
const memory_pages = memory_end - memory_start; // == bitmap bits required
|
||||
var bitmap = alloc_bitmap(memory_pages);
|
||||
const refcounters = alloc_refcounters(memory_pages);
|
||||
|
||||
const pageArray = allocPageArray(pageArrayPages);
|
||||
var availablePages: usize = 0;
|
||||
var available_pages: usize = 0;
|
||||
|
||||
for (gMemoryRegions.constSlice()) |region| {
|
||||
const offset = (region.range.start - memoryStart) / vmm.PAGE_SIZE;
|
||||
for (0..region.range.len / vmm.PAGE_SIZE) |i| {
|
||||
pageArray[offset + i].makeAvailable();
|
||||
availablePages += 1;
|
||||
for (g_memory_regions.constSlice()) |region| {
|
||||
const offset = region.range.start - memory_start;
|
||||
for (0..region.range.len) |i| {
|
||||
refcounters[offset + i] = 0;
|
||||
available_pages += 1;
|
||||
}
|
||||
}
|
||||
for (gReservedRegions.constSlice()) |region| {
|
||||
const offset = (region.range.start - memoryStart) / vmm.PAGE_SIZE;
|
||||
for (0..region.range.len / vmm.PAGE_SIZE) |i| {
|
||||
if (offset + i >= pageArray.len) {
|
||||
for (g_reserved_regions.constSlice()) |region| {
|
||||
const offset = region.range.start - memory_start;
|
||||
for (0..region.range.len) |i| {
|
||||
if (offset + i >= memory_pages) {
|
||||
break;
|
||||
}
|
||||
pageArray[offset + i].makeReserved();
|
||||
availablePages -= 1;
|
||||
refcounters[offset + i] = std.math.maxInt(u32);
|
||||
bitmap.set_bit(offset + i);
|
||||
|
||||
available_pages -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
var sizeFmt: [64]u8 = undefined;
|
||||
const sizeFmtStr = mem.formatSize(&sizeFmt, availablePages * vmm.PAGE_SIZE);
|
||||
log.info("Available memory: {s}, page array {*}", .{ sizeFmtStr, pageArray });
|
||||
var size_fmt: [64]u8 = undefined;
|
||||
const size_fmt_str = mem.format_size(&size_fmt, available_pages * vmm.PAGE_SIZE);
|
||||
log.info(
|
||||
"Available memory: {s}, bitmap {*}, refcounts {*}",
|
||||
.{ size_fmt_str, bitmap.data, refcounters },
|
||||
);
|
||||
|
||||
gPhysicalMemory.pageArray = pageArray;
|
||||
gPhysicalMemory.offset = memoryStart;
|
||||
g_physical_memory.len = memory_pages;
|
||||
g_physical_memory.memory_start = memory_start * vmm.L3.SIZE;
|
||||
g_physical_memory.usage_bitmap = bitmap;
|
||||
g_physical_memory.page_refcounters = refcounters;
|
||||
}
|
||||
|
||||
fn trace_allocation(count: usize, page: ?mem.PhysicalAddress) void {
|
||||
@@ -262,9 +337,9 @@ fn trace_free(page: mem.PhysicalAddress) void {
|
||||
|
||||
/// Allocates a single 4KiB physical memory page.
|
||||
pub fn alloc_page() ?mem.PhysicalAddress {
|
||||
gPhysicalMemoryLock.lock();
|
||||
defer gPhysicalMemoryLock.release();
|
||||
const page = gPhysicalMemory.alloc_page();
|
||||
const guard = g_physical_memory_lock.lock_irqsave();
|
||||
defer guard.release();
|
||||
const page = g_physical_memory.alloc_page();
|
||||
if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) {
|
||||
trace_allocation(1, page);
|
||||
}
|
||||
@@ -273,9 +348,9 @@ pub fn alloc_page() ?mem.PhysicalAddress {
|
||||
|
||||
/// Allocates a set of `count` contiguous 4KiB pages.
|
||||
pub fn alloc_pages(count: usize) ?mem.PhysicalAddress {
|
||||
gPhysicalMemoryLock.lock();
|
||||
defer gPhysicalMemoryLock.release();
|
||||
const pages = gPhysicalMemory.alloc_pages(count);
|
||||
const guard = g_physical_memory_lock.lock_irqsave();
|
||||
defer guard.release();
|
||||
const pages = g_physical_memory.alloc_pages(count);
|
||||
if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) {
|
||||
trace_allocation(count, pages);
|
||||
}
|
||||
@@ -293,20 +368,7 @@ pub fn free_page(page: mem.PhysicalAddress) void {
|
||||
if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) {
|
||||
trace_free(page);
|
||||
}
|
||||
gPhysicalMemoryLock.lock();
|
||||
defer gPhysicalMemoryLock.release();
|
||||
gPhysicalMemory.free_page(page);
|
||||
}
|
||||
|
||||
/// Returns a `Page` struct representing the given `page`.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// The physical memory lock must be held.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the `page` does not represent a valid managed page.
|
||||
pub fn get_page(page: mem.PhysicalAddress) *Page {
|
||||
return gPhysicalMemory.get_page(page);
|
||||
const guard = g_physical_memory_lock.lock_irqsave();
|
||||
defer guard.release();
|
||||
g_physical_memory.free_page(page);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
//! Thread-local storage implementation.
|
||||
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const vmm = @import("vmm.zig");
|
||||
const phys_memory = @import("phys.zig");
|
||||
const kernel = @import("../kernel.zig");
|
||||
|
||||
const PAGE_SIZE = vmm.PAGE_SIZE;
|
||||
const log = kernel.debug.log;
|
||||
|
||||
/// Thread-local storage layout variant used by this target platform.
|
||||
pub const TLS_VARIANT: enum {
|
||||
/// Variant I:
|
||||
///
|
||||
/// [ TCB ] [ pad to p_align ] [ MODULE 0 ] [ MODULE 1 ] ...
|
||||
/// | | |
|
||||
/// | | |
|
||||
/// tp off1 off2
|
||||
variant1,
|
||||
/// Variant II:
|
||||
///
|
||||
/// ... [ MODULE 1 ] [ MODULE 0 ] [ TCB ]
|
||||
/// | | |
|
||||
/// | | |
|
||||
/// off2 off1 tp
|
||||
variant2,
|
||||
} = switch (builtin.cpu.arch) {
|
||||
.riscv64, .aarch64 => .variant1,
|
||||
// x86-64 uses variant 2
|
||||
else => @panic("Unsupported CPU architecture"),
|
||||
};
|
||||
|
||||
extern var __tdata_start: u8;
|
||||
extern var __tdata_end: u8;
|
||||
extern var __tbss_start: u8;
|
||||
extern var __tbss_end: u8;
|
||||
|
||||
/// Allocates a storage for one per-CPU TLS block, clones the TLS image
|
||||
/// (as described by .tbss/.tdata sections) and returns the result.
|
||||
pub fn load_kernel_tls_image() []u8 {
|
||||
// Assume .tbss follows .tdata
|
||||
const tdata_start = @intFromPtr(&__tdata_start);
|
||||
const tdata_end = @intFromPtr(&__tdata_end);
|
||||
const tdata_size = tdata_end - tdata_start;
|
||||
const tbss_start = @intFromPtr(&__tbss_start);
|
||||
const tbss_end = @intFromPtr(&__tbss_end);
|
||||
const tbss_size = tbss_end - tbss_start;
|
||||
|
||||
const tdata_data = @as([*]u8, @ptrFromInt(tdata_start))[0..tdata_size];
|
||||
|
||||
switch (comptime TLS_VARIANT) {
|
||||
.variant1 => {
|
||||
const tls_size = tdata_size + tbss_size;
|
||||
const tls_page_count = (tls_size + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||
// Variant I: TLS block 0 follows TP after a certain displacement
|
||||
const tls_address = phys_memory.alloc_pages(tls_page_count).?.virtualize();
|
||||
const tls_data = @as([*]u8, @ptrFromInt(tls_address))[0..tls_size];
|
||||
|
||||
log.info("Allocated TLS @ {*}", .{tls_data});
|
||||
|
||||
@memcpy(tls_data[0..tdata_size], tdata_data);
|
||||
@memset(tls_data[tdata_size..], 0);
|
||||
|
||||
return tls_data;
|
||||
},
|
||||
.variant2 => @panic("TODO: TLS variant II"),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Range = @import("../util/range.zig").Range;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// Describes a single virtual memory range.
|
||||
///
|
||||
/// Used by `VirtualMemoryAllocator` to track allocated/used regions.
|
||||
pub const VirtualMemoryRange = struct {
|
||||
range: Range(u64),
|
||||
|
||||
prev: ?*VirtualMemoryRange = null,
|
||||
next: ?*VirtualMemoryRange = null,
|
||||
};
|
||||
|
||||
/// Virtual memory allocator implementation.
|
||||
pub const VirtualMemoryAllocator = struct {
|
||||
gpa: Allocator,
|
||||
head: ?*VirtualMemoryRange = null,
|
||||
outer_range: Range(u64),
|
||||
|
||||
/// One of errors returned by the allocation logic + underlying allocator error.
|
||||
pub const Error = error{ already_exists, invalid_region, cannot_fit } || Allocator.Error;
|
||||
|
||||
/// An iterator over VM regions being freed.
|
||||
pub const FreeIterator = struct {
|
||||
range: Range(u64),
|
||||
vma: *VirtualMemoryAllocator,
|
||||
current: ?*VirtualMemoryRange,
|
||||
|
||||
fn next(self: *@This()) Error!?Range(u64) {
|
||||
while (self.current) |n| {
|
||||
if (n.range.intersect(&self.range)) |xs| {
|
||||
if (xs.start == n.range.start) {
|
||||
if (xs.end() == n.range.end()) {
|
||||
// Whole range encompassed by requested range
|
||||
// Unlink the node
|
||||
if (n.next) |nn| {
|
||||
nn.prev = n.prev;
|
||||
}
|
||||
if (n.prev) |np| {
|
||||
np.next = n.next;
|
||||
} else {
|
||||
self.vma.head = n.next;
|
||||
}
|
||||
// Free it
|
||||
self.current = n.next;
|
||||
self.vma.gpa.destroy(n);
|
||||
|
||||
return xs;
|
||||
}
|
||||
|
||||
// Remove space from the start
|
||||
n.range.start += xs.len;
|
||||
n.range.len -= xs.len;
|
||||
// Does not touch the end, so can be sure this is the last node
|
||||
self.current = null;
|
||||
return xs;
|
||||
} else if (xs.end() == n.range.end()) {
|
||||
n.range.len -= xs.len;
|
||||
// Continue, there might be a following node affected
|
||||
self.current = n.next;
|
||||
return xs;
|
||||
} else {
|
||||
// Insert a new node after the current one
|
||||
const new_node = try self.vma.gpa.create(VirtualMemoryRange);
|
||||
new_node.* = VirtualMemoryRange {
|
||||
.range = .{ .start = xs.end(), .len = n.range.end() - xs.end() },
|
||||
.prev = n,
|
||||
.next = n.next,
|
||||
};
|
||||
n.range.len = xs.start - n.range.start;
|
||||
if (n.next) |nn| {
|
||||
nn.prev = new_node;
|
||||
}
|
||||
n.next = new_node;
|
||||
// Requested region is fully encompassed by this one, so no intersections
|
||||
// will follow
|
||||
self.current = null;
|
||||
return xs;
|
||||
}
|
||||
} else {
|
||||
// No intersect
|
||||
self.current = n.next;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/// Creates a new instance of a virtual memory allocator.
|
||||
pub fn init(gpa: Allocator, outer_range: Range(u64)) @This() {
|
||||
return .{
|
||||
.outer_range = outer_range,
|
||||
.gpa = gpa,
|
||||
};
|
||||
}
|
||||
|
||||
/// Allocates a free region of virtual memory of requested (`pfn_count`) size.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `cannot_fit` - if no free space found to fit the requested allocation.
|
||||
/// * Underlying allocator error - if allocation of a new node fails.
|
||||
pub fn allocate(self: *@This(), pfn_count: u64) Error!u64 {
|
||||
// Try to fit before first entry
|
||||
const gap_before_first = if (self.head) |n| (n.range.start - self.outer_range.start) else self.outer_range.len;
|
||||
|
||||
if (gap_before_first >= pfn_count) {
|
||||
var new_node = try self.gpa.create(VirtualMemoryRange);
|
||||
|
||||
new_node.range = .{ .start = self.outer_range.start, .len = pfn_count };
|
||||
new_node.next = self.head;
|
||||
new_node.prev = null;
|
||||
|
||||
if (self.head) |n| {
|
||||
n.prev = new_node;
|
||||
}
|
||||
|
||||
self.head = new_node;
|
||||
|
||||
return self.outer_range.start;
|
||||
}
|
||||
|
||||
// If cannot fit before first entry, find an entry to fit after
|
||||
var node = self.head;
|
||||
while (node) |n| {
|
||||
const gap =
|
||||
if (n.next) |nn|
|
||||
// Gap between this and next
|
||||
(nn.range.start - n.range.end())
|
||||
else
|
||||
// Gap between this and the end
|
||||
(self.outer_range.end() - n.range.end());
|
||||
|
||||
if (gap >= pfn_count) {
|
||||
// Insert after this
|
||||
const result = n.range.end();
|
||||
var new_node = try self.gpa.create(VirtualMemoryRange);
|
||||
new_node.prev = n;
|
||||
new_node.next = n.next;
|
||||
new_node.range = .{ .start = result, .len = pfn_count };
|
||||
if (n.next) |nn| {
|
||||
nn.prev = new_node;
|
||||
}
|
||||
n.next = new_node;
|
||||
return result;
|
||||
}
|
||||
|
||||
node = n.next;
|
||||
}
|
||||
|
||||
return error.cannot_fit;
|
||||
}
|
||||
|
||||
/// Inserts a reservation into the VM allocator.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `already_exists` - if the requested range intersects existing ranges.
|
||||
/// * Underlying allocator error - if allocation of a new node fails.
|
||||
pub fn insert(self: *@This(), region: Range(u64)) Error!void {
|
||||
// Validate that the range does not escape the outer range
|
||||
if (region.start < self.outer_range.start or region.end() > self.outer_range.end()) {
|
||||
return error.invalid_region;
|
||||
}
|
||||
|
||||
// Find the last node which is before the region supposed to be inserted
|
||||
var node = self.head;
|
||||
var insert_after: ?*VirtualMemoryRange = null;
|
||||
while (node) |n| {
|
||||
if (n.range.intersect(®ion) != null) {
|
||||
return error.already_exists;
|
||||
}
|
||||
|
||||
if (n.range.end() <= region.start) {
|
||||
insert_after = n;
|
||||
}
|
||||
|
||||
node = n.next;
|
||||
}
|
||||
|
||||
var new_node = try self.gpa.create(VirtualMemoryRange);
|
||||
|
||||
new_node.range = region;
|
||||
|
||||
if (insert_after) |ia| {
|
||||
new_node.prev = ia;
|
||||
new_node.next = ia.next;
|
||||
|
||||
if (ia.next) |ian| {
|
||||
ian.prev = new_node;
|
||||
}
|
||||
ia.next = new_node;
|
||||
} else {
|
||||
new_node.next = null;
|
||||
new_node.prev = null;
|
||||
|
||||
self.head = new_node;
|
||||
}
|
||||
}
|
||||
|
||||
/// Deallocates (shrinks/truncates) regions intersecting the requested range.
|
||||
pub fn free(self: *@This(), start_pfn: u64, pfn_count: u64) FreeIterator {
|
||||
const range = Range(u64) { .start = start_pfn, .len = pfn_count };
|
||||
return FreeIterator {
|
||||
.current = self.head,
|
||||
.vma = self,
|
||||
.range = range,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "Inserted entries in vmalloc are properly ordered" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x2000 });
|
||||
defer {
|
||||
while (vma.head) |n| {
|
||||
vma.head = n.next;
|
||||
std.testing.allocator.destroy(n);
|
||||
}
|
||||
}
|
||||
try vma.insert(.{ .start = 0x1200, .len = 0x200 });
|
||||
{
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
try std.testing.expectEqual(null, n0.next);
|
||||
try std.testing.expectEqual(null, n0.prev);
|
||||
}
|
||||
try vma.insert(.{ .start = 0x2000, .len = 0x200 });
|
||||
{
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
try std.testing.expectEqual(null, n0.prev);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x2000, n1.range.start);
|
||||
try std.testing.expectEqual(0x200, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
try std.testing.expectEqual(null, n1.next);
|
||||
}
|
||||
try vma.insert(.{ .start = 0x1400, .len = 0x200 });
|
||||
{
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
try std.testing.expectEqual(null, n0.prev);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1400, n1.range.start);
|
||||
try std.testing.expectEqual(0x200, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
const n2 = n1.next.?;
|
||||
try std.testing.expectEqual(0x2000, n2.range.start);
|
||||
try std.testing.expectEqual(0x200, n2.range.len);
|
||||
try std.testing.expectEqual(n1, n2.prev);
|
||||
try std.testing.expectEqual(null, n2.next);
|
||||
}
|
||||
}
|
||||
|
||||
test "Overlapping insertions are denied" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
||||
defer {
|
||||
while (vma.head) |n| {
|
||||
vma.head = n.next;
|
||||
std.testing.allocator.destroy(n);
|
||||
}
|
||||
}
|
||||
try vma.insert(.{ .start = 0x1200, .len = 0x200 });
|
||||
try std.testing.expectError(error.already_exists, vma.insert(.{ .start = 0x1100, .len = 0x200 }));
|
||||
try std.testing.expectError(error.already_exists, vma.insert(.{ .start = 0x1300, .len = 0x200 }));
|
||||
try std.testing.expectError(error.already_exists, vma.insert(.{ .start = 0x1100, .len = 0x400 }));
|
||||
}
|
||||
|
||||
test "Insertions outside of bounds are denied" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
||||
// As above...
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x2200, .len = 0x200 }));
|
||||
// ... so below
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x200, .len = 0x200 }));
|
||||
// Crosses from below
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x200, .len = 0x1000 }));
|
||||
// Crosses into above
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x1200, .len = 0x1000 }));
|
||||
// Encompasses whole
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x200, .len = 0x2000 }));
|
||||
}
|
||||
|
||||
test "Allocations from vmalloc" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
||||
defer {
|
||||
while (vma.head) |n| {
|
||||
vma.head = n.next;
|
||||
std.testing.allocator.destroy(n);
|
||||
}
|
||||
}
|
||||
try vma.insert(.{ .start = 0x1200, .len = 0x200 });
|
||||
try std.testing.expectEqual(0x1000, try vma.allocate(0x100));
|
||||
try std.testing.expectEqual(0x1400, try vma.allocate(0x400));
|
||||
try std.testing.expectEqual(0x1100, try vma.allocate(0x100));
|
||||
}
|
||||
|
||||
test "vmalloc free" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
||||
|
||||
try vma.insert(.{ .start = 0x1200, .len = 0x800 });
|
||||
try vma.insert(.{ .start = 0x1A00, .len = 0x400 });
|
||||
|
||||
// Remove nothing
|
||||
{
|
||||
var free_it = vma.free(0x1000, 0x200);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
}
|
||||
|
||||
// Remove a chunk in the middle of a node
|
||||
{
|
||||
var free_it = vma.free(0x1400, 0x400);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1400, r0.start);
|
||||
try std.testing.expectEqual(0x400, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1800, n1.range.start);
|
||||
try std.testing.expectEqual(0x200, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
const n2 = n1.next.?;
|
||||
try std.testing.expectEqual(0x1A00, n2.range.start);
|
||||
try std.testing.expectEqual(0x400, n2.range.len);
|
||||
try std.testing.expectEqual(n1, n2.prev);
|
||||
try std.testing.expectEqual(null, n2.next);
|
||||
}
|
||||
|
||||
// Remove from the start
|
||||
{
|
||||
var free_it = vma.free(0x1200, 0x100);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1200, r0.start);
|
||||
try std.testing.expectEqual(0x100, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1300, n0.range.start);
|
||||
try std.testing.expectEqual(0x100, n0.range.len);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1800, n1.range.start);
|
||||
try std.testing.expectEqual(0x200, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
const n2 = n1.next.?;
|
||||
try std.testing.expectEqual(0x1A00, n2.range.start);
|
||||
try std.testing.expectEqual(0x400, n2.range.len);
|
||||
try std.testing.expectEqual(n1, n2.prev);
|
||||
try std.testing.expectEqual(null, n2.next);
|
||||
}
|
||||
|
||||
// Remove from the end
|
||||
{
|
||||
var free_it = vma.free(0x1900, 0x100);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1900, r0.start);
|
||||
try std.testing.expectEqual(0x100, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1300, n0.range.start);
|
||||
try std.testing.expectEqual(0x100, n0.range.len);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1800, n1.range.start);
|
||||
try std.testing.expectEqual(0x100, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
const n2 = n1.next.?;
|
||||
try std.testing.expectEqual(0x1A00, n2.range.start);
|
||||
try std.testing.expectEqual(0x400, n2.range.len);
|
||||
try std.testing.expectEqual(n1, n2.prev);
|
||||
try std.testing.expectEqual(null, n2.next);
|
||||
}
|
||||
|
||||
// Remove single full
|
||||
{
|
||||
var free_it = vma.free(0x1000, 0x600);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1300, r0.start);
|
||||
try std.testing.expectEqual(0x100, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1800, n0.range.start);
|
||||
try std.testing.expectEqual(0x100, n0.range.len);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1A00, n1.range.start);
|
||||
try std.testing.expectEqual(0x400, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
try std.testing.expectEqual(null, n1.next);
|
||||
}
|
||||
|
||||
// Remove one full + one partial
|
||||
{
|
||||
var free_it = vma.free(0x1600, 0x600);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1800, r0.start);
|
||||
try std.testing.expectEqual(0x100, r0.len);
|
||||
const r1 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1A00, r1.start);
|
||||
try std.testing.expectEqual(0x200, r1.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1C00, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
try std.testing.expectEqual(null, n0.next);
|
||||
}
|
||||
|
||||
// Remove whatever remains
|
||||
{
|
||||
var free_it = vma.free(0, 0x20000);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1C00, r0.start);
|
||||
try std.testing.expectEqual(0x200, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
try std.testing.expectEqual(null, vma.head);
|
||||
}
|
||||
}
|
||||
+14
-1
@@ -1,7 +1,12 @@
|
||||
//! Platform-independent virtual memory management definitions.
|
||||
|
||||
const mem = @import("../mem.zig");
|
||||
|
||||
/// Last virtual memory translation level. Always 4KiB on all platforms.
|
||||
pub const L3 = mem.TranslationLevel(12);
|
||||
|
||||
/// Page size is 4KiB on all platforms.
|
||||
pub const PAGE_SIZE: usize = 0x1000;
|
||||
pub const PAGE_SIZE: usize = L3.SIZE;
|
||||
|
||||
/// Helper function to construct a "Translation Level" struct type from a bit shift.
|
||||
pub fn TranslationLevel(comptime shift: usize) type {
|
||||
@@ -28,5 +33,13 @@ pub fn TranslationLevel(comptime shift: usize) type {
|
||||
pub inline fn align_up(addr: usize) usize {
|
||||
return (addr + SIZE - 1) & ~(SIZE - 1);
|
||||
}
|
||||
|
||||
pub inline fn page_number(addr: usize) usize {
|
||||
return addr >> shift;
|
||||
}
|
||||
|
||||
pub inline fn page_count(size: usize) usize {
|
||||
return (size + SIZE - 1) / SIZE;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+23
-5
@@ -4,22 +4,40 @@ const std = @import("std");
|
||||
const arch = @import("kernel.zig").arch;
|
||||
|
||||
/// Basic spinlock implementation
|
||||
// TODO not actually IRQ safe, lol.
|
||||
pub const IrqSafeSpinlock = struct {
|
||||
pub const Spinlock = struct {
|
||||
state: std.atomic.Value(bool) = .{ .raw = false },
|
||||
|
||||
const Guard = struct {
|
||||
lock: *Spinlock,
|
||||
irq_mask: bool,
|
||||
|
||||
/// Releases the `Guard`, restoring the previous IRQ state and releasing the lock used
|
||||
/// to acquire it.
|
||||
pub fn release(self: @This()) void {
|
||||
self.lock.release();
|
||||
_ = arch.set_interrupt_mask(self.irq_mask);
|
||||
}
|
||||
};
|
||||
|
||||
/// Acquires a lock over `self`. Returns `false` if the lock is already held by someone else.
|
||||
pub fn tryLock(self: *@This()) bool {
|
||||
pub fn try_lock(self: *@This()) bool {
|
||||
return self.state.cmpxchgStrong(false, true, .acquire, .monotonic) orelse false;
|
||||
}
|
||||
|
||||
/// Acquires a lock over `self`. Will block until a lock can be acquired.
|
||||
pub fn lock(self: *@This()) void {
|
||||
while (!self.tryLock()) {
|
||||
arch.spinHint();
|
||||
while (!self.try_lock()) {
|
||||
arch.spin_hint();
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `lock()`, but additionally saves current IRQ state and masks IRQs.
|
||||
pub fn lock_irqsave(self: *@This()) Guard {
|
||||
const irq_mask = arch.set_interrupt_mask(true);
|
||||
self.lock();
|
||||
return .{ .irq_mask = irq_mask, .lock = self };
|
||||
}
|
||||
|
||||
/// Releases a lock over `self`.
|
||||
pub fn release(self: *@This()) void {
|
||||
self.state.store(false, .release);
|
||||
|
||||
+22
-18
@@ -17,14 +17,14 @@ pub const Queue = struct {
|
||||
head: ?*Thread = null,
|
||||
|
||||
/// Pointer to this CPU's thread queue.
|
||||
pub threadlocal var thisCpu: ?*Queue = null;
|
||||
pub threadlocal var t_this_cpu: ?*Queue = null;
|
||||
|
||||
/// Sets up a thread queue for the current CPU.
|
||||
pub fn initThisCpu(a: *arena.Arena) void {
|
||||
pub fn init_this_cpu(a: *arena.Arena) void {
|
||||
const idle = arch.Context.idle();
|
||||
const q = a.create(Queue);
|
||||
q.* = .{ .idle = idle };
|
||||
thisCpu = q;
|
||||
t_this_cpu = q;
|
||||
}
|
||||
|
||||
/// Enters a task on this CPU.
|
||||
@@ -46,19 +46,19 @@ pub const Queue = struct {
|
||||
// ... to thread
|
||||
if (next != curr) {
|
||||
self.current = next;
|
||||
next.switchFrom(curr);
|
||||
next.switch_from(curr);
|
||||
}
|
||||
} else {
|
||||
// ... to idle
|
||||
self.current = null;
|
||||
self.idle.switchFrom(&curr.archContext);
|
||||
self.idle.switch_from(&curr.arch_context);
|
||||
}
|
||||
} else {
|
||||
// Switching from idle
|
||||
if (self.head) |gt| {
|
||||
// ... to thread
|
||||
self.current = gt;
|
||||
gt.archContext.switchFrom(&self.idle);
|
||||
gt.arch_context.switch_from(&self.idle);
|
||||
return;
|
||||
}
|
||||
// ... back to idle
|
||||
@@ -85,7 +85,7 @@ pub const Thread = struct {
|
||||
/// Arena.
|
||||
allocator: *arena.Arena,
|
||||
/// Architecture-specific task context.
|
||||
archContext: arch.Context,
|
||||
arch_context: arch.Context,
|
||||
|
||||
/// Next thread in the queue.
|
||||
next: ?*Thread = null,
|
||||
@@ -97,19 +97,19 @@ pub const Thread = struct {
|
||||
const thread = a.create(Thread);
|
||||
thread.* = .{
|
||||
.allocator = a,
|
||||
.archContext = arch.Context.kernel(pc, arg),
|
||||
.arch_context = arch.Context.kernel(pc, arg),
|
||||
};
|
||||
return thread;
|
||||
}
|
||||
|
||||
/// Enters the thread, does not return.
|
||||
pub fn enter(self: *@This()) noreturn {
|
||||
self.archContext.enter();
|
||||
self.arch_context.enter();
|
||||
}
|
||||
|
||||
/// Switches from `from` to `self` thread.
|
||||
pub fn switchFrom(self: *@This(), from: *@This()) void {
|
||||
self.archContext.switchFrom(&from.archContext);
|
||||
pub fn switch_from(self: *@This(), from: *@This()) void {
|
||||
self.arch_context.switch_from(&from.arch_context);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,7 +123,7 @@ pub fn KStack(comptime SIZE: usize) type {
|
||||
/// Stack data represented as a slice of `SIZE` machine-sized words.
|
||||
data: *[SIZE]usize,
|
||||
/// Physical base address at which the stack is allocated.
|
||||
physicalBase: mem.PhysicalAddress,
|
||||
physical_base: mem.PhysicalAddress,
|
||||
|
||||
/// Allocates a new kernel stack.
|
||||
///
|
||||
@@ -131,10 +131,14 @@ pub fn KStack(comptime SIZE: usize) type {
|
||||
///
|
||||
/// Panics on Out-of-Memory condition. TODO Fix this.
|
||||
pub fn create() @This() {
|
||||
const physicalBase = mem.phys.alloc_pages(SIZE * @sizeOf(usize) / 0x1000) orelse @panic("OOM");
|
||||
const ptr = @as(*[SIZE]usize, @ptrFromInt(physicalBase.virtualize()));
|
||||
const physical_base = mem.phys.alloc_pages(SIZE * @sizeOf(usize) / 0x1000) orelse @panic("OOM");
|
||||
const ptr = @as(*[SIZE]usize, @ptrFromInt(physical_base.virtualize()));
|
||||
|
||||
return .{ .data = ptr, .physicalBase = physicalBase, .sp = @ptrFromInt(@intFromPtr(&ptr[0]) + SIZE * @sizeOf(usize)) };
|
||||
return .{
|
||||
.data = ptr,
|
||||
.physical_base = physical_base,
|
||||
.sp = @ptrFromInt(@intFromPtr(&ptr[0]) + SIZE * @sizeOf(usize)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Pushes a machine-sized word onto the stack.
|
||||
@@ -154,15 +158,15 @@ pub fn KStack(comptime SIZE: usize) type {
|
||||
|
||||
/// Adds a thread to some CPU queue for execution.
|
||||
pub fn enqueue(t: *Thread) void {
|
||||
Queue.thisCpu.?.enqueue(t);
|
||||
Queue.t_this_cpu.?.enqueue(t);
|
||||
}
|
||||
|
||||
/// Enters thread execution on the current CPU.
|
||||
pub fn enter() noreturn {
|
||||
Queue.thisCpu.?.enter();
|
||||
Queue.t_this_cpu.?.enter();
|
||||
}
|
||||
|
||||
/// Yields this CPU's execution to a next thread.
|
||||
pub fn yield() void {
|
||||
Queue.thisCpu.?.yield();
|
||||
Queue.t_this_cpu.?.yield();
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
pub const dtb = @import("util/dtb.zig");
|
||||
pub const range = @import("util/range.zig");
|
||||
pub const btree = @import("util/btree.zig");
|
||||
pub const rangemap = @import("util/rangemap.zig");
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
pub const Order = std.math.Order;
|
||||
|
||||
pub fn CompareFn(comptime N: type) type {
|
||||
return fn (*const N, *const N) Order;
|
||||
}
|
||||
|
||||
pub fn SearchFn(comptime N: type, comptime C: type) type {
|
||||
return fn (*const N, C) Order;
|
||||
}
|
||||
|
||||
pub fn BTree(comptime N: type, comptime compare_fn: CompareFn(N), comptime deinit_fn: ?fn (*N) void) type {
|
||||
return struct {
|
||||
gpa: Allocator,
|
||||
root: ?*Node = null,
|
||||
|
||||
pub const Error = error{ already_exists, does_not_exist } || Allocator.Error;
|
||||
|
||||
pub fn WalkFn(comptime C: type) type {
|
||||
return fn (*const Node, C) void;
|
||||
}
|
||||
|
||||
pub const Iterator = struct {
|
||||
current: ?*Node,
|
||||
|
||||
pub fn next(self: *Iterator) ?*const Node {
|
||||
while (self.current) |n| {
|
||||
const v = n;
|
||||
|
||||
if (n.right) |r| {
|
||||
// Emit
|
||||
self.current = Node.leftmost(r);
|
||||
} else {
|
||||
var nn = n;
|
||||
while (nn.parent) |p| {
|
||||
if (nn == p.right) {
|
||||
nn = p;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.current = nn.parent;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Node = struct {
|
||||
key: N,
|
||||
parent: ?*Node = null,
|
||||
left: ?*Node = null,
|
||||
right: ?*Node = null,
|
||||
|
||||
fn init(a: Allocator, key: N) Error!*Node {
|
||||
const node = try a.create(Node);
|
||||
node.* = .{
|
||||
.key = key,
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
fn deinit(node: ?*Node, a: Allocator) void {
|
||||
if (node) |n| {
|
||||
if (comptime deinit_fn) |f| {
|
||||
f(&n.key);
|
||||
}
|
||||
|
||||
Node.deinit(n.left, a);
|
||||
Node.deinit(n.right, a);
|
||||
|
||||
// Free node itself
|
||||
a.destroy(n);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(node: ?*Node, a: Allocator, key: N) Error!struct { *Node, *Node } {
|
||||
if (node) |n| {
|
||||
const ord = compare_fn(&n.key, &key);
|
||||
var inserted: *Node = undefined;
|
||||
switch (ord) {
|
||||
.lt => {
|
||||
const child, inserted = try Node.insert(n.right, a, key);
|
||||
child.parent = n;
|
||||
n.right = child;
|
||||
},
|
||||
.gt => {
|
||||
const child, inserted = try Node.insert(n.left, a, key);
|
||||
child.parent = n;
|
||||
n.left = child;
|
||||
},
|
||||
.eq => return error.already_exists,
|
||||
}
|
||||
return .{ n, inserted };
|
||||
} else {
|
||||
const n = try Node.init(a, key);
|
||||
return .{ n, n };
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_node(node: *Node, a: Allocator, destroy: bool) ?*Node {
|
||||
if (node.left == null) {
|
||||
// Only right/none
|
||||
const tmp = node.right;
|
||||
if (tmp) |t| {
|
||||
t.parent = node.parent;
|
||||
}
|
||||
// Destroy the node
|
||||
if (comptime deinit_fn) |f| {
|
||||
if (destroy) {
|
||||
f(&node.key);
|
||||
}
|
||||
}
|
||||
a.destroy(node);
|
||||
return tmp;
|
||||
}
|
||||
if (node.right == null) {
|
||||
// Only left/none
|
||||
const tmp = node.left;
|
||||
if (tmp) |t| {
|
||||
t.parent = node.parent;
|
||||
}
|
||||
// Destroy the node
|
||||
if (comptime deinit_fn) |f| {
|
||||
if (destroy) {
|
||||
f(&node.key);
|
||||
}
|
||||
}
|
||||
a.destroy(node);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
// Both
|
||||
var successor = node.right;
|
||||
while (successor) |succ| {
|
||||
if (succ.left) |l| {
|
||||
successor = l;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (successor) |succ| {
|
||||
node.key = succ.key;
|
||||
node.right = Node.remove(node.right, a, succ.key) catch unreachable;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
fn remove(node: ?*Node, a: Allocator, key: N) Error!?*Node {
|
||||
if (node) |n| {
|
||||
const ord = compare_fn(&n.key, &key);
|
||||
switch (ord) {
|
||||
.lt => n.right = try Node.remove(n.right, a, key),
|
||||
.gt => n.left = try Node.remove(n.left, a, key),
|
||||
.eq => return Node.remove_node(n, a, true),
|
||||
}
|
||||
return node;
|
||||
} else {
|
||||
return error.does_not_exist;
|
||||
}
|
||||
}
|
||||
|
||||
fn walk(node: ?*Node, ctx: anytype, walk_fn: WalkFn(@TypeOf(ctx))) void {
|
||||
if (node) |n| {
|
||||
Node.walk(n.left, ctx, walk_fn);
|
||||
walk_fn(n, ctx);
|
||||
Node.walk(n.right, ctx, walk_fn);
|
||||
}
|
||||
}
|
||||
|
||||
fn leftmost(node: ?*Node) ?*Node {
|
||||
var n = node;
|
||||
while (n) |nn| {
|
||||
if (nn.left == null) {
|
||||
break;
|
||||
}
|
||||
n = nn.left;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(a: std.mem.Allocator) @This() {
|
||||
return .{ .gpa = a };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
Node.deinit(self.root, self.gpa);
|
||||
}
|
||||
|
||||
pub fn iterator(self: *@This()) Iterator {
|
||||
return .{ .current = Node.leftmost(self.root) };
|
||||
}
|
||||
|
||||
pub fn insert(self: *@This(), key: N) Error!*Node {
|
||||
self.root, const inserted = try Node.insert(self.root, self.gpa, key);
|
||||
return inserted;
|
||||
}
|
||||
|
||||
pub fn remove(self: *@This(), key: N) Error!void {
|
||||
self.root = try Node.remove(self.root, self.gpa, key);
|
||||
}
|
||||
|
||||
pub fn remove_node(self: *@This(), node: *Node, destroy: bool) Error!void {
|
||||
if (node.parent) |p| {
|
||||
// Non-root node
|
||||
const np = Node.remove_node(node, self.gpa, destroy);
|
||||
if (np) |npp| {
|
||||
npp.parent = p;
|
||||
}
|
||||
if (node == p.right) {
|
||||
p.right = np;
|
||||
} else {
|
||||
p.left = np;
|
||||
}
|
||||
} else {
|
||||
// Root node
|
||||
const np = Node.remove_node(node, self.gpa, destroy);
|
||||
if (np) |npp| {
|
||||
npp.parent = null;
|
||||
}
|
||||
self.root = np;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup(self: *const @This(), key: N) ?*Node {
|
||||
const search_fn = struct {
|
||||
fn call(n: *const N, cx: N) Order {
|
||||
return compare_fn(n, &cx);
|
||||
}
|
||||
}.call;
|
||||
|
||||
return self.search(key, search_fn);
|
||||
}
|
||||
|
||||
pub fn search(
|
||||
self: *const @This(),
|
||||
ctx: anytype,
|
||||
search_fn: SearchFn(N, @TypeOf(ctx)),
|
||||
) ?*Node {
|
||||
var node = self.root;
|
||||
while (node) |n| {
|
||||
const ord = search_fn(&n.key, ctx);
|
||||
switch (ord) {
|
||||
.gt => node = n.left,
|
||||
.eq => return n,
|
||||
.lt => node = n.right,
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn walk(self: *@This(), ctx: anytype, walk_fn: WalkFn(@TypeOf(ctx))) void {
|
||||
Node.walk(self.root, ctx, walk_fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "BTree insertion/removal" {
|
||||
const int_compare_fn = struct {
|
||||
fn call(a: *const u32, b: *const u32) Order {
|
||||
if (a.* > b.*) {
|
||||
return .gt;
|
||||
} else if (a.* == b.*) {
|
||||
return .eq;
|
||||
} else {
|
||||
return .lt;
|
||||
}
|
||||
}
|
||||
}.call;
|
||||
const Tree = BTree(u32, int_compare_fn, null);
|
||||
var tree = Tree.init(std.testing.allocator);
|
||||
defer tree.deinit();
|
||||
|
||||
for (50..100) |i| {
|
||||
_ = try tree.insert(@truncate(i));
|
||||
}
|
||||
for (1..50) |i| {
|
||||
_ = try tree.insert(@truncate(i));
|
||||
}
|
||||
|
||||
for (1..100) |i| {
|
||||
const k = @as(u32, @truncate(i));
|
||||
try std.testing.expectEqual(k, tree.lookup(k).?.key);
|
||||
}
|
||||
|
||||
for (1..100) |i| {
|
||||
const k = 100 - @as(u32, @truncate(i));
|
||||
if (i % 2 == 0) {
|
||||
try tree.remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
for (1..100) |i| {
|
||||
const k = @as(u32, @truncate(i));
|
||||
if (i % 2 == 0) {
|
||||
try std.testing.expectEqual(null, tree.lookup(k));
|
||||
} else {
|
||||
try std.testing.expectEqual(k, tree.lookup(k).?.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "BTree removal by node" {
|
||||
const int_compare_fn = struct {
|
||||
fn call(a: *const u32, b: *const u32) Order {
|
||||
if (a.* > b.*) {
|
||||
return .gt;
|
||||
} else if (a.* == b.*) {
|
||||
return .eq;
|
||||
} else {
|
||||
return .lt;
|
||||
}
|
||||
}
|
||||
}.call;
|
||||
const Tree = BTree(u32, int_compare_fn, null);
|
||||
var tree = Tree.init(std.testing.allocator);
|
||||
defer tree.deinit();
|
||||
|
||||
_ = try tree.insert(10);
|
||||
_ = try tree.insert(11);
|
||||
_ = try tree.insert(12);
|
||||
|
||||
{
|
||||
const n = tree.lookup(10).?;
|
||||
try tree.remove_node(n, true);
|
||||
}
|
||||
|
||||
try std.testing.expectEqual(null, tree.lookup(10));
|
||||
try std.testing.expectEqual(12, tree.lookup(12).?.key);
|
||||
try std.testing.expectEqual(11, tree.lookup(11).?.key);
|
||||
}
|
||||
|
||||
test "BTree iterator" {
|
||||
const int_compare_fn = struct {
|
||||
fn call(a: *const u32, b: *const u32) Order {
|
||||
if (a.* > b.*) {
|
||||
return .gt;
|
||||
} else if (a.* == b.*) {
|
||||
return .eq;
|
||||
} else {
|
||||
return .lt;
|
||||
}
|
||||
}
|
||||
}.call;
|
||||
const Tree = BTree(u32, int_compare_fn, null);
|
||||
var tree = Tree.init(std.testing.allocator);
|
||||
defer tree.deinit();
|
||||
|
||||
for (50..100) |i| {
|
||||
_ = try tree.insert(@truncate(i));
|
||||
}
|
||||
for (1..50) |i| {
|
||||
_ = try tree.insert(@truncate(i));
|
||||
}
|
||||
|
||||
var it = tree.iterator();
|
||||
for (1..100) |i| {
|
||||
const n = it.next().?;
|
||||
try std.testing.expectEqual(i, n.key);
|
||||
}
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
+104
-100
@@ -4,7 +4,7 @@ const mem = @import("../mem.zig");
|
||||
const log = @import("../debug.zig").log;
|
||||
const std = @import("std");
|
||||
|
||||
const physMemory = mem.phys;
|
||||
const phys_memory = mem.phys;
|
||||
|
||||
const fdt_header = extern struct {
|
||||
magic: u32,
|
||||
@@ -68,13 +68,17 @@ pub const FdtNode = struct {
|
||||
depth: usize,
|
||||
|
||||
/// Returns an iterator over the node's properties.
|
||||
pub fn propIterator(self: *const @This()) FdtNodePropIterator {
|
||||
return .{ .node = self, .tagIter = self.fdt.tagIteratorAt(self.off) };
|
||||
pub fn prop_iterator(self: *const @This()) FdtNodePropIterator {
|
||||
return .{ .node = self, .tag_iter = self.fdt.tag_iterator_at(self.off) };
|
||||
}
|
||||
|
||||
/// Returns an iterator over the node's children.
|
||||
pub fn children(self: *const @This()) FdtNodeIterator {
|
||||
return .{ .tagIter = self.fdt.tagIteratorAt(self.off), .depth = self.depth + 1, .depthLower = self.depth };
|
||||
return .{
|
||||
.tag_iter = self.fdt.tag_iterator_at(self.off),
|
||||
.depth = self.depth + 1,
|
||||
.depth_lower = self.depth,
|
||||
};
|
||||
}
|
||||
|
||||
/// Looks up a child with given `name` within the node.
|
||||
@@ -90,8 +94,8 @@ pub const FdtNode = struct {
|
||||
|
||||
/// Looks up a property with given `name` within the node.
|
||||
pub fn property(self: *const @This(), name: []const u8) ?FdtNodeProp {
|
||||
var propIter = self.propIterator();
|
||||
while (propIter.next()) |prop| {
|
||||
var prop_iter = self.prop_iterator();
|
||||
while (prop_iter.next()) |prop| {
|
||||
if (std.mem.eql(u8, name, prop.name)) {
|
||||
return prop;
|
||||
}
|
||||
@@ -110,31 +114,31 @@ pub const FdtNodeProp = struct {
|
||||
value: []const u8,
|
||||
|
||||
/// Interprets the property's value as a list of strings.
|
||||
pub inline fn getStringArray(self: *const @This()) FdtStringArrayIterator {
|
||||
pub inline fn get_string_array(self: *const @This()) FdtStringArrayIterator {
|
||||
return .{ .prop = self };
|
||||
}
|
||||
|
||||
/// Interprets the property's value as a single string.
|
||||
pub inline fn getString(self: *const @This()) []const u8 {
|
||||
var sa = self.getStringArray();
|
||||
pub inline fn get_string(self: *const @This()) []const u8 {
|
||||
var sa = self.get_string_array();
|
||||
return sa.next() orelse "";
|
||||
}
|
||||
|
||||
/// Returns the length of the property in full 32-bit cells.
|
||||
pub inline fn lenU32(self: *const @This()) usize {
|
||||
pub inline fn len_cells(self: *const @This()) usize {
|
||||
return self.value.len / @sizeOf(u32);
|
||||
}
|
||||
|
||||
/// Interprets the property's value as an array of 32-bit cells and returns a cell at a given
|
||||
/// index.
|
||||
pub fn getU32(self: *const @This(), index: usize) ?u32 {
|
||||
if (index >= self.lenU32()) {
|
||||
pub fn get_cell(self: *const @This(), index: usize) ?u32 {
|
||||
if (index >= self.len_cells()) {
|
||||
return null;
|
||||
}
|
||||
return self.getU32Unchecked(index);
|
||||
return self.get_cell_unchecked(index);
|
||||
}
|
||||
|
||||
fn getU32Unchecked(self: *const @This(), index: usize) u32 {
|
||||
fn get_cell_unchecked(self: *const @This(), index: usize) u32 {
|
||||
return std.mem.bigToNative(u32, @as(*const u32, @ptrCast(@alignCast(&self.value[index * 4]))).*);
|
||||
}
|
||||
|
||||
@@ -147,9 +151,9 @@ pub const FdtNodeProp = struct {
|
||||
///
|
||||
/// * `index` parameter means a 32-bit cell index, not a tuple index.
|
||||
/// * Tuple length is assumed to be `@min(output.len, sizes.len)`.
|
||||
pub fn readCells(self: *const @This(), index: usize, output: []u64, sizes: []const usize) bool {
|
||||
pub fn read_cells(self: *const @This(), index: usize, output: []u64, sizes: []const usize) bool {
|
||||
const count = @min(output.len, sizes.len);
|
||||
const len = self.lenU32();
|
||||
const len = self.len_cells();
|
||||
var total: usize = 0;
|
||||
for (sizes[0..count]) |s| {
|
||||
total += s;
|
||||
@@ -159,11 +163,11 @@ pub const FdtNodeProp = struct {
|
||||
for (0..count) |i| {
|
||||
switch (sizes[i]) {
|
||||
1 => {
|
||||
output[i] = self.getU32Unchecked(offset);
|
||||
output[i] = self.get_cell_unchecked(offset);
|
||||
},
|
||||
2 => {
|
||||
output[i] = self.getU32Unchecked(offset + 1);
|
||||
output[i] |= @as(u64, self.getU32Unchecked(offset)) << 32;
|
||||
output[i] = self.get_cell_unchecked(offset + 1);
|
||||
output[i] |= @as(u64, self.get_cell_unchecked(offset)) << 32;
|
||||
},
|
||||
else => @panic("Invalid cell size"),
|
||||
}
|
||||
@@ -196,16 +200,16 @@ pub const FdtStringArrayIterator = struct {
|
||||
|
||||
/// An iterator over available memory regions described by a device tree.
|
||||
pub const FdtMemoryRegionIterator = struct {
|
||||
nodeIter: FdtNodeIterator,
|
||||
cellSizes: [2]usize,
|
||||
node_iter: FdtNodeIterator,
|
||||
cell_sizes: [2]usize,
|
||||
|
||||
pub fn next(self: *FdtMemoryRegionIterator) ?FdtMemoryRegion {
|
||||
while (self.nodeIter.next()) |node| {
|
||||
while (self.node_iter.next()) |node| {
|
||||
if (std.mem.startsWith(u8, node.name, "memory@")) {
|
||||
const reg = node.property("reg") orelse continue;
|
||||
var cells: [2]u64 = undefined;
|
||||
|
||||
if (reg.readCells(0, &cells, &self.cellSizes)) {
|
||||
if (reg.read_cells(0, &cells, &self.cell_sizes)) {
|
||||
return .{
|
||||
.name = node.name,
|
||||
.base = cells[0],
|
||||
@@ -222,11 +226,11 @@ pub const FdtMemoryRegionIterator = struct {
|
||||
/// An iterator over a device tree's node properties.
|
||||
pub const FdtNodePropIterator = struct {
|
||||
node: *const FdtNode,
|
||||
tagIter: FdtTagIterator,
|
||||
tag_iter: FdtTagIterator,
|
||||
depth: usize = 0,
|
||||
|
||||
fn next(self: *FdtNodePropIterator) ?FdtNodeProp {
|
||||
while (self.tagIter.next()) |tag| {
|
||||
while (self.tag_iter.next()) |tag| {
|
||||
switch (tag) {
|
||||
.begin_node => |_| {
|
||||
self.depth += 1;
|
||||
@@ -234,7 +238,7 @@ pub const FdtNodePropIterator = struct {
|
||||
.nop => {},
|
||||
.prop => |prop| {
|
||||
if (self.depth == 0) {
|
||||
const name = self.node.fdt.stringAt(prop.nameoff);
|
||||
const name = self.node.fdt.string_at(prop.nameoff);
|
||||
return .{ .node = self.node, .value = prop.data, .name = name };
|
||||
}
|
||||
},
|
||||
@@ -257,18 +261,18 @@ pub const FdtNodePropIterator = struct {
|
||||
|
||||
/// An iterator over a device tree's nodes.
|
||||
pub const FdtNodeIterator = struct {
|
||||
tagIter: FdtTagIterator,
|
||||
tag_iter: FdtTagIterator,
|
||||
depth: usize = 0,
|
||||
depthLower: ?usize = null,
|
||||
depth_lower: ?usize = null,
|
||||
|
||||
pub fn next(self: *FdtNodeIterator) ?FdtNode {
|
||||
while (self.tagIter.next()) |tag| {
|
||||
while (self.tag_iter.next()) |tag| {
|
||||
switch (tag) {
|
||||
.begin_node => |name| {
|
||||
self.depth += 1;
|
||||
return .{
|
||||
.fdt = self.tagIter.fdt,
|
||||
.off = self.tagIter.off,
|
||||
.fdt = self.tag_iter.fdt,
|
||||
.off = self.tag_iter.off,
|
||||
.name = name,
|
||||
.depth = self.depth - 1,
|
||||
};
|
||||
@@ -276,7 +280,7 @@ pub const FdtNodeIterator = struct {
|
||||
.end_node => {
|
||||
self.depth -= 1;
|
||||
|
||||
if (self.depthLower) |lower| {
|
||||
if (self.depth_lower) |lower| {
|
||||
if (self.depth == lower) {
|
||||
return null;
|
||||
}
|
||||
@@ -306,10 +310,10 @@ pub const FdtTagIterator = struct {
|
||||
|
||||
switch (tag) {
|
||||
.FDT_BEGIN_NODE => {
|
||||
const nameCStr: [*c]const u8 = @ptrCast(self.raw[self.off..]);
|
||||
const nameLength = std.mem.len(nameCStr);
|
||||
const name = self.raw[self.off .. self.off + nameLength];
|
||||
self.off += (nameLength + 4) & ~@as(usize, 3);
|
||||
const name_cstr: [*c]const u8 = @ptrCast(self.raw[self.off..]);
|
||||
const name_length = std.mem.len(name_cstr);
|
||||
const name = self.raw[self.off .. self.off + name_length];
|
||||
self.off += (name_length + 4) & ~@as(usize, 3);
|
||||
return .{ .begin_node = name };
|
||||
},
|
||||
.FDT_PROP => {
|
||||
@@ -355,7 +359,7 @@ pub const Fdt = struct {
|
||||
/// # Errors
|
||||
///
|
||||
/// * `invalid_magic` if the address provided does not have a valid magic number in its header.
|
||||
pub fn fromPhysicalAddress(phys: mem.PhysicalAddress) FdtError!@This() {
|
||||
pub fn from_physical_address(phys: mem.PhysicalAddress) FdtError!@This() {
|
||||
const virt = phys.virtualize();
|
||||
const hdr = @as(*const fdt_header, @ptrFromInt(virt));
|
||||
if (std.mem.bigToNative(u32, hdr.magic) != FDT_MAGIC) {
|
||||
@@ -377,18 +381,18 @@ pub const Fdt = struct {
|
||||
}
|
||||
|
||||
/// Returns an iterator over the device tree's raw tags.
|
||||
pub fn tagIterator(self: *const @This()) FdtTagIterator {
|
||||
return self.tagIteratorAt(0);
|
||||
pub fn tag_iterator(self: *const @This()) FdtTagIterator {
|
||||
return self.tag_iterator_at(0);
|
||||
}
|
||||
|
||||
/// Returns an iterator over the device tree's raw tags at specific byte offset.
|
||||
pub fn tagIteratorAt(self: *const @This(), off: usize) FdtTagIterator {
|
||||
pub fn tag_iterator_at(self: *const @This(), off: usize) FdtTagIterator {
|
||||
return .{ .raw = self.data(), .fdt = self, .off = off };
|
||||
}
|
||||
|
||||
/// Returns an iterator over the device tree's nodes.
|
||||
pub fn nodeIterator(self: *const @This()) FdtNodeIterator {
|
||||
return .{ .tagIter = self.tagIterator() };
|
||||
pub fn node_iterator(self: *const @This()) FdtNodeIterator {
|
||||
return .{ .tag_iter = self.tag_iterator() };
|
||||
}
|
||||
|
||||
/// Returns the root node of this device tree.
|
||||
@@ -397,9 +401,9 @@ pub const Fdt = struct {
|
||||
///
|
||||
/// Panics if the device tree does not have a root node (which means the device tree blob is
|
||||
/// malformed and the OS shouldn't be running anyway).
|
||||
pub fn rootNode(self: *const @This()) FdtNode {
|
||||
var nodeIter = self.nodeIterator();
|
||||
while (nodeIter.next()) |node| {
|
||||
pub fn root_node(self: *const @This()) FdtNode {
|
||||
var node_iter = self.node_iterator();
|
||||
while (node_iter.next()) |node| {
|
||||
if (node.depth == 0 and node.name.len == 0) {
|
||||
return node;
|
||||
}
|
||||
@@ -408,46 +412,46 @@ pub const Fdt = struct {
|
||||
}
|
||||
|
||||
/// Returns an iterator over available memory regions described by the device tree.
|
||||
pub fn memoryRegionIterator(self: *const @This()) FdtMemoryRegionIterator {
|
||||
const r = self.rootNode();
|
||||
const addressCells = if (r.property("#address-cells")) |o| (if (o.getU32(0)) |p| p else 1) else 1;
|
||||
const sizeCells = if (r.property("#size-cells")) |o| (if (o.getU32(0)) |p| p else 1) else 1;
|
||||
pub fn memory_region_iterator(self: *const @This()) FdtMemoryRegionIterator {
|
||||
const r = self.root_node();
|
||||
const address_cells = if (r.property("#address-cells")) |o| (if (o.get_cell(0)) |p| p else 1) else 1;
|
||||
const size_cells = if (r.property("#size-cells")) |o| (if (o.get_cell(0)) |p| p else 1) else 1;
|
||||
|
||||
return .{ .nodeIter = self.nodeIterator(), .cellSizes = .{ addressCells, sizeCells } };
|
||||
return .{ .node_iter = self.node_iterator(), .cell_sizes = .{ address_cells, size_cells } };
|
||||
}
|
||||
|
||||
fn stringData(self: *const @This()) [*c]const u8 {
|
||||
const offStrings = std.mem.bigToNative(u32, self.header().off_dt_strings);
|
||||
const sizeStrings = std.mem.bigToNative(u32, self.header().off_dt_strings);
|
||||
const off = @min(offStrings, self.bytes.len);
|
||||
const len = @min(sizeStrings, self.bytes.len - off);
|
||||
fn string_data(self: *const @This()) [*c]const u8 {
|
||||
const off_strings = std.mem.bigToNative(u32, self.header().off_dt_strings);
|
||||
const size_strings = std.mem.bigToNative(u32, self.header().off_dt_strings);
|
||||
const off = @min(off_strings, self.bytes.len);
|
||||
const len = @min(size_strings, self.bytes.len - off);
|
||||
return @ptrCast(self.bytes[off .. off + len]);
|
||||
}
|
||||
|
||||
/// Returns a string slice at given byte offset into the device tree's strings section.
|
||||
pub fn stringAt(self: *const @This(), off: usize) []const u8 {
|
||||
const raw = self.stringData()[off..];
|
||||
pub fn string_at(self: *const @This(), off: usize) []const u8 {
|
||||
const raw = self.string_data()[off..];
|
||||
const len = std.mem.len(raw);
|
||||
return @ptrCast(raw[0..len]);
|
||||
}
|
||||
|
||||
/// Adds information about the available and reserved memory regions described in this device
|
||||
/// tree into the physical memory management structures.
|
||||
pub fn addPhysicalMemoryToSystem(self: *const @This()) void {
|
||||
var memoryRegions = self.memoryRegionIterator();
|
||||
pub fn add_physical_memory_to_system(self: *const @This()) void {
|
||||
var memory_regions = self.memory_region_iterator();
|
||||
var cells: [2]u64 = undefined;
|
||||
|
||||
while (memoryRegions.next()) |region| {
|
||||
physMemory.addMemoryRegion(region.name, region.base, region.size);
|
||||
while (memory_regions.next()) |region| {
|
||||
phys_memory.add_memory_region(region.name, region.base, region.size);
|
||||
}
|
||||
const reservedRegions = self.rootNode().child("reserved-memory");
|
||||
if (reservedRegions) |resv| {
|
||||
const reserved_regions = self.root_node().child("reserved-memory");
|
||||
if (reserved_regions) |resv| {
|
||||
var children = resv.children();
|
||||
while (children.next()) |region| {
|
||||
if (region.property("reg")) |reg| {
|
||||
// TODO #address-cells, #size-cells
|
||||
if (reg.readCells(0, &cells, &.{ 2, 2 })) {
|
||||
physMemory.addReservedRegion(region.name, cells[0], cells[1]);
|
||||
if (reg.read_cells(0, &cells, &.{ 2, 2 })) {
|
||||
phys_memory.add_reserved_region(region.name, cells[0], cells[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -456,15 +460,15 @@ pub const Fdt = struct {
|
||||
|
||||
/// Looks up a `/slash/separated/path` inside the device tree.
|
||||
pub fn find(self: *const @This(), path: []const u8) ?FdtNode {
|
||||
const trimmedPath = std.mem.trimLeft(u8, path, "/");
|
||||
var pathElements = std.mem.splitScalar(u8, trimmedPath, '/');
|
||||
var currentNode = self.rootNode();
|
||||
if (trimmedPath.len == 0) {
|
||||
return currentNode;
|
||||
const trimmed_path = std.mem.trimLeft(u8, path, "/");
|
||||
var path_elements = std.mem.splitScalar(u8, trimmed_path, '/');
|
||||
var current_node = self.root_node();
|
||||
if (trimmed_path.len == 0) {
|
||||
return current_node;
|
||||
}
|
||||
while (pathElements.next()) |element| {
|
||||
while (path_elements.next()) |element| {
|
||||
var found: ?FdtNode = null;
|
||||
var children = currentNode.children();
|
||||
var children = current_node.children();
|
||||
while (children.next()) |child| {
|
||||
if (std.mem.eql(u8, child.name, element)) {
|
||||
found = child;
|
||||
@@ -473,17 +477,17 @@ pub const Fdt = struct {
|
||||
}
|
||||
if (found) |f| {
|
||||
log.info("{s}", .{element});
|
||||
currentNode = f;
|
||||
current_node = f;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return currentNode;
|
||||
return current_node;
|
||||
}
|
||||
|
||||
fn dump_property(property: *const FdtNodeProp, depth: usize, allStrings: bool) void {
|
||||
fn dump_property(property: *const FdtNodeProp, depth: usize, all_strings: bool) void {
|
||||
for (0..depth) |_| {
|
||||
log.writeRaw(" ");
|
||||
log.write_waw(" ");
|
||||
}
|
||||
log.write("{s}", .{property.name});
|
||||
|
||||
@@ -494,74 +498,74 @@ pub const Fdt = struct {
|
||||
or std.mem.eql(u8, property.name, "interrupt-parent") //
|
||||
) {
|
||||
// Dump as a single cell
|
||||
const v = property.getU32(0) orelse 0;
|
||||
const v = property.get_cell(0) orelse 0;
|
||||
log.write(" = {}", .{v});
|
||||
} else if (allStrings //
|
||||
} else if (all_strings //
|
||||
or std.mem.eql(u8, property.name, "compatible") //
|
||||
or std.mem.eql(u8, property.name, "model") //
|
||||
or std.mem.endsWith(u8, property.name, "-names") //
|
||||
or std.mem.endsWith(u8, property.name, "stdout-path") //
|
||||
) {
|
||||
var v = property.getStringArray();
|
||||
var v = property.get_string_array();
|
||||
var f = true;
|
||||
while (v.next()) |s| {
|
||||
if (f) {
|
||||
log.writeRaw(" = ");
|
||||
log.write_waw(" = ");
|
||||
} else {
|
||||
log.writeRaw(", ");
|
||||
log.write_waw(", ");
|
||||
}
|
||||
f = false;
|
||||
log.write("\"{s}\"", .{s});
|
||||
}
|
||||
} else {
|
||||
// Dump the rest as a cell array
|
||||
const len = property.lenU32();
|
||||
log.writeRaw(" = <");
|
||||
const len = property.len_cells();
|
||||
log.write_waw(" = <");
|
||||
for (0..len) |i| {
|
||||
if (i != 0) {
|
||||
log.writeRaw(", ");
|
||||
log.write_waw(", ");
|
||||
}
|
||||
log.write("0x{x}", .{property.getU32Unchecked(i)});
|
||||
log.write("0x{x}", .{property.get_cell_unchecked(i)});
|
||||
}
|
||||
log.writeRaw(">");
|
||||
log.write_waw(">");
|
||||
}
|
||||
|
||||
log.writeRaw(";\r\n");
|
||||
log.write_waw(";\r\n");
|
||||
}
|
||||
|
||||
fn dump_node(node: *const FdtNode, depth: usize) void {
|
||||
var anyProperties = false;
|
||||
var firstChild = true;
|
||||
var any_properties = false;
|
||||
var first_child = true;
|
||||
|
||||
for (0..depth) |_| {
|
||||
log.writeRaw(" ");
|
||||
log.write_waw(" ");
|
||||
}
|
||||
if (node.name.len != 0) {
|
||||
log.write("{s} ", .{node.name});
|
||||
}
|
||||
log.writeRaw("{\r\n");
|
||||
var properties = node.propIterator();
|
||||
const allStrings = std.mem.eql(u8, node.name, "aliases");
|
||||
log.write_waw("{\r\n");
|
||||
var properties = node.prop_iterator();
|
||||
const all_strings = std.mem.eql(u8, node.name, "aliases");
|
||||
while (properties.next()) |property| {
|
||||
dump_property(&property, depth + 1, allStrings);
|
||||
anyProperties = true;
|
||||
dump_property(&property, depth + 1, all_strings);
|
||||
any_properties = true;
|
||||
}
|
||||
var children = node.children();
|
||||
while (children.next()) |child| {
|
||||
if (anyProperties and firstChild) {
|
||||
log.writeRaw("\r\n");
|
||||
if (any_properties and first_child) {
|
||||
log.write_waw("\r\n");
|
||||
}
|
||||
firstChild = false;
|
||||
first_child = false;
|
||||
dump_node(&child, depth + 1);
|
||||
}
|
||||
for (0..depth) |_| {
|
||||
log.writeRaw(" ");
|
||||
log.write_waw(" ");
|
||||
}
|
||||
log.write("}},\r\n", .{});
|
||||
}
|
||||
|
||||
/// Dumps the structured device tree into the log output.
|
||||
pub fn dump(self: *const @This()) void {
|
||||
dump_node(&self.rootNode(), 0);
|
||||
dump_node(&self.root_node(), 0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Utilities for manipulating ranges.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// Non-inclusive range type over `T`.
|
||||
pub fn Range(comptime T: type) type {
|
||||
return struct {
|
||||
@@ -29,5 +31,19 @@ pub fn Range(comptime T: type) type {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn contains(self: *const @This(), scalar: T) bool {
|
||||
return scalar >= self.start and scalar - self.start < self.len;
|
||||
}
|
||||
|
||||
pub fn compare_disjoint(a: *const @This(), b: *const @This()) std.math.Order {
|
||||
if (a.start >= b.end()) {
|
||||
return .gt;
|
||||
} else if (b.start >= a.end()) {
|
||||
return .lt;
|
||||
} else {
|
||||
return .eq;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
const std = @import("std");
|
||||
|
||||
const btree = @import("btree.zig");
|
||||
|
||||
const Range = @import("range.zig").Range;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const BTree = btree.BTree;
|
||||
pub const Order = btree.Order;
|
||||
|
||||
pub fn RangeMap(
|
||||
comptime K: type,
|
||||
comptime V: type,
|
||||
comptime ops: struct {
|
||||
deinit_fn: ?fn(*V) void = null,
|
||||
merge_fn: ?fn (*const V, *const V) bool = null,
|
||||
},
|
||||
) type {
|
||||
return struct {
|
||||
pub const Node = struct {
|
||||
key: Range(K),
|
||||
value: V,
|
||||
|
||||
pub fn len(self: *const @This()) K {
|
||||
return self.key.len;
|
||||
}
|
||||
};
|
||||
|
||||
pub const WalkFn = fn (*const Node) void;
|
||||
|
||||
pub const Iterator = struct {
|
||||
inner: Tree.Iterator,
|
||||
|
||||
pub fn next(self: *Iterator) ?*const Node {
|
||||
if (self.inner.next()) |n| {
|
||||
return &n.key;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Tree = BTree(Node, compare_fn, deinit_node_fn);
|
||||
|
||||
pub const Error = error{
|
||||
scalar_out_of_range,
|
||||
range_out_of_bounds,
|
||||
} || Tree.Error;
|
||||
|
||||
fn compare_fn(a: *const Node, b: *const Node) Order {
|
||||
return Range(K).compare_disjoint(&a.key, &b.key);
|
||||
}
|
||||
|
||||
fn deinit_node_fn(n: *Node) void {
|
||||
if (comptime ops.deinit_fn) |f| {
|
||||
f(&n.value);
|
||||
}
|
||||
}
|
||||
|
||||
btree: Tree,
|
||||
|
||||
pub fn init(gpa: Allocator) @This() {
|
||||
return .{ .btree = Tree.init(gpa) };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
self.btree.deinit();
|
||||
}
|
||||
|
||||
/// Returns the value at a given scalar point, along with the full range it belongs to.
|
||||
pub fn get_scalar(self: *const @This(), scalar: K) ?*Node {
|
||||
return if (self.get_scalar_node(scalar)) |n| &n.key else null;
|
||||
}
|
||||
|
||||
/// Same as `get_scalar()`, but returns the underlying BST node.
|
||||
pub fn get_scalar_node(self: *const @This(), scalar: K) ?*Tree.Node {
|
||||
return self.btree.search(scalar, struct {
|
||||
fn call(n: *const Node, cx: K) Order {
|
||||
if (n.key.contains(cx)) {
|
||||
return .eq;
|
||||
} else if (cx < n.key.start) {
|
||||
return .gt;
|
||||
} else {
|
||||
return .lt;
|
||||
}
|
||||
}
|
||||
}.call);
|
||||
}
|
||||
|
||||
/// Splits a given node at a scalar point inside its interval.
|
||||
///
|
||||
/// The part of the interval before `at` is considered a "left" half, the remaining
|
||||
/// part is considered a "right" half.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The "right" halve's value after the split is left uninitialized and it is up to the
|
||||
/// caller to assign a proper value to it.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `scalar_out_of_range` if the given `at` value is not inside the node's interval.
|
||||
pub fn split_node(
|
||||
self: *@This(),
|
||||
node: *Tree.Node,
|
||||
at: K,
|
||||
) Error!?struct { *Tree.Node, *Tree.Node } {
|
||||
if (!node.key.key.contains(at)) {
|
||||
return error.scalar_out_of_range;
|
||||
}
|
||||
|
||||
const start = node.key.key.start;
|
||||
const end = node.key.key.end();
|
||||
|
||||
if (at == start or at == end - 1) {
|
||||
// Nothing to split here
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = node.key.value;
|
||||
|
||||
// Remove the node, don't drop the key
|
||||
try self.btree.remove_node(node, false);
|
||||
|
||||
const lnode = try self.btree.insert(
|
||||
.{ .key = .{ .start = start, .len = at - start }, .value = value },
|
||||
);
|
||||
const rnode = try self.btree.insert(
|
||||
.{ .key = .{ .start = at, .len = end - at }, .value = undefined },
|
||||
);
|
||||
|
||||
return .{ lnode, rnode };
|
||||
}
|
||||
|
||||
/// Maps some range to a value. Returns an error if the requested range crosses another
|
||||
/// mapped range.
|
||||
pub fn insert(self: *@This(), start: K, len: K, value: V) Error!*Tree.Node {
|
||||
try validate_range(start, len);
|
||||
|
||||
if (comptime ops.merge_fn) |merge_fn| {
|
||||
const left: ?*Tree.Node = if (start > 0) self.get_scalar_node(start - 1) else null;
|
||||
const right = self.get_scalar_node(start + len);
|
||||
|
||||
if (left) |l| {
|
||||
const l_start = l.key.key.start;
|
||||
|
||||
if (merge_fn(&l.key.value, &value)) {
|
||||
if (right) |r| {
|
||||
if (merge_fn(&r.key.value, &value)) {
|
||||
l.key.key.len += len + r.key.key.len;
|
||||
try self.btree.remove_node(r, true);
|
||||
return self.get_scalar_node(l_start).?;
|
||||
}
|
||||
}
|
||||
|
||||
l.key.key.len += len;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
if (right) |r| {
|
||||
// Only right node to potentially merge with
|
||||
if (merge_fn(&r.key.value, &value)) {
|
||||
const r_len = r.key.key.len;
|
||||
try self.btree.remove_node(r, true);
|
||||
return self.btree.insert(.{
|
||||
.key = .{ .start = start, .len = len + r_len },
|
||||
.value = value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.btree.insert(.{
|
||||
.key = .{ .start = start, .len = len },
|
||||
.value = value,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn iterator(self: *@This()) Iterator {
|
||||
return .{ .inner = self.btree.iterator() };
|
||||
}
|
||||
|
||||
pub fn node_iterator(self: *@This()) Tree.Iterator {
|
||||
return self.btree.iterator();
|
||||
}
|
||||
|
||||
pub fn walk(self: *@This(), walk_fn: WalkFn) void {
|
||||
self.btree.walk(walk_fn, struct {
|
||||
fn call(n: *const Tree.Node, cx: WalkFn) void {
|
||||
cx(&n.key);
|
||||
}
|
||||
}.call);
|
||||
}
|
||||
|
||||
fn validate_range(start: K, end: K) Error!void {
|
||||
// Check for addition overflowing the K's bit size
|
||||
if (std.math.add(K, start, end) == error.Overflow) {
|
||||
return error.range_out_of_bounds;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "Range map insertion" {
|
||||
const Map = RangeMap(u32, []const u8, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
_ = try map.insert(10, 10, "Range 2");
|
||||
_ = try map.insert(0, 10, "Range 1");
|
||||
_ = try map.insert(20, 10, "Range 3");
|
||||
|
||||
try std.testing.expectError(error.already_exists, map.insert(5, 10, "Invalid range"));
|
||||
|
||||
_ = try map.insert(1000, 10, "Range 4");
|
||||
}
|
||||
|
||||
test "Range map merging insertion" {
|
||||
const Map = RangeMap(u32, bool, .{
|
||||
.merge_fn = struct {
|
||||
fn call(lhs: *const bool, rhs: *const bool) bool {
|
||||
return !lhs.* and !rhs.*;
|
||||
}
|
||||
}.call,
|
||||
});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
// Should not merge
|
||||
_ = try map.insert(10, 10, false);
|
||||
_ = try map.insert(0, 10, true);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
try std.testing.expectEqual(true, it.next().?.value);
|
||||
try std.testing.expectEqual(false, it.next().?.value);
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Merge left + inserted + right
|
||||
_ = try map.insert(30, 10, false);
|
||||
_ = try map.insert(20, 10, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Should not merge again
|
||||
_ = try map.insert(40, 10, true);
|
||||
_ = try map.insert(50, 10, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
const n2 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
|
||||
try std.testing.expectEqual(true, n2.value);
|
||||
|
||||
const n3 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 10 }, n3.key);
|
||||
try std.testing.expectEqual(false, n3.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Should merge left + shouldn't merge right non-contiguous
|
||||
_ = try map.insert(71, 9, false);
|
||||
_ = try map.insert(60, 10, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
const n2 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
|
||||
try std.testing.expectEqual(true, n2.value);
|
||||
|
||||
const n3 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 20 }, n3.key);
|
||||
try std.testing.expectEqual(false, n3.value);
|
||||
|
||||
const n4 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 71, .len = 9 }, n4.key);
|
||||
try std.testing.expectEqual(false, n4.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Should merge left and right
|
||||
_ = try map.insert(70, 1, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
const n2 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
|
||||
try std.testing.expectEqual(true, n2.value);
|
||||
|
||||
const n3 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key);
|
||||
try std.testing.expectEqual(false, n3.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Should merge right
|
||||
_ = try map.insert(110, 10, false);
|
||||
_ = try map.insert(100, 10, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
const n2 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
|
||||
try std.testing.expectEqual(true, n2.value);
|
||||
|
||||
const n3 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key);
|
||||
try std.testing.expectEqual(false, n3.value);
|
||||
|
||||
const n4 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 100, .len = 20 }, n4.key);
|
||||
try std.testing.expectEqual(false, n4.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
}
|
||||
|
||||
test "Range map get scalar" {
|
||||
const Map = RangeMap(u32, []const u8, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
_ = try map.insert(10, 10, "Range [10..20)");
|
||||
_ = try map.insert(30, 10, "Range [30..40)");
|
||||
|
||||
{
|
||||
const n = map.get_scalar(15).?;
|
||||
try std.testing.expectEqual(10, n.key.start);
|
||||
try std.testing.expectEqual(20, n.key.end());
|
||||
try std.testing.expectEqualStrings("Range [10..20)", n.value);
|
||||
}
|
||||
{
|
||||
const n = map.get_scalar(35).?;
|
||||
try std.testing.expectEqual(30, n.key.start);
|
||||
try std.testing.expectEqual(40, n.key.end());
|
||||
try std.testing.expectEqualStrings("Range [30..40)", n.value);
|
||||
}
|
||||
{
|
||||
const n = map.get_scalar(30).?;
|
||||
try std.testing.expectEqual(30, n.key.start);
|
||||
try std.testing.expectEqual(40, n.key.end());
|
||||
try std.testing.expectEqualStrings("Range [30..40)", n.value);
|
||||
}
|
||||
try std.testing.expectEqual(null, map.get_scalar(20));
|
||||
try std.testing.expectEqual(null, map.get_scalar(21));
|
||||
try std.testing.expectEqual(null, map.get_scalar(9));
|
||||
try std.testing.expectEqual(null, map.get_scalar(100));
|
||||
try std.testing.expectEqual(null, map.get_scalar(40));
|
||||
try std.testing.expectEqual(null, map.get_scalar(41));
|
||||
}
|
||||
|
||||
test "Range map split" {
|
||||
const Map = RangeMap(u32, []const u8, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
_ = try map.insert(0x1000, 0x1000, "Range [0x1000..0x2000)");
|
||||
|
||||
const node = map.get_scalar_node(0x1000).?;
|
||||
const lnode, const rnode = (try map.split_node(node, 0x1200)).?;
|
||||
|
||||
lnode.key.value = "Left";
|
||||
rnode.key.value = "Right";
|
||||
|
||||
{
|
||||
const n = map.get_scalar(0x1100).?;
|
||||
try std.testing.expectEqual(0x1000, n.key.start);
|
||||
try std.testing.expectEqual(0x200, n.key.len);
|
||||
try std.testing.expectEqualStrings("Left", n.value);
|
||||
}
|
||||
{
|
||||
const n = map.get_scalar(0x1300).?;
|
||||
try std.testing.expectEqual(0x1200, n.key.start);
|
||||
try std.testing.expectEqual(0xE00, n.key.len);
|
||||
try std.testing.expectEqualStrings("Right", n.value);
|
||||
}
|
||||
}
|
||||
|
||||
test "Range map iterator" {
|
||||
const Map = RangeMap(u32, []const u8, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
_ = try map.insert(0x1000, 0x1000, "Range [0x1000..0x2000)");
|
||||
_ = try map.insert(0x2000, 0x1000, "Range [0x2000..0x3000)");
|
||||
_ = try map.insert(0x4000, 0x1000, "Range [0x4000..0x5000)");
|
||||
_ = try map.insert(0x3000, 0x1000, "Range [0x3000..0x4000)");
|
||||
|
||||
var it = map.iterator();
|
||||
try std.testing.expectEqualStrings("Range [0x1000..0x2000)", it.next().?.value);
|
||||
try std.testing.expectEqualStrings("Range [0x2000..0x3000)", it.next().?.value);
|
||||
try std.testing.expectEqualStrings("Range [0x3000..0x4000)", it.next().?.value);
|
||||
try std.testing.expectEqualStrings("Range [0x4000..0x5000)", it.next().?.value);
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
test "Range map should disallow overflowing ranges" {
|
||||
const Map = RangeMap(u32, bool, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
try std.testing.expectError(error.range_out_of_bounds, map.insert(0xF0000000, 0x20000000, false));
|
||||
}
|
||||
Reference in New Issue
Block a user