11 Commits

33 changed files with 1554 additions and 637 deletions
+3
View File
@@ -1,2 +1,5 @@
/zig-out /zig-out
/.zig-cache /.zig-cache
/*.dtb
/*.dts
/*.log
+36 -25
View File
@@ -6,7 +6,7 @@ const SupportedArch = enum {
aarch64, aarch64,
riscv64, riscv64,
fn makeTarget(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget { fn make_target(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget {
switch (self) { switch (self) {
.riscv64 => { .riscv64 => {
return b.resolveTargetQuery(.{ return b.resolveTargetQuery(.{
@@ -18,11 +18,11 @@ const SupportedArch = enum {
.aarch64 => { .aarch64 => {
const T = std.Target.aarch64; const T = std.Target.aarch64;
const addFeatures = T.featureSet(&.{ const add_features = T.featureSet(&.{
T.Feature.v8a, T.Feature.v8a,
T.Feature.strict_align, T.Feature.strict_align,
}); });
const subFeatures = T.featureSet(&.{ const sub_features = T.featureSet(&.{
T.Feature.neon, T.Feature.neon,
T.Feature.fp_armv8, T.Feature.fp_armv8,
}); });
@@ -31,14 +31,14 @@ const SupportedArch = enum {
.cpu_arch = .aarch64, .cpu_arch = .aarch64,
.os_tag = .freestanding, .os_tag = .freestanding,
.abi = .none, .abi = .none,
.cpu_features_add = addFeatures, .cpu_features_add = add_features,
.cpu_features_sub = subFeatures, .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) { switch (self) {
.riscv64 => { .riscv64 => {
kernel.entry = .{ .symbol_name = "__rv64_entry" }; kernel.entry = .{ .symbol_name = "__rv64_entry" };
@@ -51,6 +51,7 @@ const SupportedArch = enum {
}, },
.aarch64 => { .aarch64 => {
kernel.entry = .{ .symbol_name = "__aa64_entry" }; kernel.entry = .{ .symbol_name = "__aa64_entry" };
kernel.link_z_max_page_size = 0x1000;
kernel.setLinkerScript(b.path("etc/aarch64-unknown-none.ld")); kernel.setLinkerScript(b.path("etc/aarch64-unknown-none.ld"));
kernel.addCSourceFiles(.{ kernel.addCSourceFiles(.{
@@ -63,12 +64,12 @@ const SupportedArch = enum {
b.installArtifact(kernel); b.installArtifact(kernel);
if (self == .riscv64 or self == .aarch64) { if (self == .riscv64 or self == .aarch64) {
const fakeLinuxHeader: *std.Build.Step = try b.allocator.create(std.Build.Step); const fake_linux_header: *std.Build.Step = try b.allocator.create(std.Build.Step);
fakeLinuxHeader.* = std.Build.Step.init(.{ fake_linux_header.* = std.Build.Step.init(.{
.id = std.Build.Step.Id.custom, .id = std.Build.Step.Id.custom,
.name = "insert fake linux header", .name = "insert fake linux header",
.owner = kernel.step.owner, .owner = kernel.step.owner,
.makeFn = insertFakeLinuxImageHeader, .makeFn = insert_fake_linux_image_header,
}); });
const elf2bin = b.addSystemCommand(&.{ const elf2bin = b.addSystemCommand(&.{
@@ -79,8 +80,8 @@ const SupportedArch = enum {
"zig-out/bin/kernel.bin", "zig-out/bin/kernel.bin",
}); });
fakeLinuxHeader.dependOn(b.getInstallStep()); fake_linux_header.dependOn(b.getInstallStep());
elf2bin.step.dependOn(fakeLinuxHeader); elf2bin.step.dependOn(fake_linux_header);
return &elf2bin.step; return &elf2bin.step;
} else { } 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_MAGIC1 = "RISCV\x00\x00\x00";
const RISCV_MAGIC2 = "RSC\x05"; 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)); _ = try file.readAll(std.mem.asBytes(&ehdr));
// Figure out total image size // Figure out total image size
var imageAddrMax: u64 = 0; var image_addr_max: u64 = 0;
for (0..ehdr.e_phnum) |i| { for (0..ehdr.e_phnum) |i| {
var phdr: elf.Phdr = undefined; var phdr: elf.Phdr = undefined;
_ = try file.preadAll(std.mem.asBytes(&phdr), ehdr.e_phoff + i * ehdr.e_phentsize); _ = 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); const end = (phdr.p_vaddr + phdr.p_memsz + 0xFFF) & ~@as(u64, 0xFFF);
if (phdr.p_type == elf.PT_LOAD and end > imageAddrMax) { if (phdr.p_type == elf.PT_LOAD and end > image_addr_max) {
imageAddrMax = end; 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); _ = 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])) { 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; break;
} }
} }
@@ -140,32 +141,42 @@ fn build_riscv64(b: *std.Build) anyerror!void {
} }
pub fn build(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 arch = maybe_arch_option orelse DEFAULT_ARCH;
const target = arch.makeTarget(b); const target = arch.make_target(b);
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast }); 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, .riscv64 => .medium,
.aarch64 => .small, .aarch64 => .small,
}; };
const kernelModule = b.addModule("kernel", .{ const kernel_module = b.addModule("kernel", .{
.optimize = optimize, .optimize = optimize,
.target = target, .target = target,
.pic = true, .pic = true,
.red_zone = false, .red_zone = false,
.code_model = codeModel, .code_model = code_model,
.root_source_file = b.path("src/kernel.zig"), .root_source_file = b.path("src/kernel.zig"),
}); });
const kernel = b.addExecutable(.{ const kernel = b.addExecutable(.{
.name = "kernel", .name = "kernel",
.root_module = kernelModule, .root_module = kernel_module,
.pic = true, .pic = true,
.use_lld = true,
}); });
kernel.pie = true; kernel.pie = true;
const kernelStep = try arch.addTargetSpecific(b, kernel); const install_docs = b.addInstallDirectory(.{
.source_dir = kernel.getEmittedDocs(),
.install_dir = .prefix,
.install_subdir = "docs",
});
const docs_step = b.step("docs", "Install documentation");
docs_step.dependOn(&install_docs.step);
const kernel_step = try arch.add_target_specific(b, kernel);
// TODO QEMU binary override // TODO QEMU binary override
const qemu_info = switch (target.result.cpu.arch) { const qemu_info = switch (target.result.cpu.arch) {
@@ -194,7 +205,7 @@ pub fn build(b: *std.Build) anyerror!void {
qemu_cmd.addArgs(&.{ "-bios", "etc/boot/rv64_fw_jump.bin" }); 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); if (b.args) |args| qemu_cmd.addArgs(args);
const run_step = b.step("run", "Start the OS in qemu"); const run_step = b.step("run", "Start the OS in qemu");
run_step.dependOn(&qemu_cmd.step); run_step.dependOn(&qemu_cmd.step);
+46 -11
View File
@@ -1,15 +1,50 @@
pub fn arch() type { //! Helper module to select architecture-specific modules depending on what platform is
//! being targeted.
const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
switch (comptime builtin.cpu.arch) { pub const impl = switch (builtin.cpu.arch) {
.riscv64 => { .riscv64 => @import("arch/riscv64.zig"),
return @import("arch/riscv64.zig"); .aarch64 => @import("arch/aarch64.zig"),
}, else => @compileError("Unsupported architecture"),
.aarch64 => { };
return @import("arch/aarch64.zig");
}, /// Halts the CPU execution indefinitely, without ever returning.
else => { pub inline fn halt() noreturn {
@panic("Architecture is not supported"); 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;
+27 -21
View File
@@ -1,37 +1,43 @@
const std = @import("std"); const std = @import("std");
const boot = @import("aarch64/boot.zig"); const boot = @import("aarch64/boot.zig");
const regs = @import("aarch64/regs.zig");
export const _ = boot.aa64BspLowerEntry; export const _ = boot.aa64_bsp_lower_entry;
pub const Context = struct { pub const Context = @import("aarch64/context.zig").Context;
pub fn idle() Context {
@panic("TODO"); 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 });
}
return old;
} }
pub fn kernel(pc: usize, arg: usize) Context { pub inline fn interrupt_mask() bool {
_ = pc; return regs.DAIF.read().I;
_ = arg;
@panic("TODO");
} }
pub fn enter(self: *Context) noreturn { pub inline fn wait_for_interrupt() void {
_ = self; asm volatile ("wfi");
@panic("TODO");
} }
pub fn switchFrom(self: *Context, from: *Context) void {
_ = self;
_ = from;
@panic("TODO");
}
};
pub fn halt() noreturn { pub fn halt() noreturn {
while (true) {} while (true) {
_ = set_interrupt_mask(true);
wait_for_interrupt();
}
} }
pub fn spinHint() void { pub fn spin_hint() void {
// TODO 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 { pub inline fn barrier(comptime kind: std.builtin.AtomicOrder) void {
+80 -32
View File
@@ -1,19 +1,24 @@
const kernel = @import("../../kernel.zig"); const kernel = @import("../../kernel.zig");
const vmm = @import("vmm.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 arch = kernel.arch;
const mem = kernel.mem;
const log = kernel.debug.log; const log = kernel.debug.log;
const phys_memory = mem.phys;
extern const __aa64_bsp_stack_top: u8; 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; const address = 0x9000000;
@as(*volatile u32, @ptrFromInt(address)).* = byte; @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); const p = @intFromPtr(ptr);
if (p >= vmm.KERNEL_VIRTUAL_BASE) { if (p >= vmm.KERNEL_VIRTUAL_BASE) {
return p; return p;
@@ -22,50 +27,69 @@ fn relocAddressToUpper(ptr: *const anyopaque) usize {
} }
} }
fn aa64BspUpperEntry(realAddress: u64) callconv(.C) noreturn { fn reloc_address_to_lower(ptr: *const anyopaque) usize {
// Relocate the kernel yet again const p = @intFromPtr(ptr);
const relaStart = relocAddressToUpper(&__rela_start); if (p >= vmm.KERNEL_VIRTUAL_BASE) {
const relaEnd = relocAddressToUpper(&__rela_end); return p - vmm.KERNEL_VIRTUAL_BASE;
const relOffset = vmm.KERNEL_VIRTUAL_BASE + realAddress; } else {
return p;
arch.barrier(.acq_rel); }
aa64RelocateKernel(relOffset, relaStart, relaEnd);
arch.barrier(.acq_rel);
log.setWriteFn(&earlyDebugPrint);
log.info("Hello, dtb is at 0x{x}", .{ gDtbAddress });
arch.halt();
} }
pub export fn aa64BspLowerEntry(realAddress: u64, dtbAddress: u64) callconv(.C) noreturn { fn aa64_bsp_upper_entry(real_address: u64) callconv(.C) noreturn {
gDtbAddress = dtbAddress; // Relocate the kernel yet again
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;
vmm.mapEarly(realAddress); arch.barrier(.acq_rel);
aa64_relocate_kernel(rel_offset, rela_start, rela_end);
arch.barrier(.acq_rel);
const pc = @intFromPtr(&aa64BspUpperEntry) + vmm.KERNEL_VIRTUAL_BASE; log.set_write_fn(&early_debug_print);
exception.init();
mem.PhysicalAddress.g_virtualize_base = 0;
mem.PhysicalAddress.g_virtualize_size = 16 << 30;
setup_memory_from_fdt(real_address);
setup_per_cpu();
kernel.kernel_main();
}
pub export fn aa64_bsp_lower_entry(real_address: u64, dtb_address: u64) callconv(.C) noreturn {
g_dtb_address = dtb_address;
vmm.map_early(real_address);
const pc = @intFromPtr(&aa64_bsp_upper_entry) + vmm.KERNEL_VIRTUAL_BASE;
const sp = @intFromPtr(&__aa64_bsp_stack_top) + 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 // Functions used by the boot process
extern const __rela_start: u8; extern const __rela_start: u8;
extern const __rela_end: u8; 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 elf = @import("std").elf;
const relaTablePtr = @as([*]elf.Rela, @ptrFromInt(relaStart)); const rela_table_ptr = @as([*]elf.Rela, @ptrFromInt(rela_start));
const relaCount = (relaEnd - relaStart) / @sizeOf(elf.Rela); const rela_count = (rela_end - rela_start) / @sizeOf(elf.Rela);
const relaTable = relaTablePtr[0..relaCount]; const rela_table = rela_table_ptr[0..rela_count];
for (relaTable) |entry| { for (rela_table) |entry| {
const relaType: elf.R_AARCH64 = @enumFromInt(entry.r_type()); const rela_type: elf.R_AARCH64 = @enumFromInt(entry.r_type());
switch (relaType) { switch (rela_type) {
.RELATIVE => { .RELATIVE => {
const value = @as(*isize, @ptrFromInt(imageBase + entry.r_offset)); const value = @as(*isize, @ptrFromInt(image_base + entry.r_offset));
value.* = @as(isize, @bitCast(imageBase)) + entry.r_addend; value.* = @as(isize, @bitCast(image_base)) + entry.r_addend;
}, },
else => { else => {
arch.halt(); arch.halt();
@@ -74,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 ( asm volatile (
\\ mov sp, %[sp] \\ mov sp, %[sp]
\\ br %[pc] \\ br %[pc]
@@ -86,3 +110,27 @@ inline fn longJump(pc: usize, sp: usize, a0: usize) noreturn {
); );
unreachable; unreachable;
} }
fn setup_memory_from_fdt(real_address: usize) void {
_ = real_address;
const kernel_start = reloc_address_to_lower(&__kernel_start); // 0
const kernel_end = reloc_address_to_lower(&__kernel_end); // whatever
const fdt = dtb.Fdt.from_physical_address(.{ .raw = g_dtb_address }) catch |err| {
log.panic("Cannot initialize raw DTB: {}", .{err});
};
fdt.add_physical_memory_to_system();
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));
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);
}
+58
View File
@@ -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
+55
View File
@@ -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"));
}
+2 -2
View File
@@ -41,11 +41,11 @@ __aa64_entry:
mov x0, x19 mov x0, x19
adr x1, __rela_start adr x1, __rela_start
adr x2, __rela_end adr x2, __rela_end
bl aa64RelocateKernel bl aa64_relocate_kernel
mov x0, x19 mov x0, x19
mov x1, x20 mov x1, x20
b aa64BspLowerEntry b aa64_bsp_lower_entry
.parking_lot: .parking_lot:
wfe wfe
+108
View File
@@ -0,0 +1,108 @@
const regs = @import("regs.zig");
const kernel = @import("../../kernel.zig");
const arch = kernel.arch;
const log = kernel.debug.log;
extern const __aa64_exception_vectors: u8;
pub const ExceptionFrame = extern struct {
xN: [32]usize,
pub fn dump(self: *const ExceptionFrame, comptime level: log.Level) void {
for (0..16) |i| {
log.writeln(level, " x{:<2} = 0x{x:016} x{:<2} = 0x{x:016}", .{
i * 2,
self.xN[i * 2],
i * 2 + 1,
self.xN[i * 2 + 1],
});
}
}
};
pub fn init() void {
const vbar_el1 = @intFromPtr(&__aa64_exception_vectors);
regs.VBAR_EL1.set(vbar_el1);
}
fn common_irq_handler(frame: *ExceptionFrame) void {
_ = frame;
@panic("TODO: IRQ");
}
// EL1
// General exceptions
export fn __aa64_el1_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
const esr = regs.ESR_EL1.read();
const far = regs.FAR_EL1.get();
const elr = regs.ELR_EL1.get();
log.err("Exception in EL1:", .{});
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.as_enum()) {
.data_abort => |abort| {
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}", .{ access_type_str, access_size_str, fault_kind_str });
if (!abort.FnV) {
log.err(" FAR = 0x{x:016}", .{far});
} else {
log.err(" (FAR is not valid)", .{});
}
},
else => {},
}
frame.dump(log.Level.err);
arch.halt();
}
// IRQ
export fn __aa64_el1_irq_handler(frame: *ExceptionFrame) callconv(.C) void {
common_irq_handler(frame);
}
export fn __aa64_el1_fiq_handler(frame: *ExceptionFrame) callconv(.C) void {
_ = frame;
// TODO I've never used FIQ
arch.halt();
}
export fn __aa64_el1_serror_handler(frame: *ExceptionFrame) callconv(.C) void {
_ = frame;
// TODO
arch.halt();
}
// EL0
export fn __aa64_el0_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
// TODO EL0
_ = frame;
arch.halt();
}
export fn __aa64_el0_irq_handler(frame: *ExceptionFrame) callconv(.C) void {
common_irq_handler(frame);
}
export fn __aa64_el0_fiq_handler(frame: *ExceptionFrame) callconv(.C) void {
_ = frame;
// TODO I've never used FIQ
arch.halt();
}
export fn __aa64_el0_serror_handler(frame: *ExceptionFrame) callconv(.C) void {
_ = frame;
// TODO
arch.halt();
}
comptime {
asm (@embedFile("vectors.S"));
}
+122 -19
View File
@@ -1,3 +1,5 @@
const std = @import("std");
fn Register(comptime name: []const u8, comptime bits: type) type { fn Register(comptime name: []const u8, comptime bits: type) type {
const repr = switch (@typeInfo(bits)) { const repr = switch (@typeInfo(bits)) {
.@"struct" => |s| s.backing_integer.?, .@"struct" => |s| s.backing_integer.?,
@@ -5,11 +7,16 @@ fn Register(comptime name: []const u8, comptime bits: type) type {
}; };
return enum(repr) { return enum(repr) {
pub fn set(value: repr) void { pub fn set(value: repr) void {
asm volatile ("msr " ++ name ++ ", %[value]"::[value]"r"(value)); asm volatile ("msr " ++ name ++ ", %[value]"
:
: [value] "r" (value),
);
} }
pub fn get() repr { pub fn get() repr {
return asm volatile ("mrs %[value], " ++ name:[value]"=r"(-> repr)); return asm volatile ("mrs %[value], " ++ name
: [value] "=r" (-> repr),
);
} }
pub fn write(value: bits) void { pub fn write(value: bits) void {
@@ -30,6 +37,116 @@ fn Register(comptime name: []const u8, comptime bits: type) type {
pub const TTBR0_EL1 = Register("ttbr0_el1", u64); pub const TTBR0_EL1 = Register("ttbr0_el1", u64);
pub const TTBR1_EL1 = Register("ttbr1_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);
pub const ESR_EL1 = Register("esr_el1", packed struct(u64) {
// 0..25
ISS: u25 = 0,
// 25
IL: bool = false,
// 26..32
EC: enum(u6) {
unknown = 0b000000,
data_abort_lower_el = 0b100100,
data_abort_same_el = 0b100101,
sp_align = 0b100110,
_,
pub fn as_str(self: @This()) []const u8 {
return std.enums.tagName(@This(), self) orelse "<unknown>";
}
} = .unknown,
// 32..64
_0: u32 = 0,
// Specific variants of ESR
pub const DataAbort = packed struct(u25) {
// 0..6
DFSC: enum(u6) {
address_size_fault_l0 = 0b000000,
address_size_fault_l1 = 0b000001,
address_size_fault_l2 = 0b000010,
address_size_fault_l3 = 0b000011,
translation_fault_l0 = 0b000100,
translation_fault_l1 = 0b000101,
translation_fault_l2 = 0b000110,
translation_fault_l3 = 0b000111,
access_flag_fault_l1 = 0b001001,
access_flag_fault_l2 = 0b001010,
access_flag_fault_l3 = 0b001011,
permission_fault_l1 = 0b001101,
permission_fault_l2 = 0b001110,
permission_fault_l3 = 0b001111,
_,
pub fn as_str(self: @This()) []const u8 {
return std.enums.tagName(@This(), self) orelse "<other>";
}
} = .address_size_fault_l0,
// 6
WnR: bool = false,
// 7
S1PTW: bool = false,
// 8
CM: bool = false,
// 9
EA: bool = false,
// 10
FnV: bool = false,
// 11..14
_0: u3 = 0,
// 14
AR: bool = false,
// 15
SF: bool = false,
// 16..21
SRT: u5 = 0,
// 21
SSE: bool = false,
// 22..24
SAS: enum(u2) {
byte = 0,
half = 1,
word = 2,
dword = 3,
} = .byte,
// 24
ISV: bool = false,
};
pub const AsEnum = union(enum) {
data_abort: DataAbort,
other,
};
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,
}
}
});
pub const Cacheability = enum(u2) { pub const Cacheability = enum(u2) {
non_cacheable = 0, non_cacheable = 0,
writeback_readalloc_writealloc_cacheable = 1, writeback_readalloc_writealloc_cacheable = 1,
@@ -37,19 +154,9 @@ pub const Cacheability = enum(u2) {
writeback_readalloc_nowritealloc_cacheable = 3, writeback_readalloc_nowritealloc_cacheable = 3,
}; };
pub const Shareability = enum(u2) { pub const Shareability = enum(u2) { non_shareable = 0, outer_shareable = 1, inner_shareable = 2, _ };
non_shareable = 0,
outer_shareable = 1,
inner_shareable = 2,
_
};
pub const TranslationGranule = enum(u2) { pub const TranslationGranule = enum(u2) { kib_4 = 0, kib_64 = 1, kib_16 = 2, _ };
kib_4 = 0,
kib_64 = 1,
kib_16 = 2,
_
};
pub const TCR_EL1 = Register("tcr_el1", packed struct(u64) { pub const TCR_EL1 = Register("tcr_el1", packed struct(u64) {
// 0..6 // 0..6
@@ -84,11 +191,7 @@ pub const TCR_EL1 = Register("tcr_el1", packed struct(u64) {
// 30..32 // 30..32
TG1: TranslationGranule = .kib_4, TG1: TranslationGranule = .kib_4,
// 32..35 // 32..35
IPS: enum(u3) { IPS: enum(u3) { bits_32 = 0b000, bits_48 = 0b101, _ } = .bits_32,
bits_32 = 0b000,
bits_48 = 0b101,
_
} = .bits_32,
// 35 // 35
_1: bool = false, _1: bool = false,
// 36 // 36
+96
View File
@@ -0,0 +1,96 @@
.global __aa64_exception_vectors
// 32 general-purpose registers
.set EXC_GP_SIZE, (32 * 8)
.set EXC_STATE_SIZE, (EXC_GP_SIZE)
.macro EXC_SAVE_STATE
sub sp, sp, #EXC_STATE_SIZE
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
stp x30, x31, [sp, #16 * 15]
.endm
// Exception vector size is 0x80
.macro EXC_VECTOR el, ht, bits, kind
.p2align 7
b __aa\bits\()_el\el\ht\()_\kind
.endm
.macro EXC_HANDLER el, ht, bits, kind
.type __aa\bits\()_el\el\ht\()_\kind, %function
__aa\bits\()_el\el\ht\()_\kind:
.if \bits == 32
// TODO taking exceptions from EL0t 32-bit
b .
.endif
EXC_SAVE_STATE
mov x0, sp
mov lr, xzr
bl __aa64_el\el\()_\kind\()_handler
// TODO exception return
b .
.size __aa\bits\()_el\el\ht\()_\kind, . - __aa\bits\()_el\el\ht\()_\kind
.endm
.pushsection .text
.p2align 12
.type __aa64_exception_vectors, %object
__aa64_exception_vectors:
EXC_VECTOR 1, t, 64, sync
EXC_VECTOR 1, t, 64, irq
EXC_VECTOR 1, t, 64, fiq
EXC_VECTOR 1, t, 64, serror
EXC_VECTOR 1, h, 64, sync
EXC_VECTOR 1, h, 64, irq
EXC_VECTOR 1, h, 64, fiq
EXC_VECTOR 1, h, 64, serror
EXC_VECTOR 0, t, 64, sync
EXC_VECTOR 0, t, 64, irq
EXC_VECTOR 0, t, 64, fiq
EXC_VECTOR 0, t, 64, serror
EXC_VECTOR 0, t, 32, sync
EXC_VECTOR 0, t, 32, irq
EXC_VECTOR 0, t, 32, fiq
EXC_VECTOR 0, t, 32, serror
.size __aa64_exception_vectors, . - __aa64_exception_vectors
.p2align 7
EXC_HANDLER 1, t, 64, sync
EXC_HANDLER 1, t, 64, irq
EXC_HANDLER 1, t, 64, fiq
EXC_HANDLER 1, t, 64, serror
EXC_HANDLER 1, h, 64, sync
EXC_HANDLER 1, h, 64, irq
EXC_HANDLER 1, h, 64, fiq
EXC_HANDLER 1, h, 64, serror
EXC_HANDLER 0, t, 64, sync
EXC_HANDLER 0, t, 64, irq
EXC_HANDLER 0, t, 64, fiq
EXC_HANDLER 0, t, 64, serror
EXC_HANDLER 0, t, 32, sync
EXC_HANDLER 0, t, 32, irq
EXC_HANDLER 0, t, 32, fiq
EXC_HANDLER 0, t, 32, serror
.popsection // .text
+13 -13
View File
@@ -47,7 +47,7 @@ pub const RawEntry = packed struct(u64) {
// 55..64 // 55..64
_2: u9 = 0, _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 lhs = @as(u64, @bitCast(self));
const rhs = @as(u64, @bitCast(other)); const rhs = @as(u64, @bitCast(other));
return @as(@This(), @bitCast(lhs | rhs)); 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() { pub fn normal_page(addr: PhysicalAddress, flags: RawEntry) @This() {
return .{ return .{
.raw = flags.makeUnion(RawEntry{ .raw = flags.make_union(RawEntry{
.PPN = @as(u36, @intCast(addr.raw >> 12)), .PPN = @as(u36, @intCast(addr.raw >> 12)),
.V = true, .V = true,
.P = true, .P = true,
@@ -94,7 +94,7 @@ pub fn TableEntry(comptime Level: type) type {
pub fn device_page(addr: PhysicalAddress, flags: RawEntry) @This() { pub fn device_page(addr: PhysicalAddress, flags: RawEntry) @This() {
return .{ return .{
.raw = flags.makeUnion(RawEntry{ .raw = flags.make_union(RawEntry{
.PPN = @as(u36, @intCast(addr.raw >> 12)), .PPN = @as(u36, @intCast(addr.raw >> 12)),
.V = true, .V = true,
.P = true, .P = true,
@@ -109,7 +109,7 @@ pub fn TableEntry(comptime Level: type) type {
pub fn normal_block(addr: PhysicalAddress, flags: RawEntry) @This() { pub fn normal_block(addr: PhysicalAddress, flags: RawEntry) @This() {
return .{ return .{
.raw = flags.makeUnion(RawEntry{ .raw = flags.make_union(RawEntry{
.PPN = @as(u36, @intCast(addr.raw >> 12)), .PPN = @as(u36, @intCast(addr.raw >> 12)),
.V = true, .V = true,
.AF = true, .AF = true,
@@ -121,7 +121,7 @@ pub fn TableEntry(comptime Level: type) type {
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() { pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
return .{ return .{
.raw = flags.makeUnion(.{ .raw = flags.make_union(.{
.PPN = @as(u36, @intCast(addr.raw >> 12)), .PPN = @as(u36, @intCast(addr.raw >> 12)),
.V = true, .V = true,
.P = true, .P = true,
@@ -144,16 +144,16 @@ pub fn Table(comptime Level: type) type {
} }
// 0x0000_0000_0000_0000 .. 0x0000_0080_0000_0000 // 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 // 0xFFFF_FF80_0000_0000 .. 0xFFFF_FFFF_FFFF_FFFF
var gFixedHigh = Table(L1){}; var g_fixed_high = Table(L1){};
pub fn mapEarly(realAddress: usize) void { pub fn map_early(real_address: usize) void {
_ = realAddress; _ = real_address;
for (0..16) |i| { for (0..16) |i| {
// Identity // Identity
gFixedLow.entry(i).* = TableEntry(L1).normal_block( g_fixed_low.entry(i).* = TableEntry(L1).normal_block(
.{ .raw = i << L1.SHIFT }, .{ .raw = i << L1.SHIFT },
.{}, .{},
); );
@@ -161,14 +161,14 @@ pub fn mapEarly(realAddress: usize) void {
for (0..16) |i| { for (0..16) |i| {
// Identity + KERNEL_VIRTUAL_BASE // Identity + KERNEL_VIRTUAL_BASE
gFixedHigh.entry(i).* = TableEntry(L1).normal_block( g_fixed_high.entry(i).* = TableEntry(L1).normal_block(
.{ .raw = i << L1.SHIFT }, .{ .raw = i << L1.SHIFT },
.{}, .{},
); );
} }
const ttbr0 = @intFromPtr(&gFixedLow); const ttbr0 = @intFromPtr(&g_fixed_low);
const ttbr1 = @intFromPtr(&gFixedHigh); const ttbr1 = @intFromPtr(&g_fixed_high);
regs.TTBR0_EL1.set(ttbr0); regs.TTBR0_EL1.set(ttbr0);
regs.TTBR1_EL1.set(ttbr1); regs.TTBR1_EL1.set(ttbr1);
+15 -65
View File
@@ -1,77 +1,27 @@
//! RISC-V 64-bit platform-specific implementations.
const boot = @import("riscv64/boot.zig"); const boot = @import("riscv64/boot.zig");
const regs = @import("riscv64/regs.zig"); const regs = @import("riscv64/regs.zig");
const thread = @import("../thread.zig");
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const Arena = @import("../arena.zig").Arena; export const _ = boot.rv64_bsp_lower_entry;
export const _ = boot.rv64BspLowerEntry; /// This CPU's HART (HARdware Thread) ID.
pub threadlocal var t_hart_id: u32 = 0;
extern fn __rv64_enter_task(cx: *Context) callconv(.C) noreturn; /// RISC-V task context
extern fn __rv64_switch_task(dcx: *Context, scx: *Context) callconv(.C) void; pub const Context = @import("riscv64/context.zig").Context;
extern fn __rv64_task_enter_kernel() callconv(.C) noreturn;
fn idleFunction() callconv(.naked) noreturn {
asm volatile ("j .");
}
pub threadlocal var tHartId: u32 = 0;
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),
pub fn idle() @This() {
const entry = @intFromPtr(&idleFunction);
return Context.kernel(entry, 0);
}
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 };
}
pub fn enter(self: *@This()) noreturn {
__rv64_enter_task(self);
}
pub fn switchFrom(self: *@This(), from: *@This()) void {
__rv64_switch_task(self, from);
}
};
pub inline fn halt() noreturn { pub inline fn halt() noreturn {
while (true) { while (true) {
_ = setInterruptMask(true); _ = set_interrupt_mask(true);
waitForInterrupt(); wait_for_interrupt();
} }
} }
pub inline fn setInterruptMask(mask: bool) bool { pub inline fn set_interrupt_mask(mask: bool) bool {
const old = interruptMask(); const old = interrupt_mask();
if (mask) { if (mask) {
regs.SSTATUS.modify(.{}, .{ .SIE = true }); regs.SSTATUS.modify(.{}, .{ .SIE = true });
} else { } else {
@@ -80,15 +30,15 @@ pub inline fn setInterruptMask(mask: bool) bool {
return old; return old;
} }
pub fn interruptMask() bool { pub fn interrupt_mask() bool {
return regs.SSTATUS.read().SIE; return regs.SSTATUS.read().SIE;
} }
pub inline fn waitForInterrupt() void { pub inline fn wait_for_interrupt() void {
asm volatile ("wfi"); asm volatile ("wfi");
} }
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 // Don't want to explicitly enable Zihintpause ext, so just paste this as raw opcode
asm volatile (".word 0x0100000f"); asm volatile (".word 0x0100000f");
} }
@@ -109,7 +59,7 @@ pub inline fn barrier(comptime ordering: std.builtin.AtomicOrder) void {
asm volatile ("" ::: "memory"); asm volatile ("" ::: "memory");
} }
pub inline fn setThreadPointer(tp: usize) void { pub inline fn set_thread_pointer(tp: usize) void {
asm volatile ("mv tp, %[tp]" asm volatile ("mv tp, %[tp]"
: :
: [tp] "r" (tp), : [tp] "r" (tp),
+57 -91
View File
@@ -7,68 +7,68 @@ const dtb = @import("../../util/dtb.zig");
const mem = @import("../../mem.zig"); const mem = @import("../../mem.zig");
const arena = @import("../../arena.zig"); const arena = @import("../../arena.zig");
const exception = @import("exception.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 PAGE_SIZE = mem.vmm.PAGE_SIZE;
const log = debug.log; const log = debug.log;
const arch = kernel.arch; const arch = kernel.arch;
extern const __rv64_bsp_stack_top: u8; extern const __rv64_bsp_stack_top: u8;
extern const __kernel_start: u8;
extern const __kernel_end: u8;
var gDtbAddress: usize = 0; var g_dtb_address: usize = 0;
var gBspHartId: u32 = 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; _ = unused;
arch.barrier(.acq_rel); arch.barrier(.acq_rel);
// Relocate the kernel yet again, this time to another base // Relocate the kernel yet again, this time to another base
const relaStart = @intFromPtr(&__rela_start); const rela_start = @intFromPtr(&__rela_start);
const relaEnd = @intFromPtr(&__rela_end); const rela_end = @intFromPtr(&__rela_end);
const relOffset = vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(realAddress); const rel_offset = vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(real_address);
arch.barrier(.acq_rel); arch.barrier(.acq_rel);
rv64RelocateKernel(relOffset, relaStart, relaEnd); rv64_relocate_kernel(rel_offset, rela_start, rela_end);
vmm.unmapEarly(); vmm.unmap_early();
// Setup exception handling // Setup exception handling
exception.init(); exception.init();
debug.log.setWriteFn(&sbi.debugPrintByte); debug.log.set_write_fn(&sbi.debug_print_byte);
kernel.mem.PhysicalAddress.gVirtualizeBase = 0; kernel.mem.PhysicalAddress.g_virtualize_base = 0;
kernel.mem.PhysicalAddress.gVirtualizeSize = vmm.virtualizeRange(); kernel.mem.PhysicalAddress.g_virtualize_size = vmm.virtualize_range();
// Setup physical memory management // Setup physical memory management
setupMemoryFromFdt(realAddress); setup_memory_from_fdt(real_address);
setupPerCpu(); setup_per_cpu();
arch.tHartId = gBspHartId; arch.impl.t_hart_id = g_bsp_hart_id;
kernel.kernel_main(); kernel.kernel_main();
} }
pub export fn rv64BspLowerEntry(realAddress: usize, bspHartId: usize, dtbAddress: usize) callconv(.C) noreturn { pub export fn rv64_bsp_lower_entry(real_address: usize, bsp_hart_id: usize, dtb_address: usize) callconv(.C) noreturn {
debug.log.setWriteFn(&sbi.debugPrintByte); debug.log.set_write_fn(&sbi.debug_print_byte);
gDtbAddress = dtbAddress; g_dtb_address = dtb_address;
gBspHartId = @truncate(bspHartId); 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 // &bspUpperEntry will yield a pointer like: X + P, where
// * X is symbol's raw address, // * X is symbol's raw address,
// * P is the physical load base of the image (0x80200000 on rv64 usually) // * 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 // Relocate the address to point to Y + P, where Y is the virtual load base
// const kernelL1Offset = realAddress & ((1 << 30) - 1); const real_address_l1_offset = vmm.L1.offset(real_address);
const realAddressL1Offset = vmm.L1.offset(realAddress); const virtual_entry = @intFromPtr(&bsp_upper_entry) + vmm.KERNEL_VIRTUAL_BASE //
const virtualEntry = @intFromPtr(&bspUpperEntry) + vmm.KERNEL_VIRTUAL_BASE - realAddress + realAddressL1Offset; - real_address + real_address_l1_offset;
const virtualSp = @intFromPtr(&__rv64_bsp_stack_top) + vmm.KERNEL_VIRTUAL_BASE - realAddress + realAddressL1Offset; 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(); arch.halt();
} }
@@ -77,48 +77,28 @@ pub export fn rv64BspLowerEntry(realAddress: usize, bspHartId: usize, dtbAddress
extern const __rela_start: u8; extern const __rela_start: u8;
extern const __rela_end: u8; extern const __rela_end: u8;
extern var __tdata_start: u8; extern var __kernel_start: u8;
extern var __tdata_end: u8; extern var __kernel_end: u8;
extern var __tbss_start: u8;
extern var __tbss_end: u8;
fn setupPerCpu() void { fn setup_per_cpu() void {
// Assume .tbss follows .tdata const tls_data = tls.load_kernel_tls_image();
const tdataStart = @intFromPtr(&__tdata_start); const tp = @intFromPtr(tls_data.ptr);
const tdataEnd = @intFromPtr(&__tdata_end); log.info("Set TP = 0x{x}", .{tp});
const tdataSize = tdataEnd - tdataStart; arch.set_thread_pointer(tp);
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);
} }
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 elf = @import("std").elf;
const relaTablePtr = @as([*]elf.Rela, @ptrFromInt(relaStart)); const rela_table_ptr = @as([*]elf.Rela, @ptrFromInt(rela_start));
const relaCount = (relaEnd - relaStart) / @sizeOf(elf.Rela); const rela_count = (rela_end - rela_start) / @sizeOf(elf.Rela);
const relaTable = relaTablePtr[0..relaCount]; const rela_table = rela_table_ptr[0..rela_count];
for (relaTable) |entry| { for (rela_table) |entry| {
const relaType: elf.R_RISCV = @enumFromInt(entry.r_type()); const rela_type: elf.R_RISCV = @enumFromInt(entry.r_type());
switch (relaType) { switch (rela_type) {
.RELATIVE => { .RELATIVE => {
const value = @as(*isize, @ptrFromInt(imageBase + entry.r_offset)); const value = @as(*isize, @ptrFromInt(image_base + entry.r_offset));
value.* = @as(isize, @bitCast(imageBase)) + entry.r_addend; value.* = @as(isize, @bitCast(image_base)) + entry.r_addend;
}, },
else => { else => {
arch.halt(); arch.halt();
@@ -127,41 +107,27 @@ export fn rv64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: usize)
} }
} }
fn setupMemoryFromFdt(realAddress: usize) void { fn setup_memory_from_fdt(real_address: usize) void {
const kernelStart = @intFromPtr(&__kernel_start); const kernel_start = @intFromPtr(&__kernel_start);
const kernelEnd = @intFromPtr(&__kernel_end); const kernel_end = @intFromPtr(&__kernel_end);
var cells: [2]u64 = undefined; const fdt = dtb.Fdt.from_physical_address(.{ .raw = g_dtb_address }) catch |err| {
const fdt = dtb.Fdt.fromPhysicalAddress(.{ .raw = gDtbAddress }) catch |err| {
log.panic("Cannot initialize raw DTB: {}", .{err}); log.panic("Cannot initialize raw DTB: {}", .{err});
}; };
fdt.add_physical_memory_to_system();
var memoryRegions = fdt.memoryRegionIterator(); 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));
while (memoryRegions.next()) |region| { phys_memory.init();
physMemory.addMemoryRegion(region.name, region.base, region.size);
}
const reservedRegions = fdt.rootNode().child("reserved-memory");
if (reservedRegions) |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]);
}
}
}
} }
physMemory.addReservedRegion("kernel", kernelStart - (vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(realAddress)) + realAddress, kernelEnd - kernelStart); inline fn long_jump(pc: usize, sp: usize, a0: usize, a1: usize) noreturn {
physMemory.addReservedRegion("fdt", gDtbAddress, vmm.L3.align_up(fdt.bytes.len));
physMemory.init();
}
inline fn longJump(pc: usize, sp: usize, a0: usize, a1: usize) noreturn {
asm volatile ( asm volatile (
\\ mv sp, %[sp] \\ mv sp, %[sp]
\\ jr %[pc] \\ jr %[pc]
+67
View File
@@ -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
+62
View File
@@ -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"));
}
+6 -74
View File
@@ -1,5 +1,5 @@
.set ENTRY_SYMBOL, rv64BspLowerEntry .set ENTRY_SYMBOL, rv64_bsp_lower_entry
.set RELOC_SYMBOL, rv64RelocateKernel .set RELOC_SYMBOL, rv64_relocate_kernel
.global __rv64_entry .global __rv64_entry
.global __rv64_bsp_stack_top .global __rv64_bsp_stack_top
@@ -25,7 +25,7 @@ __rv64_entry:
.ascii "RISCV\x00\x00\x00" // Magic 1 .ascii "RISCV\x00\x00\x00" // Magic 1
.ascii "RSC\x05" // Magic 2 .ascii "RSC\x05" // Magic 2
.long 0 .long 0
.option pop .option pop // rvc
.option push .option push
.option norvc .option norvc
@@ -80,80 +80,12 @@ __rv64_real_entry:
jr t0 jr t0
.size __rv64_entry, . - __rv64_entry .size __rv64_entry, . - __rv64_entry
.option pop .option pop // norvc
.popsection .popsection // .text.entry
.pushsection .bss .pushsection .bss
.p2align 4 .p2align 4
__rv64_bsp_stack_bottom: __rv64_bsp_stack_bottom:
.skip 65536 .skip 65536
__rv64_bsp_stack_top: __rv64_bsp_stack_top:
.popsection .popsection // .bss
.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
+7 -8
View File
@@ -27,7 +27,7 @@ const SbiResult = union(enum) {
ok: u64, ok: u64,
err: SbiError, err: SbiError,
fn fromSbi(a0: u64, a1: u64) SbiResult { fn from_sbi(a0: u64, a1: u64) SbiResult {
if (a0 == 0) { if (a0 == 0) {
return .{ .ok = a1 }; return .{ .ok = a1 };
} else { } else {
@@ -36,21 +36,20 @@ 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 a0: u64 = undefined;
var a1: u64 = undefined; var a1: u64 = undefined;
asm volatile ( asm volatile ("ecall"
"ecall"
: [ret0] "={a0}" (a0), : [ret0] "={a0}" (a0),
[ret1] "={a1}" (a1), [ret1] "={a1}" (a1),
: [arg0] "{a0}" (arg0), : [arg0] "{a0}" (arg0),
[func] "{a6}" (func), [func] "{a6}" (func),
[extn] "{a7}" (ext) [extn] "{a7}" (ext),
: "a2", "a3", "a4", "a5" : "a2", "a3", "a4", "a5"
); );
return SbiResult.fromSbi(a0, a1); return SbiResult.from_sbi(a0, a1);
} }
pub fn debugPrintByte(byte: u8) void { pub fn debug_print_byte(byte: u8) void {
_ = sbiCall1(.dbcn, 0x02, @as(u64, byte)); _ = sbi_call1(.dbcn, 0x02, @as(u64, byte));
} }
+21 -15
View File
@@ -38,7 +38,7 @@ pub const RawEntry = packed struct(u64) {
// 49..64: Unused bits // 49..64: Unused bits
_pad1: u15 = 0, _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 lhs = @as(u64, @bitCast(self));
const rhs = @as(u64, @bitCast(other)); const rhs = @as(u64, @bitCast(other));
return @as(@This(), @bitCast(lhs | rhs)); 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() { pub fn page(addr: PhysicalAddress, flags: RawEntry) @This() {
return .{ return .{
.raw = flags.makeUnion(.{ .raw = flags.make_union(.{
.address = @as(u39, @intCast(addr.raw >> 12)), .address = @as(u39, @intCast(addr.raw >> 12)),
.r = true, .r = true,
.v = true, .v = true,
@@ -84,7 +84,7 @@ pub fn TableEntry(comptime Level: type) type {
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() { pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
flags.clear(.{ .r = true, .w = true, .x = true }); flags.clear(.{ .r = true, .w = true, .x = true });
return .{ .raw = flags.makeUnion(.{ return .{ .raw = flags.make_union(.{
.address = @as(u39, @intCast(addr.raw >> 12)), .address = @as(u39, @intCast(addr.raw >> 12)),
.v = true, .v = true,
}) }; }) };
@@ -108,37 +108,43 @@ pub fn Table(comptime Level: type) type {
}; };
} }
var gFixed = Table(L1).empty(); var g_fixed = Table(L1).empty();
var gFixedLock: sync.IrqSafeSpinlock = .{}; var g_fixed_lock: sync.Spinlock = .{};
pub fn virtualizeRange() usize { pub fn virtualize_range() usize {
return EARLY_MAPPING_SIZE * L1.SIZE; return EARLY_MAPPING_SIZE * L1.SIZE;
} }
pub fn unmapEarly() void { pub fn unmap_early() void {
// Make lower half mappings non-executable // Make lower half mappings non-executable
gFixedLock.lock(); const guard = g_fixed_lock.lock_irqsave();
defer gFixedLock.release(); defer guard.release();
for (0..EARLY_MAPPING_SIZE) |i| { for (0..EARLY_MAPPING_SIZE) |i| {
gFixed.entry(i).* = .page( g_fixed.entry(i).* = .page(
.{ .raw = L1.address(i) }, .{ .raw = L1.address(i) },
.{ .r = true, .w = true }, .{ .r = true, .w = true },
); );
} }
} }
pub fn mapEarly(realAddress: usize) void { pub fn map_early(real_address: usize) void {
const realL1 = L1.index(realAddress); const real_l1 = L1.index(real_address);
// Identity map first 16GiB of memory // Identity map first 16GiB of memory
for (0..EARLY_MAPPING_SIZE) |i| { 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 // 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 }); regs.SATP.write(.{ .PPN = @intCast(address >> 12), .MODE = .sv39 });
} }
+16 -9
View File
@@ -1,27 +1,34 @@
const physMemory = @import("mem/phys.zig"); //! Simple bump allocator arena.
const phys_memory = @import("mem/phys.zig");
const log = @import("debug.zig").log; const log = @import("debug.zig").log;
const mem = @import("mem.zig"); const mem = @import("mem.zig");
/// Bump allocator implementation.
pub const Arena = struct { pub const Arena = struct {
physBase: mem.PhysicalAddress, phys_base: mem.PhysicalAddress,
capacity: usize, capacity: usize,
len: usize, len: usize,
/// Creates a new `Arena` of given `cap`acity.
///
/// Requires initialized physical memory management.
pub fn init(cap: usize) ?Arena { pub fn init(cap: usize) ?Arena {
const physBase = physMemory.alloc_pages(cap / mem.vmm.PAGE_SIZE) orelse return null; const phys_base = phys_memory.alloc_pages(cap / mem.vmm.PAGE_SIZE) orelse return null;
return .{ return .{ .phys_base = phys_base, .capacity = cap, .len = 0 };
.physBase = physBase,
.capacity = cap,
.len = 0
};
} }
/// Allocates an object of type `T` within this arena.
///
/// # Panics
///
/// Panics if the arena runs out of memory.
pub fn create(self: *@This(), comptime T: type) *T { pub fn create(self: *@This(), comptime T: type) *T {
if (self.len + @sizeOf(T) > self.capacity) { if (self.len + @sizeOf(T) > self.capacity) {
log.panic("Out of memory. Cannot allocate {} bytes", .{@sizeOf(T)}); 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)); const ptr = @as(*T, @ptrFromInt(v));
self.len += @sizeOf(T); self.len += @sizeOf(T);
+31 -15
View File
@@ -1,73 +1,89 @@
//! Kernel debug output and logging functions.
const std = @import("std"); const std = @import("std");
fn dummyWrite(_: u8) void {} fn dummy_write(_: u8) void {}
/// The main method of kernel logging.
pub const log = struct { pub const log = struct {
/// Level to emit log records at.
pub const Level = enum { pub const Level = enum {
/// Debug.
debug, debug,
/// Info.
info, info,
/// Warning.
warn, warn,
/// Error/Fatal.
err, err,
}; };
var writeFn: *const fn(u8) void = dummyWrite; var write_fn: *const fn (u8) void = dummy_write;
const writer: std.io.GenericWriter(u0, error{}, writeWrapperFn) = .{ const writer: std.io.GenericWriter(u0, error{}, write_wrapper_fn) = .{ .context = 0 };
.context = 0
};
fn writeWrapperFn(context: u0, data: []const u8) error{}!usize { fn write_wrapper_fn(context: u0, data: []const u8) error{}!usize {
_ = context; _ = context;
for (data) |byte| { for (data) |byte| {
writeFn(byte); write_fn(byte);
} }
return data.len; return data.len;
} }
pub fn setWriteFn(f: *const fn(u8) void) void { /// Replaces the function to print debug bytes with a new one.
writeFn = f; pub fn set_write_fn(f: *const fn (u8) void) void {
write_fn = f;
} }
/// Emit an `info`-level log record.
pub inline fn info(comptime format: []const u8, args: anytype) void { pub inline fn info(comptime format: []const u8, args: anytype) void {
writeln(.info, format, args); writeln(.info, format, args);
} }
/// Emit a `debug`-level log record.
pub inline fn debug(comptime format: []const u8, args: anytype) void { pub inline fn debug(comptime format: []const u8, args: anytype) void {
writeln(.debug, format, args); writeln(.debug, format, args);
} }
/// Emit a `warn`-level log record.
pub inline fn warn(comptime format: []const u8, args: anytype) void { pub inline fn warn(comptime format: []const u8, args: anytype) void {
writeln(.warn, format, args); writeln(.warn, format, args);
} }
/// Emit a `err`-level log record.
pub inline fn err(comptime format: []const u8, args: anytype) void { pub inline fn err(comptime format: []const u8, args: anytype) void {
writeln(.err, format, args); writeln(.err, format, args);
} }
pub fn writeRaw(data: []const u8) void { /// Write raw byte data into the debugging output.
_ = 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.
pub fn write(comptime format: []const u8, args: anytype) void { pub fn write(comptime format: []const u8, args: anytype) void {
writer.print(format, args) catch return; writer.print(format, args) catch return;
} }
/// Write a formatted log record into the debugging output.
pub fn writeln(comptime level: Level, comptime format: []const u8, args: anytype) void { pub fn writeln(comptime level: Level, comptime format: []const u8, args: anytype) void {
const prefix = comptime logPrefix(level); const prefix = comptime log_prefix(level);
const suffix = comptime logSuffix(level); const suffix = comptime log_suffix(level);
writer.print(prefix ++ format ++ suffix ++ "\r\n", args) catch return; writer.print(prefix ++ format ++ suffix ++ "\r\n", args) catch return;
} }
/// Helper function to emit a `Not yet implemented` message and panic.
pub fn todo(comptime msg: []const u8, args: anytype) noreturn { pub fn todo(comptime msg: []const u8, args: anytype) noreturn {
err("Not yet implemented: " ++ msg, args); err("Not yet implemented: " ++ msg, args);
@panic("Not yet implemented"); @panic("Not yet implemented");
} }
/// Helper function to emit a `PANIC` message and panic.
pub fn panic(comptime msg: []const u8, args: anytype) noreturn { pub fn panic(comptime msg: []const u8, args: anytype) noreturn {
err("PANIC: " ++ msg, args); err("PANIC: " ++ msg, args);
@panic("Explicit kernel panic"); @panic("Explicit kernel panic");
} }
fn logPrefix(comptime level: Level) []const u8 { fn log_prefix(comptime level: Level) []const u8 {
switch (level) { switch (level) {
.debug => return "", .debug => return "",
.info => return "\x1B[1;36m", .info => return "\x1B[1;36m",
@@ -75,7 +91,7 @@ pub const log = struct {
.err => return "\x1B[1;31m", .err => return "\x1B[1;31m",
} }
} }
fn logSuffix(comptime level: Level) []const u8 { fn log_suffix(comptime level: Level) []const u8 {
if (level == .debug) { if (level == .debug) {
return ""; return "";
} else { } else {
+16 -3
View File
@@ -1,13 +1,17 @@
// export const _ = @import("arch/riscv64/boot.zig").rv64BspLowerEntry; //! zing microkernel.
pub const arch = @import("arch.zig").arch();
pub const arch = @import("arch.zig");
pub const mem = @import("mem.zig"); pub const mem = @import("mem.zig");
pub const debug = @import("debug.zig"); pub const debug = @import("debug.zig");
pub const arena = @import("arena.zig"); pub const arena = @import("arena.zig");
pub const thread = @import("thread.zig"); pub const thread = @import("thread.zig");
pub const util = @import("util.zig");
pub const sync = @import("sync.zig");
pub const log = debug.log; pub const log = debug.log;
pub const vmm = mem.vmm; pub const vmm = mem.vmm;
/// If set to `true`, will emit log messages for physical page allocations/deallocations.
pub const TRACE_PHYSICAL_ALLOCATOR: bool = false; pub const TRACE_PHYSICAL_ALLOCATOR: bool = false;
const std = @import("std"); const std = @import("std");
@@ -25,10 +29,18 @@ noinline fn f1(arg: usize, c: usize) void {
log.write("\x1B[1;{}H{}", .{ arg + 1, (c + arg) % 10 }); log.write("\x1B[1;{}H{}", .{ arg + 1, (c + arg) % 10 });
} }
/// Platform-independent entry point for the kernel.
///
/// # Invariants
///
/// The following preconditions must be met to invoke this function:
///
/// * Physical memory must be initialized.
/// * (optional) Logging should be set up.
pub export fn kernel_main() callconv(.C) noreturn { pub export fn kernel_main() callconv(.C) noreturn {
log.write("\x1B[2J", .{}); log.write("\x1B[2J", .{});
var a = arena.Arena.init(256 * 0x1000) orelse @panic("Could not setup kernel arena"); 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); const pc = @intFromPtr(&f0);
for (0..4) |i| { for (0..4) |i| {
@@ -39,6 +51,7 @@ pub export fn kernel_main() callconv(.C) noreturn {
thread.enter(); thread.enter();
} }
/// Kernel's panic handler
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, return_address: ?usize) noreturn { pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, return_address: ?usize) noreturn {
_ = error_return_trace; _ = error_return_trace;
+36 -17
View File
@@ -1,3 +1,5 @@
//! Platform-independent memory management functions.
const std = @import("std"); const std = @import("std");
pub const vmm = @import("mem/vmm.zig"); pub const vmm = @import("mem/vmm.zig");
@@ -5,36 +7,56 @@ pub const phys = @import("mem/phys.zig");
pub const TranslationLevel = vmm.TranslationLevel; pub const TranslationLevel = vmm.TranslationLevel;
/// A representation for physical address.
pub const PhysicalAddress = packed struct(u64) { pub const PhysicalAddress = packed struct(u64) {
raw: u64, raw: u64,
/// NULL/zero physical address constant.
pub const NULL: @This() = .{ .raw = 0 }; pub const NULL: @This() = .{ .raw = 0 };
pub var gVirtualizeBase: usize = 0; /// Base address to add to a given `PhysicalAddress` in order to "virtualize" it.
pub var gVirtualizeSize: usize = 0; pub var g_virtualize_base: usize = 0;
/// Maximum `PhysicalAddress` that can be represented as a virtual address.
pub var g_virtualize_size: usize = 0;
/// Adds an `offset` to this `PhysicalAddress`
pub fn add(self: @This(), offset: usize) @This() { pub fn add(self: @This(), offset: usize) @This() {
return .{ .raw = self.raw + @as(u64, @intCast(offset)) }; return .{ .raw = self.raw + @as(u64, @intCast(offset)) };
} }
/// "Virtualizes" a `PhysicalAddress` by turning it into a virtual address representation of
/// the memory pointed to by this physical address.
///
/// # Panics
///
/// Panics if the physical address points to a memory that cannot be represented by a virtual
/// address.
pub fn virtualize(self: @This()) usize { pub fn virtualize(self: @This()) usize {
if (self.raw > gVirtualizeSize) { if (self.raw > g_virtualize_size) {
@panic("Physical address out of virtualize bounds"); @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
/// physical form.
///
/// # Panics
///
/// Panics if the virtual address provided is outside of virtualizable memory range.
pub fn from_virtualized(virt: usize) @This() { 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"); @panic("Invalid virtualized physical address");
} }
return .{ .raw = virt - gVirtualizeBase }; return .{ .raw = virt - g_virtualize_base };
} }
}; };
pub fn formatSize(buffer: []u8, size: u64) []const u8 { /// 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 format_size(buffer: []u8, size: u64) []const u8 {
const KIBI: u64 = 1024; const KIBI: u64 = 1024;
const MIBI: u64 = KIBI * 1024; const MIBI: u64 = KIBI * 1024;
const GIBI: u64 = MIBI * 1024; const GIBI: u64 = MIBI * 1024;
@@ -51,23 +73,20 @@ pub fn formatSize(buffer: []u8, size: u64) []const u8 {
const integer = size / div; const integer = size / div;
const dot = size >= 1024; const dot = size >= 1024;
const iLen = std.fmt.formatIntBuf(buffer, integer, 10, .lower, .{}); const ilen = std.fmt.formatIntBuf(buffer, integer, 10, .lower, .{});
var len = iLen; var len = ilen;
var fLen: usize = 0; var flen: usize = 0;
if (dot and integer < 100) { if (dot and integer < 100) {
const fractional = (((size * 1000) / div) % 1000) / 10; const fractional = (((size * 1000) / div) % 1000) / 10;
if (iLen < buffer.len + 1) { if (ilen < buffer.len + 1) {
buffer[iLen] = '.'; buffer[ilen] = '.';
fLen = 1 + std.fmt.formatIntBuf(buffer[iLen + 1..], fractional, 10, .lower, .{ flen = 1 + std.fmt.formatIntBuf(buffer[ilen + 1 ..], fractional, 10, .lower, .{ .fill = '0', .width = 2 });
.fill = '0',
.width = 2
});
} }
} }
len += fLen; len += flen;
if (len + suffix.len < buffer.len) { if (len + suffix.len < buffer.len) {
std.mem.copyForwards(u8, buffer[len..], suffix); std.mem.copyForwards(u8, buffer[len..], suffix);
+129 -82
View File
@@ -1,3 +1,5 @@
//! Physical memory management implementation.
const std = @import("std"); const std = @import("std");
const kernel = @import("../kernel.zig"); const kernel = @import("../kernel.zig");
@@ -7,46 +9,55 @@ const vmm = @import("vmm.zig");
const sync = @import("../sync.zig"); const sync = @import("../sync.zig");
const Range = @import("../util/range.zig").Range; const Range = @import("../util/range.zig").Range;
const Spinlock = sync.IrqSafeSpinlock; const Spinlock = sync.Spinlock;
pub const MemoryRegion = struct { name: []const u8, range: Range(u64) }; /// 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.
range: Range(u64),
};
const Page = extern struct { /// 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, refcount: u32 = 0,
unused: [3]u32 = undefined, unused: [3]u32 = undefined,
fn isUsed(self: *const @This()) bool { /// Returns `true` if the page is allocated/used.
pub fn is_used(self: *const @This()) bool {
return self.refcount != 0; return self.refcount != 0;
} }
fn makeAvailable(self: *@This()) void { fn make_available(self: *@This()) void {
self.refcount = 0; self.refcount = 0;
} }
fn makeReserved(self: *@This()) void { fn make_reserved(self: *@This()) void {
self.refcount = std.math.maxInt(u32); self.refcount = std.math.maxInt(u32);
} }
}; };
const PhysicalMemoryManager = struct { const PhysicalMemoryManager = struct {
pageArray: []Page, page_array: []Page,
offset: u64 = 0, offset: u64 = 0,
lastFree: usize = 0, last_free: usize = 0,
const RECORDS_PER_PAGE: usize = vmm.PAGE_SIZE / @sizeOf(Page); const RECORDS_PER_PAGE: usize = vmm.PAGE_SIZE / @sizeOf(Page);
fn alloc_page(self: *@This()) ?mem.PhysicalAddress { fn alloc_page(self: *@This()) ?mem.PhysicalAddress {
for (self.lastFree..self.pageArray.len) |i| { for (self.last_free..self.page_array.len) |i| {
if (self.pageArray[i].refcount == 0) { if (self.page_array[i].refcount == 0) {
self.pageArray[i].refcount += 1; self.page_array[i].refcount += 1;
self.lastFree = (i + 1) % self.pageArray.len; self.last_free = (i + 1) % self.page_array.len;
return .{ .raw = self.offset + i * vmm.PAGE_SIZE }; return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
} }
} }
for (0..self.lastFree) |i| { for (0..self.last_free) |i| {
if (self.pageArray[i].refcount == 0) { if (self.page_array[i].refcount == 0) {
self.pageArray[i].refcount += 1; self.page_array[i].refcount += 1;
self.lastFree = (i + 1) % self.pageArray.len; self.last_free = (i + 1) % self.page_array.len;
return .{ .raw = self.offset + i * vmm.PAGE_SIZE }; return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
} }
} }
@@ -54,19 +65,19 @@ const PhysicalMemoryManager = struct {
} }
fn alloc_pages(self: *@This(), count: usize) ?mem.PhysicalAddress { fn alloc_pages(self: *@This(), count: usize) ?mem.PhysicalAddress {
if (self.lastFree + count < self.pageArray.len) { if (self.last_free + count < self.page_array.len) {
if (self.alloc_from(self.lastFree, self.pageArray.len, count)) |p| { if (self.alloc_from(self.last_free, self.page_array.len, count)) |p| {
return 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 { fn alloc_from(self: *@This(), start: usize, end: usize, count: usize) ?mem.PhysicalAddress {
for (start..end) |i| { for (start..end) |i| {
var taken = false; var taken = false;
for (0..count) |j| { for (0..count) |j| {
if (self.pageArray[i + j].isUsed()) { if (self.page_array[i + j].is_used()) {
taken = true; taken = true;
break; break;
} }
@@ -74,7 +85,7 @@ const PhysicalMemoryManager = struct {
if (!taken) { if (!taken) {
for (0..count) |j| { for (0..count) |j| {
self.pageArray[i + j].refcount = 1; self.page_array[i + j].refcount = 1;
} }
return .{ .raw = self.offset + i * vmm.PAGE_SIZE }; return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
} }
@@ -88,7 +99,7 @@ const PhysicalMemoryManager = struct {
log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw}); log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw});
} }
const index = (page.raw - self.offset) / vmm.PAGE_SIZE; const index = (page.raw - self.offset) / vmm.PAGE_SIZE;
if (index >= self.pageArray.len) { if (index >= self.page_array.len) {
log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw}); log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw});
} }
return index; return index;
@@ -96,39 +107,51 @@ const PhysicalMemoryManager = struct {
fn free_page(self: *@This(), page: mem.PhysicalAddress) void { fn free_page(self: *@This(), page: mem.PhysicalAddress) void {
const index = self.valid_index(page); const index = self.valid_index(page);
if (self.pageArray[index].refcount == 0) { if (self.page_array[index].refcount == 0) {
log.panic("free_page: double free of page 0x{x} detected", .{page.raw}); log.panic("free_page: double free of page 0x{x} detected", .{page.raw});
} }
self.pageArray[index].refcount -= 1; self.page_array[index].refcount -= 1;
if (self.pageArray[index].refcount == 0) { if (self.page_array[index].refcount == 0) {
self.lastFree = index; self.last_free = index;
} }
} }
fn get_page(self: *@This(), page: mem.PhysicalAddress) *Page { fn get_page(self: *@This(), page: mem.PhysicalAddress) *Page {
const index = self.valid_index(page); const index = self.valid_index(page);
return &self.pageArray[index]; return &self.page_array[index];
} }
}; };
var gMemoryRegions: std.BoundedArray(MemoryRegion, 16) = .{}; var g_memory_regions: std.BoundedArray(MemoryRegion, 16) = .{};
var gReservedRegions: std.BoundedArray(MemoryRegion, 16) = .{}; var g_reserved_regions: std.BoundedArray(MemoryRegion, 16) = .{};
var gPhysicalMemoryLock = Spinlock{}; var g_physical_memory_lock = Spinlock{};
var gPhysicalMemory = PhysicalMemoryManager{ .pageArray = undefined }; var g_physical_memory = PhysicalMemoryManager{ .page_array = undefined };
pub fn addMemoryRegion(name: []const u8, base: u64, size: u64) void { /// Adds an available memory region to the list.
///
/// # Note
///
/// Only meaningful to call before calling `init()`.
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 }); 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"); g_memory_regions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) //
catch @panic("memory regions overflow");
} }
pub fn addReservedRegion(name: []const u8, base: u64, size: u64) void { /// Adds an reserved memory region to the list.
///
/// # Note
///
/// Only meaningful to call before calling `init()`.
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 }); 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"); g_reserved_regions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) //
catch @panic("reserved regions overflow");
} }
fn isReservedIn(page: u64) ?*const MemoryRegion { fn is_reserved_in(page: u64) ?*const MemoryRegion {
for (0..gReservedRegions.len) |i| { for (0..g_reserved_regions.len) |i| {
const region = &gReservedRegions.buffer[i]; const region = &g_reserved_regions.buffer[i];
if (page >= region.range.start and page < region.range.end()) { if (page >= region.range.start and page < region.range.end()) {
return region; return region;
} }
@@ -136,12 +159,12 @@ fn isReservedIn(page: u64) ?*const MemoryRegion {
return null; 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); var offset = @as(u64, 0);
while (offset < region.range.len) { while (offset < region.range.len) {
var taken: ?*const MemoryRegion = null; var taken: ?*const MemoryRegion = null;
for (0..pageCount) |i| { for (0..page_count) |i| {
if (isReservedIn(region.range.start + offset + i * vmm.PAGE_SIZE)) |resv| { if (is_reserved_in(region.range.start + offset + i * vmm.PAGE_SIZE)) |resv| {
taken = resv; taken = resv;
break; break;
} }
@@ -153,17 +176,17 @@ fn allocFromRegion(region: *const MemoryRegion, reason: []const u8, pageCount: u
} }
const base = region.range.start + offset; const base = region.range.start + offset;
addReservedRegion(reason, base, pageCount * vmm.PAGE_SIZE); add_reserved_region(reason, base, page_count * vmm.PAGE_SIZE);
return base; return base;
} }
return null; return null;
} }
fn allocPageArray(pageCount: usize) []Page { fn alloc_page_array(page_count: usize) []Page {
for (gMemoryRegions.constSlice()) |region| { for (g_memory_regions.constSlice()) |region| {
if (allocFromRegion(&region, "page-array", pageCount)) |physAddress| { if (alloc_from_region(&region, "page-array", page_count)) |physAddress| {
const vaddr = (mem.PhysicalAddress{ .raw = physAddress }).virtualize(); const vaddr = (mem.PhysicalAddress{ .raw = physAddress }).virtualize();
const len = pageCount * PhysicalMemoryManager.RECORDS_PER_PAGE; const len = page_count * PhysicalMemoryManager.RECORDS_PER_PAGE;
const ptr: [*]Page = @ptrFromInt(vaddr); const ptr: [*]Page = @ptrFromInt(vaddr);
const slice: []Page = ptr[0..len]; const slice: []Page = ptr[0..len];
for (0..len) |i| { for (0..len) |i| {
@@ -175,49 +198,56 @@ fn allocPageArray(pageCount: usize) []Page {
@panic("TODO"); @panic("TODO");
} }
/// Initializes the physical memory management.
///
/// # Notes
///
/// 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 { pub fn init() void {
var memoryStart: u64 = std.math.maxInt(u64); var memory_start: u64 = std.math.maxInt(u64);
var memoryEnd: u64 = std.math.minInt(u64); var memory_end: u64 = std.math.minInt(u64);
for (gMemoryRegions.constSlice()) |region| { for (g_memory_regions.constSlice()) |region| {
if (region.range.start < memoryStart) { if (region.range.start < memory_start) {
memoryStart = region.range.start; memory_start = region.range.start;
} }
if (region.range.end() > memoryEnd) { if (region.range.end() > memory_end) {
memoryEnd = region.range.end(); memory_end = region.range.end();
} }
} }
const memoryPages = (memoryEnd - memoryStart) / vmm.PAGE_SIZE; // == bitmap bits required const memory_pages = (memory_end - memory_start) / vmm.PAGE_SIZE; // == bitmap bits required
const pageArrayPages = (memoryPages + PhysicalMemoryManager.RECORDS_PER_PAGE - 1) / PhysicalMemoryManager.RECORDS_PER_PAGE; const page_array_pages = (memory_pages + PhysicalMemoryManager.RECORDS_PER_PAGE - 1) //
/ PhysicalMemoryManager.RECORDS_PER_PAGE;
const pageArray = allocPageArray(pageArrayPages); const page_array = alloc_page_array(page_array_pages);
var availablePages: usize = 0; var available_pages: usize = 0;
for (gMemoryRegions.constSlice()) |region| { for (g_memory_regions.constSlice()) |region| {
const offset = (region.range.start - memoryStart) / vmm.PAGE_SIZE; const offset = (region.range.start - memory_start) / vmm.PAGE_SIZE;
for (0..region.range.len / vmm.PAGE_SIZE) |i| { for (0..region.range.len / vmm.PAGE_SIZE) |i| {
pageArray[offset + i].makeAvailable(); page_array[offset + i].make_available();
availablePages += 1; available_pages += 1;
} }
} }
for (gReservedRegions.constSlice()) |region| { for (g_reserved_regions.constSlice()) |region| {
const offset = (region.range.start - memoryStart) / vmm.PAGE_SIZE; const offset = (region.range.start - memory_start) / vmm.PAGE_SIZE;
for (0..region.range.len / vmm.PAGE_SIZE) |i| { for (0..region.range.len / vmm.PAGE_SIZE) |i| {
if (offset + i >= pageArray.len) { if (offset + i >= page_array.len) {
break; break;
} }
pageArray[offset + i].makeReserved(); page_array[offset + i].make_reserved();
availablePages -= 1; available_pages -= 1;
} }
} }
var sizeFmt: [64]u8 = undefined; var size_fmt: [64]u8 = undefined;
const sizeFmtStr = mem.formatSize(&sizeFmt, availablePages * vmm.PAGE_SIZE); const size_fmt_str = mem.format_size(&size_fmt, available_pages * vmm.PAGE_SIZE);
log.info("Available memory: {s}, page array {*}", .{ sizeFmtStr, pageArray }); log.info("Available memory: {s}, page array {*}", .{ size_fmt_str, page_array });
gPhysicalMemory.pageArray = pageArray; g_physical_memory.page_array = page_array;
gPhysicalMemory.offset = memoryStart; g_physical_memory.offset = memory_start;
} }
fn trace_allocation(count: usize, page: ?mem.PhysicalAddress) void { fn trace_allocation(count: usize, page: ?mem.PhysicalAddress) void {
@@ -232,36 +262,53 @@ fn trace_free(page: mem.PhysicalAddress) void {
log.debug("free 0x{x}", .{page.raw}); log.debug("free 0x{x}", .{page.raw});
} }
/// Allocates a single 4KiB physical memory page.
pub fn alloc_page() ?mem.PhysicalAddress { pub fn alloc_page() ?mem.PhysicalAddress {
gPhysicalMemoryLock.lock(); const guard = g_physical_memory_lock.lock_irqsave();
defer gPhysicalMemoryLock.release(); defer guard.release();
const page = gPhysicalMemory.alloc_page(); const page = g_physical_memory.alloc_page();
if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) { if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) {
trace_allocation(1, page); trace_allocation(1, page);
} }
return page; return page;
} }
/// Allocates a set of `count` contiguous 4KiB pages.
pub fn alloc_pages(count: usize) ?mem.PhysicalAddress { pub fn alloc_pages(count: usize) ?mem.PhysicalAddress {
gPhysicalMemoryLock.lock(); const guard = g_physical_memory_lock.lock_irqsave();
defer gPhysicalMemoryLock.release(); defer guard.release();
const pages = gPhysicalMemory.alloc_pages(count); const pages = g_physical_memory.alloc_pages(count);
if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) { if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) {
trace_allocation(count, pages); trace_allocation(count, pages);
} }
return pages; return pages;
} }
/// Deallocates a single page of physical memory.
///
/// # Panics
///
/// * If the `page` does not represent a valid managed page.
/// * If the `page` was not previously allocated.
/// * If a double free is detected.
pub fn free_page(page: mem.PhysicalAddress) void { pub fn free_page(page: mem.PhysicalAddress) void {
if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) { if (comptime kernel.TRACE_PHYSICAL_ALLOCATOR) {
trace_free(page); trace_free(page);
} }
gPhysicalMemoryLock.lock(); const guard = g_physical_memory_lock.lock_irqsave();
defer gPhysicalMemoryLock.release(); defer guard.release();
gPhysicalMemory.free_page(page); g_physical_memory.free_page(page);
} }
// NOTE: Physical memory lock must be held /// 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 { pub fn get_page(page: mem.PhysicalAddress) *Page {
return gPhysicalMemory.get_page(page); return g_physical_memory.get_page(page);
} }
+69
View File
@@ -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"),
}
}
+4
View File
@@ -1,5 +1,9 @@
//! Platform-independent virtual memory management definitions.
/// Page size is 4KiB on all platforms.
pub const PAGE_SIZE: usize = 0x1000; pub const PAGE_SIZE: usize = 0x1000;
/// Helper function to construct a "Translation Level" struct type from a bit shift.
pub fn TranslationLevel(comptime shift: usize) type { pub fn TranslationLevel(comptime shift: usize) type {
return struct { return struct {
pub const SHIFT: usize = shift; pub const SHIFT: usize = shift;
+29 -4
View File
@@ -1,19 +1,44 @@
//! Kernel synchronization primitives.
const std = @import("std"); const std = @import("std");
const arch = @import("kernel.zig").arch; const arch = @import("kernel.zig").arch;
pub const IrqSafeSpinlock = struct { /// Basic spinlock implementation
pub const Spinlock = struct {
state: std.atomic.Value(bool) = .{ .raw = false }, state: std.atomic.Value(bool) = .{ .raw = false },
pub fn tryLock(self: *@This()) bool { 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 try_lock(self: *@This()) bool {
return self.state.cmpxchgStrong(false, true, .acquire, .monotonic) orelse false; 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 { pub fn lock(self: *@This()) void {
while (!self.tryLock()) { while (!self.try_lock()) {
arch.spinHint(); 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 { pub fn release(self: *@This()) void {
self.state.store(false, .release); self.state.store(false, .release);
} }
+58 -18
View File
@@ -1,3 +1,5 @@
//! Thread management utilities and data structures.
const std = @import("std"); const std = @import("std");
const arena = @import("arena.zig"); const arena = @import("arena.zig");
@@ -5,20 +7,27 @@ const arch = @import("kernel.zig").arch;
const log = @import("debug.zig").log; const log = @import("debug.zig").log;
const mem = @import("mem.zig"); const mem = @import("mem.zig");
/// Per-CPU thread queue structure.
pub const Queue = struct { pub const Queue = struct {
/// Idle task context. Used when there are no other tasks running.
idle: arch.Context, idle: arch.Context,
/// Current thread pointer.
current: ?*Thread = null, current: ?*Thread = null,
/// Thread queue head pointer.
head: ?*Thread = null, head: ?*Thread = null,
pub threadlocal var thisCpu: ?*Queue = null; /// Pointer to this CPU's thread queue.
pub threadlocal var t_this_cpu: ?*Queue = null;
pub fn initThisCpu(a: *arena.Arena) void { /// Sets up a thread queue for the current CPU.
pub fn init_this_cpu(a: *arena.Arena) void {
const idle = arch.Context.idle(); const idle = arch.Context.idle();
const q = a.create(Queue); const q = a.create(Queue);
q.* = .{ .idle = idle }; q.* = .{ .idle = idle };
thisCpu = q; t_this_cpu = q;
} }
/// Enters a task on this CPU.
pub fn enter(self: *@This()) noreturn { pub fn enter(self: *@This()) noreturn {
if (self.head) |gt| { if (self.head) |gt| {
self.current = gt; self.current = gt;
@@ -29,6 +38,7 @@ pub const Queue = struct {
} }
} }
/// Yields CPU to the next available task.
pub fn yield(self: *@This()) void { pub fn yield(self: *@This()) void {
if (self.current) |curr| { if (self.current) |curr| {
// Switching from thread // Switching from thread
@@ -36,25 +46,26 @@ pub const Queue = struct {
// ... to thread // ... to thread
if (next != curr) { if (next != curr) {
self.current = next; self.current = next;
next.switchFrom(curr); next.switch_from(curr);
} }
} else { } else {
// ... to idle // ... to idle
self.current = null; self.current = null;
self.idle.switchFrom(&curr.archContext); self.idle.switch_from(&curr.arch_context);
} }
} else { } else {
// Switching from idle // Switching from idle
if (self.head) |gt| { if (self.head) |gt| {
// ... to thread // ... to thread
self.current = gt; self.current = gt;
gt.archContext.switchFrom(&self.idle); gt.arch_context.switch_from(&self.idle);
return; return;
} }
// ... back to idle // ... back to idle
} }
} }
/// Adds an available task to this queue.
pub fn enqueue(self: *@This(), t: *Thread) void { pub fn enqueue(self: *@This(), t: *Thread) void {
if (self.head) |gt| { if (self.head) |gt| {
t.next = gt; t.next = gt;
@@ -69,46 +80,72 @@ pub const Queue = struct {
} }
}; };
/// Represents a single execution thread.
pub const Thread = struct { pub const Thread = struct {
/// Arena.
allocator: *arena.Arena, allocator: *arena.Arena,
archContext: arch.Context, /// Architecture-specific task context.
arch_context: arch.Context,
/// Next thread in the queue.
next: ?*Thread = null, next: ?*Thread = null,
/// Previous thread in the queue.
prev: ?*Thread = null, prev: ?*Thread = null,
/// Creates a new (kernel) thread with given `pc` (entry point) and `arg`ument.
pub fn create(a: *arena.Arena, pc: usize, arg: usize) *Thread { pub fn create(a: *arena.Arena, pc: usize, arg: usize) *Thread {
const thread = a.create(Thread); const thread = a.create(Thread);
thread.* = .{ thread.* = .{
.allocator = a, .allocator = a,
.archContext = arch.Context.kernel(pc, arg), .arch_context = arch.Context.kernel(pc, arg),
}; };
return thread; return thread;
} }
/// Enters the thread, does not return.
pub fn enter(self: *@This()) noreturn { pub fn enter(self: *@This()) noreturn {
self.archContext.enter(); self.arch_context.enter();
} }
pub fn switchFrom(self: *@This(), from: *@This()) void { /// Switches from `from` to `self` thread.
self.archContext.switchFrom(&from.archContext); pub fn switch_from(self: *@This(), from: *@This()) void {
self.arch_context.switch_from(&from.arch_context);
} }
}; };
/// Helper data structure to represent kernel stacks in task contexts.
pub fn KStack(comptime SIZE: usize) type { pub fn KStack(comptime SIZE: usize) type {
return extern struct { return extern struct {
// Has to be at exactly offset 0x00, used in assembly // Has to be at exactly offset 0x00, used in assembly
/// Stack pointer. Aliases `data`.
sp: *usize, sp: *usize,
/// Stack data represented as a slice of `SIZE` machine-sized words.
data: *[SIZE]usize, data: *[SIZE]usize,
physicalBase: mem.PhysicalAddress, /// Physical base address at which the stack is allocated.
physical_base: mem.PhysicalAddress,
/// Allocates a new kernel stack.
///
/// # Panics
///
/// Panics on Out-of-Memory condition. TODO Fix this.
pub fn create() @This() { pub fn create() @This() {
const physicalBase = mem.phys.alloc_pages(SIZE * @sizeOf(usize) / 0x1000) orelse @panic("OOM"); const physical_base = mem.phys.alloc_pages(SIZE * @sizeOf(usize) / 0x1000) orelse @panic("OOM");
const ptr = @as(*[SIZE]usize, @ptrFromInt(physicalBase.virtualize())); 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.
///
/// # Panics
///
/// Panics if a push would overflow the stack.
pub fn push(self: *@This(), value: usize) void { pub fn push(self: *@This(), value: usize) void {
if (self.sp == &self.data[0]) { if (self.sp == &self.data[0]) {
@panic("KStack overflow"); @panic("KStack overflow");
@@ -119,14 +156,17 @@ pub fn KStack(comptime SIZE: usize) type {
}; };
} }
/// Adds a thread to some CPU queue for execution.
pub fn enqueue(t: *Thread) void { 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 { 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 { pub fn yield() void {
Queue.thisCpu.?.yield(); Queue.t_this_cpu.?.yield();
} }
+2
View File
@@ -0,0 +1,2 @@
pub const dtb = @import("util/dtb.zig");
pub const range = @import("util/range.zig");
+261 -98
View File
@@ -1,7 +1,11 @@
//! Flattened Device Tree manipulation utilities
const mem = @import("../mem.zig"); const mem = @import("../mem.zig");
const log = @import("../debug.zig").log; const log = @import("../debug.zig").log;
const std = @import("std"); const std = @import("std");
const phys_memory = mem.phys;
const fdt_header = extern struct { const fdt_header = extern struct {
magic: u32, magic: u32,
totalsize: u32, totalsize: u32,
@@ -15,7 +19,14 @@ const fdt_header = extern struct {
size_dt_struct: u32, size_dt_struct: u32,
}; };
const fdt_op = enum(u32) { FDT_BEGIN_NODE = 1, FDT_END_NODE = 2, FDT_PROP = 3, FDT_NOP = 4, FDT_END = 9, _ }; const fdt_op = enum(u32) {
FDT_BEGIN_NODE = 1,
FDT_END_NODE = 2,
FDT_PROP = 3,
FDT_NOP = 4,
FDT_END = 9,
_,
};
const fdt_prop = extern struct { const fdt_prop = extern struct {
len: u32, len: u32,
@@ -36,26 +47,41 @@ const FdtTag = union(enum) {
end, end,
}; };
/// Represents a memory region described by some device tree node.
/// Can either mean a range of reserved memory or a range of available memory depending on the
/// function used to obtain it.
pub const FdtMemoryRegion = struct { pub const FdtMemoryRegion = struct {
name: []const u8, name: []const u8,
base: u64, base: u64,
size: u64, size: u64,
}; };
/// Represents a single node within a device tree.
pub const FdtNode = struct { pub const FdtNode = struct {
/// Parent device tree pointer.
fdt: *const Fdt, fdt: *const Fdt,
/// Offset within the device tree raw data blob.
off: usize, off: usize,
/// Name string of the node.
name: []const u8, name: []const u8,
/// Depth of the node within the tree.
depth: usize, depth: usize,
pub fn propIterator(self: *const @This()) FdtNodePropIterator { /// Returns an iterator over the node's properties.
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 { 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.
pub fn child(self: *const @This(), name: []const u8) ?FdtNode { pub fn child(self: *const @This(), name: []const u8) ?FdtNode {
var iter = self.children(); var iter = self.children();
while (iter.next()) |c| { while (iter.next()) |c| {
@@ -66,9 +92,10 @@ pub const FdtNode = struct {
return null; return null;
} }
/// Looks up a property with given `name` within the node.
pub fn property(self: *const @This(), name: []const u8) ?FdtNodeProp { pub fn property(self: *const @This(), name: []const u8) ?FdtNodeProp {
var propIter = self.propIterator(); var prop_iter = self.prop_iterator();
while (propIter.next()) |prop| { while (prop_iter.next()) |prop| {
if (std.mem.eql(u8, name, prop.name)) { if (std.mem.eql(u8, name, prop.name)) {
return prop; return prop;
} }
@@ -77,33 +104,56 @@ pub const FdtNode = struct {
} }
}; };
/// Represents a property of some node within a device tree.
pub const FdtNodeProp = struct { pub const FdtNodeProp = struct {
/// Node to which this property belongs.
node: *const FdtNode, node: *const FdtNode,
/// Name string.
name: []const u8, name: []const u8,
/// Value of the property represented as a raw byte slice.
value: []const u8, value: []const u8,
pub inline fn getStringArray(self: *const @This()) FdtStringArrayIterator { /// Interprets the property's value as a list of strings.
pub inline fn get_string_array(self: *const @This()) FdtStringArrayIterator {
return .{ .prop = self }; return .{ .prop = self };
} }
pub inline fn lenU32(self: *const @This()) usize { /// Interprets the property's value as a single string.
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 len_cells(self: *const @This()) usize {
return self.value.len / @sizeOf(u32); return self.value.len / @sizeOf(u32);
} }
pub fn getU32(self: *const @This(), index: usize) ?u32 { /// Interprets the property's value as an array of 32-bit cells and returns a cell at a given
if (index >= self.lenU32()) { /// index.
pub fn get_cell(self: *const @This(), index: usize) ?u32 {
if (index >= self.len_cells()) {
return null; 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]))).*); return std.mem.bigToNative(u32, @as(*const u32, @ptrCast(@alignCast(&self.value[index * 4]))).*);
} }
pub fn readCells(self: *const @This(), index: usize, output: []u64, sizes: []const usize) bool { /// Interprets the property's value as an array of tuples of cells with sizes described by
/// `sizes` and reads a single such tuple into the `output`.
///
/// Returns `true` if a whole tuple can be read at given `index`.
///
/// # Notes
///
/// * `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 read_cells(self: *const @This(), index: usize, output: []u64, sizes: []const usize) bool {
const count = @min(output.len, sizes.len); const count = @min(output.len, sizes.len);
const len = self.lenU32(); const len = self.len_cells();
var total: usize = 0; var total: usize = 0;
for (sizes[0..count]) |s| { for (sizes[0..count]) |s| {
total += s; total += s;
@@ -113,11 +163,11 @@ pub const FdtNodeProp = struct {
for (0..count) |i| { for (0..count) |i| {
switch (sizes[i]) { switch (sizes[i]) {
1 => { 1 => {
output[i] = self.getU32Unchecked(offset); output[i] = self.get_cell_unchecked(offset);
}, },
2 => { 2 => {
output[i] = self.getU32Unchecked(offset + 1); output[i] = self.get_cell_unchecked(offset + 1);
output[i] |= @as(u64, self.getU32Unchecked(offset)) << 32; output[i] |= @as(u64, self.get_cell_unchecked(offset)) << 32;
}, },
else => @panic("Invalid cell size"), else => @panic("Invalid cell size"),
} }
@@ -131,6 +181,7 @@ pub const FdtNodeProp = struct {
} }
}; };
/// An iterator over a string list property value.
pub const FdtStringArrayIterator = struct { pub const FdtStringArrayIterator = struct {
prop: *const FdtNodeProp, prop: *const FdtNodeProp,
off: usize = 0, off: usize = 0,
@@ -147,17 +198,18 @@ pub const FdtStringArrayIterator = struct {
} }
}; };
/// An iterator over available memory regions described by a device tree.
pub const FdtMemoryRegionIterator = struct { pub const FdtMemoryRegionIterator = struct {
nodeIter: FdtNodeIterator, node_iter: FdtNodeIterator,
cellSizes: [2]usize, cell_sizes: [2]usize,
pub fn next(self: *FdtMemoryRegionIterator) ?FdtMemoryRegion { 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@")) { if (std.mem.startsWith(u8, node.name, "memory@")) {
const reg = node.property("reg") orelse continue; const reg = node.property("reg") orelse continue;
var cells: [2]u64 = undefined; var cells: [2]u64 = undefined;
if (reg.readCells(0, &cells, &self.cellSizes)) { if (reg.read_cells(0, &cells, &self.cell_sizes)) {
return .{ return .{
.name = node.name, .name = node.name,
.base = cells[0], .base = cells[0],
@@ -171,13 +223,14 @@ pub const FdtMemoryRegionIterator = struct {
} }
}; };
/// An iterator over a device tree's node properties.
pub const FdtNodePropIterator = struct { pub const FdtNodePropIterator = struct {
node: *const FdtNode, node: *const FdtNode,
tagIter: FdtTagIterator, tag_iter: FdtTagIterator,
depth: usize = 0, depth: usize = 0,
fn next(self: *FdtNodePropIterator) ?FdtNodeProp { fn next(self: *FdtNodePropIterator) ?FdtNodeProp {
while (self.tagIter.next()) |tag| { while (self.tag_iter.next()) |tag| {
switch (tag) { switch (tag) {
.begin_node => |_| { .begin_node => |_| {
self.depth += 1; self.depth += 1;
@@ -185,7 +238,7 @@ pub const FdtNodePropIterator = struct {
.nop => {}, .nop => {},
.prop => |prop| { .prop => |prop| {
if (self.depth == 0) { 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 }; return .{ .node = self.node, .value = prop.data, .name = name };
} }
}, },
@@ -206,19 +259,20 @@ pub const FdtNodePropIterator = struct {
} }
}; };
/// An iterator over a device tree's nodes.
pub const FdtNodeIterator = struct { pub const FdtNodeIterator = struct {
tagIter: FdtTagIterator, tag_iter: FdtTagIterator,
depth: usize = 0, depth: usize = 0,
depthLower: ?usize = null, depth_lower: ?usize = null,
pub fn next(self: *FdtNodeIterator) ?FdtNode { pub fn next(self: *FdtNodeIterator) ?FdtNode {
while (self.tagIter.next()) |tag| { while (self.tag_iter.next()) |tag| {
switch (tag) { switch (tag) {
.begin_node => |name| { .begin_node => |name| {
self.depth += 1; self.depth += 1;
return .{ return .{
.fdt = self.tagIter.fdt, .fdt = self.tag_iter.fdt,
.off = self.tagIter.off, .off = self.tag_iter.off,
.name = name, .name = name,
.depth = self.depth - 1, .depth = self.depth - 1,
}; };
@@ -226,7 +280,7 @@ pub const FdtNodeIterator = struct {
.end_node => { .end_node => {
self.depth -= 1; self.depth -= 1;
if (self.depthLower) |lower| { if (self.depth_lower) |lower| {
if (self.depth == lower) { if (self.depth == lower) {
return null; return null;
} }
@@ -239,6 +293,7 @@ pub const FdtNodeIterator = struct {
} }
}; };
/// An iterator over a device tree's raw tags.
pub const FdtTagIterator = struct { pub const FdtTagIterator = struct {
fdt: *const Fdt, fdt: *const Fdt,
raw: []const u8, raw: []const u8,
@@ -255,10 +310,10 @@ pub const FdtTagIterator = struct {
switch (tag) { switch (tag) {
.FDT_BEGIN_NODE => { .FDT_BEGIN_NODE => {
const nameCStr: [*c]const u8 = @ptrCast(self.raw[self.off..]); const name_cstr: [*c]const u8 = @ptrCast(self.raw[self.off..]);
const nameLength = std.mem.len(nameCStr); const name_length = std.mem.len(name_cstr);
const name = self.raw[self.off .. self.off + nameLength]; const name = self.raw[self.off .. self.off + name_length];
self.off += (nameLength + 4) & ~@as(usize, 3); self.off += (name_length + 4) & ~@as(usize, 3);
return .{ .begin_node = name }; return .{ .begin_node = name };
}, },
.FDT_PROP => { .FDT_PROP => {
@@ -288,14 +343,23 @@ pub const FdtTagIterator = struct {
} }
}; };
/// An error returned by the FDT manipulation functions.
pub const FdtError = error{invalid_magic}; pub const FdtError = error{invalid_magic};
/// Magic number describing a valid device tree (Big-Endian).
pub const FDT_MAGIC: u32 = 0xD00DFEED; pub const FDT_MAGIC: u32 = 0xD00DFEED;
/// Represents a Flattened Device Tree.
pub const Fdt = struct { pub const Fdt = struct {
/// Raw device tree bytes, including the header.
bytes: []const u8, bytes: []const u8,
pub fn fromPhysicalAddress(phys: mem.PhysicalAddress) FdtError!@This() { /// Constructs a FDT struct from a physical address.
///
/// # Errors
///
/// * `invalid_magic` if the address provided does not have a valid magic number in its header.
pub fn from_physical_address(phys: mem.PhysicalAddress) FdtError!@This() {
const virt = phys.virtualize(); const virt = phys.virtualize();
const hdr = @as(*const fdt_header, @ptrFromInt(virt)); const hdr = @as(*const fdt_header, @ptrFromInt(virt));
if (std.mem.bigToNative(u32, hdr.magic) != FDT_MAGIC) { if (std.mem.bigToNative(u32, hdr.magic) != FDT_MAGIC) {
@@ -306,6 +370,7 @@ pub const Fdt = struct {
return .{ .bytes = x[0..totalsize] }; return .{ .bytes = x[0..totalsize] };
} }
/// Returns the header pointer of this device tree.
pub fn header(self: *const @This()) *const fdt_header { pub fn header(self: *const @This()) *const fdt_header {
return @ptrCast(@alignCast(&self.bytes[0])); return @ptrCast(@alignCast(&self.bytes[0]));
} }
@@ -315,21 +380,30 @@ pub const Fdt = struct {
return self.bytes[off..]; return self.bytes[off..];
} }
pub fn tagIterator(self: *const @This()) FdtTagIterator { /// Returns an iterator over the device tree's raw tags.
return self.tagIteratorAt(0); pub fn tag_iterator(self: *const @This()) FdtTagIterator {
return self.tag_iterator_at(0);
} }
pub fn tagIteratorAt(self: *const @This(), off: usize) FdtTagIterator { /// Returns an iterator over the device tree's raw tags at specific byte offset.
pub fn tag_iterator_at(self: *const @This(), off: usize) FdtTagIterator {
return .{ .raw = self.data(), .fdt = self, .off = off }; return .{ .raw = self.data(), .fdt = self, .off = off };
} }
pub fn nodeIterator(self: *const @This()) FdtNodeIterator { /// Returns an iterator over the device tree's nodes.
return .{ .tagIter = self.tagIterator() }; pub fn node_iterator(self: *const @This()) FdtNodeIterator {
return .{ .tag_iter = self.tag_iterator() };
} }
pub fn rootNode(self: *const @This()) FdtNode { /// Returns the root node of this device tree.
var nodeIter = self.nodeIterator(); ///
while (nodeIter.next()) |node| { /// # Panics
///
/// 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 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) { if (node.depth == 0 and node.name.len == 0) {
return node; return node;
} }
@@ -337,72 +411,161 @@ pub const Fdt = struct {
@panic("Unreachable code"); @panic("Unreachable code");
} }
pub fn memoryRegionIterator(self: *const @This()) FdtMemoryRegionIterator { /// Returns an iterator over available memory regions described by the device tree.
const r = self.rootNode(); pub fn memory_region_iterator(self: *const @This()) FdtMemoryRegionIterator {
const addressCells = if (r.property("#address-cells")) |o| (if (o.getU32(0)) |p| p else 1) else 1; const r = self.root_node();
const sizeCells = if (r.property("#size-cells")) |o| (if (o.getU32(0)) |p| p else 1) else 1; 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 { fn string_data(self: *const @This()) [*c]const u8 {
const offStrings = std.mem.bigToNative(u32, self.header().off_dt_strings); const off_strings = std.mem.bigToNative(u32, self.header().off_dt_strings);
const sizeStrings = std.mem.bigToNative(u32, self.header().off_dt_strings); const size_strings = std.mem.bigToNative(u32, self.header().off_dt_strings);
const off = @min(offStrings, self.bytes.len); const off = @min(off_strings, self.bytes.len);
const len = @min(sizeStrings, self.bytes.len - off); const len = @min(size_strings, self.bytes.len - off);
return @ptrCast(self.bytes[off .. off + len]); return @ptrCast(self.bytes[off .. off + len]);
} }
pub fn stringAt(self: *const @This(), off: usize) []const u8 { /// Returns a string slice at given byte offset into the device tree's strings section.
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); const len = std.mem.len(raw);
return @ptrCast(raw[0..len]); return @ptrCast(raw[0..len]);
} }
pub fn dump(self: *const @This()) void { /// Adds information about the available and reserved memory regions described in this device
const root = self.rootNode(); /// tree into the physical memory management structures.
pub fn add_physical_memory_to_system(self: *const @This()) void {
var memory_regions = self.memory_region_iterator();
var cells: [2]u64 = undefined; var cells: [2]u64 = undefined;
if (root.child("reserved-memory")) |resv| {
var regions = resv.children();
while (regions.next()) |region| {
if (region.property("reg")) |reg| {
if (reg.readCells(0, &cells, &.{ 2, 2 })) {
log.info("Reserved memory region {s}: base=0x{x}, size=0x{x}", .{ region.name, cells[0], cells[1] });
}
}
}
}
// var memIter = self.memoryRegionIterator();
// while (memIter.next()) |region| {
// log.info("base=0x{x}, size=0x{x}", .{ region.base, region.size });
// }
// var nodeIter = self.nodeIterator(); while (memory_regions.next()) |region| {
// while (nodeIter.next()) |node| { phys_memory.add_memory_region(region.name, region.base, region.size);
// for (0..node.depth) |_| { }
// log.writeRaw(" "); const reserved_regions = self.root_node().child("reserved-memory");
// } if (reserved_regions) |resv| {
// if (node.name.len == 0) { var children = resv.children();
// log.info("Root node, depth = {}", .{ node.depth }); while (children.next()) |region| {
// } else { if (region.property("reg")) |reg| {
// log.info("Node {s}, depth {}", .{ node.name, node.depth }); // TODO #address-cells, #size-cells
// } if (reg.read_cells(0, &cells, &.{ 2, 2 })) {
// var nodePropIter = node.propIterator(); phys_memory.add_reserved_region(region.name, cells[0], cells[1]);
// while (nodePropIter.next()) |prop| { }
// for (0..node.depth + 1) |_| { }
// log.writeRaw(" "); }
// } }
// log.info("Prop {s}", .{ prop.name }); }
// if (std.mem.eql(u8, prop.name, "compatible")) {
// var strings = prop.getStringArray(); /// Looks up a `/slash/separated/path` inside the device tree.
// while (strings.next()) |s| { pub fn find(self: *const @This(), path: []const u8) ?FdtNode {
// for (0..node.depth + 2) |_| { const trimmed_path = std.mem.trimLeft(u8, path, "/");
// log.writeRaw(" "); var path_elements = std.mem.splitScalar(u8, trimmed_path, '/');
// } var current_node = self.root_node();
// log.info("= {s}", .{ s }); if (trimmed_path.len == 0) {
// } return current_node;
// } }
// } while (path_elements.next()) |element| {
// } var found: ?FdtNode = null;
var children = current_node.children();
while (children.next()) |child| {
if (std.mem.eql(u8, child.name, element)) {
found = child;
break;
}
}
if (found) |f| {
log.info("{s}", .{element});
current_node = f;
} else {
return null;
}
}
return current_node;
}
fn dump_property(property: *const FdtNodeProp, depth: usize, all_strings: bool) void {
for (0..depth) |_| {
log.write_waw(" ");
}
log.write("{s}", .{property.name});
if (std.mem.eql(u8, property.name, "#size-cells") //
or std.mem.eql(u8, property.name, "#address-cells") //
or std.mem.eql(u8, property.name, "#interrupt-cells") //
or std.mem.eql(u8, property.name, "phandle") //
or std.mem.eql(u8, property.name, "interrupt-parent") //
) {
// Dump as a single cell
const v = property.get_cell(0) orelse 0;
log.write(" = {}", .{v});
} 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.get_string_array();
var f = true;
while (v.next()) |s| {
if (f) {
log.write_waw(" = ");
} else {
log.write_waw(", ");
}
f = false;
log.write("\"{s}\"", .{s});
}
} else {
// Dump the rest as a cell array
const len = property.len_cells();
log.write_waw(" = <");
for (0..len) |i| {
if (i != 0) {
log.write_waw(", ");
}
log.write("0x{x}", .{property.get_cell_unchecked(i)});
}
log.write_waw(">");
}
log.write_waw(";\r\n");
}
fn dump_node(node: *const FdtNode, depth: usize) void {
var any_properties = false;
var first_child = true;
for (0..depth) |_| {
log.write_waw(" ");
}
if (node.name.len != 0) {
log.write("{s} ", .{node.name});
}
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, all_strings);
any_properties = true;
}
var children = node.children();
while (children.next()) |child| {
if (any_properties and first_child) {
log.write_waw("\r\n");
}
first_child = false;
dump_node(&child, depth + 1);
}
for (0..depth) |_| {
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.root_node(), 0);
} }
}; };
View File
+7
View File
@@ -1,12 +1,19 @@
//! Utilities for manipulating ranges.
/// Non-inclusive range type over `T`.
pub fn Range(comptime T: type) type { pub fn Range(comptime T: type) type {
return struct { return struct {
/// Range start.
start: T, start: T,
/// Range length.
len: T, len: T,
/// Returns `start + len` of the range.
pub fn end(self: *const @This()) T { pub fn end(self: *const @This()) T {
return self.start + self.len; return self.start + self.len;
} }
/// Returns a range representing an intersections of `self` with an`other` range.
pub fn intersect(self: *const @This(), other: *const @This()) ?Range(T) { pub fn intersect(self: *const @This(), other: *const @This()) ?Range(T) {
if (self.start < other.start) { if (self.start < other.start) {
const p = other.start - self.start; const p = other.start - self.start;