Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9911c7ea9b | |||
| b77568ca24 | |||
| a377cd68b3 | |||
| ed78052736 | |||
| 32f636b149 | |||
| cb84b24354 |
-21
@@ -1,21 +0,0 @@
|
|||||||
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) { _ };
|
|
||||||
@@ -6,7 +6,7 @@ const SupportedArch = enum {
|
|||||||
aarch64,
|
aarch64,
|
||||||
riscv64,
|
riscv64,
|
||||||
|
|
||||||
fn make_kernel_target(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(.{
|
||||||
@@ -38,47 +38,7 @@ const SupportedArch = enum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_userspace_target(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget {
|
fn add_target_specific(self: SupportedArch, b: *std.Build, kernel: *std.Build.Step.Compile) anyerror!*std.Build.Step {
|
||||||
// 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) {
|
switch (self) {
|
||||||
.riscv64 => {
|
.riscv64 => {
|
||||||
kernel.entry = .{ .symbol_name = "__rv64_entry" };
|
kernel.entry = .{ .symbol_name = "__rv64_entry" };
|
||||||
@@ -101,7 +61,6 @@ const SupportedArch = enum {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel.step.dependOn(user);
|
|
||||||
b.installArtifact(kernel);
|
b.installArtifact(kernel);
|
||||||
|
|
||||||
if (self == .riscv64 or self == .aarch64) {
|
if (self == .riscv64 or self == .aarch64) {
|
||||||
@@ -177,44 +136,29 @@ fn insert_fake_linux_image_header(step: *std.Build.Step, opts: std.Build.Step.Ma
|
|||||||
_ = opts;
|
_ = opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_riscv64(b: *std.Build) anyerror!void {
|
||||||
|
_ = b;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(b: *std.Build) anyerror!void {
|
pub fn build(b: *std.Build) anyerror!void {
|
||||||
const maybe_arch_option = b.option(SupportedArch, "arch", "Architecture to use");
|
const maybe_arch_option = b.option(SupportedArch, "arch", "Architecture to use");
|
||||||
|
|
||||||
const arch = maybe_arch_option orelse DEFAULT_ARCH;
|
const arch = maybe_arch_option orelse DEFAULT_ARCH;
|
||||||
const kernel_target = arch.make_kernel_target(b);
|
const target = arch.make_target(b);
|
||||||
const user_target = arch.make_userspace_target(b);
|
|
||||||
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
||||||
|
|
||||||
const kernel_code_model: std.builtin.CodeModel = switch (arch) {
|
const code_model: std.builtin.CodeModel = switch (arch) {
|
||||||
.riscv64 => .medium,
|
.riscv64 => .medium,
|
||||||
.aarch64 => .small,
|
.aarch64 => .small,
|
||||||
};
|
};
|
||||||
const user_code_model = kernel_code_model;
|
|
||||||
|
|
||||||
const kernel_module = b.addModule("kernel", .{
|
const kernel_module = b.addModule("kernel", .{
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.target = kernel_target,
|
.target = target,
|
||||||
.pic = true,
|
.pic = true,
|
||||||
.red_zone = false,
|
.red_zone = false,
|
||||||
.code_model = kernel_code_model,
|
.code_model = code_model,
|
||||||
.root_source_file = b.path("src/kernel.zig"),
|
.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(.{
|
const kernel = b.addExecutable(.{
|
||||||
.name = "kernel",
|
.name = "kernel",
|
||||||
.root_module = kernel_module,
|
.root_module = kernel_module,
|
||||||
@@ -223,15 +167,6 @@ pub fn build(b: *std.Build) anyerror!void {
|
|||||||
});
|
});
|
||||||
kernel.pie = true;
|
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(.{
|
const install_docs = b.addInstallDirectory(.{
|
||||||
.source_dir = kernel.getEmittedDocs(),
|
.source_dir = kernel.getEmittedDocs(),
|
||||||
.install_dir = .prefix,
|
.install_dir = .prefix,
|
||||||
@@ -241,11 +176,10 @@ pub fn build(b: *std.Build) anyerror!void {
|
|||||||
const docs_step = b.step("docs", "Install documentation");
|
const docs_step = b.step("docs", "Install documentation");
|
||||||
docs_step.dependOn(&install_docs.step);
|
docs_step.dependOn(&install_docs.step);
|
||||||
|
|
||||||
const user_step = try arch.configure_user(b, user);
|
const kernel_step = try arch.add_target_specific(b, kernel);
|
||||||
const kernel_step = try arch.configure_kernel(b, kernel, user_step);
|
|
||||||
|
|
||||||
// TODO QEMU binary override
|
// TODO QEMU binary override
|
||||||
const qemu_info = switch (kernel_target.result.cpu.arch) {
|
const qemu_info = switch (target.result.cpu.arch) {
|
||||||
.riscv64 => .{ "qemu-system-riscv64", "rv64" },
|
.riscv64 => .{ "qemu-system-riscv64", "rv64" },
|
||||||
.aarch64 => .{ "qemu-system-aarch64", "cortex-a72" },
|
.aarch64 => .{ "qemu-system-aarch64", "cortex-a72" },
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
@@ -267,7 +201,7 @@ pub fn build(b: *std.Build) anyerror!void {
|
|||||||
"none",
|
"none",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (kernel_target.result.cpu.arch == .riscv64) {
|
if (target.result.cpu.arch == .riscv64) {
|
||||||
qemu_cmd.addArgs(&.{ "-bios", "etc/boot/rv64_fw_jump.bin" });
|
qemu_cmd.addArgs(&.{ "-bios", "etc/boot/rv64_fw_jump.bin" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ SECTIONS {
|
|||||||
|
|
||||||
.tdata : ALIGN(4K) {
|
.tdata : ALIGN(4K) {
|
||||||
PROVIDE(__tdata_start = .);
|
PROVIDE(__tdata_start = .);
|
||||||
/* Storage for thread-locals used in assembly */
|
|
||||||
KEEP(*(.tdata.assembly*));
|
|
||||||
*(.tdata*)
|
*(.tdata*)
|
||||||
PROVIDE(__tdata_end = .);
|
PROVIDE(__tdata_end = .);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-25
@@ -4,33 +4,10 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
pub const cpu: enum {
|
pub const impl = switch (builtin.cpu.arch) {
|
||||||
riscv64,
|
|
||||||
aarch64,
|
|
||||||
} = switch (builtin.cpu.arch) {
|
|
||||||
.riscv64 => .riscv64,
|
|
||||||
.aarch64 => .aarch64,
|
|
||||||
else => @compileError("Unsupported architecture"),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const impl = switch (cpu) {
|
|
||||||
.riscv64 => @import("arch/riscv64.zig"),
|
.riscv64 => @import("arch/riscv64.zig"),
|
||||||
.aarch64 => @import("arch/aarch64.zig"),
|
.aarch64 => @import("arch/aarch64.zig"),
|
||||||
};
|
else => @compileError("Unsupported architecture"),
|
||||||
|
|
||||||
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.
|
/// Halts the CPU execution indefinitely, without ever returning.
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ const std = @import("std");
|
|||||||
const boot = @import("aarch64/boot.zig");
|
const boot = @import("aarch64/boot.zig");
|
||||||
const regs = @import("aarch64/regs.zig");
|
const regs = @import("aarch64/regs.zig");
|
||||||
|
|
||||||
pub const vmm = @import("aarch64/vmm.zig");
|
|
||||||
|
|
||||||
export const _ = boot.aa64_bsp_lower_entry;
|
export const _ = boot.aa64_bsp_lower_entry;
|
||||||
|
|
||||||
pub const Context = @import("aarch64/context.zig").Context;
|
pub const Context = @import("aarch64/context.zig").Context;
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ extern const __aa64_bsp_stack_top: u8;
|
|||||||
|
|
||||||
var g_dtb_address: u64 = undefined;
|
var g_dtb_address: u64 = undefined;
|
||||||
|
|
||||||
fn early_debug_print_high(byte: u8) void {
|
fn early_debug_print(byte: u8) void {
|
||||||
// TODO this is incorrect: writes should come to a memory region marked as device memory,
|
const address = 0x9000000;
|
||||||
// "virtualize" range is normal memory.
|
|
||||||
const address = 0x9000000 + vmm.VIRTUALIZE_BASE;
|
|
||||||
@as(*volatile u32, @ptrFromInt(address)).* = byte;
|
@as(*volatile u32, @ptrFromInt(address)).* = byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,15 +44,14 @@ fn aa64_bsp_upper_entry(real_address: u64) callconv(.C) noreturn {
|
|||||||
|
|
||||||
arch.barrier(.acq_rel);
|
arch.barrier(.acq_rel);
|
||||||
aa64_relocate_kernel(rel_offset, rela_start, rela_end);
|
aa64_relocate_kernel(rel_offset, rela_start, rela_end);
|
||||||
vmm.unmap_early();
|
|
||||||
arch.barrier(.acq_rel);
|
arch.barrier(.acq_rel);
|
||||||
|
|
||||||
log.set_write_fn(&early_debug_print_high);
|
log.set_write_fn(&early_debug_print);
|
||||||
|
|
||||||
exception.init();
|
exception.init();
|
||||||
|
|
||||||
mem.PhysicalAddress.g_virtualize_base = vmm.VIRTUALIZE_BASE;
|
mem.PhysicalAddress.g_virtualize_base = 0;
|
||||||
mem.PhysicalAddress.g_virtualize_size = 16 << vmm.L1.SHIFT;
|
mem.PhysicalAddress.g_virtualize_size = 16 << 30;
|
||||||
|
|
||||||
setup_memory_from_fdt(real_address);
|
setup_memory_from_fdt(real_address);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
.global __aa64_enter_task
|
.global __aa64_enter_task
|
||||||
.global __aa64_switch_task
|
.global __aa64_switch_task
|
||||||
.global __aa64_task_enter_kernel
|
.global __aa64_task_enter_kernel
|
||||||
.global __aa64_task_enter_user
|
|
||||||
|
|
||||||
.set CONTEXT_SIZE, (12 * 8)
|
.set CONTEXT_SIZE, (12 * 8)
|
||||||
|
|
||||||
@@ -29,51 +28,14 @@
|
|||||||
|
|
||||||
.pushsection .text
|
.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:
|
__aa64_task_enter_kernel:
|
||||||
// arg, entry
|
// arg, entry
|
||||||
ldp x0, x1, [sp]
|
ldp x0, lr, [sp]
|
||||||
// return address
|
add sp, sp, #16
|
||||||
ldr lr, [sp, #16]
|
|
||||||
add sp, sp, #24
|
|
||||||
|
|
||||||
msr elr_el1, x1
|
// TODO enter task via eret to EL1t
|
||||||
|
|
||||||
// SPSR_ELx_M[4:0] = 0b100, a return to EL1t, AArch64 mode
|
ret
|
||||||
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:
|
__aa64_switch_task:
|
||||||
// x0 -- "dst" context
|
// x0 -- "dst" context
|
||||||
|
|||||||
@@ -1,83 +1,30 @@
|
|||||||
const thread = @import("../../thread.zig");
|
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");
|
|
||||||
|
|
||||||
const ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
|
fn idle_function() callconv(.naked) noreturn {
|
||||||
const arch = kernel.arch;
|
asm volatile ("b .");
|
||||||
|
}
|
||||||
|
|
||||||
extern fn __aa64_enter_task(cx: *Context) callconv(.C) noreturn;
|
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_switch_task(dcx: *Context, scx: *Context) callconv(.C) void;
|
||||||
extern fn __aa64_task_enter_kernel() callconv(.C) noreturn;
|
extern fn __aa64_task_enter_kernel() callconv(.C) noreturn;
|
||||||
extern fn __aa64_task_enter_user() callconv(.C) noreturn;
|
|
||||||
|
|
||||||
pub const Context = extern struct {
|
pub const Context = extern struct {
|
||||||
const STACK_SIZE: usize = 16384;
|
const STACK_SIZE: usize = 16384;
|
||||||
|
|
||||||
kstack: thread.KStack(STACK_SIZE),
|
kstack: thread.KStack(STACK_SIZE),
|
||||||
|
|
||||||
ttbr0: u64 = 0,
|
|
||||||
|
|
||||||
pub fn idle() Context {
|
pub fn idle() Context {
|
||||||
return Context.kernel(&thread.idle_function, 0);
|
const entry = @intFromPtr(&idle_function);
|
||||||
|
return Context.kernel(entry, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user(address_space: *const ProcessAddressSpace, pc: usize, sp: usize, arg: usize) @This() {
|
pub fn kernel(pc: usize, arg: usize) Context {
|
||||||
const space_physical = address_space.physical_address();
|
|
||||||
const space_asid = address_space.asid();
|
|
||||||
|
|
||||||
var ks = thread.KStack(STACK_SIZE).create();
|
var ks = thread.KStack(STACK_SIZE).create();
|
||||||
|
const entry = @intFromPtr(&__aa64_task_enter_kernel);
|
||||||
|
|
||||||
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(pc);
|
||||||
ks.push(arg);
|
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(entry); // x30/lr
|
||||||
ks.push(0); // x29
|
ks.push(0); // x29
|
||||||
ks.push(0); // x28
|
ks.push(0); // x28
|
||||||
@@ -90,6 +37,16 @@ pub const Context = extern struct {
|
|||||||
ks.push(0); // x21
|
ks.push(0); // x21
|
||||||
ks.push(0); // x20
|
ks.push(0); // x20
|
||||||
ks.push(0); // x19
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,11 @@ const kernel = @import("../../kernel.zig");
|
|||||||
|
|
||||||
const arch = kernel.arch;
|
const arch = kernel.arch;
|
||||||
const log = kernel.debug.log;
|
const log = kernel.debug.log;
|
||||||
const syscall = kernel.syscall;
|
|
||||||
|
|
||||||
extern const __aa64_exception_vectors: u8;
|
extern const __aa64_exception_vectors: u8;
|
||||||
|
|
||||||
pub const ExceptionFrame = extern struct {
|
pub const ExceptionFrame = extern struct {
|
||||||
xN: [32]usize,
|
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 {
|
pub fn dump(self: *const ExceptionFrame, comptime level: log.Level) void {
|
||||||
for (0..16) |i| {
|
for (0..16) |i| {
|
||||||
@@ -46,7 +41,6 @@ export fn __aa64_el1_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
|
|||||||
|
|
||||||
log.err("Exception in EL1:", .{});
|
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(" 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});
|
log.err(" ELR = 0x{x:016}", .{elr});
|
||||||
|
|
||||||
switch (esr.as_enum()) {
|
switch (esr.as_enum()) {
|
||||||
@@ -88,21 +82,8 @@ export fn __aa64_el1_serror_handler(frame: *ExceptionFrame) callconv(.C) void {
|
|||||||
|
|
||||||
// EL0
|
// EL0
|
||||||
export fn __aa64_el0_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
|
export fn __aa64_el0_sync_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||||
const esr = regs.ESR_EL1.read();
|
// TODO EL0
|
||||||
|
_ = frame;
|
||||||
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();
|
arch.halt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,12 +93,14 @@ export fn __aa64_el0_irq_handler(frame: *ExceptionFrame) callconv(.C) void {
|
|||||||
|
|
||||||
export fn __aa64_el0_fiq_handler(frame: *ExceptionFrame) callconv(.C) void {
|
export fn __aa64_el0_fiq_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||||
_ = frame;
|
_ = frame;
|
||||||
@panic("__aa64_el0_fiq_handler");
|
// TODO I've never used FIQ
|
||||||
|
arch.halt();
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn __aa64_el0_serror_handler(frame: *ExceptionFrame) callconv(.C) void {
|
export fn __aa64_el0_serror_handler(frame: *ExceptionFrame) callconv(.C) void {
|
||||||
_ = frame;
|
_ = frame;
|
||||||
@panic("__aa64_el0_serror_handler");
|
// TODO
|
||||||
|
arch.halt();
|
||||||
}
|
}
|
||||||
|
|
||||||
comptime {
|
comptime {
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ fn Register(comptime name: []const u8, comptime bits: type) type {
|
|||||||
else => bits,
|
else => bits,
|
||||||
};
|
};
|
||||||
return enum(repr) {
|
return enum(repr) {
|
||||||
pub const Bits = bits;
|
|
||||||
|
|
||||||
pub fn set(value: repr) void {
|
pub fn set(value: repr) void {
|
||||||
asm volatile ("msr " ++ name ++ ", %[value]"
|
asm volatile ("msr " ++ name ++ ", %[value]"
|
||||||
:
|
:
|
||||||
@@ -36,15 +34,8 @@ fn Register(comptime name: []const u8, comptime bits: type) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const TTBR = packed struct(u64) {
|
pub const TTBR0_EL1 = Register("ttbr0_el1", u64);
|
||||||
// 0..48
|
pub const TTBR1_EL1 = Register("ttbr1_el1", u64);
|
||||||
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
|
// NOTE: tpidr_el0 is used until codegen can emit TLS instructions against tpidr_el1
|
||||||
pub const TPIDR_EL0 = Register("tpidr_el0", u64);
|
pub const TPIDR_EL0 = Register("tpidr_el0", u64);
|
||||||
@@ -76,7 +67,6 @@ pub const ESR_EL1 = Register("esr_el1", packed struct(u64) {
|
|||||||
// 26..32
|
// 26..32
|
||||||
EC: enum(u6) {
|
EC: enum(u6) {
|
||||||
unknown = 0b000000,
|
unknown = 0b000000,
|
||||||
svc = 0b010101,
|
|
||||||
data_abort_lower_el = 0b100100,
|
data_abort_lower_el = 0b100100,
|
||||||
data_abort_same_el = 0b100101,
|
data_abort_same_el = 0b100101,
|
||||||
sp_align = 0b100110,
|
sp_align = 0b100110,
|
||||||
@@ -146,16 +136,14 @@ pub const ESR_EL1 = Register("esr_el1", packed struct(u64) {
|
|||||||
|
|
||||||
pub const AsEnum = union(enum) {
|
pub const AsEnum = union(enum) {
|
||||||
data_abort: DataAbort,
|
data_abort: DataAbort,
|
||||||
svc,
|
|
||||||
other,
|
other,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn as_enum(self: @This()) AsEnum {
|
pub fn as_enum(self: @This()) AsEnum {
|
||||||
return switch (self.EC) {
|
switch (self.EC) {
|
||||||
.data_abort_lower_el, .data_abort_same_el => .{ .data_abort = @bitCast(self.ISS) },
|
.data_abort_lower_el, .data_abort_same_el => return .{ .data_abort = @bitCast(self.ISS) },
|
||||||
.svc => .svc,
|
else => return .other,
|
||||||
else => .other,
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,11 @@
|
|||||||
|
|
||||||
// 32 general-purpose registers
|
// 32 general-purpose registers
|
||||||
.set EXC_GP_SIZE, (32 * 8)
|
.set EXC_GP_SIZE, (32 * 8)
|
||||||
// 4 special-purpose registers
|
.set EXC_STATE_SIZE, (EXC_GP_SIZE)
|
||||||
.set EXC_SP_SIZE, (4 * 8)
|
|
||||||
.set EXC_STATE_SIZE, (EXC_GP_SIZE + EXC_SP_SIZE)
|
|
||||||
|
|
||||||
.macro EXC_SAVE_STATE
|
.macro EXC_SAVE_STATE
|
||||||
sub sp, sp, #EXC_STATE_SIZE
|
sub sp, sp, #EXC_STATE_SIZE
|
||||||
|
|
||||||
// General-purpose block
|
|
||||||
stp x0, x1, [sp, #16 * 0]
|
stp x0, x1, [sp, #16 * 0]
|
||||||
stp x2, x3, [sp, #16 * 1]
|
stp x2, x3, [sp, #16 * 1]
|
||||||
stp x4, x5, [sp, #16 * 2]
|
stp x4, x5, [sp, #16 * 2]
|
||||||
@@ -26,45 +23,6 @@
|
|||||||
stp x26, x27, [sp, #16 * 13]
|
stp x26, x27, [sp, #16 * 13]
|
||||||
stp x28, x29, [sp, #16 * 14]
|
stp x28, x29, [sp, #16 * 14]
|
||||||
stp x30, x31, [sp, #16 * 15]
|
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
|
.endm
|
||||||
|
|
||||||
// Exception vector size is 0x80
|
// Exception vector size is 0x80
|
||||||
@@ -80,22 +38,13 @@ __aa\bits\()_el\el\ht\()_\kind:
|
|||||||
// TODO taking exceptions from EL0t 32-bit
|
// TODO taking exceptions from EL0t 32-bit
|
||||||
b .
|
b .
|
||||||
.endif
|
.endif
|
||||||
|
|
||||||
EXC_SAVE_STATE
|
EXC_SAVE_STATE
|
||||||
|
|
||||||
dsb ish
|
|
||||||
isb sy
|
|
||||||
|
|
||||||
mov x0, sp
|
mov x0, sp
|
||||||
mov lr, xzr
|
mov lr, xzr
|
||||||
bl __aa64_el\el\()_\kind\()_handler
|
bl __aa64_el\el\()_\kind\()_handler
|
||||||
|
// TODO exception return
|
||||||
EXC_RESTORE_STATE
|
b .
|
||||||
|
|
||||||
ic iallu
|
|
||||||
dsb ishst
|
|
||||||
isb sy
|
|
||||||
|
|
||||||
eret
|
|
||||||
.size __aa\bits\()_el\el\ht\()_\kind, . - __aa\bits\()_el\el\ht\()_\kind
|
.size __aa\bits\()_el\el\ht\()_\kind, . - __aa\bits\()_el\el\ht\()_\kind
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
|
|||||||
+18
-154
@@ -1,21 +1,14 @@
|
|||||||
const std = @import("std");
|
|
||||||
const mem = @import("../../mem.zig");
|
const mem = @import("../../mem.zig");
|
||||||
const regs = @import("regs.zig");
|
const regs = @import("regs.zig");
|
||||||
const kernel = @import("../../kernel.zig");
|
|
||||||
|
|
||||||
const PhysicalAddress = mem.PhysicalAddress;
|
const PhysicalAddress = mem.PhysicalAddress;
|
||||||
const AtomicU8 = std.atomic.Value(u8);
|
|
||||||
const log = kernel.log;
|
|
||||||
|
|
||||||
pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFF8000000000;
|
pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFF8000000000;
|
||||||
pub const KERNEL_L1_INDEX: usize = L1.index(KERNEL_VIRTUAL_BASE);
|
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, L2);
|
pub const L1 = mem.TranslationLevel(30);
|
||||||
pub const L2 = mem.TranslationLevel(21, L3);
|
pub const L2 = mem.TranslationLevel(21);
|
||||||
pub const L3 = mem.vmm.L3;
|
pub const L3 = mem.TranslationLevel(12);
|
||||||
|
|
||||||
pub const RawEntry = packed struct(u64) {
|
pub const RawEntry = packed struct(u64) {
|
||||||
// 0
|
// 0
|
||||||
@@ -142,153 +135,31 @@ pub fn Table(comptime Level: type) type {
|
|||||||
return struct {
|
return struct {
|
||||||
pub const Entry = TableEntry(Level);
|
pub const Entry = TableEntry(Level);
|
||||||
|
|
||||||
pub const Error = mem.vmm.AddressSpaceError;
|
|
||||||
|
|
||||||
entries: [512]Entry align(4096) = [_]Entry{.INVALID} ** 512,
|
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 {
|
pub inline fn entry(self: *@This(), index: usize) *Entry {
|
||||||
return &self.entries[index];
|
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 {};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ProcessAddressSpace = struct {
|
// 0x0000_0000_0000_0000 .. 0x0000_0080_0000_0000
|
||||||
l1: *Table(L1),
|
var g_fixed_low = 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
|
// 0xFFFF_FF80_0000_0000 .. 0xFFFF_FFFF_FFFF_FFFF
|
||||||
var g_fixed_high = Table(L1){};
|
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 {
|
pub fn map_early(real_address: usize) void {
|
||||||
_ = real_address;
|
_ = real_address;
|
||||||
|
|
||||||
for (0..L1.page_count(KERNEL_VIRTUAL_SIZE)) |i| {
|
for (0..16) |i| {
|
||||||
|
// Identity
|
||||||
|
g_fixed_low.entry(i).* = TableEntry(L1).normal_block(
|
||||||
|
.{ .raw = i << L1.SHIFT },
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0..16) |i| {
|
||||||
// Identity + KERNEL_VIRTUAL_BASE
|
// Identity + KERNEL_VIRTUAL_BASE
|
||||||
g_fixed_high.entry(i).* = TableEntry(L1).normal_block(
|
g_fixed_high.entry(i).* = TableEntry(L1).normal_block(
|
||||||
.{ .raw = i << L1.SHIFT },
|
.{ .raw = i << L1.SHIFT },
|
||||||
@@ -296,18 +167,11 @@ pub fn map_early(real_address: usize) void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (0..16) |i| {
|
const ttbr0 = @intFromPtr(&g_fixed_low);
|
||||||
// Identity + VIRTUALIZE_BASE for "Whole RAM mapping"
|
const ttbr1 = @intFromPtr(&g_fixed_high);
|
||||||
g_fixed_high.entry(VIRTUALIZE_BASE_L1I + i).* = TableEntry(L1).normal_block(
|
|
||||||
.{ .raw = i << L1.SHIFT },
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ttbr = @intFromPtr(&g_fixed_high);
|
regs.TTBR0_EL1.set(ttbr0);
|
||||||
|
regs.TTBR1_EL1.set(ttbr1);
|
||||||
regs.TTBR0_EL1.write(.{ .BADDR = @truncate(ttbr) });
|
|
||||||
regs.TTBR1_EL1.write(.{ .BADDR = @truncate(ttbr) });
|
|
||||||
|
|
||||||
regs.TCR_EL1.write(.{
|
regs.TCR_EL1.write(.{
|
||||||
.AS = .asid_8bit,
|
.AS = .asid_8bit,
|
||||||
|
|||||||
@@ -5,19 +5,11 @@ const regs = @import("riscv64/regs.zig");
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
pub const vmm = @import("riscv64/vmm.zig");
|
|
||||||
|
|
||||||
export const _ = boot.rv64_bsp_lower_entry;
|
export const _ = boot.rv64_bsp_lower_entry;
|
||||||
|
|
||||||
/// This CPU's HART (HARdware Thread) ID.
|
/// This CPU's HART (HARdware Thread) ID.
|
||||||
pub threadlocal var t_hart_id: u32 = 0;
|
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
|
/// RISC-V task context
|
||||||
pub const Context = @import("riscv64/context.zig").Context;
|
pub const Context = @import("riscv64/context.zig").Context;
|
||||||
|
|
||||||
|
|||||||
@@ -37,12 +37,9 @@ fn bsp_upper_entry(real_address: usize, unused: usize) callconv(.C) noreturn {
|
|||||||
exception.init();
|
exception.init();
|
||||||
|
|
||||||
debug.log.set_write_fn(&sbi.debug_print_byte);
|
debug.log.set_write_fn(&sbi.debug_print_byte);
|
||||||
kernel.mem.PhysicalAddress.g_virtualize_base = vmm.VIRTUALIZE_BASE;
|
kernel.mem.PhysicalAddress.g_virtualize_base = 0;
|
||||||
kernel.mem.PhysicalAddress.g_virtualize_size = vmm.virtualize_range();
|
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 physical memory management
|
||||||
setup_memory_from_fdt(real_address);
|
setup_memory_from_fdt(real_address);
|
||||||
|
|
||||||
@@ -58,8 +55,6 @@ pub export fn rv64_bsp_lower_entry(real_address: usize, bsp_hart_id: usize, dtb_
|
|||||||
g_dtb_address = dtb_address;
|
g_dtb_address = dtb_address;
|
||||||
g_bsp_hart_id = @truncate(bsp_hart_id);
|
g_bsp_hart_id = @truncate(bsp_hart_id);
|
||||||
|
|
||||||
vmm.g_kernel_real_base = real_address;
|
|
||||||
|
|
||||||
vmm.map_early(real_address);
|
vmm.map_early(real_address);
|
||||||
|
|
||||||
// &bspUpperEntry will yield a pointer like: X + P, where
|
// &bspUpperEntry will yield a pointer like: X + P, where
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
.global __rv64_enter_task
|
.global __rv64_enter_task
|
||||||
.global __rv64_switch_task
|
.global __rv64_switch_task
|
||||||
.global __rv64_task_enter_user
|
|
||||||
.global __rv64_task_enter_kernel
|
.global __rv64_task_enter_kernel
|
||||||
|
|
||||||
.macro LOAD_TASK_STATE
|
.macro LOAD_TASK_STATE
|
||||||
@@ -45,48 +44,13 @@
|
|||||||
sd s0, 13 * 8(sp)
|
sd s0, 13 * 8(sp)
|
||||||
.endm
|
.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:
|
__rv64_task_enter_kernel:
|
||||||
ld a0, (sp) // argument
|
ld a0, (sp) // argument
|
||||||
ld t0, 8(sp) // entry
|
ld ra, 8(sp) // entry
|
||||||
ld ra, 16(sp) // return address
|
addi sp, sp, 16
|
||||||
addi sp, sp, 24
|
|
||||||
|
|
||||||
// Set SPP to indicate a return to S-mode
|
// TODO S-mode -> S-mode return via sret
|
||||||
csrr t1, sstatus
|
ret
|
||||||
// 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:
|
__rv64_switch_task:
|
||||||
// a0 - new context
|
// a0 - new context
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
const thread = @import("../../thread.zig");
|
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");
|
|
||||||
|
|
||||||
const ProcessAddressSpace = mem.vmm.ProcessAddressSpace;
|
fn idle_function() callconv(.naked) noreturn {
|
||||||
const log = kernel.log;
|
asm volatile ("j .");
|
||||||
|
}
|
||||||
|
|
||||||
extern fn __rv64_enter_task(cx: *Context) callconv(.C) noreturn;
|
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_switch_task(dcx: *Context, scx: *Context) callconv(.C) void;
|
||||||
extern fn __rv64_task_enter_kernel() callconv(.C) noreturn;
|
extern fn __rv64_task_enter_kernel() callconv(.C) noreturn;
|
||||||
extern fn __rv64_task_enter_user() callconv(.C) noreturn;
|
|
||||||
|
|
||||||
pub const Context = extern struct {
|
pub const Context = extern struct {
|
||||||
const STACK_SIZE: usize = 8192;
|
const STACK_SIZE: usize = 8192;
|
||||||
@@ -19,69 +14,20 @@ pub const Context = extern struct {
|
|||||||
// Has to be exactly at offset 0x00, used in assembly.
|
// Has to be exactly at offset 0x00, used in assembly.
|
||||||
kstack: thread.KStack(STACK_SIZE),
|
kstack: thread.KStack(STACK_SIZE),
|
||||||
|
|
||||||
satp: u64 = 0,
|
|
||||||
|
|
||||||
/// Constructs an idle context struct.
|
/// Constructs an idle context struct.
|
||||||
pub fn idle() @This() {
|
pub fn idle() @This() {
|
||||||
return Context.kernel(&thread.idle_function, 0);
|
const entry = @intFromPtr(&idle_function);
|
||||||
}
|
return Context.kernel(entry, 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.
|
/// Constructs a kernel task context with entry point in `pc` and an `arg`ument.
|
||||||
pub fn kernel(function: *const thread.KernelThreadFn, arg: usize) @This() {
|
pub fn kernel(pc: usize, arg: usize) @This() {
|
||||||
var ks = thread.KStack(STACK_SIZE).create();
|
var ks = thread.KStack(STACK_SIZE).create();
|
||||||
|
const entry = @intFromPtr(&__rv64_task_enter_kernel);
|
||||||
|
|
||||||
const table_physical = vmm.kernel_table_physical();
|
ks.push(pc);
|
||||||
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);
|
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); // x8/s0/fp
|
||||||
ks.push(0); // x9/s1
|
ks.push(0); // x9/s1
|
||||||
ks.push(0); // x18/s2
|
ks.push(0); // x18/s2
|
||||||
@@ -96,6 +42,18 @@ pub const Context = extern struct {
|
|||||||
ks.push(0); // x27/s11
|
ks.push(0); // x27/s11
|
||||||
ks.push(0); // x4/gp
|
ks.push(0); // x4/gp
|
||||||
ks.push(entry); // x1/ra return address
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
const regs = @import("regs.zig");
|
const regs = @import("regs.zig");
|
||||||
const kernel = @import("../../kernel.zig");
|
const debug = @import("../../debug.zig");
|
||||||
|
const arch = @import("../../kernel.zig").arch;
|
||||||
|
|
||||||
const syscall = kernel.syscall;
|
const log = debug.log;
|
||||||
const arch = kernel.arch;
|
|
||||||
const log = kernel.log;
|
|
||||||
|
|
||||||
extern fn __rv64_exception_vectors() void;
|
extern fn __rv64_exception_vectors() void;
|
||||||
|
|
||||||
@@ -52,13 +51,6 @@ pub const ExceptionFrame = extern struct {
|
|||||||
sN: [12]usize,
|
sN: [12]usize,
|
||||||
aN: [8]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 {
|
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, " 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] });
|
log.writeln(level, " t0 = 0x{x:016} t1 = 0x{x:016}", .{ self.tN[0], self.tN[1] });
|
||||||
@@ -86,38 +78,17 @@ pub fn init() void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export fn rv64SmodeTrapGeneral(frame: *ExceptionFrame) callconv(.C) 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) {
|
if (scause.INTERRUPT) {
|
||||||
return rv64SmodeTrapInterrupt(frame);
|
return rv64SmodeTrapInterrupt(frame);
|
||||||
}
|
}
|
||||||
const sstatus = @as(regs.SSTATUS.Bits, @bitCast(frame.sstatus));
|
|
||||||
const cause = @as(ExceptionCause, @enumFromInt(scause.CODE));
|
const cause = @as(ExceptionCause, @enumFromInt(scause.CODE));
|
||||||
|
const epc = regs.SEPC.get();
|
||||||
|
const tval = regs.STVAL.get();
|
||||||
|
|
||||||
const is_umode = !sstatus.SPP;
|
log.err("S-mode exception:", .{});
|
||||||
|
|
||||||
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(" Cause: {s} (0x{x})", .{ cause.name(), scause.CODE });
|
||||||
log.err(" stval = 0x{x:016} sepc = 0x{x:016}", .{ frame.stval, frame.sepc });
|
log.err(" stval = 0x{x:016} sepc = 0x{x:016}", .{ tval, epc });
|
||||||
frame.dump(.err);
|
frame.dump(.err);
|
||||||
|
|
||||||
@panic("Unhandled exception in S-mode");
|
@panic("Unhandled exception in S-mode");
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ fn Register(comptime name: []const u8, comptime bits: type) type {
|
|||||||
else => bits,
|
else => bits,
|
||||||
};
|
};
|
||||||
return enum(repr) {
|
return enum(repr) {
|
||||||
pub const Bits = bits;
|
|
||||||
|
|
||||||
pub fn set(value: repr) void {
|
pub fn set(value: repr) void {
|
||||||
asm volatile ("csrw " ++ name ++ ", %[value]"
|
asm volatile ("csrw " ++ name ++ ", %[value]"
|
||||||
:
|
:
|
||||||
|
|||||||
+7
-107
@@ -3,17 +3,8 @@
|
|||||||
.extern rv64SmodeTrapInterrupt
|
.extern rv64SmodeTrapInterrupt
|
||||||
|
|
||||||
// ra, gp, 7×tN, 12×sN, 8×aN
|
// ra, gp, 7×tN, 12×sN, 8×aN
|
||||||
.set GP_REGS_SIZE, ((2 + 7 + 12 + 8) * 8)
|
.set GP_REGS_SIZE, (2 + 7 + 12 + 8) * 8
|
||||||
// U-mode sp, sstatus, sepc, stval, scause, sscratch
|
.set TRAP_CONTEXT_SIZE, (GP_REGS_SIZE)
|
||||||
.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
|
.macro SAVE_GP_REGS
|
||||||
sd ra, 0 * 8(sp)
|
sd ra, 0 * 8(sp)
|
||||||
@@ -50,110 +41,19 @@
|
|||||||
sd a7, 28 * 8(sp)
|
sd a7, 28 * 8(sp)
|
||||||
.endm
|
.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
|
.macro SMODE_TRAP n, handler
|
||||||
.type __rv64_smode_trap_\n, @function
|
.type __rv64_smode_trap_\n, @function
|
||||||
__rv64_smode_trap_\n:
|
__rv64_smode_trap_\n:
|
||||||
// If coming from U-mode, sscratch = kernel-mode tp
|
// TODO properly handle traps coming from U-mode
|
||||||
// If coming from S-mode, sscratch = 0
|
// TODO save CSRs
|
||||||
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)
|
addi sp, sp, -(TRAP_CONTEXT_SIZE)
|
||||||
|
|
||||||
SAVE_GP_REGS
|
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
|
mv a0, sp
|
||||||
|
|
||||||
call \handler
|
call \handler
|
||||||
|
|
||||||
// Return from exception
|
// TODO return from exception
|
||||||
ld t0, REG_SSTATUS (sp)
|
j .
|
||||||
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
|
.size __rv64_smode_trap_\n, . - __rv64_smode_trap_\n
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
|
|||||||
+15
-136
@@ -1,24 +1,18 @@
|
|||||||
const std = @import("std");
|
|
||||||
const sync = @import("../../sync.zig");
|
const sync = @import("../../sync.zig");
|
||||||
const regs = @import("regs.zig");
|
const regs = @import("regs.zig");
|
||||||
const mem = @import("../../mem.zig");
|
const mem = @import("../../mem.zig");
|
||||||
const kernel = @import("../../kernel.zig");
|
const arch = @import("../../kernel.zig").arch;
|
||||||
|
|
||||||
const log = kernel.log;
|
|
||||||
const arch = kernel.arch;
|
|
||||||
const PhysicalAddress = mem.PhysicalAddress;
|
const PhysicalAddress = mem.PhysicalAddress;
|
||||||
const AtomicU8 = std.atomic.Value(u8);
|
|
||||||
|
|
||||||
pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFFF000000000;
|
pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFFF000000000;
|
||||||
pub const KERNEL_VIRTUAL_L1I: usize = (KERNEL_VIRTUAL_BASE >> L1.SHIFT) & 511;
|
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
|
// 16 GiB
|
||||||
const EARLY_MAPPING_SIZE: usize = 16;
|
const EARLY_MAPPING_SIZE: usize = 16;
|
||||||
|
|
||||||
pub const L1 = mem.TranslationLevel(30, L2);
|
pub const L1 = mem.TranslationLevel(30);
|
||||||
pub const L2 = mem.TranslationLevel(21, L3);
|
pub const L2 = mem.TranslationLevel(21);
|
||||||
pub const L3 = mem.vmm.L3;
|
pub const L3 = mem.TranslationLevel(12);
|
||||||
|
|
||||||
pub const RawEntry = packed struct(u64) {
|
pub const RawEntry = packed struct(u64) {
|
||||||
// 0: Valid
|
// 0: Valid
|
||||||
@@ -51,19 +45,15 @@ pub const RawEntry = packed struct(u64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(self: *@This(), mask: @This()) void {
|
pub fn clear(self: *@This(), mask: @This()) void {
|
||||||
const lhs = @as(*u64, @ptrCast(self));
|
const lhs = @as(*u64, @bitCast(self));
|
||||||
const rhs = @as(u64, @bitCast(mask));
|
const rhs = @as(u64, @bitCast(mask));
|
||||||
lhs.* &= ~rhs;
|
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 {
|
pub fn TableEntry(comptime Level: type) type {
|
||||||
_ = Level;
|
_ = Level;
|
||||||
return packed struct(u64) {
|
return struct {
|
||||||
raw: RawEntry,
|
raw: RawEntry,
|
||||||
|
|
||||||
pub const INVALID: @This() = .{ .raw = .{} };
|
pub const INVALID: @This() = .{ .raw = .{} };
|
||||||
@@ -93,9 +83,8 @@ pub fn TableEntry(comptime Level: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
|
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
|
||||||
var f = flags;
|
flags.clear(.{ .r = true, .w = true, .x = true });
|
||||||
f.clear(.{ .r = true, .w = true, .x = true });
|
return .{ .raw = flags.make_union(.{
|
||||||
return .{ .raw = f.make_union(.{
|
|
||||||
.address = @as(u39, @intCast(addr.raw >> 12)),
|
.address = @as(u39, @intCast(addr.raw >> 12)),
|
||||||
.v = true,
|
.v = true,
|
||||||
}) };
|
}) };
|
||||||
@@ -104,140 +93,37 @@ pub fn TableEntry(comptime Level: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn Table(comptime Level: type) type {
|
pub fn Table(comptime Level: type) type {
|
||||||
return extern struct {
|
return struct {
|
||||||
pub const Entry = TableEntry(Level);
|
pub const Entry = TableEntry(Level);
|
||||||
|
|
||||||
entries: [512]Entry align(4096),
|
entries: [512]Entry align(4096),
|
||||||
|
|
||||||
pub const Error = mem.vmm.AddressSpaceError;
|
|
||||||
|
|
||||||
pub fn empty() @This() {
|
pub fn empty() @This() {
|
||||||
return .{ .entries = [_]Entry{.INVALID} ** 512 };
|
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 {
|
pub inline fn entry(self: *@This(), index: usize) *Entry {
|
||||||
return &self.entries[index];
|
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 = Table(L1).empty();
|
||||||
var g_fixed_lock: sync.Spinlock = .{};
|
var g_fixed_lock: sync.Spinlock = .{};
|
||||||
pub var g_kernel_real_base: u64 = undefined;
|
|
||||||
extern var __kernel_start: u8;
|
|
||||||
|
|
||||||
pub fn virtualize_range() usize {
|
pub fn virtualize_range() usize {
|
||||||
return EARLY_MAPPING_SIZE * L1.SIZE;
|
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 {
|
pub fn unmap_early() void {
|
||||||
// Unmap lower half
|
// Make lower half mappings non-executable
|
||||||
const guard = g_fixed_lock.lock_irqsave();
|
const guard = g_fixed_lock.lock_irqsave();
|
||||||
defer guard.release();
|
defer guard.release();
|
||||||
for (0..EARLY_MAPPING_SIZE) |i| {
|
for (0..EARLY_MAPPING_SIZE) |i| {
|
||||||
g_fixed.entry(i).* = .INVALID;
|
g_fixed.entry(i).* = .page(
|
||||||
|
.{ .raw = L1.address(i) },
|
||||||
|
.{ .r = true, .w = true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,20 +138,13 @@ 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
|
// Map 1GiB at KERNEL_VIRTUAL_BASE -> physical 1GiB where the kernel is loaded
|
||||||
g_fixed.entry(KERNEL_VIRTUAL_L1I).* = .page(
|
g_fixed.entry(KERNEL_VIRTUAL_L1I).* = .page(
|
||||||
.{ .raw = L1.address(real_l1) },
|
.{ .raw = L1.address(real_l1) },
|
||||||
.{ .r = true, .w = true, .x = true },
|
.{ .r = true, .w = true, .x = true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const address = @intFromPtr(&g_fixed);
|
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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ pub const log = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write raw byte data into the debugging output.
|
/// Write raw byte data into the debugging output.
|
||||||
pub fn write_raw(data: []const u8) void {
|
pub fn write_waw(data: []const u8) void {
|
||||||
_ = write_wrapper_fn(0, data) catch return;
|
_ = write_wrapper_fn(0, data) catch return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -7,7 +7,6 @@ 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 util = @import("util.zig");
|
||||||
pub const sync = @import("sync.zig");
|
pub const sync = @import("sync.zig");
|
||||||
pub const syscall = @import("syscall.zig");
|
|
||||||
|
|
||||||
pub const log = debug.log;
|
pub const log = debug.log;
|
||||||
pub const vmm = mem.vmm;
|
pub const vmm = mem.vmm;
|
||||||
@@ -17,10 +16,13 @@ pub const TRACE_PHYSICAL_ALLOCATOR: bool = false;
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const userspace_code = @embedFile("userspace");
|
fn f0(arg: usize) callconv(.C) noreturn {
|
||||||
|
var c: usize = 0;
|
||||||
fn f0(arg: usize) callconv(.C) void {
|
while (true) {
|
||||||
log.info("Argument is {}", .{arg});
|
f1(arg, c);
|
||||||
|
c += 1;
|
||||||
|
thread.yield();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noinline fn f1(arg: usize, c: usize) void {
|
noinline fn f1(arg: usize, c: usize) void {
|
||||||
@@ -36,17 +38,15 @@ noinline fn f1(arg: usize, c: usize) void {
|
|||||||
/// * Physical memory must be initialized.
|
/// * Physical memory must be initialized.
|
||||||
/// * (optional) Logging should be set up.
|
/// * (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", .{});
|
||||||
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.init_this_cpu(&a);
|
thread.Queue.init_this_cpu(&a);
|
||||||
|
|
||||||
log.info("Userspace code size: {} bytes", .{userspace_code.len});
|
const pc = @intFromPtr(&f0);
|
||||||
const t1 = thread.test_create_user_from_code(&a, userspace_code) catch @panic("Could not create test thread");
|
for (0..4) |i| {
|
||||||
|
const t = thread.Thread.create(&a, pc, i);
|
||||||
const t = thread.Thread.create_kernel(&a, &f0, 1234);
|
thread.enqueue(t);
|
||||||
thread.enqueue(t);
|
}
|
||||||
thread.enqueue(t1);
|
|
||||||
|
|
||||||
log.info("Test", .{});
|
|
||||||
|
|
||||||
thread.enter();
|
thread.enter();
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ pub const PhysicalAddress = packed struct(u64) {
|
|||||||
///
|
///
|
||||||
/// Panics if the virtual address provided is outside of virtualizable memory range.
|
/// 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 < g_virtualize_base or virt - g_virtualize_base > g_virtualize_size) {
|
if ((virt < g_virtualize_base) || (virt - g_virtualize_base > g_virtualize_size)) {
|
||||||
@panic("Invalid virtualized physical address");
|
@panic("Invalid virtualized physical address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+91
-151
@@ -15,77 +15,58 @@ const Spinlock = sync.Spinlock;
|
|||||||
pub const MemoryRegion = struct {
|
pub const MemoryRegion = struct {
|
||||||
/// Name string, used to represent where the memory comes from.
|
/// Name string, used to represent where the memory comes from.
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
/// Page frame number range of the region.
|
/// Byte range of the memory region.
|
||||||
range: Range(u64),
|
range: Range(u64),
|
||||||
};
|
};
|
||||||
|
|
||||||
const Bitmap = struct {
|
/// Represents information about a single managed physical memory page.
|
||||||
data: []u64,
|
pub const Page = extern struct {
|
||||||
|
/// Reference count of the page. Zero means the page is not allocated.
|
||||||
|
refcount: u32 = 0,
|
||||||
|
unused: [3]u32 = undefined,
|
||||||
|
|
||||||
const Self = @This();
|
/// Returns `true` if the page is allocated/used.
|
||||||
|
pub fn is_used(self: *const @This()) bool {
|
||||||
pub const empty: Self = .{ .data = &.{} };
|
return self.refcount != 0;
|
||||||
|
|
||||||
fn get_bit(self: *Self, index: usize) u1 {
|
|
||||||
const word_index = index / 64;
|
|
||||||
const bit_index = index % 64;
|
|
||||||
const masked = self.data[word_index] & (@as(u64, 1) << @intCast(bit_index));
|
|
||||||
return if (masked == 0) 0 else 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_bit(self: *Self, index: usize) void {
|
fn make_available(self: *@This()) void {
|
||||||
const word_index = index / 64;
|
self.refcount = 0;
|
||||||
const bit_index = index % 64;
|
|
||||||
self.data[word_index] |= (@as(u64, 1) << @intCast(bit_index));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_bit(self: *Self, index: usize) void {
|
fn make_reserved(self: *@This()) void {
|
||||||
const word_index = index / 64;
|
self.refcount = std.math.maxInt(u32);
|
||||||
const bit_index = index % 64;
|
|
||||||
self.data[word_index] &= ~(@as(u64, 1) << @intCast(bit_index));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const PhysicalMemoryManager = struct {
|
const PhysicalMemoryManager = struct {
|
||||||
memory_start: u64,
|
page_array: []Page,
|
||||||
last_free: usize,
|
offset: u64 = 0,
|
||||||
len: usize,
|
last_free: usize = 0,
|
||||||
|
|
||||||
/// Each bit represents a page, there can be more u64s than needed
|
const RECORDS_PER_PAGE: usize = vmm.PAGE_SIZE / @sizeOf(Page);
|
||||||
usage_bitmap: Bitmap,
|
|
||||||
page_refcounters: []u32,
|
|
||||||
|
|
||||||
const empty: @This() = .{
|
|
||||||
.memory_start = 0,
|
|
||||||
.last_free = 0,
|
|
||||||
.len = 0,
|
|
||||||
.usage_bitmap = .empty,
|
|
||||||
.page_refcounters = &.{},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn alloc_page(self: *@This()) ?mem.PhysicalAddress {
|
fn alloc_page(self: *@This()) ?mem.PhysicalAddress {
|
||||||
for (self.last_free..self.len) |i| {
|
for (self.last_free..self.page_array.len) |i| {
|
||||||
if (!self.is_page_used(i)) {
|
if (self.page_array[i].refcount == 0) {
|
||||||
self.page_refcounters[i] += 1;
|
self.page_array[i].refcount += 1;
|
||||||
self.set_page_used(i);
|
self.last_free = (i + 1) % self.page_array.len;
|
||||||
self.last_free = (i + 1) % self.len;
|
return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
|
||||||
return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (0..self.last_free) |i| {
|
for (0..self.last_free) |i| {
|
||||||
if (!self.is_page_used(i)) {
|
if (self.page_array[i].refcount == 0) {
|
||||||
self.page_refcounters[i] += 1;
|
self.page_array[i].refcount += 1;
|
||||||
self.set_page_used(i);
|
self.last_free = (i + 1) % self.page_array.len;
|
||||||
self.last_free = (i + 1) % self.len;
|
return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
|
||||||
return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_pages(self: *@This(), count: usize) ?mem.PhysicalAddress {
|
fn alloc_pages(self: *@This(), count: usize) ?mem.PhysicalAddress {
|
||||||
if (self.last_free + count < self.len) {
|
if (self.last_free + count < self.page_array.len) {
|
||||||
if (self.alloc_from(self.last_free, self.len, count)) |p| {
|
if (self.alloc_from(self.last_free, self.page_array.len, count)) |p| {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,19 +75,19 @@ const PhysicalMemoryManager = struct {
|
|||||||
|
|
||||||
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| {
|
||||||
const taken = taken: {
|
var taken = false;
|
||||||
for (0..count) |j|
|
for (0..count) |j| {
|
||||||
if (self.is_page_used(i + j))
|
if (self.page_array[i + j].is_used()) {
|
||||||
break :taken true;
|
taken = true;
|
||||||
break :taken false;
|
break;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!taken) {
|
if (!taken) {
|
||||||
for (0..count) |j| {
|
for (0..count) |j| {
|
||||||
self.page_refcounters[i + j] = 1;
|
self.page_array[i + j].refcount = 1;
|
||||||
self.set_page_used(i + j);
|
|
||||||
}
|
}
|
||||||
return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE };
|
return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,11 +95,11 @@ const PhysicalMemoryManager = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn valid_index(self: *@This(), page: mem.PhysicalAddress) usize {
|
fn valid_index(self: *@This(), page: mem.PhysicalAddress) usize {
|
||||||
if (page.raw < self.memory_start) {
|
if (page.raw < self.offset) {
|
||||||
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.memory_start) / vmm.PAGE_SIZE;
|
const index = (page.raw - self.offset) / vmm.PAGE_SIZE;
|
||||||
if (index >= self.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;
|
||||||
@@ -126,75 +107,52 @@ 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.is_page_used(index)) {
|
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.page_refcounters[index] -= 1;
|
self.page_array[index].refcount -= 1;
|
||||||
if (self.page_refcounters[index] == 0) {
|
if (self.page_array[index].refcount == 0) {
|
||||||
self.clear_page_used(index);
|
|
||||||
self.last_free = index;
|
self.last_free = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_page_used(self: *@This(), index: usize) bool {
|
fn get_page(self: *@This(), page: mem.PhysicalAddress) *Page {
|
||||||
return self.usage_bitmap.get_bit(index) == 1;
|
const index = self.valid_index(page);
|
||||||
}
|
return &self.page_array[index];
|
||||||
|
|
||||||
fn set_page_used(self: *@This(), index: usize) void {
|
|
||||||
self.usage_bitmap.set_bit(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_page_used(self: *@This(), index: usize) void {
|
|
||||||
self.usage_bitmap.clear_bit(index);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var g_memory_regions: std.BoundedArray(MemoryRegion, 16) = .{};
|
var g_memory_regions: std.BoundedArray(MemoryRegion, 16) = .{};
|
||||||
var g_reserved_regions: std.BoundedArray(MemoryRegion, 16) = .{};
|
var g_reserved_regions: std.BoundedArray(MemoryRegion, 16) = .{};
|
||||||
var g_physical_memory_lock = Spinlock{};
|
var g_physical_memory_lock = Spinlock{};
|
||||||
var g_physical_memory = PhysicalMemoryManager.empty;
|
var g_physical_memory = PhysicalMemoryManager{ .page_array = undefined };
|
||||||
|
|
||||||
/// Adds an available memory region to the list.
|
/// Adds an available memory region to the list.
|
||||||
///
|
///
|
||||||
/// `base` and `size` are in bytes. Regions are page-aligned "inwards", meaning the function will
|
|
||||||
/// only add the range of full pages of the specified region. If a combination is provided that
|
|
||||||
/// does not yield any full 4KiB pages (e.g. `base=0x1234, size=0x123`), it is ignored.
|
|
||||||
///
|
|
||||||
/// # Note
|
/// # Note
|
||||||
///
|
///
|
||||||
/// Only meaningful to call before calling `init()`.
|
/// Only meaningful to call before calling `init()`.
|
||||||
pub fn add_memory_region(name: []const u8, base: u64, size: u64) void {
|
pub fn add_memory_region(name: []const u8, base: u64, size: u64) void {
|
||||||
log.info("Memory: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
log.info("Memory: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
||||||
const start = vmm.L3.align_up(base) / vmm.L3.SIZE;
|
g_memory_regions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) //
|
||||||
const len = vmm.L3.align_down(base + size) / vmm.L3.SIZE - start;
|
catch @panic("memory regions overflow");
|
||||||
if (len > 0) {
|
|
||||||
g_memory_regions.append(.{ .name = name, .range = .{ .start = start, .len = len } }) //
|
|
||||||
catch @panic("memory regions overflow");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds an reserved memory region to the list.
|
/// Adds an reserved memory region to the list.
|
||||||
///
|
///
|
||||||
/// `base` and `size` are in bytes. Regions are page-aligned "outwards", meaning that the
|
|
||||||
/// reservation extends to any pages affected by the specified region.
|
|
||||||
///
|
|
||||||
/// # Note
|
/// # Note
|
||||||
///
|
///
|
||||||
/// Only meaningful to call before calling `init()`.
|
/// Only meaningful to call before calling `init()`.
|
||||||
pub fn add_reserved_region(name: []const u8, base: u64, size: u64) void {
|
pub fn add_reserved_region(name: []const u8, base: u64, size: u64) void {
|
||||||
log.info("Reserved: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
log.info("Reserved: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
||||||
const start = base / vmm.L3.SIZE;
|
g_reserved_regions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) //
|
||||||
const len = vmm.L3.align_up(base + size) / vmm.L3.SIZE - start;
|
catch @panic("reserved regions overflow");
|
||||||
if (len > 0) {
|
|
||||||
g_reserved_regions.append(.{ .name = name, .range = .{ .start = start, .len = len } }) //
|
|
||||||
catch @panic("reserved regions overflow");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_reserved_in(page_index: u64) ?*const MemoryRegion {
|
fn is_reserved_in(page: u64) ?*const MemoryRegion {
|
||||||
for (0..g_reserved_regions.len) |i| {
|
for (0..g_reserved_regions.len) |i| {
|
||||||
const region = &g_reserved_regions.buffer[i];
|
const region = &g_reserved_regions.buffer[i];
|
||||||
if (page_index >= region.range.start and page_index < region.range.end()) {
|
if (page >= region.range.start and page < region.range.end()) {
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +164,7 @@ fn alloc_from_region(region: *const MemoryRegion, reason: []const u8, page_count
|
|||||||
while (offset < region.range.len) {
|
while (offset < region.range.len) {
|
||||||
var taken: ?*const MemoryRegion = null;
|
var taken: ?*const MemoryRegion = null;
|
||||||
for (0..page_count) |i| {
|
for (0..page_count) |i| {
|
||||||
if (is_reserved_in(region.range.start + offset + i)) |resv| {
|
if (is_reserved_in(region.range.start + offset + i * vmm.PAGE_SIZE)) |resv| {
|
||||||
taken = resv;
|
taken = resv;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -217,51 +175,27 @@ fn alloc_from_region(region: *const MemoryRegion, reason: []const u8, page_count
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = (region.range.start + offset) * vmm.L3.SIZE;
|
const base = region.range.start + offset;
|
||||||
add_reserved_region(reason, base, page_count * vmm.PAGE_SIZE);
|
add_reserved_region(reason, base, page_count * vmm.PAGE_SIZE);
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocates a slice of type `T` that spans the `page_count` pages.
|
fn alloc_page_array(page_count: usize) []Page {
|
||||||
fn alloc_slice_pages(comptime T: type, reason: []const u8, page_count: usize) []T {
|
|
||||||
for (g_memory_regions.constSlice()) |region| {
|
for (g_memory_regions.constSlice()) |region| {
|
||||||
if (alloc_from_region(®ion, reason, page_count)) |physAddress| {
|
if (alloc_from_region(®ion, "page-array", page_count)) |physAddress| {
|
||||||
const vaddr = (mem.PhysicalAddress{ .raw = physAddress }).virtualize();
|
const vaddr = (mem.PhysicalAddress{ .raw = physAddress }).virtualize();
|
||||||
const len = (page_count * vmm.PAGE_SIZE) / @sizeOf(T);
|
const len = page_count * PhysicalMemoryManager.RECORDS_PER_PAGE;
|
||||||
const ptr: [*]T = @ptrFromInt(vaddr);
|
const ptr: [*]Page = @ptrFromInt(vaddr);
|
||||||
const slice: []T = ptr[0..len];
|
const slice: []Page = ptr[0..len];
|
||||||
|
for (0..len) |i| {
|
||||||
|
slice[i].refcount = std.math.maxInt(u32);
|
||||||
|
}
|
||||||
return slice;
|
return slice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@panic("Failed to allocate a slice");
|
@panic("TODO");
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocates a slice of type `T` that has at least `min_len` items, allocates in 4KiB pages.
|
|
||||||
/// The items are zeroed out.
|
|
||||||
fn alloc_slice(comptime T: type, reason: []const u8, min_len: usize) []T {
|
|
||||||
const min_alloc_bytes = min_len * @sizeOf(T);
|
|
||||||
// Round up to make sure we have enough space for the data
|
|
||||||
const needed_pages = vmm.L3.page_count(min_alloc_bytes);
|
|
||||||
const slice = alloc_slice_pages(T, reason, needed_pages);
|
|
||||||
const slice_as_bytes = std.mem.sliceAsBytes(slice);
|
|
||||||
@memset(slice_as_bytes, 0);
|
|
||||||
return slice;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocates a bitmap that has at least `bits_required` total bits.
|
|
||||||
/// It can have more since we allocate 4KiB pages and the backing type is []u64
|
|
||||||
fn alloc_bitmap(bits_required: usize) Bitmap {
|
|
||||||
// Round up to the upper u64 that has at least `pages` bits
|
|
||||||
const bitmap_entries = (bits_required + 63) / 64;
|
|
||||||
const bitmap = alloc_slice(u64, "bitmap", bitmap_entries);
|
|
||||||
return .{ .data = bitmap };
|
|
||||||
}
|
|
||||||
fn alloc_refcounters(count: usize) []u32 {
|
|
||||||
const refcounters = alloc_slice(u32, "refcounters", count);
|
|
||||||
@memset(refcounters, std.math.maxInt(u32));
|
|
||||||
return refcounters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes the physical memory management.
|
/// Initializes the physical memory management.
|
||||||
@@ -275,7 +209,6 @@ pub fn init() void {
|
|||||||
var memory_end: u64 = std.math.minInt(u64);
|
var memory_end: u64 = std.math.minInt(u64);
|
||||||
|
|
||||||
for (g_memory_regions.constSlice()) |region| {
|
for (g_memory_regions.constSlice()) |region| {
|
||||||
log.info("Region: {}..{}", .{ region.range.start, region.range.end() });
|
|
||||||
if (region.range.start < memory_start) {
|
if (region.range.start < memory_start) {
|
||||||
memory_start = region.range.start;
|
memory_start = region.range.start;
|
||||||
}
|
}
|
||||||
@@ -284,43 +217,37 @@ pub fn init() void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const memory_pages = memory_end - memory_start; // == bitmap bits required
|
const memory_pages = (memory_end - memory_start) / vmm.PAGE_SIZE; // == bitmap bits required
|
||||||
var bitmap = alloc_bitmap(memory_pages);
|
const page_array_pages = (memory_pages + PhysicalMemoryManager.RECORDS_PER_PAGE - 1) //
|
||||||
const refcounters = alloc_refcounters(memory_pages);
|
/ PhysicalMemoryManager.RECORDS_PER_PAGE;
|
||||||
|
|
||||||
|
const page_array = alloc_page_array(page_array_pages);
|
||||||
var available_pages: usize = 0;
|
var available_pages: usize = 0;
|
||||||
|
|
||||||
for (g_memory_regions.constSlice()) |region| {
|
for (g_memory_regions.constSlice()) |region| {
|
||||||
const offset = region.range.start - memory_start;
|
const offset = (region.range.start - memory_start) / vmm.PAGE_SIZE;
|
||||||
for (0..region.range.len) |i| {
|
for (0..region.range.len / vmm.PAGE_SIZE) |i| {
|
||||||
refcounters[offset + i] = 0;
|
page_array[offset + i].make_available();
|
||||||
available_pages += 1;
|
available_pages += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (g_reserved_regions.constSlice()) |region| {
|
for (g_reserved_regions.constSlice()) |region| {
|
||||||
const offset = region.range.start - memory_start;
|
const offset = (region.range.start - memory_start) / vmm.PAGE_SIZE;
|
||||||
for (0..region.range.len) |i| {
|
for (0..region.range.len / vmm.PAGE_SIZE) |i| {
|
||||||
if (offset + i >= memory_pages) {
|
if (offset + i >= page_array.len) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
refcounters[offset + i] = std.math.maxInt(u32);
|
page_array[offset + i].make_reserved();
|
||||||
bitmap.set_bit(offset + i);
|
|
||||||
|
|
||||||
available_pages -= 1;
|
available_pages -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var size_fmt: [64]u8 = undefined;
|
var size_fmt: [64]u8 = undefined;
|
||||||
const size_fmt_str = mem.format_size(&size_fmt, available_pages * vmm.PAGE_SIZE);
|
const size_fmt_str = mem.format_size(&size_fmt, available_pages * vmm.PAGE_SIZE);
|
||||||
log.info(
|
log.info("Available memory: {s}, page array {*}", .{ size_fmt_str, page_array });
|
||||||
"Available memory: {s}, bitmap {*}, refcounts {*}",
|
|
||||||
.{ size_fmt_str, bitmap.data, refcounters },
|
|
||||||
);
|
|
||||||
|
|
||||||
g_physical_memory.len = memory_pages;
|
g_physical_memory.page_array = page_array;
|
||||||
g_physical_memory.memory_start = memory_start * vmm.L3.SIZE;
|
g_physical_memory.offset = memory_start;
|
||||||
g_physical_memory.usage_bitmap = bitmap;
|
|
||||||
g_physical_memory.page_refcounters = refcounters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trace_allocation(count: usize, page: ?mem.PhysicalAddress) void {
|
fn trace_allocation(count: usize, page: ?mem.PhysicalAddress) void {
|
||||||
@@ -372,3 +299,16 @@ pub fn free_page(page: mem.PhysicalAddress) void {
|
|||||||
defer guard.release();
|
defer guard.release();
|
||||||
g_physical_memory.free_page(page);
|
g_physical_memory.free_page(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a `Page` struct representing the given `page`.
|
||||||
|
///
|
||||||
|
/// # Invariants
|
||||||
|
///
|
||||||
|
/// The physical memory lock must be held.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic if the `page` does not represent a valid managed page.
|
||||||
|
pub fn get_page(page: mem.PhysicalAddress) *Page {
|
||||||
|
return g_physical_memory.get_page(page);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,443 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Arena = @import("../arena.zig").Arena;
|
|
||||||
const Range = @import("../util/range.zig").Range;
|
|
||||||
|
|
||||||
/// Describes a single virtual memory range.
|
|
||||||
///
|
|
||||||
/// Used by `VirtualMemoryAllocator` to track allocated/used regions.
|
|
||||||
pub const VirtualMemoryRange = struct {
|
|
||||||
range: Range(u64),
|
|
||||||
|
|
||||||
prev: ?*VirtualMemoryRange = null,
|
|
||||||
next: ?*VirtualMemoryRange = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Virtual memory allocator implementation.
|
|
||||||
pub const VirtualMemoryAllocator = struct {
|
|
||||||
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 };
|
|
||||||
|
|
||||||
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 {
|
|
||||||
range: Range(u64),
|
|
||||||
vma: *VirtualMemoryAllocator,
|
|
||||||
current: ?*VirtualMemoryRange,
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if (xs.end() == n.range.end()) {
|
|
||||||
// Whole range encompassed by requested range
|
|
||||||
// Unlink the node
|
|
||||||
if (n.next) |nn| {
|
|
||||||
nn.prev = n.prev;
|
|
||||||
}
|
|
||||||
if (n.prev) |np| {
|
|
||||||
np.next = n.next;
|
|
||||||
} else {
|
|
||||||
self.vma.head = n.next;
|
|
||||||
}
|
|
||||||
// Free it
|
|
||||||
self.current = n.next;
|
|
||||||
// self.vma.arena.destroy(n);
|
|
||||||
|
|
||||||
return xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove space from the start
|
|
||||||
n.range.start += xs.len;
|
|
||||||
n.range.len -= xs.len;
|
|
||||||
// Does not touch the end, so can be sure this is the last node
|
|
||||||
self.current = null;
|
|
||||||
return xs;
|
|
||||||
} else if (xs.end() == n.range.end()) {
|
|
||||||
n.range.len -= xs.len;
|
|
||||||
// Continue, there might be a following node affected
|
|
||||||
self.current = n.next;
|
|
||||||
return xs;
|
|
||||||
} else {
|
|
||||||
// Insert a new node after the current one
|
|
||||||
const new_node = self.vma.arena.create(VirtualMemoryRange);
|
|
||||||
new_node.* = VirtualMemoryRange{
|
|
||||||
.range = .{ .start = xs.end(), .len = n.range.end() - xs.end() },
|
|
||||||
.prev = n,
|
|
||||||
.next = n.next,
|
|
||||||
};
|
|
||||||
n.range.len = xs.start - n.range.start;
|
|
||||||
if (n.next) |nn| {
|
|
||||||
nn.prev = new_node;
|
|
||||||
}
|
|
||||||
n.next = new_node;
|
|
||||||
// Requested region is fully encompassed by this one, so no intersections
|
|
||||||
// will follow
|
|
||||||
self.current = null;
|
|
||||||
return xs;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No intersect
|
|
||||||
self.current = n.next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Creates a new instance of a virtual memory allocator.
|
|
||||||
pub fn init(arena: *Arena, outer_range: Range(u64)) @This() {
|
|
||||||
return .{
|
|
||||||
.outer_range = outer_range,
|
|
||||||
.arena = arena,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocates a free region of virtual memory of requested (`pfn_count`) size.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// * `cannot_fit` - if no free space found to fit the requested allocation.
|
|
||||||
/// * Underlying allocator error - if allocation of a new node fails.
|
|
||||||
pub fn allocate(self: *@This(), pfn_count: u64) Error!u64 {
|
|
||||||
// Try to fit before first entry
|
|
||||||
const gap_before_first = if (self.head) |n| (n.range.start - self.outer_range.start) else self.outer_range.len;
|
|
||||||
|
|
||||||
if (gap_before_first >= pfn_count) {
|
|
||||||
var new_node = self.arena.create(VirtualMemoryRange);
|
|
||||||
|
|
||||||
new_node.range = .{ .start = self.outer_range.start, .len = pfn_count };
|
|
||||||
new_node.next = self.head;
|
|
||||||
new_node.prev = null;
|
|
||||||
|
|
||||||
if (self.head) |n| {
|
|
||||||
n.prev = new_node;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.head = new_node;
|
|
||||||
|
|
||||||
return self.outer_range.start;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If cannot fit before first entry, find an entry to fit after
|
|
||||||
var node = self.head;
|
|
||||||
while (node) |n| {
|
|
||||||
const gap =
|
|
||||||
if (n.next) |nn|
|
|
||||||
// Gap between this and next
|
|
||||||
(nn.range.start - n.range.end())
|
|
||||||
else
|
|
||||||
// Gap between this and the end
|
|
||||||
(self.outer_range.end() - n.range.end());
|
|
||||||
|
|
||||||
if (gap >= pfn_count) {
|
|
||||||
// Insert after this
|
|
||||||
const result = n.range.end();
|
|
||||||
var new_node = self.arena.create(VirtualMemoryRange);
|
|
||||||
new_node.prev = n;
|
|
||||||
new_node.next = n.next;
|
|
||||||
new_node.range = .{ .start = result, .len = pfn_count };
|
|
||||||
if (n.next) |nn| {
|
|
||||||
nn.prev = new_node;
|
|
||||||
}
|
|
||||||
n.next = new_node;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
node = n.next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.cannot_fit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts a reservation into the VM allocator.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// * `already_exists` - if the requested range intersects existing ranges.
|
|
||||||
/// * Underlying allocator error - if allocation of a new node fails.
|
|
||||||
pub fn insert(self: *@This(), region: Range(u64)) Error!void {
|
|
||||||
// Validate that the range does not escape the outer range
|
|
||||||
if (region.start < self.outer_range.start or region.end() > self.outer_range.end()) {
|
|
||||||
return error.invalid_region;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the last node which is before the region supposed to be inserted
|
|
||||||
var node = self.head;
|
|
||||||
var insert_after: ?*VirtualMemoryRange = null;
|
|
||||||
while (node) |n| {
|
|
||||||
if (n.range.intersect(®ion) != null) {
|
|
||||||
return error.already_exists;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n.range.end() <= region.start) {
|
|
||||||
insert_after = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
node = n.next;
|
|
||||||
}
|
|
||||||
|
|
||||||
var new_node = self.arena.create(VirtualMemoryRange);
|
|
||||||
|
|
||||||
new_node.range = region;
|
|
||||||
|
|
||||||
if (insert_after) |ia| {
|
|
||||||
new_node.prev = ia;
|
|
||||||
new_node.next = ia.next;
|
|
||||||
|
|
||||||
if (ia.next) |ian| {
|
|
||||||
ian.prev = new_node;
|
|
||||||
}
|
|
||||||
ia.next = new_node;
|
|
||||||
} else {
|
|
||||||
new_node.next = null;
|
|
||||||
new_node.prev = null;
|
|
||||||
|
|
||||||
self.head = new_node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deallocates (shrinks/truncates) regions intersecting the requested range.
|
|
||||||
pub fn free(self: *@This(), start_pfn: u64, pfn_count: u64) FreeIterator {
|
|
||||||
const range = Range(u64){ .start = start_pfn, .len = pfn_count };
|
|
||||||
return FreeIterator{
|
|
||||||
.current = self.head,
|
|
||||||
.vma = self,
|
|
||||||
.range = range,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drain(self: *@This()) DrainIterator {
|
|
||||||
return DrainIterator{ .vma = self };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test "Inserted entries in vmalloc are properly ordered" {
|
|
||||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x2000 });
|
|
||||||
defer {
|
|
||||||
while (vma.head) |n| {
|
|
||||||
vma.head = n.next;
|
|
||||||
std.testing.allocator.destroy(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try vma.insert(.{ .start = 0x1200, .len = 0x200 });
|
|
||||||
{
|
|
||||||
const n0 = vma.head.?;
|
|
||||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n0.range.len);
|
|
||||||
try std.testing.expectEqual(null, n0.next);
|
|
||||||
try std.testing.expectEqual(null, n0.prev);
|
|
||||||
}
|
|
||||||
try vma.insert(.{ .start = 0x2000, .len = 0x200 });
|
|
||||||
{
|
|
||||||
const n0 = vma.head.?;
|
|
||||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n0.range.len);
|
|
||||||
try std.testing.expectEqual(null, n0.prev);
|
|
||||||
const n1 = n0.next.?;
|
|
||||||
try std.testing.expectEqual(0x2000, n1.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n1.range.len);
|
|
||||||
try std.testing.expectEqual(n0, n1.prev);
|
|
||||||
try std.testing.expectEqual(null, n1.next);
|
|
||||||
}
|
|
||||||
try vma.insert(.{ .start = 0x1400, .len = 0x200 });
|
|
||||||
{
|
|
||||||
const n0 = vma.head.?;
|
|
||||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n0.range.len);
|
|
||||||
try std.testing.expectEqual(null, n0.prev);
|
|
||||||
const n1 = n0.next.?;
|
|
||||||
try std.testing.expectEqual(0x1400, n1.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n1.range.len);
|
|
||||||
try std.testing.expectEqual(n0, n1.prev);
|
|
||||||
const n2 = n1.next.?;
|
|
||||||
try std.testing.expectEqual(0x2000, n2.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n2.range.len);
|
|
||||||
try std.testing.expectEqual(n1, n2.prev);
|
|
||||||
try std.testing.expectEqual(null, n2.next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Overlapping insertions are denied" {
|
|
||||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
|
||||||
defer {
|
|
||||||
while (vma.head) |n| {
|
|
||||||
vma.head = n.next;
|
|
||||||
std.testing.allocator.destroy(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try vma.insert(.{ .start = 0x1200, .len = 0x200 });
|
|
||||||
try std.testing.expectError(error.already_exists, vma.insert(.{ .start = 0x1100, .len = 0x200 }));
|
|
||||||
try std.testing.expectError(error.already_exists, vma.insert(.{ .start = 0x1300, .len = 0x200 }));
|
|
||||||
try std.testing.expectError(error.already_exists, vma.insert(.{ .start = 0x1100, .len = 0x400 }));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Insertions outside of bounds are denied" {
|
|
||||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
|
||||||
// As above...
|
|
||||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x2200, .len = 0x200 }));
|
|
||||||
// ... so below
|
|
||||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x200, .len = 0x200 }));
|
|
||||||
// Crosses from below
|
|
||||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x200, .len = 0x1000 }));
|
|
||||||
// Crosses into above
|
|
||||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x1200, .len = 0x1000 }));
|
|
||||||
// Encompasses whole
|
|
||||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x200, .len = 0x2000 }));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Allocations from vmalloc" {
|
|
||||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
|
||||||
defer {
|
|
||||||
while (vma.head) |n| {
|
|
||||||
vma.head = n.next;
|
|
||||||
std.testing.allocator.destroy(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try vma.insert(.{ .start = 0x1200, .len = 0x200 });
|
|
||||||
try std.testing.expectEqual(0x1000, try vma.allocate(0x100));
|
|
||||||
try std.testing.expectEqual(0x1400, try vma.allocate(0x400));
|
|
||||||
try std.testing.expectEqual(0x1100, try vma.allocate(0x100));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "vmalloc free" {
|
|
||||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
|
||||||
|
|
||||||
try vma.insert(.{ .start = 0x1200, .len = 0x800 });
|
|
||||||
try vma.insert(.{ .start = 0x1A00, .len = 0x400 });
|
|
||||||
|
|
||||||
// Remove nothing
|
|
||||||
{
|
|
||||||
var free_it = vma.free(0x1000, 0x200);
|
|
||||||
try std.testing.expectEqual(null, free_it.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a chunk in the middle of a node
|
|
||||||
{
|
|
||||||
var free_it = vma.free(0x1400, 0x400);
|
|
||||||
const r0 = (try free_it.next()).?;
|
|
||||||
try std.testing.expectEqual(0x1400, r0.start);
|
|
||||||
try std.testing.expectEqual(0x400, r0.len);
|
|
||||||
try std.testing.expectEqual(null, free_it.next());
|
|
||||||
|
|
||||||
const n0 = vma.head.?;
|
|
||||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n0.range.len);
|
|
||||||
const n1 = n0.next.?;
|
|
||||||
try std.testing.expectEqual(0x1800, n1.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n1.range.len);
|
|
||||||
try std.testing.expectEqual(n0, n1.prev);
|
|
||||||
const n2 = n1.next.?;
|
|
||||||
try std.testing.expectEqual(0x1A00, n2.range.start);
|
|
||||||
try std.testing.expectEqual(0x400, n2.range.len);
|
|
||||||
try std.testing.expectEqual(n1, n2.prev);
|
|
||||||
try std.testing.expectEqual(null, n2.next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from the start
|
|
||||||
{
|
|
||||||
var free_it = vma.free(0x1200, 0x100);
|
|
||||||
const r0 = (try free_it.next()).?;
|
|
||||||
try std.testing.expectEqual(0x1200, r0.start);
|
|
||||||
try std.testing.expectEqual(0x100, r0.len);
|
|
||||||
try std.testing.expectEqual(null, free_it.next());
|
|
||||||
|
|
||||||
const n0 = vma.head.?;
|
|
||||||
try std.testing.expectEqual(0x1300, n0.range.start);
|
|
||||||
try std.testing.expectEqual(0x100, n0.range.len);
|
|
||||||
const n1 = n0.next.?;
|
|
||||||
try std.testing.expectEqual(0x1800, n1.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n1.range.len);
|
|
||||||
try std.testing.expectEqual(n0, n1.prev);
|
|
||||||
const n2 = n1.next.?;
|
|
||||||
try std.testing.expectEqual(0x1A00, n2.range.start);
|
|
||||||
try std.testing.expectEqual(0x400, n2.range.len);
|
|
||||||
try std.testing.expectEqual(n1, n2.prev);
|
|
||||||
try std.testing.expectEqual(null, n2.next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from the end
|
|
||||||
{
|
|
||||||
var free_it = vma.free(0x1900, 0x100);
|
|
||||||
const r0 = (try free_it.next()).?;
|
|
||||||
try std.testing.expectEqual(0x1900, r0.start);
|
|
||||||
try std.testing.expectEqual(0x100, r0.len);
|
|
||||||
try std.testing.expectEqual(null, free_it.next());
|
|
||||||
|
|
||||||
const n0 = vma.head.?;
|
|
||||||
try std.testing.expectEqual(0x1300, n0.range.start);
|
|
||||||
try std.testing.expectEqual(0x100, n0.range.len);
|
|
||||||
const n1 = n0.next.?;
|
|
||||||
try std.testing.expectEqual(0x1800, n1.range.start);
|
|
||||||
try std.testing.expectEqual(0x100, n1.range.len);
|
|
||||||
try std.testing.expectEqual(n0, n1.prev);
|
|
||||||
const n2 = n1.next.?;
|
|
||||||
try std.testing.expectEqual(0x1A00, n2.range.start);
|
|
||||||
try std.testing.expectEqual(0x400, n2.range.len);
|
|
||||||
try std.testing.expectEqual(n1, n2.prev);
|
|
||||||
try std.testing.expectEqual(null, n2.next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove single full
|
|
||||||
{
|
|
||||||
var free_it = vma.free(0x1000, 0x600);
|
|
||||||
const r0 = (try free_it.next()).?;
|
|
||||||
try std.testing.expectEqual(0x1300, r0.start);
|
|
||||||
try std.testing.expectEqual(0x100, r0.len);
|
|
||||||
try std.testing.expectEqual(null, free_it.next());
|
|
||||||
|
|
||||||
const n0 = vma.head.?;
|
|
||||||
try std.testing.expectEqual(0x1800, n0.range.start);
|
|
||||||
try std.testing.expectEqual(0x100, n0.range.len);
|
|
||||||
const n1 = n0.next.?;
|
|
||||||
try std.testing.expectEqual(0x1A00, n1.range.start);
|
|
||||||
try std.testing.expectEqual(0x400, n1.range.len);
|
|
||||||
try std.testing.expectEqual(n0, n1.prev);
|
|
||||||
try std.testing.expectEqual(null, n1.next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove one full + one partial
|
|
||||||
{
|
|
||||||
var free_it = vma.free(0x1600, 0x600);
|
|
||||||
const r0 = (try free_it.next()).?;
|
|
||||||
try std.testing.expectEqual(0x1800, r0.start);
|
|
||||||
try std.testing.expectEqual(0x100, r0.len);
|
|
||||||
const r1 = (try free_it.next()).?;
|
|
||||||
try std.testing.expectEqual(0x1A00, r1.start);
|
|
||||||
try std.testing.expectEqual(0x200, r1.len);
|
|
||||||
try std.testing.expectEqual(null, free_it.next());
|
|
||||||
|
|
||||||
const n0 = vma.head.?;
|
|
||||||
try std.testing.expectEqual(0x1C00, n0.range.start);
|
|
||||||
try std.testing.expectEqual(0x200, n0.range.len);
|
|
||||||
try std.testing.expectEqual(null, n0.next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove whatever remains
|
|
||||||
{
|
|
||||||
var free_it = vma.free(0, 0x20000);
|
|
||||||
const r0 = (try free_it.next()).?;
|
|
||||||
try std.testing.expectEqual(0x1C00, r0.start);
|
|
||||||
try std.testing.expectEqual(0x200, r0.len);
|
|
||||||
try std.testing.expectEqual(null, free_it.next());
|
|
||||||
|
|
||||||
try std.testing.expectEqual(null, vma.head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-77
@@ -1,31 +1,13 @@
|
|||||||
//! Platform-independent virtual memory management definitions.
|
//! 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, null);
|
|
||||||
|
|
||||||
/// Page size is 4KiB on all platforms.
|
/// Page size is 4KiB on all platforms.
|
||||||
pub const PAGE_SIZE: usize = L3.SIZE;
|
pub const PAGE_SIZE: usize = 0x1000;
|
||||||
|
|
||||||
pub const AddressSpaceError = error{
|
|
||||||
out_of_pages,
|
|
||||||
} || vmalloc.VirtualMemoryAllocator.Error;
|
|
||||||
|
|
||||||
/// Helper function to construct a "Translation Level" struct type from a bit shift.
|
/// Helper function to construct a "Translation Level" struct type from a bit shift.
|
||||||
pub fn TranslationLevel(comptime shift: usize, comptime Next: ?type) type {
|
pub fn TranslationLevel(comptime shift: usize) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub const SHIFT: usize = shift;
|
pub const SHIFT: usize = shift;
|
||||||
pub const SIZE: usize = 1 << shift;
|
pub const SIZE: usize = 1 << shift;
|
||||||
pub const NextLevel = Next;
|
|
||||||
|
|
||||||
pub inline fn index(addr: usize) usize {
|
pub inline fn index(addr: usize) usize {
|
||||||
return (addr >> shift) & 511;
|
return (addr >> shift) & 511;
|
||||||
@@ -46,62 +28,5 @@ pub fn TranslationLevel(comptime shift: usize, comptime Next: ?type) type {
|
|||||||
pub inline fn align_up(addr: usize) usize {
|
pub inline fn align_up(addr: usize) usize {
|
||||||
return (addr + SIZE - 1) & ~(SIZE - 1);
|
return (addr + SIZE - 1) & ~(SIZE - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn page_number(addr: usize) usize {
|
|
||||||
return addr >> shift;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn page_count(size: usize) usize {
|
|
||||||
return (size + SIZE - 1) / SIZE;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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
@@ -1,103 +0,0 @@
|
|||||||
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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
+27
-223
@@ -6,137 +6,6 @@ const arena = @import("arena.zig");
|
|||||||
const arch = @import("kernel.zig").arch;
|
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");
|
||||||
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.
|
/// Per-CPU thread queue structure.
|
||||||
pub const Queue = struct {
|
pub const Queue = struct {
|
||||||
@@ -146,8 +15,6 @@ pub const Queue = struct {
|
|||||||
current: ?*Thread = null,
|
current: ?*Thread = null,
|
||||||
/// Thread queue head pointer.
|
/// Thread queue head pointer.
|
||||||
head: ?*Thread = null,
|
head: ?*Thread = null,
|
||||||
/// Queue's lock
|
|
||||||
lock: sync.Spinlock = .{},
|
|
||||||
|
|
||||||
/// Pointer to this CPU's thread queue.
|
/// Pointer to this CPU's thread queue.
|
||||||
pub threadlocal var t_this_cpu: ?*Queue = null;
|
pub threadlocal var t_this_cpu: ?*Queue = null;
|
||||||
@@ -173,7 +40,6 @@ pub const Queue = struct {
|
|||||||
|
|
||||||
/// Yields CPU to the next available task.
|
/// Yields CPU to the next available task.
|
||||||
pub fn yield(self: *@This()) void {
|
pub fn yield(self: *@This()) void {
|
||||||
// TODO locking here
|
|
||||||
if (self.current) |curr| {
|
if (self.current) |curr| {
|
||||||
// Switching from thread
|
// Switching from thread
|
||||||
if (curr.next) |next| {
|
if (curr.next) |next| {
|
||||||
@@ -182,12 +48,6 @@ pub const Queue = struct {
|
|||||||
self.current = next;
|
self.current = next;
|
||||||
next.switch_from(curr);
|
next.switch_from(curr);
|
||||||
}
|
}
|
||||||
} else if (self.head) |h| {
|
|
||||||
// ... to thread (head)
|
|
||||||
if (h != curr) {
|
|
||||||
self.current = h;
|
|
||||||
h.switch_from(curr);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// ... to idle
|
// ... to idle
|
||||||
self.current = null;
|
self.current = null;
|
||||||
@@ -207,11 +67,6 @@ pub const Queue = struct {
|
|||||||
|
|
||||||
/// Adds an available task to this queue.
|
/// Adds an available task to this queue.
|
||||||
pub fn enqueue(self: *@This(), t: *Thread) void {
|
pub fn enqueue(self: *@This(), t: *Thread) void {
|
||||||
var guard = self.lock.lock_irqsave();
|
|
||||||
defer guard.release();
|
|
||||||
|
|
||||||
t.queue = self;
|
|
||||||
|
|
||||||
if (self.head) |gt| {
|
if (self.head) |gt| {
|
||||||
t.next = gt;
|
t.next = gt;
|
||||||
t.prev = gt.prev;
|
t.prev = gt.prev;
|
||||||
@@ -223,43 +78,38 @@ pub const Queue = struct {
|
|||||||
t.prev = t;
|
t.prev = t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// # Invariants
|
/// Represents a single execution thread.
|
||||||
///
|
pub const Thread = struct {
|
||||||
/// `t` must be a thread within the `self` queue.
|
/// Arena.
|
||||||
fn dequeue(self: *@This(), t: *Thread) void {
|
allocator: *arena.Arena,
|
||||||
var guard = self.lock.lock_irqsave();
|
/// Architecture-specific task context.
|
||||||
defer guard.release();
|
arch_context: arch.Context,
|
||||||
|
|
||||||
t.queue = null;
|
/// Next thread in the queue.
|
||||||
|
next: ?*Thread = null,
|
||||||
|
/// Previous thread in the queue.
|
||||||
|
prev: ?*Thread = null,
|
||||||
|
|
||||||
if (t == self.head) {
|
/// Creates a new (kernel) thread with given `pc` (entry point) and `arg`ument.
|
||||||
if (t.next == t) {
|
pub fn create(a: *arena.Arena, pc: usize, arg: usize) *Thread {
|
||||||
self.head = null;
|
const thread = a.create(Thread);
|
||||||
t.next = null;
|
thread.* = .{
|
||||||
t.prev = null;
|
.allocator = a,
|
||||||
return;
|
.arch_context = arch.Context.kernel(pc, arg),
|
||||||
}
|
};
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
if (t.next) |tn| {
|
/// Enters the thread, does not return.
|
||||||
tn.prev = t.prev;
|
pub fn enter(self: *@This()) noreturn {
|
||||||
}
|
self.arch_context.enter();
|
||||||
if (t.prev) |tp| {
|
}
|
||||||
tp.next = t.next;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.head = t.next;
|
/// Switches from `from` to `self` thread.
|
||||||
} else {
|
pub fn switch_from(self: *@This(), from: *@This()) void {
|
||||||
if (t.next) |tn| {
|
self.arch_context.switch_from(&from.arch_context);
|
||||||
tn.prev = t.prev;
|
|
||||||
}
|
|
||||||
if (t.prev) |tp| {
|
|
||||||
tp.next = t.next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.next = null;
|
|
||||||
t.prev = null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -320,49 +170,3 @@ pub fn enter() noreturn {
|
|||||||
pub fn yield() void {
|
pub fn yield() void {
|
||||||
Queue.t_this_cpu.?.yield();
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
pub const dtb = @import("util/dtb.zig");
|
pub const dtb = @import("util/dtb.zig");
|
||||||
pub const range = @import("util/range.zig");
|
pub const range = @import("util/range.zig");
|
||||||
pub const btree = @import("util/btree.zig");
|
|
||||||
pub const rangemap = @import("util/rangemap.zig");
|
|
||||||
|
|||||||
@@ -1,368 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
pub const Order = std.math.Order;
|
|
||||||
|
|
||||||
pub fn CompareFn(comptime N: type) type {
|
|
||||||
return fn (*const N, *const N) Order;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn SearchFn(comptime N: type, comptime C: type) type {
|
|
||||||
return fn (*const N, C) Order;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn BTree(comptime N: type, comptime compare_fn: CompareFn(N), comptime deinit_fn: ?fn (*N) void) type {
|
|
||||||
return struct {
|
|
||||||
gpa: Allocator,
|
|
||||||
root: ?*Node = null,
|
|
||||||
|
|
||||||
pub const Error = error{ already_exists, does_not_exist } || Allocator.Error;
|
|
||||||
|
|
||||||
pub fn WalkFn(comptime C: type) type {
|
|
||||||
return fn (*const Node, C) void;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Iterator = struct {
|
|
||||||
current: ?*Node,
|
|
||||||
|
|
||||||
pub fn next(self: *Iterator) ?*const Node {
|
|
||||||
while (self.current) |n| {
|
|
||||||
const v = n;
|
|
||||||
|
|
||||||
if (n.right) |r| {
|
|
||||||
// Emit
|
|
||||||
self.current = Node.leftmost(r);
|
|
||||||
} else {
|
|
||||||
var nn = n;
|
|
||||||
while (nn.parent) |p| {
|
|
||||||
if (nn == p.right) {
|
|
||||||
nn = p;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.current = nn.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Node = struct {
|
|
||||||
key: N,
|
|
||||||
parent: ?*Node = null,
|
|
||||||
left: ?*Node = null,
|
|
||||||
right: ?*Node = null,
|
|
||||||
|
|
||||||
fn init(a: Allocator, key: N) Error!*Node {
|
|
||||||
const node = try a.create(Node);
|
|
||||||
node.* = .{
|
|
||||||
.key = key,
|
|
||||||
};
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(node: ?*Node, a: Allocator) void {
|
|
||||||
if (node) |n| {
|
|
||||||
if (comptime deinit_fn) |f| {
|
|
||||||
f(&n.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
Node.deinit(n.left, a);
|
|
||||||
Node.deinit(n.right, a);
|
|
||||||
|
|
||||||
// Free node itself
|
|
||||||
a.destroy(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert(node: ?*Node, a: Allocator, key: N) Error!struct { *Node, *Node } {
|
|
||||||
if (node) |n| {
|
|
||||||
const ord = compare_fn(&n.key, &key);
|
|
||||||
var inserted: *Node = undefined;
|
|
||||||
switch (ord) {
|
|
||||||
.lt => {
|
|
||||||
const child, inserted = try Node.insert(n.right, a, key);
|
|
||||||
child.parent = n;
|
|
||||||
n.right = child;
|
|
||||||
},
|
|
||||||
.gt => {
|
|
||||||
const child, inserted = try Node.insert(n.left, a, key);
|
|
||||||
child.parent = n;
|
|
||||||
n.left = child;
|
|
||||||
},
|
|
||||||
.eq => return error.already_exists,
|
|
||||||
}
|
|
||||||
return .{ n, inserted };
|
|
||||||
} else {
|
|
||||||
const n = try Node.init(a, key);
|
|
||||||
return .{ n, n };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_node(node: *Node, a: Allocator, destroy: bool) ?*Node {
|
|
||||||
if (node.left == null) {
|
|
||||||
// Only right/none
|
|
||||||
const tmp = node.right;
|
|
||||||
if (tmp) |t| {
|
|
||||||
t.parent = node.parent;
|
|
||||||
}
|
|
||||||
// Destroy the node
|
|
||||||
if (comptime deinit_fn) |f| {
|
|
||||||
if (destroy) {
|
|
||||||
f(&node.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.destroy(node);
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
if (node.right == null) {
|
|
||||||
// Only left/none
|
|
||||||
const tmp = node.left;
|
|
||||||
if (tmp) |t| {
|
|
||||||
t.parent = node.parent;
|
|
||||||
}
|
|
||||||
// Destroy the node
|
|
||||||
if (comptime deinit_fn) |f| {
|
|
||||||
if (destroy) {
|
|
||||||
f(&node.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.destroy(node);
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both
|
|
||||||
var successor = node.right;
|
|
||||||
while (successor) |succ| {
|
|
||||||
if (succ.left) |l| {
|
|
||||||
successor = l;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (successor) |succ| {
|
|
||||||
node.key = succ.key;
|
|
||||||
node.right = Node.remove(node.right, a, succ.key) catch unreachable;
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(node: ?*Node, a: Allocator, key: N) Error!?*Node {
|
|
||||||
if (node) |n| {
|
|
||||||
const ord = compare_fn(&n.key, &key);
|
|
||||||
switch (ord) {
|
|
||||||
.lt => n.right = try Node.remove(n.right, a, key),
|
|
||||||
.gt => n.left = try Node.remove(n.left, a, key),
|
|
||||||
.eq => return Node.remove_node(n, a, true),
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
} else {
|
|
||||||
return error.does_not_exist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk(node: ?*Node, ctx: anytype, walk_fn: WalkFn(@TypeOf(ctx))) void {
|
|
||||||
if (node) |n| {
|
|
||||||
Node.walk(n.left, ctx, walk_fn);
|
|
||||||
walk_fn(n, ctx);
|
|
||||||
Node.walk(n.right, ctx, walk_fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn leftmost(node: ?*Node) ?*Node {
|
|
||||||
var n = node;
|
|
||||||
while (n) |nn| {
|
|
||||||
if (nn.left == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
n = nn.left;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(a: std.mem.Allocator) @This() {
|
|
||||||
return .{ .gpa = a };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *@This()) void {
|
|
||||||
Node.deinit(self.root, self.gpa);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iterator(self: *@This()) Iterator {
|
|
||||||
return .{ .current = Node.leftmost(self.root) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(self: *@This(), key: N) Error!*Node {
|
|
||||||
self.root, const inserted = try Node.insert(self.root, self.gpa, key);
|
|
||||||
return inserted;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(self: *@This(), key: N) Error!void {
|
|
||||||
self.root = try Node.remove(self.root, self.gpa, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_node(self: *@This(), node: *Node, destroy: bool) Error!void {
|
|
||||||
if (node.parent) |p| {
|
|
||||||
// Non-root node
|
|
||||||
const np = Node.remove_node(node, self.gpa, destroy);
|
|
||||||
if (np) |npp| {
|
|
||||||
npp.parent = p;
|
|
||||||
}
|
|
||||||
if (node == p.right) {
|
|
||||||
p.right = np;
|
|
||||||
} else {
|
|
||||||
p.left = np;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Root node
|
|
||||||
const np = Node.remove_node(node, self.gpa, destroy);
|
|
||||||
if (np) |npp| {
|
|
||||||
npp.parent = null;
|
|
||||||
}
|
|
||||||
self.root = np;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lookup(self: *const @This(), key: N) ?*Node {
|
|
||||||
const search_fn = struct {
|
|
||||||
fn call(n: *const N, cx: N) Order {
|
|
||||||
return compare_fn(n, &cx);
|
|
||||||
}
|
|
||||||
}.call;
|
|
||||||
|
|
||||||
return self.search(key, search_fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn search(
|
|
||||||
self: *const @This(),
|
|
||||||
ctx: anytype,
|
|
||||||
search_fn: SearchFn(N, @TypeOf(ctx)),
|
|
||||||
) ?*Node {
|
|
||||||
var node = self.root;
|
|
||||||
while (node) |n| {
|
|
||||||
const ord = search_fn(&n.key, ctx);
|
|
||||||
switch (ord) {
|
|
||||||
.gt => node = n.left,
|
|
||||||
.eq => return n,
|
|
||||||
.lt => node = n.right,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn walk(self: *@This(), ctx: anytype, walk_fn: WalkFn(@TypeOf(ctx))) void {
|
|
||||||
Node.walk(self.root, ctx, walk_fn);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "BTree insertion/removal" {
|
|
||||||
const int_compare_fn = struct {
|
|
||||||
fn call(a: *const u32, b: *const u32) Order {
|
|
||||||
if (a.* > b.*) {
|
|
||||||
return .gt;
|
|
||||||
} else if (a.* == b.*) {
|
|
||||||
return .eq;
|
|
||||||
} else {
|
|
||||||
return .lt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.call;
|
|
||||||
const Tree = BTree(u32, int_compare_fn, null);
|
|
||||||
var tree = Tree.init(std.testing.allocator);
|
|
||||||
defer tree.deinit();
|
|
||||||
|
|
||||||
for (50..100) |i| {
|
|
||||||
_ = try tree.insert(@truncate(i));
|
|
||||||
}
|
|
||||||
for (1..50) |i| {
|
|
||||||
_ = try tree.insert(@truncate(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (1..100) |i| {
|
|
||||||
const k = @as(u32, @truncate(i));
|
|
||||||
try std.testing.expectEqual(k, tree.lookup(k).?.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (1..100) |i| {
|
|
||||||
const k = 100 - @as(u32, @truncate(i));
|
|
||||||
if (i % 2 == 0) {
|
|
||||||
try tree.remove(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (1..100) |i| {
|
|
||||||
const k = @as(u32, @truncate(i));
|
|
||||||
if (i % 2 == 0) {
|
|
||||||
try std.testing.expectEqual(null, tree.lookup(k));
|
|
||||||
} else {
|
|
||||||
try std.testing.expectEqual(k, tree.lookup(k).?.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "BTree removal by node" {
|
|
||||||
const int_compare_fn = struct {
|
|
||||||
fn call(a: *const u32, b: *const u32) Order {
|
|
||||||
if (a.* > b.*) {
|
|
||||||
return .gt;
|
|
||||||
} else if (a.* == b.*) {
|
|
||||||
return .eq;
|
|
||||||
} else {
|
|
||||||
return .lt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.call;
|
|
||||||
const Tree = BTree(u32, int_compare_fn, null);
|
|
||||||
var tree = Tree.init(std.testing.allocator);
|
|
||||||
defer tree.deinit();
|
|
||||||
|
|
||||||
_ = try tree.insert(10);
|
|
||||||
_ = try tree.insert(11);
|
|
||||||
_ = try tree.insert(12);
|
|
||||||
|
|
||||||
{
|
|
||||||
const n = tree.lookup(10).?;
|
|
||||||
try tree.remove_node(n, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
try std.testing.expectEqual(null, tree.lookup(10));
|
|
||||||
try std.testing.expectEqual(12, tree.lookup(12).?.key);
|
|
||||||
try std.testing.expectEqual(11, tree.lookup(11).?.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "BTree iterator" {
|
|
||||||
const int_compare_fn = struct {
|
|
||||||
fn call(a: *const u32, b: *const u32) Order {
|
|
||||||
if (a.* > b.*) {
|
|
||||||
return .gt;
|
|
||||||
} else if (a.* == b.*) {
|
|
||||||
return .eq;
|
|
||||||
} else {
|
|
||||||
return .lt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.call;
|
|
||||||
const Tree = BTree(u32, int_compare_fn, null);
|
|
||||||
var tree = Tree.init(std.testing.allocator);
|
|
||||||
defer tree.deinit();
|
|
||||||
|
|
||||||
for (50..100) |i| {
|
|
||||||
_ = try tree.insert(@truncate(i));
|
|
||||||
}
|
|
||||||
for (1..50) |i| {
|
|
||||||
_ = try tree.insert(@truncate(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
var it = tree.iterator();
|
|
||||||
for (1..100) |i| {
|
|
||||||
const n = it.next().?;
|
|
||||||
try std.testing.expectEqual(i, n.key);
|
|
||||||
}
|
|
||||||
try std.testing.expectEqual(null, it.next());
|
|
||||||
}
|
|
||||||
+11
-11
@@ -487,7 +487,7 @@ pub const Fdt = struct {
|
|||||||
|
|
||||||
fn dump_property(property: *const FdtNodeProp, depth: usize, all_strings: bool) void {
|
fn dump_property(property: *const FdtNodeProp, depth: usize, all_strings: bool) void {
|
||||||
for (0..depth) |_| {
|
for (0..depth) |_| {
|
||||||
log.write_raw(" ");
|
log.write_waw(" ");
|
||||||
}
|
}
|
||||||
log.write("{s}", .{property.name});
|
log.write("{s}", .{property.name});
|
||||||
|
|
||||||
@@ -510,9 +510,9 @@ pub const Fdt = struct {
|
|||||||
var f = true;
|
var f = true;
|
||||||
while (v.next()) |s| {
|
while (v.next()) |s| {
|
||||||
if (f) {
|
if (f) {
|
||||||
log.write_raw(" = ");
|
log.write_waw(" = ");
|
||||||
} else {
|
} else {
|
||||||
log.write_raw(", ");
|
log.write_waw(", ");
|
||||||
}
|
}
|
||||||
f = false;
|
f = false;
|
||||||
log.write("\"{s}\"", .{s});
|
log.write("\"{s}\"", .{s});
|
||||||
@@ -520,17 +520,17 @@ pub const Fdt = struct {
|
|||||||
} else {
|
} else {
|
||||||
// Dump the rest as a cell array
|
// Dump the rest as a cell array
|
||||||
const len = property.len_cells();
|
const len = property.len_cells();
|
||||||
log.write_raw(" = <");
|
log.write_waw(" = <");
|
||||||
for (0..len) |i| {
|
for (0..len) |i| {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
log.write_raw(", ");
|
log.write_waw(", ");
|
||||||
}
|
}
|
||||||
log.write("0x{x}", .{property.get_cell_unchecked(i)});
|
log.write("0x{x}", .{property.get_cell_unchecked(i)});
|
||||||
}
|
}
|
||||||
log.write_raw(">");
|
log.write_waw(">");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.write_raw(";\r\n");
|
log.write_waw(";\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dump_node(node: *const FdtNode, depth: usize) void {
|
fn dump_node(node: *const FdtNode, depth: usize) void {
|
||||||
@@ -538,12 +538,12 @@ pub const Fdt = struct {
|
|||||||
var first_child = true;
|
var first_child = true;
|
||||||
|
|
||||||
for (0..depth) |_| {
|
for (0..depth) |_| {
|
||||||
log.write_raw(" ");
|
log.write_waw(" ");
|
||||||
}
|
}
|
||||||
if (node.name.len != 0) {
|
if (node.name.len != 0) {
|
||||||
log.write("{s} ", .{node.name});
|
log.write("{s} ", .{node.name});
|
||||||
}
|
}
|
||||||
log.write_raw("{\r\n");
|
log.write_waw("{\r\n");
|
||||||
var properties = node.prop_iterator();
|
var properties = node.prop_iterator();
|
||||||
const all_strings = std.mem.eql(u8, node.name, "aliases");
|
const all_strings = std.mem.eql(u8, node.name, "aliases");
|
||||||
while (properties.next()) |property| {
|
while (properties.next()) |property| {
|
||||||
@@ -553,13 +553,13 @@ pub const Fdt = struct {
|
|||||||
var children = node.children();
|
var children = node.children();
|
||||||
while (children.next()) |child| {
|
while (children.next()) |child| {
|
||||||
if (any_properties and first_child) {
|
if (any_properties and first_child) {
|
||||||
log.write_raw("\r\n");
|
log.write_waw("\r\n");
|
||||||
}
|
}
|
||||||
first_child = false;
|
first_child = false;
|
||||||
dump_node(&child, depth + 1);
|
dump_node(&child, depth + 1);
|
||||||
}
|
}
|
||||||
for (0..depth) |_| {
|
for (0..depth) |_| {
|
||||||
log.write_raw(" ");
|
log.write_waw(" ");
|
||||||
}
|
}
|
||||||
log.write("}},\r\n", .{});
|
log.write("}},\r\n", .{});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
//! Utilities for manipulating ranges.
|
//! Utilities for manipulating ranges.
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
/// Non-inclusive range type over `T`.
|
/// Non-inclusive range type over `T`.
|
||||||
pub fn Range(comptime T: type) type {
|
pub fn Range(comptime T: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
@@ -31,19 +29,5 @@ pub fn Range(comptime T: type) type {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(self: *const @This(), scalar: T) bool {
|
|
||||||
return scalar >= self.start and scalar - self.start < self.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compare_disjoint(a: *const @This(), b: *const @This()) std.math.Order {
|
|
||||||
if (a.start >= b.end()) {
|
|
||||||
return .gt;
|
|
||||||
} else if (b.start >= a.end()) {
|
|
||||||
return .lt;
|
|
||||||
} else {
|
|
||||||
return .eq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,450 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const btree = @import("btree.zig");
|
|
||||||
|
|
||||||
const Range = @import("range.zig").Range;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const BTree = btree.BTree;
|
|
||||||
pub const Order = btree.Order;
|
|
||||||
|
|
||||||
pub fn RangeMap(
|
|
||||||
comptime K: type,
|
|
||||||
comptime V: type,
|
|
||||||
comptime ops: struct {
|
|
||||||
deinit_fn: ?fn (*V) void = null,
|
|
||||||
merge_fn: ?fn (*const V, *const V) bool = null,
|
|
||||||
},
|
|
||||||
) type {
|
|
||||||
return struct {
|
|
||||||
pub const Node = struct {
|
|
||||||
key: Range(K),
|
|
||||||
value: V,
|
|
||||||
|
|
||||||
pub fn len(self: *const @This()) K {
|
|
||||||
return self.key.len;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const WalkFn = fn (*const Node) void;
|
|
||||||
|
|
||||||
pub const Iterator = struct {
|
|
||||||
inner: Tree.Iterator,
|
|
||||||
|
|
||||||
pub fn next(self: *Iterator) ?*const Node {
|
|
||||||
if (self.inner.next()) |n| {
|
|
||||||
return &n.key;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Tree = BTree(Node, compare_fn, deinit_node_fn);
|
|
||||||
|
|
||||||
pub const Error = error{
|
|
||||||
scalar_out_of_range,
|
|
||||||
range_out_of_bounds,
|
|
||||||
} || Tree.Error;
|
|
||||||
|
|
||||||
fn compare_fn(a: *const Node, b: *const Node) Order {
|
|
||||||
return Range(K).compare_disjoint(&a.key, &b.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit_node_fn(n: *Node) void {
|
|
||||||
if (comptime ops.deinit_fn) |f| {
|
|
||||||
f(&n.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
btree: Tree,
|
|
||||||
|
|
||||||
pub fn init(gpa: Allocator) @This() {
|
|
||||||
return .{ .btree = Tree.init(gpa) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *@This()) void {
|
|
||||||
self.btree.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the value at a given scalar point, along with the full range it belongs to.
|
|
||||||
pub fn get_scalar(self: *const @This(), scalar: K) ?*Node {
|
|
||||||
return if (self.get_scalar_node(scalar)) |n| &n.key else null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as `get_scalar()`, but returns the underlying BST node.
|
|
||||||
pub fn get_scalar_node(self: *const @This(), scalar: K) ?*Tree.Node {
|
|
||||||
return self.btree.search(scalar, struct {
|
|
||||||
fn call(n: *const Node, cx: K) Order {
|
|
||||||
if (n.key.contains(cx)) {
|
|
||||||
return .eq;
|
|
||||||
} else if (cx < n.key.start) {
|
|
||||||
return .gt;
|
|
||||||
} else {
|
|
||||||
return .lt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.call);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Splits a given node at a scalar point inside its interval.
|
|
||||||
///
|
|
||||||
/// The part of the interval before `at` is considered a "left" half, the remaining
|
|
||||||
/// part is considered a "right" half.
|
|
||||||
///
|
|
||||||
/// # Note
|
|
||||||
///
|
|
||||||
/// The "right" halve's value after the split is left uninitialized and it is up to the
|
|
||||||
/// caller to assign a proper value to it.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// * `scalar_out_of_range` if the given `at` value is not inside the node's interval.
|
|
||||||
pub fn split_node(
|
|
||||||
self: *@This(),
|
|
||||||
node: *Tree.Node,
|
|
||||||
at: K,
|
|
||||||
) Error!?struct { *Tree.Node, *Tree.Node } {
|
|
||||||
if (!node.key.key.contains(at)) {
|
|
||||||
return error.scalar_out_of_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = node.key.key.start;
|
|
||||||
const end = node.key.key.end();
|
|
||||||
|
|
||||||
if (at == start or at == end - 1) {
|
|
||||||
// Nothing to split here
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = node.key.value;
|
|
||||||
|
|
||||||
// Remove the node, don't drop the key
|
|
||||||
try self.btree.remove_node(node, false);
|
|
||||||
|
|
||||||
const lnode = try self.btree.insert(
|
|
||||||
.{ .key = .{ .start = start, .len = at - start }, .value = value },
|
|
||||||
);
|
|
||||||
const rnode = try self.btree.insert(
|
|
||||||
.{ .key = .{ .start = at, .len = end - at }, .value = undefined },
|
|
||||||
);
|
|
||||||
|
|
||||||
return .{ lnode, rnode };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps some range to a value. Returns an error if the requested range crosses another
|
|
||||||
/// mapped range.
|
|
||||||
pub fn insert(self: *@This(), start: K, len: K, value: V) Error!*Tree.Node {
|
|
||||||
try validate_range(start, len);
|
|
||||||
|
|
||||||
if (comptime ops.merge_fn) |merge_fn| {
|
|
||||||
const left: ?*Tree.Node = if (start > 0) self.get_scalar_node(start - 1) else null;
|
|
||||||
const right = self.get_scalar_node(start + len);
|
|
||||||
|
|
||||||
if (left) |l| {
|
|
||||||
const l_start = l.key.key.start;
|
|
||||||
|
|
||||||
if (merge_fn(&l.key.value, &value)) {
|
|
||||||
if (right) |r| {
|
|
||||||
if (merge_fn(&r.key.value, &value)) {
|
|
||||||
l.key.key.len += len + r.key.key.len;
|
|
||||||
try self.btree.remove_node(r, true);
|
|
||||||
return self.get_scalar_node(l_start).?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
l.key.key.len += len;
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (right) |r| {
|
|
||||||
// Only right node to potentially merge with
|
|
||||||
if (merge_fn(&r.key.value, &value)) {
|
|
||||||
const r_len = r.key.key.len;
|
|
||||||
try self.btree.remove_node(r, true);
|
|
||||||
return self.btree.insert(.{
|
|
||||||
.key = .{ .start = start, .len = len + r_len },
|
|
||||||
.value = value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.btree.insert(.{
|
|
||||||
.key = .{ .start = start, .len = len },
|
|
||||||
.value = value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iterator(self: *@This()) Iterator {
|
|
||||||
return .{ .inner = self.btree.iterator() };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn node_iterator(self: *@This()) Tree.Iterator {
|
|
||||||
return self.btree.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn walk(self: *@This(), walk_fn: WalkFn) void {
|
|
||||||
self.btree.walk(walk_fn, struct {
|
|
||||||
fn call(n: *const Tree.Node, cx: WalkFn) void {
|
|
||||||
cx(&n.key);
|
|
||||||
}
|
|
||||||
}.call);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_range(start: K, end: K) Error!void {
|
|
||||||
// Check for addition overflowing the K's bit size
|
|
||||||
if (std.math.add(K, start, end) == error.Overflow) {
|
|
||||||
return error.range_out_of_bounds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Range map insertion" {
|
|
||||||
const Map = RangeMap(u32, []const u8, .{});
|
|
||||||
var map = Map.init(std.testing.allocator);
|
|
||||||
defer map.deinit();
|
|
||||||
|
|
||||||
_ = try map.insert(10, 10, "Range 2");
|
|
||||||
_ = try map.insert(0, 10, "Range 1");
|
|
||||||
_ = try map.insert(20, 10, "Range 3");
|
|
||||||
|
|
||||||
try std.testing.expectError(error.already_exists, map.insert(5, 10, "Invalid range"));
|
|
||||||
|
|
||||||
_ = try map.insert(1000, 10, "Range 4");
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Range map merging insertion" {
|
|
||||||
const Map = RangeMap(u32, bool, .{
|
|
||||||
.merge_fn = struct {
|
|
||||||
fn call(lhs: *const bool, rhs: *const bool) bool {
|
|
||||||
return !lhs.* and !rhs.*;
|
|
||||||
}
|
|
||||||
}.call,
|
|
||||||
});
|
|
||||||
var map = Map.init(std.testing.allocator);
|
|
||||||
defer map.deinit();
|
|
||||||
|
|
||||||
// Should not merge
|
|
||||||
_ = try map.insert(10, 10, false);
|
|
||||||
_ = try map.insert(0, 10, true);
|
|
||||||
|
|
||||||
{
|
|
||||||
var it = map.iterator();
|
|
||||||
try std.testing.expectEqual(true, it.next().?.value);
|
|
||||||
try std.testing.expectEqual(false, it.next().?.value);
|
|
||||||
try std.testing.expectEqual(null, it.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge left + inserted + right
|
|
||||||
_ = try map.insert(30, 10, false);
|
|
||||||
_ = try map.insert(20, 10, false);
|
|
||||||
|
|
||||||
{
|
|
||||||
var it = map.iterator();
|
|
||||||
const n0 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
|
|
||||||
try std.testing.expectEqual(true, n0.value);
|
|
||||||
|
|
||||||
const n1 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
|
|
||||||
try std.testing.expectEqual(false, n1.value);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(null, it.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should not merge again
|
|
||||||
_ = try map.insert(40, 10, true);
|
|
||||||
_ = try map.insert(50, 10, false);
|
|
||||||
|
|
||||||
{
|
|
||||||
var it = map.iterator();
|
|
||||||
const n0 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
|
|
||||||
try std.testing.expectEqual(true, n0.value);
|
|
||||||
|
|
||||||
const n1 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
|
|
||||||
try std.testing.expectEqual(false, n1.value);
|
|
||||||
|
|
||||||
const n2 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key);
|
|
||||||
try std.testing.expectEqual(true, n2.value);
|
|
||||||
|
|
||||||
const n3 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 50, .len = 10 }, n3.key);
|
|
||||||
try std.testing.expectEqual(false, n3.value);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(null, it.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should merge left + shouldn't merge right non-contiguous
|
|
||||||
_ = try map.insert(71, 9, false);
|
|
||||||
_ = try map.insert(60, 10, false);
|
|
||||||
|
|
||||||
{
|
|
||||||
var it = map.iterator();
|
|
||||||
const n0 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
|
|
||||||
try std.testing.expectEqual(true, n0.value);
|
|
||||||
|
|
||||||
const n1 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
|
|
||||||
try std.testing.expectEqual(false, n1.value);
|
|
||||||
|
|
||||||
const n2 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key);
|
|
||||||
try std.testing.expectEqual(true, n2.value);
|
|
||||||
|
|
||||||
const n3 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 50, .len = 20 }, n3.key);
|
|
||||||
try std.testing.expectEqual(false, n3.value);
|
|
||||||
|
|
||||||
const n4 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 71, .len = 9 }, n4.key);
|
|
||||||
try std.testing.expectEqual(false, n4.value);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(null, it.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should merge left and right
|
|
||||||
_ = try map.insert(70, 1, false);
|
|
||||||
|
|
||||||
{
|
|
||||||
var it = map.iterator();
|
|
||||||
const n0 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
|
|
||||||
try std.testing.expectEqual(true, n0.value);
|
|
||||||
|
|
||||||
const n1 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
|
|
||||||
try std.testing.expectEqual(false, n1.value);
|
|
||||||
|
|
||||||
const n2 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key);
|
|
||||||
try std.testing.expectEqual(true, n2.value);
|
|
||||||
|
|
||||||
const n3 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 50, .len = 30 }, n3.key);
|
|
||||||
try std.testing.expectEqual(false, n3.value);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(null, it.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should merge right
|
|
||||||
_ = try map.insert(110, 10, false);
|
|
||||||
_ = try map.insert(100, 10, false);
|
|
||||||
|
|
||||||
{
|
|
||||||
var it = map.iterator();
|
|
||||||
const n0 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key);
|
|
||||||
try std.testing.expectEqual(true, n0.value);
|
|
||||||
|
|
||||||
const n1 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key);
|
|
||||||
try std.testing.expectEqual(false, n1.value);
|
|
||||||
|
|
||||||
const n2 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key);
|
|
||||||
try std.testing.expectEqual(true, n2.value);
|
|
||||||
|
|
||||||
const n3 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 50, .len = 30 }, n3.key);
|
|
||||||
try std.testing.expectEqual(false, n3.value);
|
|
||||||
|
|
||||||
const n4 = it.next().?;
|
|
||||||
try std.testing.expectEqual(Range(u32){ .start = 100, .len = 20 }, n4.key);
|
|
||||||
try std.testing.expectEqual(false, n4.value);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(null, it.next());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Range map get scalar" {
|
|
||||||
const Map = RangeMap(u32, []const u8, .{});
|
|
||||||
var map = Map.init(std.testing.allocator);
|
|
||||||
defer map.deinit();
|
|
||||||
|
|
||||||
_ = try map.insert(10, 10, "Range [10..20)");
|
|
||||||
_ = try map.insert(30, 10, "Range [30..40)");
|
|
||||||
|
|
||||||
{
|
|
||||||
const n = map.get_scalar(15).?;
|
|
||||||
try std.testing.expectEqual(10, n.key.start);
|
|
||||||
try std.testing.expectEqual(20, n.key.end());
|
|
||||||
try std.testing.expectEqualStrings("Range [10..20)", n.value);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const n = map.get_scalar(35).?;
|
|
||||||
try std.testing.expectEqual(30, n.key.start);
|
|
||||||
try std.testing.expectEqual(40, n.key.end());
|
|
||||||
try std.testing.expectEqualStrings("Range [30..40)", n.value);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const n = map.get_scalar(30).?;
|
|
||||||
try std.testing.expectEqual(30, n.key.start);
|
|
||||||
try std.testing.expectEqual(40, n.key.end());
|
|
||||||
try std.testing.expectEqualStrings("Range [30..40)", n.value);
|
|
||||||
}
|
|
||||||
try std.testing.expectEqual(null, map.get_scalar(20));
|
|
||||||
try std.testing.expectEqual(null, map.get_scalar(21));
|
|
||||||
try std.testing.expectEqual(null, map.get_scalar(9));
|
|
||||||
try std.testing.expectEqual(null, map.get_scalar(100));
|
|
||||||
try std.testing.expectEqual(null, map.get_scalar(40));
|
|
||||||
try std.testing.expectEqual(null, map.get_scalar(41));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Range map split" {
|
|
||||||
const Map = RangeMap(u32, []const u8, .{});
|
|
||||||
var map = Map.init(std.testing.allocator);
|
|
||||||
defer map.deinit();
|
|
||||||
|
|
||||||
_ = try map.insert(0x1000, 0x1000, "Range [0x1000..0x2000)");
|
|
||||||
|
|
||||||
const node = map.get_scalar_node(0x1000).?;
|
|
||||||
const lnode, const rnode = (try map.split_node(node, 0x1200)).?;
|
|
||||||
|
|
||||||
lnode.key.value = "Left";
|
|
||||||
rnode.key.value = "Right";
|
|
||||||
|
|
||||||
{
|
|
||||||
const n = map.get_scalar(0x1100).?;
|
|
||||||
try std.testing.expectEqual(0x1000, n.key.start);
|
|
||||||
try std.testing.expectEqual(0x200, n.key.len);
|
|
||||||
try std.testing.expectEqualStrings("Left", n.value);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const n = map.get_scalar(0x1300).?;
|
|
||||||
try std.testing.expectEqual(0x1200, n.key.start);
|
|
||||||
try std.testing.expectEqual(0xE00, n.key.len);
|
|
||||||
try std.testing.expectEqualStrings("Right", n.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Range map iterator" {
|
|
||||||
const Map = RangeMap(u32, []const u8, .{});
|
|
||||||
var map = Map.init(std.testing.allocator);
|
|
||||||
defer map.deinit();
|
|
||||||
|
|
||||||
_ = try map.insert(0x1000, 0x1000, "Range [0x1000..0x2000)");
|
|
||||||
_ = try map.insert(0x2000, 0x1000, "Range [0x2000..0x3000)");
|
|
||||||
_ = try map.insert(0x4000, 0x1000, "Range [0x4000..0x5000)");
|
|
||||||
_ = try map.insert(0x3000, 0x1000, "Range [0x3000..0x4000)");
|
|
||||||
|
|
||||||
var it = map.iterator();
|
|
||||||
try std.testing.expectEqualStrings("Range [0x1000..0x2000)", it.next().?.value);
|
|
||||||
try std.testing.expectEqualStrings("Range [0x2000..0x3000)", it.next().?.value);
|
|
||||||
try std.testing.expectEqualStrings("Range [0x3000..0x4000)", it.next().?.value);
|
|
||||||
try std.testing.expectEqualStrings("Range [0x4000..0x5000)", it.next().?.value);
|
|
||||||
try std.testing.expectEqual(null, it.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Range map should disallow overflowing ranges" {
|
|
||||||
const Map = RangeMap(u32, bool, .{});
|
|
||||||
var map = Map.init(std.testing.allocator);
|
|
||||||
defer map.deinit();
|
|
||||||
|
|
||||||
try std.testing.expectError(error.range_out_of_bounds, map.insert(0xF0000000, 0x20000000, false));
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
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*)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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"
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
SECTIONS {
|
|
||||||
. = 0x200000;
|
|
||||||
|
|
||||||
.text : {
|
|
||||||
*(.text.entry*);
|
|
||||||
*(.text*)
|
|
||||||
}
|
|
||||||
|
|
||||||
.rodata : ALIGN(4K) {
|
|
||||||
*(.rodata*)
|
|
||||||
}
|
|
||||||
|
|
||||||
.data : ALIGN(4K) {
|
|
||||||
*(.data*)
|
|
||||||
}
|
|
||||||
|
|
||||||
.bss : ALIGN(4K) {
|
|
||||||
*(.bss*)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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);
|
|
||||||
// }
|
|
||||||
Reference in New Issue
Block a user