10 Commits

37 changed files with 1651 additions and 223 deletions
+21
View File
@@ -0,0 +1,21 @@
pub const SyscallNumber = enum(usize) {
SYS_send = 1,
SYS_recv = 2,
SYS_sendrecv = 3,
_,
};
pub const ProcessObjectAction = enum(usize) {
ZO_process_exit = 1,
_,
};
pub const PhysicalMemoryObjectAction = enum(usize) {
ZO_physical_memory_allocate = 1,
ZO_physical_memory_free = 2,
_,
};
pub const MAX_SYSCALL: usize = 32;
pub const Handle = enum(u32) { _ };
+79 -13
View File
@@ -6,7 +6,7 @@ const SupportedArch = enum {
aarch64,
riscv64,
fn make_target(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget {
fn make_kernel_target(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget {
switch (self) {
.riscv64 => {
return b.resolveTargetQuery(.{
@@ -38,7 +38,47 @@ const SupportedArch = enum {
}
}
fn add_target_specific(self: SupportedArch, b: *std.Build, kernel: *std.Build.Step.Compile) anyerror!*std.Build.Step {
fn make_userspace_target(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget {
// TODO: it's the same for now, until userspace support for CPU extensions like floating
// point is added.
return self.make_kernel_target(b);
}
fn configure_user(
self: SupportedArch,
b: *std.Build,
user: *std.Build.Step.Compile,
) anyerror!*std.Build.Step {
switch (self) {
.riscv64 => {
user.setLinkerScript(b.path("user/riscv64.ld"));
},
.aarch64 => {
user.setLinkerScript(b.path("user/aarch64.ld"));
},
}
b.installArtifact(user);
const elf2bin = b.addSystemCommand(&.{
"llvm-objcopy",
"-O",
"binary",
"zig-out/bin/userspace",
"zig-out/bin/userspace.bin",
});
elf2bin.step.dependOn(&user.step);
return &elf2bin.step;
}
fn configure_kernel(
self: SupportedArch,
b: *std.Build,
kernel: *std.Build.Step.Compile,
user: *std.Build.Step,
) anyerror!*std.Build.Step {
switch (self) {
.riscv64 => {
kernel.entry = .{ .symbol_name = "__rv64_entry" };
@@ -61,6 +101,7 @@ const SupportedArch = enum {
},
}
kernel.step.dependOn(user);
b.installArtifact(kernel);
if (self == .riscv64 or self == .aarch64) {
@@ -136,29 +177,44 @@ fn insert_fake_linux_image_header(step: *std.Build.Step, opts: std.Build.Step.Ma
_ = opts;
}
fn build_riscv64(b: *std.Build) anyerror!void {
_ = b;
}
pub fn build(b: *std.Build) anyerror!void {
const maybe_arch_option = b.option(SupportedArch, "arch", "Architecture to use");
const arch = maybe_arch_option orelse DEFAULT_ARCH;
const target = arch.make_target(b);
const kernel_target = arch.make_kernel_target(b);
const user_target = arch.make_userspace_target(b);
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
const code_model: std.builtin.CodeModel = switch (arch) {
const kernel_code_model: std.builtin.CodeModel = switch (arch) {
.riscv64 => .medium,
.aarch64 => .small,
};
const user_code_model = kernel_code_model;
const kernel_module = b.addModule("kernel", .{
.optimize = optimize,
.target = target,
.target = kernel_target,
.pic = true,
.red_zone = false,
.code_model = code_model,
.code_model = kernel_code_model,
.root_source_file = b.path("src/kernel.zig"),
});
kernel_module.addAnonymousImport("userspace", .{ .root_source_file = b.path("zig-out/bin/userspace.bin") });
const abi_module = b.addModule("abi", .{
.optimize = optimize,
.red_zone = false,
.target = kernel_target,
.code_model = kernel_code_model,
.root_source_file = b.path("abi/abi.zig"),
});
const user_module = b.addModule("userspace", .{
.optimize = optimize,
.target = user_target,
.red_zone = false,
.code_model = user_code_model,
.root_source_file = b.path("user/main.zig"),
});
const kernel = b.addExecutable(.{
.name = "kernel",
.root_module = kernel_module,
@@ -167,6 +223,15 @@ pub fn build(b: *std.Build) anyerror!void {
});
kernel.pie = true;
user_module.addImport("abi", abi_module);
kernel_module.addImport("abi", abi_module);
const user = b.addExecutable(.{
.name = "userspace",
.root_module = user_module,
.use_lld = true,
});
const install_docs = b.addInstallDirectory(.{
.source_dir = kernel.getEmittedDocs(),
.install_dir = .prefix,
@@ -176,10 +241,11 @@ pub fn build(b: *std.Build) anyerror!void {
const docs_step = b.step("docs", "Install documentation");
docs_step.dependOn(&install_docs.step);
const kernel_step = try arch.add_target_specific(b, kernel);
const user_step = try arch.configure_user(b, user);
const kernel_step = try arch.configure_kernel(b, kernel, user_step);
// TODO QEMU binary override
const qemu_info = switch (target.result.cpu.arch) {
const qemu_info = switch (kernel_target.result.cpu.arch) {
.riscv64 => .{ "qemu-system-riscv64", "rv64" },
.aarch64 => .{ "qemu-system-aarch64", "cortex-a72" },
else => unreachable,
@@ -201,7 +267,7 @@ pub fn build(b: *std.Build) anyerror!void {
"none",
});
if (target.result.cpu.arch == .riscv64) {
if (kernel_target.result.cpu.arch == .riscv64) {
qemu_cmd.addArgs(&.{ "-bios", "etc/boot/rv64_fw_jump.bin" });
}
+2
View File
@@ -23,6 +23,8 @@ SECTIONS {
.tdata : ALIGN(4K) {
PROVIDE(__tdata_start = .);
/* Storage for thread-locals used in assembly */
KEEP(*(.tdata.assembly*));
*(.tdata*)
PROVIDE(__tdata_end = .);
}
+24 -3
View File
@@ -4,14 +4,35 @@
const std = @import("std");
const builtin = @import("builtin");
pub const impl = switch (builtin.cpu.arch) {
.riscv64 => @import("arch/riscv64.zig"),
.aarch64 => @import("arch/aarch64.zig"),
pub const cpu: enum {
riscv64,
aarch64,
} = switch (builtin.cpu.arch) {
.riscv64 => .riscv64,
.aarch64 => .aarch64,
else => @compileError("Unsupported architecture"),
};
pub const impl = switch (cpu) {
.riscv64 => @import("arch/riscv64.zig"),
.aarch64 => @import("arch/aarch64.zig"),
};
pub const vmm = impl.vmm;
pub const IrqGuard = struct {
state: bool,
pub fn acquire() @This() {
const state = set_interrupt_mask(true);
return .{ .state = state };
}
pub fn release(self: @This()) void {
set_interrupt_mask(self.state);
}
};
/// Halts the CPU execution indefinitely, without ever returning.
pub inline fn halt() noreturn {
impl.halt();
+8 -5
View File
@@ -13,8 +13,10 @@ extern const __aa64_bsp_stack_top: u8;
var g_dtb_address: u64 = undefined;
fn early_debug_print(byte: u8) void {
const address = 0x9000000;
fn early_debug_print_high(byte: u8) void {
// TODO this is incorrect: writes should come to a memory region marked as device memory,
// "virtualize" range is normal memory.
const address = 0x9000000 + vmm.VIRTUALIZE_BASE;
@as(*volatile u32, @ptrFromInt(address)).* = byte;
}
@@ -44,14 +46,15 @@ fn aa64_bsp_upper_entry(real_address: u64) callconv(.C) noreturn {
arch.barrier(.acq_rel);
aa64_relocate_kernel(rel_offset, rela_start, rela_end);
vmm.unmap_early();
arch.barrier(.acq_rel);
log.set_write_fn(&early_debug_print);
log.set_write_fn(&early_debug_print_high);
exception.init();
mem.PhysicalAddress.g_virtualize_base = 0;
mem.PhysicalAddress.g_virtualize_size = 16 << 30;
mem.PhysicalAddress.g_virtualize_base = vmm.VIRTUALIZE_BASE;
mem.PhysicalAddress.g_virtualize_size = 16 << vmm.L1.SHIFT;
setup_memory_from_fdt(real_address);
+42 -4
View File
@@ -3,6 +3,7 @@
.global __aa64_enter_task
.global __aa64_switch_task
.global __aa64_task_enter_kernel
.global __aa64_task_enter_user
.set CONTEXT_SIZE, (12 * 8)
@@ -28,14 +29,51 @@
.pushsection .text
.set SPSR_ELx_I, (1 << 9)
.set SPSR_ELx_EL1h, (0b0101)
__aa64_task_enter_user:
// x0 == sp, ...
ldr x0, [sp, #16 * 0]
msr sp_el0, x0
// x0 == arg, x1 == entry
ldp x0, x1, [sp, #16 * 1]
add sp, sp, #32
msr elr_el1, x1
// SPSR_ELx_M[4:0] = 0, a return to EL0, AArch64 mode
mov x1, #SPSR_ELx_I
msr spsr_el1, x1
mov lr, xzr
dsb ish
isb sy
eret
__aa64_task_enter_kernel:
// arg, entry
ldp x0, lr, [sp]
add sp, sp, #16
ldp x0, x1, [sp]
// return address
ldr lr, [sp, #16]
add sp, sp, #24
// TODO enter task via eret to EL1t
msr elr_el1, x1
ret
// SPSR_ELx_M[4:0] = 0b100, a return to EL1t, AArch64 mode
mov x1, #SPSR_ELx_EL1h
orr x1, x1, #SPSR_ELx_I
msr spsr_el1, x1
mov x1, xzr
dsb ish
isb sy
eret
__aa64_switch_task:
// x0 -- "dst" context
+61 -18
View File
@@ -1,30 +1,83 @@
const thread = @import("../../thread.zig");
const vmm = @import("vmm.zig");
const mem = @import("../../mem.zig");
const regs = @import("regs.zig");
const kernel = @import("../../kernel.zig");
fn idle_function() callconv(.naked) noreturn {
asm volatile ("b .");
}
const ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
const arch = kernel.arch;
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;
extern fn __aa64_task_enter_user() callconv(.C) noreturn;
pub const Context = extern struct {
const STACK_SIZE: usize = 16384;
kstack: thread.KStack(STACK_SIZE),
ttbr0: u64 = 0,
pub fn idle() Context {
const entry = @intFromPtr(&idle_function);
return Context.kernel(entry, 0);
return Context.kernel(&thread.idle_function, 0);
}
pub fn kernel(pc: usize, arg: usize) Context {
var ks = thread.KStack(STACK_SIZE).create();
const entry = @intFromPtr(&__aa64_task_enter_kernel);
pub fn user(address_space: *const ProcessAddressSpace, pc: usize, sp: usize, arg: usize) @This() {
const space_physical = address_space.physical_address();
const space_asid = address_space.asid();
var ks = thread.KStack(STACK_SIZE).create();
const ttbr0 = @as(u64, @bitCast(regs.TTBR0_EL1.Bits{
.BADDR = @truncate(space_physical.raw),
.ASID = @truncate(space_asid),
}));
// Arguments to __aa64_task_enter_user
ks.push(pc);
ks.push(arg);
ks.push(0); // Padding
ks.push(sp);
setup_stack_common(&ks, @intFromPtr(&__aa64_task_enter_user));
return .{ .kstack = ks, .ttbr0 = ttbr0 };
}
pub fn kernel(function: *const thread.KernelThreadFn, arg: usize) Context {
var ks = thread.KStack(STACK_SIZE).create();
// Arguments to __aa64_task_enter_kernel
ks.push(@intFromPtr(&thread.kernel_return));
ks.push(@intFromPtr(function));
ks.push(arg);
setup_stack_common(&ks, @intFromPtr(&__aa64_task_enter_kernel));
return Context{ .kstack = ks };
}
pub fn enter(self: *Context) noreturn {
self.load_state();
__aa64_enter_task(self);
}
pub fn switch_from(self: *Context, from: *Context) void {
from.store_state();
self.load_state();
__aa64_switch_task(self, from);
}
pub fn load_state(self: *Context) void {
regs.TTBR0_EL1.set(self.ttbr0);
}
pub fn store_state(self: *Context) void {
_ = self;
}
fn setup_stack_common(ks: *thread.KStack(STACK_SIZE), entry: usize) void {
ks.push(entry); // x30/lr
ks.push(0); // x29
ks.push(0); // x28
@@ -37,16 +90,6 @@ pub const Context = extern struct {
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);
}
};
+23 -6
View File
@@ -3,11 +3,16 @@ const kernel = @import("../../kernel.zig");
const arch = kernel.arch;
const log = kernel.debug.log;
const syscall = kernel.syscall;
extern const __aa64_exception_vectors: u8;
pub const ExceptionFrame = extern struct {
xN: [32]usize,
spsr_el1: usize,
elr_el1: usize,
sp_el0: usize,
_0: usize,
pub fn dump(self: *const ExceptionFrame, comptime level: log.Level) void {
for (0..16) |i| {
@@ -41,6 +46,7 @@ export fn __aa64_el1_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
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(" ESR = 0x{x:016}", .{@as(u64, @bitCast(esr))});
log.err(" ELR = 0x{x:016}", .{elr});
switch (esr.as_enum()) {
@@ -82,8 +88,21 @@ export fn __aa64_el1_serror_handler(frame: *ExceptionFrame) callconv(.C) void {
// EL0
export fn __aa64_el0_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
// TODO EL0
_ = frame;
const esr = regs.ESR_EL1.read();
switch (esr.as_enum()) {
.svc => {
syscall.syscall_handler(frame.xN[8], frame.xN[0..6]);
return;
},
else => {},
}
log.err("Unhandled exception in EL0:", .{});
log.err(" EC = {s} (0b{b:06}) ISS = 0x{x}", .{ esr.EC.as_str(), @intFromEnum(esr.EC), esr.ISS });
log.err(" ESR = 0x{x:016}", .{@as(u64, @bitCast(esr))});
log.err(" ELR = 0x{x:016}", .{frame.elr_el1});
frame.dump(log.Level.err);
arch.halt();
}
@@ -93,14 +112,12 @@ export fn __aa64_el0_irq_handler(frame: *ExceptionFrame) callconv(.C) void {
export fn __aa64_el0_fiq_handler(frame: *ExceptionFrame) callconv(.C) void {
_ = frame;
// TODO I've never used FIQ
arch.halt();
@panic("__aa64_el0_fiq_handler");
}
export fn __aa64_el0_serror_handler(frame: *ExceptionFrame) callconv(.C) void {
_ = frame;
// TODO
arch.halt();
@panic("__aa64_el0_serror_handler");
}
comptime {
+18 -6
View File
@@ -6,6 +6,8 @@ fn Register(comptime name: []const u8, comptime bits: type) type {
else => bits,
};
return enum(repr) {
pub const Bits = bits;
pub fn set(value: repr) void {
asm volatile ("msr " ++ name ++ ", %[value]"
:
@@ -34,8 +36,15 @@ fn Register(comptime name: []const u8, comptime bits: type) type {
};
}
pub const TTBR0_EL1 = Register("ttbr0_el1", u64);
pub const TTBR1_EL1 = Register("ttbr1_el1", u64);
pub const TTBR = packed struct(u64) {
// 0..48
BADDR: u48 = 0,
// 48..64
ASID: u16 = 0,
};
pub const TTBR0_EL1 = Register("ttbr0_el1", TTBR);
pub const TTBR1_EL1 = Register("ttbr1_el1", TTBR);
// NOTE: tpidr_el0 is used until codegen can emit TLS instructions against tpidr_el1
pub const TPIDR_EL0 = Register("tpidr_el0", u64);
@@ -67,6 +76,7 @@ pub const ESR_EL1 = Register("esr_el1", packed struct(u64) {
// 26..32
EC: enum(u6) {
unknown = 0b000000,
svc = 0b010101,
data_abort_lower_el = 0b100100,
data_abort_same_el = 0b100101,
sp_align = 0b100110,
@@ -136,14 +146,16 @@ pub const ESR_EL1 = Register("esr_el1", packed struct(u64) {
pub const AsEnum = union(enum) {
data_abort: DataAbort,
svc,
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,
}
return switch (self.EC) {
.data_abort_lower_el, .data_abort_same_el => .{ .data_abort = @bitCast(self.ISS) },
.svc => .svc,
else => .other,
};
}
});
+55 -4
View File
@@ -2,11 +2,14 @@
// 32 general-purpose registers
.set EXC_GP_SIZE, (32 * 8)
.set EXC_STATE_SIZE, (EXC_GP_SIZE)
// 4 special-purpose registers
.set EXC_SP_SIZE, (4 * 8)
.set EXC_STATE_SIZE, (EXC_GP_SIZE + EXC_SP_SIZE)
.macro EXC_SAVE_STATE
sub sp, sp, #EXC_STATE_SIZE
// General-purpose block
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
@@ -23,6 +26,45 @@
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
stp x30, x31, [sp, #16 * 15]
// Special-purpose block
mrs x0, spsr_el1
mrs x1, elr_el1
mrs x2, sp_el0
mov x3, xzr // Padding
stp x0, x1, [sp, #EXC_GP_SIZE + 16 * 0]
stp x2, x3, [sp, #EXC_GP_SIZE + 16 * 1]
.endm
.macro EXC_RESTORE_STATE
// Special-purpose block
ldp x0, x1, [sp, #EXC_GP_SIZE + 16 * 0]
ldp x2, x3, [sp, #EXC_GP_SIZE + 16 * 1]
msr spsr_el1, x0
msr elr_el1, x1
msr sp_el0, x2
// General-purpose block
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp x18, x19, [sp, #16 * 9]
ldp x20, x21, [sp, #16 * 10]
ldp x22, x23, [sp, #16 * 11]
ldp x24, x25, [sp, #16 * 12]
ldp x26, x27, [sp, #16 * 13]
ldp x28, x29, [sp, #16 * 14]
ldp x30, x31, [sp, #16 * 15]
add sp, sp, #EXC_STATE_SIZE
.endm
// Exception vector size is 0x80
@@ -38,13 +80,22 @@ __aa\bits\()_el\el\ht\()_\kind:
// TODO taking exceptions from EL0t 32-bit
b .
.endif
EXC_SAVE_STATE
dsb ish
isb sy
mov x0, sp
mov lr, xzr
bl __aa64_el\el\()_\kind\()_handler
// TODO exception return
b .
EXC_RESTORE_STATE
ic iallu
dsb ishst
isb sy
eret
.size __aa\bits\()_el\el\ht\()_\kind, . - __aa\bits\()_el\el\ht\()_\kind
.endm
+153 -17
View File
@@ -1,13 +1,20 @@
const std = @import("std");
const mem = @import("../../mem.zig");
const regs = @import("regs.zig");
const kernel = @import("../../kernel.zig");
const PhysicalAddress = mem.PhysicalAddress;
const AtomicU8 = std.atomic.Value(u8);
const log = kernel.log;
pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFF8000000000;
pub const KERNEL_L1_INDEX: usize = L1.index(KERNEL_VIRTUAL_BASE);
pub const KERNEL_VIRTUAL_SIZE: usize = 16 * L1.SIZE;
pub const VIRTUALIZE_BASE: usize = KERNEL_VIRTUAL_BASE + KERNEL_VIRTUAL_SIZE;
pub const VIRTUALIZE_BASE_L1I: usize = L1.index(VIRTUALIZE_BASE);
pub const L1 = mem.TranslationLevel(30);
pub const L2 = mem.TranslationLevel(21);
pub const L1 = mem.TranslationLevel(30, L2);
pub const L2 = mem.TranslationLevel(21, L3);
pub const L3 = mem.vmm.L3;
pub const RawEntry = packed struct(u64) {
@@ -135,31 +142,153 @@ pub fn Table(comptime Level: type) type {
return struct {
pub const Entry = TableEntry(Level);
pub const Error = mem.vmm.AddressSpaceError;
entries: [512]Entry align(4096) = [_]Entry{.INVALID} ** 512,
pub fn allocate_empty() Error!*@This() {
const page = mem.phys.alloc_page() orelse return error.out_of_pages;
const table = @as(*@This(), @ptrFromInt(page.virtualize()));
for (0..512) |i| {
table.entry(i).* = .INVALID;
}
return table;
}
pub fn from_physical_address(physical: PhysicalAddress) *@This() {
return @ptrFromInt(physical.virtualize());
}
pub inline fn entry(self: *@This(), index: usize) *Entry {
return &self.entries[index];
}
pub fn physical_address(self: *const @This()) PhysicalAddress {
return PhysicalAddress.from_virtualized(@intFromPtr(self));
}
pub usingnamespace if (Level.NextLevel) |NextLevel| struct {
pub fn get_next_level(self: *Table(Level), index: usize) ?*Table(NextLevel) {
const ent = self.entry(index);
if (ent.raw.V and !ent.raw.P) {
return Table(NextLevel).from_physical_address(ent.address());
}
return null;
}
pub fn get_or_create_next_level(self: *Table(Level), index: usize) Error!*Table(NextLevel) {
const ent = self.entry(index);
if (ent.raw.V) {
if (!ent.raw.P) {
@panic("TODO: mixed hugepages and tables");
}
// Entry is a table
return Table(NextLevel).from_physical_address(ent.address());
} else {
const table = try Table(NextLevel).allocate_empty();
const physical = table.physical_address();
ent.* = TableEntry(Level).table(physical, .{});
return table;
}
}
} else struct {};
};
}
// 0x0000_0000_0000_0000 .. 0x0000_0080_0000_0000
var g_fixed_low = Table(L1){};
pub const ProcessAddressSpace = struct {
l1: *Table(L1),
asid: u8,
pub const Error = mem.vmm.AddressSpaceError;
var g_asid: AtomicU8 = .{ .raw = 1 };
pub fn init() Error!ProcessAddressSpace {
const table = try Table(L1).allocate_empty();
const asid = g_asid.fetchAdd(1, .seq_cst);
return .{ .l1 = table, .asid = asid };
}
pub fn physical_address(self: *const @This()) PhysicalAddress {
return self.l1.physical_address();
}
pub fn map_page(self: *@This(), virtual: usize, physical: PhysicalAddress) Error!void {
// TODO align check on both virtual and physical
const l1i = L1.index(virtual);
const l2i = L2.index(virtual);
const l3i = L3.index(virtual);
const l2 = try self.l1.get_or_create_next_level(l1i);
const l3 = try l2.get_or_create_next_level(l2i);
const entry = l3.entry(l3i);
if (entry.raw.V) {
@panic("TODO: handle already present");
}
entry.* = TableEntry(L3).normal_page(physical, RawEntry{ .AP = .both_readwrite, .NG = true });
tlb_flush_vma_asid(virtual, self.asid);
log.debug("Map 0x{x} -> page 0x{x}", .{ virtual, physical.raw });
}
};
pub inline fn tlb_flush_vma(vma: usize) void {
const xt = vma >> 12;
asm volatile (
\\ dsb ishst
\\ tlbi vaae1, %[xt]
\\ dsb ish
\\ isb sy
:
: [xt] "r" (xt),
: "memory"
);
}
pub inline fn tlb_flush_vma_asid(vma: usize, asid: usize) void {
const xt = (vma >> 12) | (asid << 48);
asm volatile (
\\ dsb ishst
\\ tlbi vae1, %[xt]
\\ dsb ish
\\ isb sy
:
: [xt] "r" (xt),
: "memory"
);
}
pub inline fn tlb_flush_asid(asid: usize) void {
const xt = asid << 48;
asm volatile (
\\ dsb ishst
\\ tlbi aside1, %[xt]
\\ dsb ish
\\ isb sy
:
: [xt] "r" (xt),
: "memory"
);
}
// 0xFFFF_FF80_0000_0000 .. 0xFFFF_FFFF_FFFF_FFFF
var g_fixed_high = Table(L1){};
pub fn unmap_early() void {
// Flush whole ASID 0
tlb_flush_asid(0);
regs.TTBR0_EL1.set(0);
}
pub fn map_early(real_address: usize) void {
_ = real_address;
for (0..16) |i| {
// Identity
g_fixed_low.entry(i).* = TableEntry(L1).normal_block(
.{ .raw = i << L1.SHIFT },
.{},
);
}
for (0..16) |i| {
for (0..L1.page_count(KERNEL_VIRTUAL_SIZE)) |i| {
// Identity + KERNEL_VIRTUAL_BASE
g_fixed_high.entry(i).* = TableEntry(L1).normal_block(
.{ .raw = i << L1.SHIFT },
@@ -167,11 +296,18 @@ pub fn map_early(real_address: usize) void {
);
}
const ttbr0 = @intFromPtr(&g_fixed_low);
const ttbr1 = @intFromPtr(&g_fixed_high);
for (0..16) |i| {
// Identity + VIRTUALIZE_BASE for "Whole RAM mapping"
g_fixed_high.entry(VIRTUALIZE_BASE_L1I + i).* = TableEntry(L1).normal_block(
.{ .raw = i << L1.SHIFT },
.{},
);
}
regs.TTBR0_EL1.set(ttbr0);
regs.TTBR1_EL1.set(ttbr1);
const ttbr = @intFromPtr(&g_fixed_high);
regs.TTBR0_EL1.write(.{ .BADDR = @truncate(ttbr) });
regs.TTBR1_EL1.write(.{ .BADDR = @truncate(ttbr) });
regs.TCR_EL1.write(.{
.AS = .asid_8bit,
+6
View File
@@ -12,6 +12,12 @@ export const _ = boot.rv64_bsp_lower_entry;
/// This CPU's HART (HARdware Thread) ID.
pub threadlocal var t_hart_id: u32 = 0;
// Linked as .tdata.assembly to ensure `tp == &this`
pub export threadlocal var t_tdata_assembly: extern struct {
kernel_stack_pointer: usize, // tp + 0x00
user_stack_pointer: usize, // tp + 0x08
} linksection(".tdata.assembly") = undefined;
/// RISC-V task context
pub const Context = @import("riscv64/context.zig").Context;
+6 -1
View File
@@ -37,9 +37,12 @@ fn bsp_upper_entry(real_address: usize, unused: usize) callconv(.C) noreturn {
exception.init();
debug.log.set_write_fn(&sbi.debug_print_byte);
kernel.mem.PhysicalAddress.g_virtualize_base = 0;
kernel.mem.PhysicalAddress.g_virtualize_base = vmm.VIRTUALIZE_BASE;
kernel.mem.PhysicalAddress.g_virtualize_size = vmm.virtualize_range();
// Enable supervisor access to user memory
regs.SSTATUS.modify(.{ .SUM = true }, .{});
// Setup physical memory management
setup_memory_from_fdt(real_address);
@@ -55,6 +58,8 @@ pub export fn rv64_bsp_lower_entry(real_address: usize, bsp_hart_id: usize, dtb_
g_dtb_address = dtb_address;
g_bsp_hart_id = @truncate(bsp_hart_id);
vmm.g_kernel_real_base = real_address;
vmm.map_early(real_address);
// &bspUpperEntry will yield a pointer like: X + P, where
+40 -4
View File
@@ -4,6 +4,7 @@
.global __rv64_enter_task
.global __rv64_switch_task
.global __rv64_task_enter_user
.global __rv64_task_enter_kernel
.macro LOAD_TASK_STATE
@@ -44,13 +45,48 @@
sd s0, 13 * 8(sp)
.endm
.set SSTATUS_SPP, (1 << 8)
.set SSTATUS_SPIE, (1 << 5)
__rv64_task_enter_user:
csrw sscratch, tp
// TODO setup user thread pointer
ld a0, (sp) // argument
ld ra, 16(sp) // entry
ld sp, 8(sp) // stack
mv tp, zero
// Clear SPP to zero to indicate a return to U-mode
li t1, SSTATUS_SPP
not t1, t1
csrr t0, sstatus
// TODO enable interrupts via SPIE
// ori t0, t0, SSTATUS_SPIE
and t0, t0, t1
csrw sstatus, t0
csrw sepc, ra
sret
__rv64_task_enter_kernel:
ld a0, (sp) // argument
ld ra, 8(sp) // entry
addi sp, sp, 16
ld t0, 8(sp) // entry
ld ra, 16(sp) // return address
addi sp, sp, 24
// TODO S-mode -> S-mode return via sret
ret
// Set SPP to indicate a return to S-mode
csrr t1, sstatus
// TODO enable interrupts via SPIE
// ori t0, t0, SSTATUS_SPIE
ori t1, t1, SSTATUS_SPP
csrw sstatus, t1
csrw sepc, t0
csrw sscratch, zero
sret
__rv64_switch_task:
// a0 - new context
+62 -20
View File
@@ -1,12 +1,17 @@
const thread = @import("../../thread.zig");
const mem = @import("../../mem.zig");
const kernel = @import("../../kernel.zig");
const regs = @import("regs.zig");
const vmm = @import("vmm.zig");
const riscv64 = @import("../riscv64.zig");
fn idle_function() callconv(.naked) noreturn {
asm volatile ("j .");
}
const ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
const log = kernel.log;
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;
extern fn __rv64_task_enter_user() callconv(.C) noreturn;
pub const Context = extern struct {
const STACK_SIZE: usize = 8192;
@@ -14,20 +19,69 @@ pub const Context = extern struct {
// Has to be exactly at offset 0x00, used in assembly.
kstack: thread.KStack(STACK_SIZE),
satp: u64 = 0,
/// Constructs an idle context struct.
pub fn idle() @This() {
const entry = @intFromPtr(&idle_function);
return Context.kernel(entry, 0);
return Context.kernel(&thread.idle_function, 0);
}
pub fn user(address_space: *const ProcessAddressSpace, pc: usize, sp: usize, arg: usize) @This() {
const space_physical = address_space.physical_address();
const space_asid = address_space.asid();
const satp = regs.SATP.Bits{ .PPN = @truncate(space_physical.raw >> 12), .ASID = @truncate(space_asid), .MODE = .sv39 };
var ks = thread.KStack(STACK_SIZE).create();
ks.push(pc);
ks.push(sp);
ks.push(arg);
setup_stack_common(&ks, @intFromPtr(&__rv64_task_enter_user));
return .{ .kstack = ks, .satp = @bitCast(satp) };
}
/// Constructs a kernel task context with entry point in `pc` and an `arg`ument.
pub fn kernel(pc: usize, arg: usize) @This() {
pub fn kernel(function: *const thread.KernelThreadFn, arg: usize) @This() {
var ks = thread.KStack(STACK_SIZE).create();
const entry = @intFromPtr(&__rv64_task_enter_kernel);
ks.push(pc);
const table_physical = vmm.kernel_table_physical();
const satp = regs.SATP.Bits{ .PPN = @truncate(table_physical >> 12), .MODE = .sv39 };
ks.push(@intFromPtr(&thread.kernel_return));
ks.push(@intFromPtr(function));
ks.push(arg);
setup_stack_common(&ks, @intFromPtr(&__rv64_task_enter_kernel));
return .{ .kstack = ks, .satp = @bitCast(satp) };
}
/// Low-level task context entry function.
pub fn enter(self: *@This()) noreturn {
self.load_state();
__rv64_enter_task(self);
}
/// Low-level task context switch function.
pub fn switch_from(self: *@This(), from: *@This()) void {
from.store_state();
self.load_state();
__rv64_switch_task(self, from);
}
fn load_state(self: *@This()) void {
riscv64.t_tdata_assembly.kernel_stack_pointer = @intFromPtr(self.kstack.sp);
regs.SATP.set(self.satp);
}
fn store_state(self: *@This()) void {
_ = self;
}
fn setup_stack_common(ks: *thread.KStack(STACK_SIZE), entry: usize) void {
ks.push(0); // x8/s0/fp
ks.push(0); // x9/s1
ks.push(0); // x18/s2
@@ -42,18 +96,6 @@ pub const Context = extern struct {
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);
}
};
+37 -8
View File
@@ -1,8 +1,9 @@
const regs = @import("regs.zig");
const debug = @import("../../debug.zig");
const arch = @import("../../kernel.zig").arch;
const kernel = @import("../../kernel.zig");
const log = debug.log;
const syscall = kernel.syscall;
const arch = kernel.arch;
const log = kernel.log;
extern fn __rv64_exception_vectors() void;
@@ -51,6 +52,13 @@ pub const ExceptionFrame = extern struct {
sN: [12]usize,
aN: [8]usize,
umode_sp: usize,
sstatus: usize,
sepc: usize,
stval: usize,
scause: usize,
sscratch: usize,
pub fn dump(self: *const @This(), comptime level: log.Level) void {
log.writeln(level, " ra = 0x{x:016} gp = 0x{x:016}", .{ self.ra, self.gp });
log.writeln(level, " t0 = 0x{x:016} t1 = 0x{x:016}", .{ self.tN[0], self.tN[1] });
@@ -78,17 +86,38 @@ pub fn init() void {
}
export fn rv64SmodeTrapGeneral(frame: *ExceptionFrame) callconv(.C) void {
const scause = regs.SCAUSE.read();
// const scause = regs.SCAUSE.read();
const scause = @as(regs.SCAUSE.Bits, @bitCast(frame.scause));
if (scause.INTERRUPT) {
return rv64SmodeTrapInterrupt(frame);
}
const sstatus = @as(regs.SSTATUS.Bits, @bitCast(frame.sstatus));
const cause = @as(ExceptionCause, @enumFromInt(scause.CODE));
const epc = regs.SEPC.get();
const tval = regs.STVAL.get();
log.err("S-mode exception:", .{});
const is_umode = !sstatus.SPP;
switch (cause) {
.ecall_umode => {
// TODO assert that `is_umode`
const func = frame.aN[0];
const args = frame.aN[1..7];
const result = syscall.syscall_handler(func, args);
frame.aN[0] = result;
// Add size of `ecall` instruction to return epc to execute the next instruction
// instead of falling back to the same `ecall` instruction that caused this
// interrupt.
frame.sepc += 4;
return;
},
else => {},
}
const mode_str = if (is_umode) "U-mode" else "S-mode";
log.err("{s} exception:", .{mode_str});
log.err(" Cause: {s} (0x{x})", .{ cause.name(), scause.CODE });
log.err(" stval = 0x{x:016} sepc = 0x{x:016}", .{ tval, epc });
log.err(" stval = 0x{x:016} sepc = 0x{x:016}", .{ frame.stval, frame.sepc });
frame.dump(.err);
@panic("Unhandled exception in S-mode");
+2
View File
@@ -4,6 +4,8 @@ fn Register(comptime name: []const u8, comptime bits: type) type {
else => bits,
};
return enum(repr) {
pub const Bits = bits;
pub fn set(value: repr) void {
asm volatile ("csrw " ++ name ++ ", %[value]"
:
+109 -9
View File
@@ -3,8 +3,17 @@
.extern rv64SmodeTrapInterrupt
// ra, gp, 7×tN, 12×sN, 8×aN
.set GP_REGS_SIZE, (2 + 7 + 12 + 8) * 8
.set TRAP_CONTEXT_SIZE, (GP_REGS_SIZE)
.set GP_REGS_SIZE, ((2 + 7 + 12 + 8) * 8)
// U-mode sp, sstatus, sepc, stval, scause, sscratch
.set SP_REGS_SIZE, (6 * 8)
.set TRAP_CONTEXT_SIZE, (GP_REGS_SIZE + SP_REGS_SIZE)
.set REG_UMODE_SP, (GP_REGS_SIZE + 0 * 8)
.set REG_SSTATUS, (GP_REGS_SIZE + 1 * 8)
.set REG_SEPC, (GP_REGS_SIZE + 2 * 8)
.set REG_STVAL, (GP_REGS_SIZE + 3 * 8)
.set REG_SCAUSE, (GP_REGS_SIZE + 4 * 8)
.set REG_SSCRATCH, (GP_REGS_SIZE + 5 * 8)
.macro SAVE_GP_REGS
sd ra, 0 * 8(sp)
@@ -41,19 +50,110 @@
sd a7, 28 * 8(sp)
.endm
.macro RESTORE_GP_REGS
ld ra, 0 * 8(sp)
ld gp, 1 * 8(sp)
ld t0, 2 * 8(sp)
ld t1, 3 * 8(sp)
ld t2, 4 * 8(sp)
ld t3, 5 * 8(sp)
ld t4, 6 * 8(sp)
ld t5, 7 * 8(sp)
ld t6, 8 * 8(sp)
ld s0, 9 * 8(sp)
ld s1, 10 * 8(sp)
ld s2, 11 * 8(sp)
ld s3, 12 * 8(sp)
ld s4, 13 * 8(sp)
ld s5, 14 * 8(sp)
ld s6, 15 * 8(sp)
ld s7, 16 * 8(sp)
ld s8, 17 * 8(sp)
ld s9, 18 * 8(sp)
ld s10, 19 * 8(sp)
ld s11, 20 * 8(sp)
ld a0, 21 * 8(sp)
ld a1, 22 * 8(sp)
ld a2, 23 * 8(sp)
ld a3, 24 * 8(sp)
ld a4, 25 * 8(sp)
ld a5, 26 * 8(sp)
ld a6, 27 * 8(sp)
ld a7, 28 * 8(sp)
.endm
.set TPREL_KERNEL_STACK, (0 * 8)
.set TPREL_USER_STACK, (1 * 8)
.set SSTATUS_SPP, (1 << 8)
.macro SMODE_TRAP n, handler
.type __rv64_smode_trap_\n, @function
__rv64_smode_trap_\n:
// TODO properly handle traps coming from U-mode
// TODO save CSRs
addi sp, sp, -(TRAP_CONTEXT_SIZE)
SAVE_GP_REGS
mv a0, sp
// If coming from U-mode, sscratch = kernel-mode tp
// If coming from S-mode, sscratch = 0
csrrw tp, sscratch, tp
bnez tp, 1f
// Coming from S-mode
sd sp, TPREL_KERNEL_STACK(tp) // tdata_assembly.kernel_stack = sp
// [fallthrough]
1:
// Coming from U-mode
sd sp, TPREL_USER_STACK(tp) // tdata_assembly.user_stack = sp
ld sp, TPREL_KERNEL_STACK(tp) // sp = tdata_assembly.kernel_stack
// Store pre-trap context
addi sp, sp, -(TRAP_CONTEXT_SIZE)
SAVE_GP_REGS
// Save special-purpose registers
ld t0, TPREL_USER_STACK(tp)
csrr t1, sstatus
csrr t2, sepc
csrr t3, stval
csrr t4, scause
csrr t5, sscratch
sd t0, REG_UMODE_SP (sp)
sd t1, REG_SSTATUS (sp)
sd t2, REG_SEPC (sp)
sd t3, REG_STVAL (sp)
sd t4, REG_SCAUSE (sp)
sd t5, REG_SSCRATCH (sp)
// Reset sscratch to zero to make sure a nested S-mode -> S-mode exception
// happens properly
csrw sscratch, zero
mv a0, sp
call \handler
// TODO return from exception
j .
// Return from exception
ld t0, REG_SSTATUS (sp)
andi t0, t0, SSTATUS_SPP
bnez t0, 2f
// sstatus.SPP == 0, return to U-mode
// Restore sscratch to its proper value
csrw sscratch, tp
// [fallthrough]
2:
// sstatus.SPP == 1, return to S-mode
ld t0, REG_SSTATUS (sp)
ld t1, REG_SEPC (sp)
csrw sstatus, t0
csrw sepc, t1
// Restore general-purpose registers
RESTORE_GP_REGS
ld tp, REG_SSCRATCH (sp)
ld sp, REG_UMODE_SP (sp)
sret
.size __rv64_smode_trap_\n, . - __rv64_smode_trap_\n
.endm
+135 -14
View File
@@ -1,17 +1,23 @@
const std = @import("std");
const sync = @import("../../sync.zig");
const regs = @import("regs.zig");
const mem = @import("../../mem.zig");
const arch = @import("../../kernel.zig").arch;
const kernel = @import("../../kernel.zig");
const log = kernel.log;
const arch = kernel.arch;
const PhysicalAddress = mem.PhysicalAddress;
const AtomicU8 = std.atomic.Value(u8);
pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFFF000000000;
pub const KERNEL_VIRTUAL_L1I: usize = (KERNEL_VIRTUAL_BASE >> L1.SHIFT) & 511;
pub const VIRTUALIZE_BASE: usize = KERNEL_VIRTUAL_BASE + L1.SIZE;
pub const VIRTUALIZE_BASE_L1I: usize = L1.index(VIRTUALIZE_BASE);
// 16 GiB
const EARLY_MAPPING_SIZE: usize = 16;
pub const L1 = mem.TranslationLevel(30);
pub const L2 = mem.TranslationLevel(21);
pub const L1 = mem.TranslationLevel(30, L2);
pub const L2 = mem.TranslationLevel(21, L3);
pub const L3 = mem.vmm.L3;
pub const RawEntry = packed struct(u64) {
@@ -45,15 +51,19 @@ pub const RawEntry = packed struct(u64) {
}
pub fn clear(self: *@This(), mask: @This()) void {
const lhs = @as(*u64, @bitCast(self));
const lhs = @as(*u64, @ptrCast(self));
const rhs = @as(u64, @bitCast(mask));
lhs.* &= ~rhs;
}
pub fn is_table(self: @This()) bool {
return !self.r and !self.w and !self.x;
}
};
pub fn TableEntry(comptime Level: type) type {
_ = Level;
return struct {
return packed struct(u64) {
raw: RawEntry,
pub const INVALID: @This() = .{ .raw = .{} };
@@ -83,8 +93,9 @@ pub fn TableEntry(comptime Level: type) type {
}
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
flags.clear(.{ .r = true, .w = true, .x = true });
return .{ .raw = flags.make_union(.{
var f = flags;
f.clear(.{ .r = true, .w = true, .x = true });
return .{ .raw = f.make_union(.{
.address = @as(u39, @intCast(addr.raw >> 12)),
.v = true,
}) };
@@ -93,37 +104,140 @@ pub fn TableEntry(comptime Level: type) type {
}
pub fn Table(comptime Level: type) type {
return struct {
return extern struct {
pub const Entry = TableEntry(Level);
entries: [512]Entry align(4096),
pub const Error = mem.vmm.AddressSpaceError;
pub fn empty() @This() {
return .{ .entries = [_]Entry{.INVALID} ** 512 };
}
pub fn from_physical_address(physical: PhysicalAddress) *@This() {
return @ptrFromInt(physical.virtualize());
}
pub fn allocate_empty() Error!*@This() {
const page = mem.phys.alloc_page() orelse return error.out_of_pages;
const table = @as(*@This(), @ptrFromInt(page.virtualize()));
for (0..512) |i| {
table.entry(i).* = .INVALID;
}
return table;
}
pub fn physical_address(self: *const @This()) PhysicalAddress {
return PhysicalAddress.from_virtualized(@intFromPtr(self));
}
pub inline fn entry(self: *@This(), index: usize) *Entry {
return &self.entries[index];
}
pub usingnamespace if (Level.NextLevel) |NextLevel| struct {
pub fn get_next_level(self: *Table(Level), index: usize) ?*Table(NextLevel) {
const ent = self.entry(index);
if (ent.raw.v and ent.raw.is_table()) {
return Table(NextLevel).from_physical_address(ent.address());
}
return null;
}
pub fn get_or_create_next_level(self: *Table(Level), index: usize) Error!*Table(NextLevel) {
const ent = self.entry(index);
if (ent.raw.v) {
// TODO handle mixed hugepages + tables
if (!ent.raw.is_table()) {
@panic("TODO: handle mixed hugepages and tables");
}
// It is a table
return Table(NextLevel).from_physical_address(ent.address());
} else {
// Allocate a new entry
const table = try Table(NextLevel).allocate_empty();
const physical = table.physical_address();
ent.* = TableEntry(Level).table(physical, .{});
return table;
}
}
} else struct {};
};
}
pub const ProcessAddressSpace = struct {
l1: *Table(L1),
asid: u8,
pub const Error = mem.vmm.AddressSpaceError;
var g_asid: AtomicU8 = .{ .raw = 1 };
pub fn init() Error!ProcessAddressSpace {
const table = try Table(L1).allocate_empty();
// Copy kernel's mappings
for (KERNEL_VIRTUAL_L1I..512) |i| {
table.entry(i).* = g_fixed.entry(i).*;
}
const asid = g_asid.fetchAdd(1, .seq_cst);
return .{ .l1 = table, .asid = asid };
}
pub fn physical_address(self: *const @This()) PhysicalAddress {
return self.l1.physical_address();
}
pub fn map_page(self: *@This(), virtual: usize, physical: PhysicalAddress) Error!void {
// TODO align check on both virtual and physical
const l1i = L1.index(virtual);
const l2i = L2.index(virtual);
const l3i = L3.index(virtual);
const l2 = try self.l1.get_or_create_next_level(l1i);
const l3 = try l2.get_or_create_next_level(l2i);
const entry = l3.entry(l3i);
if (entry.raw.v) {
@panic("TODO: handle already present");
}
entry.* = TableEntry(L3).page(physical, .{
.r = true,
.w = true,
.x = true,
.u = true,
});
flush_vma_asid(virtual, self.asid);
log.debug("Map 0x{x} -> page 0x{x}", .{ virtual, physical.raw });
}
};
var g_fixed = Table(L1).empty();
var g_fixed_lock: sync.Spinlock = .{};
pub var g_kernel_real_base: u64 = undefined;
extern var __kernel_start: u8;
pub fn virtualize_range() usize {
return EARLY_MAPPING_SIZE * L1.SIZE;
}
pub fn kernel_table_physical() u64 {
const address = @as(usize, @intFromPtr(&g_fixed));
const kernel_start = @intFromPtr(&__kernel_start);
return address - kernel_start + g_kernel_real_base;
}
pub fn unmap_early() void {
// Make lower half mappings non-executable
// Unmap lower half
const guard = g_fixed_lock.lock_irqsave();
defer guard.release();
for (0..EARLY_MAPPING_SIZE) |i| {
g_fixed.entry(i).* = .page(
.{ .raw = L1.address(i) },
.{ .r = true, .w = true },
);
g_fixed.entry(i).* = .INVALID;
}
}
@@ -138,13 +252,20 @@ pub fn map_early(real_address: usize) void {
);
}
for (0..EARLY_MAPPING_SIZE) |i| {
g_fixed.entry(i + VIRTUALIZE_BASE_L1I).* = .page(
.{ .raw = L1.address(i) },
.{ .r = true, .w = true },
);
}
// Map 1GiB at KERNEL_VIRTUAL_BASE -> physical 1GiB where the kernel is loaded
g_fixed.entry(KERNEL_VIRTUAL_L1I).* = .page(
.{ .raw = L1.address(real_l1) },
.{ .r = true, .w = true, .x = true },
);
const address = @as(usize, @intFromPtr(&g_fixed));
const address = @intFromPtr(&g_fixed);
regs.SATP.write(.{ .PPN = @intCast(address >> 12), .MODE = .sv39 });
}
+1 -1
View File
@@ -55,7 +55,7 @@ pub const log = struct {
}
/// Write raw byte data into the debugging output.
pub fn write_waw(data: []const u8) void {
pub fn write_raw(data: []const u8) void {
_ = write_wrapper_fn(0, data) catch return;
}
+13 -13
View File
@@ -7,6 +7,7 @@ pub const arena = @import("arena.zig");
pub const thread = @import("thread.zig");
pub const util = @import("util.zig");
pub const sync = @import("sync.zig");
pub const syscall = @import("syscall.zig");
pub const log = debug.log;
pub const vmm = mem.vmm;
@@ -16,13 +17,10 @@ pub const TRACE_PHYSICAL_ALLOCATOR: bool = false;
const std = @import("std");
fn f0(arg: usize) callconv(.C) noreturn {
var c: usize = 0;
while (true) {
f1(arg, c);
c += 1;
thread.yield();
}
const userspace_code = @embedFile("userspace");
fn f0(arg: usize) callconv(.C) void {
log.info("Argument is {}", .{arg});
}
noinline fn f1(arg: usize, c: usize) void {
@@ -38,15 +36,17 @@ noinline fn f1(arg: usize, c: usize) void {
/// * Physical memory must be initialized.
/// * (optional) Logging should be set up.
pub export fn kernel_main() callconv(.C) noreturn {
log.write("\x1B[2J", .{});
var a = arena.Arena.init(256 * 0x1000) orelse @panic("Could not setup kernel arena");
thread.Queue.init_this_cpu(&a);
const pc = @intFromPtr(&f0);
for (0..4) |i| {
const t = thread.Thread.create(&a, pc, i);
thread.enqueue(t);
}
log.info("Userspace code size: {} bytes", .{userspace_code.len});
const t1 = thread.test_create_user_from_code(&a, userspace_code) catch @panic("Could not create test thread");
const t = thread.Thread.create_kernel(&a, &f0, 1234);
thread.enqueue(t);
thread.enqueue(t1);
log.info("Test", .{});
thread.enter();
}
+1 -1
View File
@@ -46,7 +46,7 @@ pub const PhysicalAddress = packed struct(u64) {
///
/// Panics if the virtual address provided is outside of virtualizable memory range.
pub fn from_virtualized(virt: usize) @This() {
if ((virt < g_virtualize_base) || (virt - g_virtualize_base > g_virtualize_size)) {
if (virt < g_virtualize_base or virt - g_virtualize_base > g_virtualize_size) {
@panic("Invalid virtualized physical address");
}
+32 -15
View File
@@ -1,9 +1,8 @@
const std = @import("std");
const Arena = @import("../arena.zig").Arena;
const Range = @import("../util/range.zig").Range;
const Allocator = std.mem.Allocator;
/// Describes a single virtual memory range.
///
/// Used by `VirtualMemoryAllocator` to track allocated/used regions.
@@ -16,12 +15,26 @@ pub const VirtualMemoryRange = struct {
/// Virtual memory allocator implementation.
pub const VirtualMemoryAllocator = struct {
gpa: Allocator,
arena: *Arena,
head: ?*VirtualMemoryRange = null,
outer_range: Range(u64),
/// One of errors returned by the allocation logic + underlying allocator error.
pub const Error = error{ already_exists, invalid_region, cannot_fit } || Allocator.Error;
pub const Error = error{ already_exists, invalid_region, cannot_fit };
pub const DrainIterator = struct {
vma: *VirtualMemoryAllocator,
pub fn next(self: *@This()) ?Range(u64) {
while (self.vma.head) |head| {
self.vma.head = head.next;
const range = head.range;
// TODO free the range
return range;
}
return null;
}
};
/// An iterator over VM regions being freed.
pub const FreeIterator = struct {
@@ -29,7 +42,7 @@ pub const VirtualMemoryAllocator = struct {
vma: *VirtualMemoryAllocator,
current: ?*VirtualMemoryRange,
fn next(self: *@This()) Error!?Range(u64) {
pub fn next(self: *@This()) Error!?Range(u64) {
while (self.current) |n| {
if (n.range.intersect(&self.range)) |xs| {
if (xs.start == n.range.start) {
@@ -46,7 +59,7 @@ pub const VirtualMemoryAllocator = struct {
}
// Free it
self.current = n.next;
self.vma.gpa.destroy(n);
// self.vma.arena.destroy(n);
return xs;
}
@@ -64,8 +77,8 @@ pub const VirtualMemoryAllocator = struct {
return xs;
} else {
// Insert a new node after the current one
const new_node = try self.vma.gpa.create(VirtualMemoryRange);
new_node.* = VirtualMemoryRange {
const new_node = self.vma.arena.create(VirtualMemoryRange);
new_node.* = VirtualMemoryRange{
.range = .{ .start = xs.end(), .len = n.range.end() - xs.end() },
.prev = n,
.next = n.next,
@@ -90,10 +103,10 @@ pub const VirtualMemoryAllocator = struct {
};
/// Creates a new instance of a virtual memory allocator.
pub fn init(gpa: Allocator, outer_range: Range(u64)) @This() {
pub fn init(arena: *Arena, outer_range: Range(u64)) @This() {
return .{
.outer_range = outer_range,
.gpa = gpa,
.arena = arena,
};
}
@@ -108,7 +121,7 @@ pub const VirtualMemoryAllocator = struct {
const gap_before_first = if (self.head) |n| (n.range.start - self.outer_range.start) else self.outer_range.len;
if (gap_before_first >= pfn_count) {
var new_node = try self.gpa.create(VirtualMemoryRange);
var new_node = self.arena.create(VirtualMemoryRange);
new_node.range = .{ .start = self.outer_range.start, .len = pfn_count };
new_node.next = self.head;
@@ -137,7 +150,7 @@ pub const VirtualMemoryAllocator = struct {
if (gap >= pfn_count) {
// Insert after this
const result = n.range.end();
var new_node = try self.gpa.create(VirtualMemoryRange);
var new_node = self.arena.create(VirtualMemoryRange);
new_node.prev = n;
new_node.next = n.next;
new_node.range = .{ .start = result, .len = pfn_count };
@@ -181,7 +194,7 @@ pub const VirtualMemoryAllocator = struct {
node = n.next;
}
var new_node = try self.gpa.create(VirtualMemoryRange);
var new_node = self.arena.create(VirtualMemoryRange);
new_node.range = region;
@@ -203,13 +216,17 @@ pub const VirtualMemoryAllocator = struct {
/// Deallocates (shrinks/truncates) regions intersecting the requested range.
pub fn free(self: *@This(), start_pfn: u64, pfn_count: u64) FreeIterator {
const range = Range(u64) { .start = start_pfn, .len = pfn_count };
return FreeIterator {
const range = Range(u64){ .start = start_pfn, .len = pfn_count };
return FreeIterator{
.current = self.head,
.vma = self,
.range = range,
};
}
pub fn drain(self: *@This()) DrainIterator {
return DrainIterator{ .vma = self };
}
};
test "Inserted entries in vmalloc are properly ordered" {
+64 -2
View File
@@ -1,18 +1,31 @@
//! Platform-independent virtual memory management definitions.
const mem = @import("../mem.zig");
const arena = @import("../arena.zig");
const vmalloc = @import("vmalloc.zig");
const kernel = @import("../kernel.zig");
const sync = @import("../sync.zig");
const arch = kernel.arch;
const log = kernel.log;
const Arena = arena.Arena;
/// Last virtual memory translation level. Always 4KiB on all platforms.
pub const L3 = mem.TranslationLevel(12);
pub const L3 = mem.TranslationLevel(12, null);
/// Page size is 4KiB on all platforms.
pub const PAGE_SIZE: usize = L3.SIZE;
pub const AddressSpaceError = error{
out_of_pages,
} || vmalloc.VirtualMemoryAllocator.Error;
/// 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, comptime Next: ?type) type {
return struct {
pub const SHIFT: usize = shift;
pub const SIZE: usize = 1 << shift;
pub const NextLevel = Next;
pub inline fn index(addr: usize) usize {
return (addr >> shift) & 511;
@@ -43,3 +56,52 @@ pub fn TranslationLevel(comptime shift: usize) type {
}
};
}
pub const ProcessAddressSpace = struct {
inner: arch.vmm.ProcessAddressSpace,
allocator: vmalloc.VirtualMemoryAllocator,
lock: sync.Spinlock,
pub fn init(a: *Arena) AddressSpaceError!ProcessAddressSpace {
// 0x200000..0x600000
const inner = try arch.vmm.ProcessAddressSpace.init();
const allocator = vmalloc.VirtualMemoryAllocator.init(a, .{ .start = 512, .len = 1024 });
return .{ .inner = inner, .allocator = allocator, .lock = .{} };
}
pub fn clear(self: *@This()) void {
var drain = self.allocator.drain();
while (drain.next()) |range| {
log.info("Free range: 0x{x}..0x{x}", .{ range.start * L3.SIZE, range.end() * L3.SIZE });
// TODO unmap/free pages
}
}
pub fn map_single_page(
self: *@This(),
virtual: usize,
physical: mem.PhysicalAddress,
) AddressSpaceError!void {
self.lock.lock();
defer self.lock.release();
try self.allocator.insert(.{ .start = L3.page_number(virtual), .len = 1 });
errdefer {
var it = self.allocator.free(L3.page_number(virtual), 1);
while (it.next() catch unreachable) |n| {
// TODO: inner.unmap_page()
_ = n;
}
}
try self.inner.map_page(virtual, physical);
}
pub fn physical_address(self: *const @This()) mem.PhysicalAddress {
return self.inner.physical_address();
}
pub fn asid(self: *const @This()) u64 {
return self.inner.asid;
}
};
+103
View File
@@ -0,0 +1,103 @@
const mem = @import("mem.zig");
const kernel = @import("kernel.zig");
const thread = @import("thread.zig");
const abi = @import("abi");
const ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
const log = kernel.log;
const Thread = thread.Thread;
pub const PhysicalMemoryObject = struct {
fn send(self: *const PhysicalMemoryObject, caller: *Thread, message: *const [5]usize) usize {
_ = self;
_ = caller;
_ = message;
@panic("TODO: physical memory object messaging");
}
fn sendrecv(self: *const PhysicalMemoryObject, caller: *Thread, message: *[5]usize) usize {
switch (@as(abi.PhysicalMemoryObjectAction, @enumFromInt(message[0]))) {
.ZO_physical_memory_allocate => {
// TODO somehow track ownership of the memory by the process
const pages = mem.phys.alloc_pages(message[1]) orelse {
return 1;
};
log.info("{*}: allocated {} pages: 0x{x}", .{caller, message[1], pages.raw});
message[0] = pages.raw;
return 0;
},
.ZO_physical_memory_free => {
@panic("TODO: ZO_physical_memory_free");
},
else => {
@panic("TODO: invalid message to Physical Memory Object");
}
}
_ = self;
}
};
pub const ProcessObject = struct {
inner: *Thread,
fn send(self: *const ProcessObject, caller: *Thread, message: *const [5]usize) usize {
_ = self;
// TODO define this in IDL/ABI
switch (@as(abi.ProcessObjectAction, @enumFromInt(message[0]))) {
.ZO_process_exit => {
log.info("{*} exited with code 0x{x} ({})", .{caller, message[1], message[1]});
Thread.exit_current();
},
else => {
@panic("TODO: invalid message to Process Object");
}
}
}
fn sendrecv(self: *const ProcessObject, caller: *Thread, message: *[5]usize) usize {
_ = self;
_ = caller;
_ = message;
@panic("TODO: ProcessObject sendrecv()");
}
};
pub const DebugObject = struct {
fn send(self: *const DebugObject, caller: *Thread, message: *const [5]usize) usize {
_ = self;
// Debug has only one message
const text = @as([*]u8, @ptrFromInt(message[0]))[0..message[1]];
log.info("{*}: {s}", .{ caller, text });
return 0;
}
fn sendrecv(self: *const DebugObject, caller: *Thread, message: *[5]usize) usize {
_ = self;
_ = caller;
_ = message;
@panic("TODO: DebugObject sendrecv()");
}
};
pub const Object = union(enum) {
physical_memory: PhysicalMemoryObject,
process: ProcessObject,
debug: DebugObject,
// TODO userspace "object" can be placed here
pub fn send(self: Object, caller: *Thread, message: *const [5]usize) usize {
return switch (self) {
.physical_memory => |physical_memory| physical_memory.send(caller, message),
.process => |process| process.send(caller, message),
.debug => |debug| debug.send(caller, message),
};
}
pub fn sendrecv(self: Object, caller: *Thread, message: *[5]usize) usize {
return switch (self) {
.physical_memory => |physical_memory| physical_memory.sendrecv(caller, message),
.process => |process| process.sendrecv(caller, message),
.debug => |debug| debug.sendrecv(caller, message),
};
}
};
+55
View File
@@ -0,0 +1,55 @@
const kernel = @import("kernel.zig");
const abi = @import("abi");
const thread = kernel.thread;
const Thread = thread.Thread;
const log = kernel.log;
pub const SyscallFn = *const fn (*Thread, *[6]usize) void;
pub const syscall_table: [abi.MAX_SYSCALL]?SyscallFn = make_syscall_table();
fn make_syscall_table() [abi.MAX_SYSCALL]?SyscallFn {
const SC = abi.SyscallNumber;
var array = [_]?SyscallFn{undefined} ** abi.MAX_SYSCALL;
array[@intFromEnum(SC.SYS_send)] = sys_send;
array[@intFromEnum(SC.SYS_recv)] = sys_recv;
array[@intFromEnum(SC.SYS_sendrecv)] = sys_sendrecv;
return array;
}
fn sys_send(cthread: *Thread, frame: *[6]usize) void {
const handle: abi.Handle = @enumFromInt(frame[0]);
const object = cthread.handle(handle) orelse {
@panic("TODO: userspace invoked non-existent handle");
};
frame[0] = object.send(cthread, frame[1..6]);
}
fn sys_recv(cthread: *Thread, frame: *[6]usize) void {
_ = cthread;
_ = frame;
@panic("TODO: SYS_recv()");
}
fn sys_sendrecv(cthread: *Thread, frame: *[6]usize) void {
const handle: abi.Handle = @enumFromInt(frame[0]);
const object = cthread.handle(handle) orelse {
@panic("TODO: userspace invoked non-existent handle");
};
frame[0] = object.sendrecv(cthread, frame[1..6]);
}
fn sys_undefined_syscall(cthread: *Thread, frame: *[6]usize) void {
_ = frame;
log.warn("{*} invoked an undefined syscall", .{cthread});
Thread.exit_current();
}
pub fn syscall_handler(func: usize, frame: *[6]usize) void {
const cthread = thread.Thread.current();
const handler = if (func >= abi.MAX_SYSCALL) sys_undefined_syscall //
else syscall_table[func] //
orelse sys_undefined_syscall;
handler(cthread, frame);
}
+223 -27
View File
@@ -6,6 +6,137 @@ const arena = @import("arena.zig");
const arch = @import("kernel.zig").arch;
const log = @import("debug.zig").log;
const mem = @import("mem.zig");
const sync = @import("sync.zig");
const object = @import("object.zig");
const abi = @import("abi");
const ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
const Object = object.Object;
const Handle = abi.Handle;
// TODO: are kernel threads needed at all if we're doing a microkernel?
/// Signature for kernel thread entry
pub const KernelThreadFn = fn (usize) callconv(.C) void;
pub fn kernel_return() callconv(.C) noreturn {
Thread.exit_current();
}
/// Task to run when there are no real threads in the queue
pub fn idle_function(arg: usize) callconv(.C) noreturn {
_ = arg;
while (true) {
arch.wait_for_interrupt();
}
}
/// Represents a single execution thread.
pub const Thread = struct {
const MAX_HANDLES: usize = 64;
/// Arena.
allocator: *arena.Arena,
/// Architecture-specific task context.
arch_context: arch.Context,
/// Queue to which this thread belongs
queue: ?*Queue = null,
/// Next thread in the queue.
next: ?*Thread = null,
/// Previous thread in the queue.
prev: ?*Thread = null,
// TODO move to process
address_space: ?ProcessAddressSpace = null,
// TODO move to process
handle_table: [MAX_HANDLES]?Object = [_]?Object{null} ** MAX_HANDLES,
pub const Error = error{out_of_memory} || mem.vmm.AddressSpaceError;
/// Creates a new (kernel) thread with given `function` and `arg`ument.
pub fn create_kernel(a: *arena.Arena, function: *const KernelThreadFn, arg: usize) *Thread {
const thread = a.create(Thread);
thread.* = .{
.allocator = a,
.arch_context = arch.Context.kernel(function, arg),
};
return thread;
}
pub fn create_user(
a: *arena.Arena,
address_space: ProcessAddressSpace,
pc: usize,
sp: usize,
arg: usize,
) *Thread {
const thread = a.create(Thread);
thread.* = .{
.allocator = a,
.address_space = address_space,
.arch_context = arch.Context.user(&address_space, pc, sp, arg),
};
// "self" process object is granted to all processes
const process_object = Object{ .process = object.ProcessObject {
.inner = thread,
} };
_ = thread.grant(process_object);
return thread;
}
/// Enters the thread, does not return.
pub fn enter(self: *@This()) noreturn {
self.arch_context.enter();
}
/// Switches from `from` to `self` thread.
pub fn switch_from(self: *@This(), from: *@This()) void {
self.arch_context.switch_from(&from.arch_context);
}
pub fn grant(self: *@This(), obj: Object) Handle {
for (0..MAX_HANDLES) |i| {
if (self.handle_table[i] == null) {
self.handle_table[i] = obj;
return @enumFromInt(i + 1);
}
}
@panic("TODO: ran out of objects");
}
pub fn handle(self: *@This(), h: Handle) ?Object {
const index = @as(usize, @intFromEnum(h));
if (index > MAX_HANDLES or index == 0) {
return null;
}
return self.handle_table[index - 1];
}
pub fn dequeue(self: *@This()) void {
// TODO queueing information should be put under a lock for SMP to work properly
if (self.queue) |q| {
q.dequeue(self);
}
}
pub fn current() *@This() {
return Queue.t_this_cpu.?.current.?;
}
pub fn exit_current() noreturn {
// Mask IRQs so they don't break current thread's state
const mask = arch.IrqGuard.acquire();
defer mask.release();
const curr = Thread.current();
curr.dequeue();
yield();
@panic("This code should not be reachable");
}
};
/// Per-CPU thread queue structure.
pub const Queue = struct {
@@ -15,6 +146,8 @@ pub const Queue = struct {
current: ?*Thread = null,
/// Thread queue head pointer.
head: ?*Thread = null,
/// Queue's lock
lock: sync.Spinlock = .{},
/// Pointer to this CPU's thread queue.
pub threadlocal var t_this_cpu: ?*Queue = null;
@@ -40,6 +173,7 @@ pub const Queue = struct {
/// Yields CPU to the next available task.
pub fn yield(self: *@This()) void {
// TODO locking here
if (self.current) |curr| {
// Switching from thread
if (curr.next) |next| {
@@ -48,6 +182,12 @@ pub const Queue = struct {
self.current = next;
next.switch_from(curr);
}
} else if (self.head) |h| {
// ... to thread (head)
if (h != curr) {
self.current = h;
h.switch_from(curr);
}
} else {
// ... to idle
self.current = null;
@@ -67,6 +207,11 @@ pub const Queue = struct {
/// Adds an available task to this queue.
pub fn enqueue(self: *@This(), t: *Thread) void {
var guard = self.lock.lock_irqsave();
defer guard.release();
t.queue = self;
if (self.head) |gt| {
t.next = gt;
t.prev = gt.prev;
@@ -78,38 +223,43 @@ pub const Queue = struct {
t.prev = t;
}
}
};
/// Represents a single execution thread.
pub const Thread = struct {
/// Arena.
allocator: *arena.Arena,
/// Architecture-specific task context.
arch_context: arch.Context,
/// # Invariants
///
/// `t` must be a thread within the `self` queue.
fn dequeue(self: *@This(), t: *Thread) void {
var guard = self.lock.lock_irqsave();
defer guard.release();
/// Next thread in the queue.
next: ?*Thread = null,
/// Previous thread in the queue.
prev: ?*Thread = null,
t.queue = 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 {
const thread = a.create(Thread);
thread.* = .{
.allocator = a,
.arch_context = arch.Context.kernel(pc, arg),
};
return thread;
}
if (t == self.head) {
if (t.next == t) {
self.head = null;
t.next = null;
t.prev = null;
return;
}
/// Enters the thread, does not return.
pub fn enter(self: *@This()) noreturn {
self.arch_context.enter();
}
if (t.next) |tn| {
tn.prev = t.prev;
}
if (t.prev) |tp| {
tp.next = t.next;
}
/// Switches from `from` to `self` thread.
pub fn switch_from(self: *@This(), from: *@This()) void {
self.arch_context.switch_from(&from.arch_context);
self.head = t.next;
} else {
if (t.next) |tn| {
tn.prev = t.prev;
}
if (t.prev) |tp| {
tp.next = t.next;
}
}
t.next = null;
t.prev = null;
}
};
@@ -170,3 +320,49 @@ pub fn enter() noreturn {
pub fn yield() void {
Queue.t_this_cpu.?.yield();
}
pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Error!*Thread {
const L3 = mem.vmm.L3;
const CODE_BASE: usize = 0x200000;
var address_space = try ProcessAddressSpace.init(a);
errdefer {
address_space.clear();
}
// @ 0x200000
const code_page_count = L3.page_count(code.len);
log.info("Code is {} pages", .{code_page_count});
var offset: usize = 0;
var address: usize = CODE_BASE;
while (offset < code.len) {
const page_offset = address % L3.SIZE;
const amount = @min(L3.SIZE - page_offset, code.len - offset);
const page = mem.phys.alloc_page() orelse return error.out_of_memory;
try address_space.map_single_page(address, page);
const page_data = @as([*]u8, @ptrFromInt(page.virtualize()))[0..amount];
@memcpy(page_data, code[offset .. offset + amount]);
address += amount;
offset += amount;
}
// @ 0x400000
const sp_base = 0x400000;
for (0..8) |i| {
const page = mem.phys.alloc_page() orelse return error.out_of_memory;
try address_space.map_single_page(sp_base + i * L3.SIZE, page);
}
const sp = sp_base + 8 * L3.SIZE;
log.info("Enter with sp = 0x{x}", .{sp});
const thread = Thread.create_user(a, address_space, CODE_BASE, sp, 1234);
_ = thread.grant(Object { .physical_memory = object.PhysicalMemoryObject {} });
_ = thread.grant(Object { .debug = object.DebugObject {} });
return thread;
}
+11 -11
View File
@@ -487,7 +487,7 @@ pub const Fdt = struct {
fn dump_property(property: *const FdtNodeProp, depth: usize, all_strings: bool) void {
for (0..depth) |_| {
log.write_waw(" ");
log.write_raw(" ");
}
log.write("{s}", .{property.name});
@@ -510,9 +510,9 @@ pub const Fdt = struct {
var f = true;
while (v.next()) |s| {
if (f) {
log.write_waw(" = ");
log.write_raw(" = ");
} else {
log.write_waw(", ");
log.write_raw(", ");
}
f = false;
log.write("\"{s}\"", .{s});
@@ -520,17 +520,17 @@ pub const Fdt = struct {
} else {
// Dump the rest as a cell array
const len = property.len_cells();
log.write_waw(" = <");
log.write_raw(" = <");
for (0..len) |i| {
if (i != 0) {
log.write_waw(", ");
log.write_raw(", ");
}
log.write("0x{x}", .{property.get_cell_unchecked(i)});
}
log.write_waw(">");
log.write_raw(">");
}
log.write_waw(";\r\n");
log.write_raw(";\r\n");
}
fn dump_node(node: *const FdtNode, depth: usize) void {
@@ -538,12 +538,12 @@ pub const Fdt = struct {
var first_child = true;
for (0..depth) |_| {
log.write_waw(" ");
log.write_raw(" ");
}
if (node.name.len != 0) {
log.write("{s} ", .{node.name});
}
log.write_waw("{\r\n");
log.write_raw("{\r\n");
var properties = node.prop_iterator();
const all_strings = std.mem.eql(u8, node.name, "aliases");
while (properties.next()) |property| {
@@ -553,13 +553,13 @@ pub const Fdt = struct {
var children = node.children();
while (children.next()) |child| {
if (any_properties and first_child) {
log.write_waw("\r\n");
log.write_raw("\r\n");
}
first_child = false;
dump_node(&child, depth + 1);
}
for (0..depth) |_| {
log.write_waw(" ");
log.write_raw(" ");
}
log.write("}},\r\n", .{});
}
+21 -21
View File
@@ -11,7 +11,7 @@ pub fn RangeMap(
comptime K: type,
comptime V: type,
comptime ops: struct {
deinit_fn: ?fn(*V) void = null,
deinit_fn: ?fn (*V) void = null,
merge_fn: ?fn (*const V, *const V) bool = null,
},
) type {
@@ -244,11 +244,11 @@ test "Range map merging insertion" {
{
var it = map.iterator();
const n0 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(true, n0.value);
const n1 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(false, n1.value);
try std.testing.expectEqual(null, it.next());
@@ -261,19 +261,19 @@ test "Range map merging insertion" {
{
var it = map.iterator();
const n0 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(true, n0.value);
const n1 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(false, n1.value);
const n2 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key);
try std.testing.expectEqual(true, n2.value);
const n3 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 10 }, n3.key);
try std.testing.expectEqual(Range(u32){ .start = 50, .len = 10 }, n3.key);
try std.testing.expectEqual(false, n3.value);
try std.testing.expectEqual(null, it.next());
@@ -286,23 +286,23 @@ test "Range map merging insertion" {
{
var it = map.iterator();
const n0 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(true, n0.value);
const n1 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(false, n1.value);
const n2 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key);
try std.testing.expectEqual(true, n2.value);
const n3 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 20 }, n3.key);
try std.testing.expectEqual(Range(u32){ .start = 50, .len = 20 }, n3.key);
try std.testing.expectEqual(false, n3.value);
const n4 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 71, .len = 9 }, n4.key);
try std.testing.expectEqual(Range(u32){ .start = 71, .len = 9 }, n4.key);
try std.testing.expectEqual(false, n4.value);
try std.testing.expectEqual(null, it.next());
@@ -314,19 +314,19 @@ test "Range map merging insertion" {
{
var it = map.iterator();
const n0 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(true, n0.value);
const n1 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(false, n1.value);
const n2 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key);
try std.testing.expectEqual(true, n2.value);
const n3 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key);
try std.testing.expectEqual(Range(u32){ .start = 50, .len = 30 }, n3.key);
try std.testing.expectEqual(false, n3.value);
try std.testing.expectEqual(null, it.next());
@@ -339,23 +339,23 @@ test "Range map merging insertion" {
{
var it = map.iterator();
const n0 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
try std.testing.expectEqual(true, n0.value);
const n1 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
try std.testing.expectEqual(false, n1.value);
const n2 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key);
try std.testing.expectEqual(true, n2.value);
const n3 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key);
try std.testing.expectEqual(Range(u32){ .start = 50, .len = 30 }, n3.key);
try std.testing.expectEqual(false, n3.value);
const n4 = it.next().?;
try std.testing.expectEqual(Range(u32) { .start = 100, .len = 20 }, n4.key);
try std.testing.expectEqual(Range(u32){ .start = 100, .len = 20 }, n4.key);
try std.testing.expectEqual(false, n4.value);
try std.testing.expectEqual(null, it.next());
+27
View File
@@ -0,0 +1,27 @@
SECTIONS {
. = 0x200000;
.text : {
*(.text.entry*);
*(.text*)
}
.rodata : ALIGN(4K) {
*(.rodata*)
}
.eh_frame_hdr : {
*(.eh_frame_hdr*)
}
.eh_frame : {
*(.eh_frame*)
}
.data : ALIGN(4K) {
*(.data*)
}
.bss : ALIGN(4K) {
*(.bss*)
}
}
+9
View File
@@ -0,0 +1,9 @@
const builtin = @import("builtin");
const impl = switch (builtin.cpu.arch) {
.riscv64 => @import("arch/riscv64.zig"),
.aarch64 => @import("arch/aarch64.zig"),
else => @compileError("Unsupported architecture"),
};
pub const syscall = impl.syscall;
+55
View File
@@ -0,0 +1,55 @@
const abi = @import("abi");
pub const syscall = struct {
pub fn send(handle: u32, msg: *const [5]usize) usize {
return asm volatile ("svc #0"
: [result] "={x0}" (-> usize),
: [a0] "{x0}" (handle),
[a1] "{x1}" (msg[0]),
[a2] "{x2}" (msg[1]),
[a3] "{x3}" (msg[2]),
[a4] "{x4}" (msg[3]),
[a5] "{x5}" (msg[4]),
[func] "{x8}" (@intFromEnum(abi.SyscallNumber.SYS_send)),
: "memory"
);
}
pub fn sendrecv(handle: u32, msg: *const[5]usize, buffer: *[5]usize) usize {
return asm volatile (
\\ svc #0
\\ stp x1, x2, [%[buf], #16 * 0]
\\ stp x3, x4, [%[buf], #16 * 1]
\\ str x5, [%[buf], #16 * 2]
: [result] "={x0}" (-> usize),
: [a0] "{x0}" (handle),
[a1] "{x1}" (msg[0]),
[a2] "{x2}" (msg[1]),
[a3] "{x3}" (msg[2]),
[a4] "{x4}" (msg[3]),
[a5] "{x5}" (msg[4]),
[func] "{x8}" (@intFromEnum(abi.SyscallNumber.SYS_sendrecv)),
[buf] "r" (buffer),
: "memory"
);
}
// pub fn syscall1(func: abi.SyscallNumber, arg0: usize) usize {
// return asm volatile ("svc #0"
// : [result] "={x0}" (-> usize),
// : [arg0] "{x0}" (arg0),
// [func] "{x8}" (@intFromEnum(func)),
// : "memory"
// );
// }
// pub fn syscall2(func: abi.SyscallNumber, arg0: usize, arg1: usize) usize {
// return asm volatile ("svc #0"
// : [result] "={x0}" (-> usize),
// : [arg0] "{x0}" (arg0),
// [arg1] "{x1}" (arg1),
// [func] "{x8}" (@intFromEnum(func)),
// : "memory"
// );
// }
};
+22
View File
@@ -0,0 +1,22 @@
const abi = @import("abi");
pub const syscall = struct {
pub fn syscall1(func: abi.SyscallNumber, arg0: usize) usize {
return asm volatile ("ecall"
: [result] "={a0}" (-> usize),
: [func] "{a0}" (@intFromEnum(func)),
[arg0] "{a1}" (arg0),
: "memory"
);
}
pub fn syscall2(func: abi.SyscallNumber, arg0: usize, arg1: usize) usize {
return asm volatile ("ecall"
: [result] "={a0}" (-> usize),
: [func] "{a0}" (@intFromEnum(func)),
[arg0] "{a1}" (arg0),
[arg1] "{a2}" (arg1),
: "memory"
);
}
};
+28
View File
@@ -0,0 +1,28 @@
const std = @import("std");
const syscall = @import("syscall.zig");
pub const BUFFER_SIZE: usize = 512;
pub const Writer = struct {
buffer: [BUFFER_SIZE]u8 = undefined,
position: usize = 0,
fn write(self: *Writer, bytes: []const u8) error{}!usize {
const amount = @min(BUFFER_SIZE - self.position, bytes.len);
if (amount != 0) {
@memcpy(self.buffer[self.position .. self.position + amount], bytes[0..amount]);
self.position += amount;
}
return amount;
}
};
pub fn println(comptime format: []const u8, args: anytype) void {
const W = std.io.GenericWriter(*Writer, error{}, Writer.write);
var context = Writer{};
var w = W{ .context = &context };
w.print(format, args) catch return;
if (context.position != 0) {
syscall.debug_write(context.buffer[0..context.position]);
}
}
+58
View File
@@ -0,0 +1,58 @@
pub const arch = @import("arch.zig");
pub const syscall = @import("syscall.zig");
pub const log = @import("log.zig");
const abi = @import("abi");
const Handle = abi.Handle;
fn debug_write(handle: Handle, message: []const u8) void {
_ = syscall.send(handle, &.{
@intFromPtr(message.ptr),
message.len,
0,
0,
0,
});
}
fn exit_process(handle: Handle, code: u32) noreturn {
_ = syscall.send(handle, &.{
@intFromEnum(abi.ProcessObjectAction.ZO_process_exit),
code,
0,
0,
0,
});
unreachable;
}
fn allocate_memory(handle: Handle, count: usize) u64 {
var output: [5]usize = undefined;
// TODO error code
_ = syscall.sendrecv(
handle,
&.{
@intFromEnum(abi.PhysicalMemoryObjectAction.ZO_physical_memory_allocate),
count,
0,
0,
0,
},
&output,
);
return output[0];
}
export fn _start(arg: usize) linksection(".text.entry") callconv(.C) noreturn {
// TODO make the kernel provide those dynamically somehow
const SELF_PROCESS_HANDLE: Handle = @enumFromInt(1);
const PHYSICAL_MEMORY_HANDLE: Handle = @enumFromInt(2);
const DEBUG_HANDLE: Handle = @enumFromInt(3);
_ = arg;
const mem = allocate_memory(PHYSICAL_MEMORY_HANDLE, 8);
_ = mem;
debug_write(DEBUG_HANDLE, "Hello!!!");
exit_process(SELF_PROCESS_HANDLE, 4321);
}
+20
View File
@@ -0,0 +1,20 @@
SECTIONS {
. = 0x200000;
.text : {
*(.text.entry*);
*(.text*)
}
.rodata : ALIGN(4K) {
*(.rodata*)
}
.data : ALIGN(4K) {
*(.data*)
}
.bss : ALIGN(4K) {
*(.bss*)
}
}
+25
View File
@@ -0,0 +1,25 @@
const arch = @import("arch.zig");
const abi = @import("abi");
const sc = arch.syscall;
pub inline fn send(object: abi.Handle, msg: *const [5]usize) usize {
return sc.send(@intFromEnum(object), msg);
}
pub inline fn recv(object: abi.Handle, buffer: *[5]usize) usize {
return sc.recv(@intFromEnum(object), buffer);
}
pub inline fn sendrecv(object: abi.Handle, msg: *const [5]usize, buffer: *[5]usize) usize {
return sc.sendrecv(@intFromEnum(object), msg, buffer);
}
// pub fn exit(code: u32) noreturn {
// _ = sc.syscall1(SC.SYS_exit, code);
// unreachable;
// }
//
// pub fn debug_write(text: []const u8) void {
// _ = sc.syscall2(SC.SYS_debug_write, @intFromPtr(text.ptr), text.len);
// }