Initial commit

This commit is contained in:
2025-03-13 18:06:14 +02:00
commit 467e4a944a
16 changed files with 621 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/zig-out
/.zig-cache
+74
View File
@@ -0,0 +1,74 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const optimize = .Debug;
const target = b.standardTargetOptions(.{
.default_target = .{
.cpu_arch = .riscv64,
.os_tag = .freestanding,
.abi = .none,
}
});
const kernel_module = b.addModule("kernel", .{
.optimize = optimize,
.target = target,
.pic = true,
.red_zone = false,
.code_model = .medium,
.root_source_file = b.path("src/kernel.zig")
});
const kernel = b.addExecutable(.{
.name = "kernel",
.root_module = kernel_module,
.pic = true
});
kernel.pie = true;
kernel.entry = .{.symbol_name = "__rv64_entry"};
kernel.setLinkerScript(b.path("etc/riscv64-unknown-none.ld"));
kernel.addCSourceFiles(.{
.files = &.{
"src/arch/riscv64/entry.S"
},
.flags = &.{},
});
b.installArtifact(kernel);
const elf2bin = b.addSystemCommand(&.{
"llvm-objcopy",
"-O", "binary",
"zig-out/bin/kernel",
"zig-out/bin/kernel.bin"
});
// TODO QEMU binary override
const qemu_info = switch (target.result.cpu.arch) {
.riscv64 => .{ "qemu-system-riscv64", "rv64" },
else => unreachable,
};
const qemu_cmd = b.addSystemCommand(&.{
qemu_info[0],
"-M", "virt",
"-kernel", "zig-out/bin/kernel.bin",
"-m", "256M",
"-cpu", qemu_info[1],
"-serial", "mon:stdio",
"-display", "none"
});
if (target.result.cpu.arch == .riscv64) {
qemu_cmd.addArgs(&.{
"-bios", "etc/boot/rv64_fw_jump.bin"
});
}
elf2bin.step.dependOn(b.getInstallStep());
qemu_cmd.step.dependOn(&elf2bin.step);
if (b.args) |args| qemu_cmd.addArgs(args);
const run_step = b.step("run", "Start the OS in qemu");
run_step.dependOn(&qemu_cmd.step);
}
Binary file not shown.
+39
View File
@@ -0,0 +1,39 @@
ENTRY(__rv64_entry);
SECTIONS {
. = 0x0;
.text : ALIGN(4K) {
*(.text.entry*)
*(.text*)
}
.rodata : ALIGN(4K) {
*(.rodata*)
*(.got*)
*(.plt*)
}
.dynamic : ALIGN(4K) {
*(.dynamic*)
}
.rela : ALIGN(4K) {
PROVIDE(__rela_start = .);
*(.rela*)
PROVIDE(__rela_end = .);
}
.data : ALIGN(4K) {
*(.data*)
}
.bss : {
. = ALIGN(4K);
PROVIDE(__bss_start = .);
*(COMMON)
*(.bss*)
. = ALIGN(4K);
PROVIDE(__bss_end = .);
}
}
+7
View File
@@ -0,0 +1,7 @@
gdb-remote localhost:1234
target modules add -s kernel zig-out/bin/kernel
# target modules load -f zig-out/bin/kernel -s 0x80200000
target modules load -f zig-out/bin/kernel -s 0x200200000
breakpoint set -n arch.riscv64.boot.rv64BspEntryLower
+12
View File
@@ -0,0 +1,12 @@
pub fn arch() type {
const builtin = @import("builtin");
switch (comptime builtin.cpu.arch) {
.riscv64 => {
return @import("arch/riscv64.zig").arch();
},
else => {
@panic("Architecture is not supported");
},
}
}
+23
View File
@@ -0,0 +1,23 @@
const boot = @import("riscv64/boot.zig");
export const _ = boot.rv64BspLowerEntry;
pub fn arch() type {
return struct {
pub inline fn halt() noreturn {
while (true) {
_ = setInterruptMask(true);
pause();
}
}
pub inline fn setInterruptMask(mask: bool) bool {
// TODO
_ = mask;
return true;
}
pub inline fn pause() void {
asm volatile ("wfi");
}
};
}
+115
View File
@@ -0,0 +1,115 @@
const sbi = @import("sbi.zig");
const debug = @import("../../debug.zig");
const arch = @import("../../kernel.zig").arch;
const vmm = @import("vmm.zig");
const regs = @import("regs.zig");
const log = debug.log;
extern const __rela_start: u8;
extern const __rela_end: u8;
extern const __rv64_bsp_stack_top: u8;
pub export fn rv64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: usize) void {
const elf = @import("std").elf;
const relaTablePtr = @as([*]elf.Rela, @ptrFromInt(relaStart));
const relaCount = (relaEnd - relaStart) / @sizeOf(elf.Rela);
const relaTable = relaTablePtr[0..relaCount];
for (relaTable) |entry| {
if (entry.r_type() == 0x03) {
const value = @as(*isize, @ptrFromInt(imageBase + entry.r_offset));
value.* = @as(isize, @bitCast(imageBase)) + entry.r_addend;
} else {
arch.halt();
}
}
}
fn bspUpperEntry(a0: usize, a1: usize) callconv(.C) noreturn {
asm volatile ("":::"memory");
_ = a1;
// Relocate the kernel yet again, this time to another base
const relaStart = @intFromPtr(&__rela_start);
const relaEnd = @intFromPtr(&__rela_end);
const relOffset = vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(a0);
asm volatile ("":::"memory");
rv64RelocateKernel(relOffset, relaStart, relaEnd);
asm volatile ("":::"memory");
// Can unmap lower half now
for (0..4) |i| {
vmm.fixed.entry(i).* = .INVALID;
}
asm volatile ("":::"memory");
debug.log.setWriteFn(&sbi.debugPrintByte);
log.info("Still alive", .{});
arch.halt();
}
fn longJump(pc: usize, sp: usize, a0: usize, a1: usize) noreturn {
asm volatile (
\\ mv sp, %[sp]
\\ jr %[pc]
:
: [a0]"{a0}"(a0),
[a1]"{a1}"(a1),
[pc]"r"(pc),
[sp]"r"(sp)
:"memory"
);
unreachable;
}
fn setupMmu(realAddress: usize) void {
var table = &vmm.fixed;
const realL1 = vmm.L1.index(realAddress);
// Lower half
for (0..4) |i| {
table.entry(i).* = vmm.TableEntry(vmm.L1).page(
.{ .raw = i * vmm.L1.SIZE },
.{ .r = true, .w = true, .x = true }
);
}
// Map 1GiB at KERNEL_VIRTUAL_BASE -> physical 1GiB where the kernel is loaded
table.entry(vmm.KERNEL_VIRTUAL_L1I).* = vmm.TableEntry(vmm.L1).page(
.{ .raw = vmm.L1.address(realL1) },
.{ .r = true, .w = true, .x = true }
);
const address = @as(usize, @intFromPtr(table));
regs.SATP.write(.{
.PPN = @intCast(address >> 12),
.MODE = .sv39
});
}
pub export fn rv64BspLowerEntry(realAddress: usize) callconv(.C) noreturn {
debug.log.setWriteFn(&sbi.debugPrintByte);
setupMmu(realAddress);
// &bspUpperEntry will yield a pointer like: X + P, where
// * X is symbol's raw address,
// * P is the physical load base of the image (0x80200000 on rv64 usually)
//
// Relocate the address to point to Y + P, where Y is the virtual load base
// const kernelL1Offset = realAddress & ((1 << 30) - 1);
const realAddressL1Offset = vmm.L1.offset(realAddress);
const virtualEntry = @intFromPtr(&bspUpperEntry) + vmm.KERNEL_VIRTUAL_BASE - realAddress + realAddressL1Offset;
const virtualSp = @intFromPtr(&__rv64_bsp_stack_top) + vmm.KERNEL_VIRTUAL_BASE - realAddress + realAddressL1Offset;
longJump(virtualEntry, virtualSp, realAddress, 0);
arch.halt();
}
+67
View File
@@ -0,0 +1,67 @@
.set ENTRY_SYMBOL, rv64BspLowerEntry
.set RELOC_SYMBOL, rv64RelocateKernel
.global __rv64_entry
.global __rv64_bsp_stack_top
.extern ENTRY_SYMBOL
.extern RELOC_SYMBOL
.pushsection .text.entry
.option push
.option norvc
.type __rv64_entry, @function
__rv64_entry:
auipc s0, 0 // a0 = real PC (also a real load address/offset)
csrw sie, zero
csrw sip, zero
csrw satp, zero
mv tp, zero
// Zero the .bss
// NOTE: I don't trust the assembler to place a proper pair of instructions
// in place of a `la`, so do this manually
.P00: auipc t0, %pcrel_hi(__bss_start)
addi t0, t0, %pcrel_lo(.P00)
.P01: auipc t1, %pcrel_hi(__bss_end)
addi t1, t1, %pcrel_lo(.P01)
.L01:
beq t0, t1, .L02
sd zero, (t0)
addi t0, t0, 8
j .L01
.L02:
.P02: auipc sp, %pcrel_hi(__rv64_bsp_stack_top)
addi sp, sp, %pcrel_lo(.P02)
// Relocate the kernel
.P03: auipc a1, %pcrel_hi(__rela_start)
addi a1, a1, %pcrel_lo(.P03)
.P04: auipc a2, %pcrel_hi(__rela_end)
addi a2, a2, %pcrel_lo(.P04)
.P05: auipc t0, %pcrel_hi(RELOC_SYMBOL)
addi t0, t0, %pcrel_lo(.P05)
mv a0, s0
jalr t0
.P06: auipc t0, %pcrel_hi(ENTRY_SYMBOL)
addi t0, t0, %pcrel_lo(.P06)
mv a0, s0
jr t0
.size __rv64_entry, . - __rv64_entry
.option pop
.popsection
.pushsection .bss
.p2align 4
__rv64_bsp_stack_bottom:
.skip 65536
__rv64_bsp_stack_top:
.popsection
+35
View File
@@ -0,0 +1,35 @@
fn makeRegister(comptime name: []const u8, comptime bits: type) type {
const repr = @typeInfo(bits).@"struct".backing_integer.?;
return enum(repr) {
pub fn set(value: repr) void {
asm volatile ("csrw " ++ name ++ ", %[value]"::[value]"r"(value));
}
pub fn get() repr {
return asm volatile ("csrr %[value], " ++ name:[value]"=r"(-> repr));
}
pub fn write(value: bits) void {
set(@bitCast(value));
}
pub fn read() bits {
return @bitCast(get());
}
pub usingnamespace bits;
};
}
pub const SATP = makeRegister("satp", packed struct(u64) {
// 0..44
PPN: u44 = 0,
// 44..60
ASID: u16 = 0,
// 60..64
MODE: enum(u4) {
bare = 0,
sv39 = 8,
_,
} = .bare
});
+56
View File
@@ -0,0 +1,56 @@
const std = @import("std");
const SbiExtension = enum(u64) {
hsm = 0x48534D,
time = 0x54494D45,
dbcn = 0x4442434E,
spi = 0x735049,
};
const SbiError = enum(i64) {
failed = -1,
not_supported = -2,
invalid_param = -3,
denied = -4,
invalid_address = -5,
already_available = -6,
already_started = -7,
already_stopped = -8,
no_shmem = -9,
invalid_state = -10,
bad_range = -11,
timeout = -12,
io = -13,
};
const SbiResult = union(enum) {
ok: u64,
err: SbiError,
fn fromSbi(a0: u64, a1: u64) SbiResult {
if (a0 == 0) {
return .{ .ok = a1 };
} else {
return .{ .err = @enumFromInt(a0) };
}
}
};
fn sbiCall1(ext: SbiExtension, func: u64, arg0: u64) SbiResult {
var a0: u64 = undefined;
var a1: u64 = undefined;
asm volatile (
"ecall"
: [ret0] "={a0}" (a0),
[ret1] "={a1}" (a1),
: [arg0] "{a0}" (arg0),
[func] "{a6}" (func),
[extn] "{a7}" (ext)
: "a2", "a3", "a4", "a5"
);
return SbiResult.fromSbi(a0, a1);
}
pub fn debugPrintByte(byte: u8) void {
_ = sbiCall1(.dbcn, 0x02, @as(u64, byte));
}
+127
View File
@@ -0,0 +1,127 @@
const PhysicalAddress = @import("../../mem.zig").PhysicalAddress;
pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFFF000000000;
pub const KERNEL_VIRTUAL_L1I: usize = (KERNEL_VIRTUAL_BASE >> L1.SHIFT) & 511;
fn translationLevel(comptime shift: usize) type {
return struct {
pub const SHIFT: usize = shift;
pub const SIZE: usize = 1 << shift;
pub inline fn index(addr: usize) usize {
return (addr >> shift) & 511;
}
pub inline fn offset(addr: usize) usize {
return addr & ((1 << shift) - 1);
}
pub inline fn address(idx: usize) usize {
return idx << shift;
}
};
}
pub const L1 = translationLevel(30);
pub const L2 = translationLevel(21);
pub const L3 = translationLevel(12);
pub const RawEntry = packed struct(u64) {
// 0: Valid
v: bool = false,
// 1 : Read
r: bool = false,
// 2: Write
w: bool = false,
// 3: Execute
x: bool = false,
// 4: U-mode access
u: bool = false,
// 5: Global bit
g: bool = false,
// 6: Access bit
a: bool = false,
// 7: dirty bit
d: bool = false,
// 8..10 Unused bits
_pad0: u2 = 0,
// 10..49: Address
address: u39 = 0,
// 49..64: Unused bits
_pad1: u15 = 0,
pub fn makeUnion(self: @This(), other: @This()) @This() {
const lhs = @as(u64, @bitCast(self));
const rhs = @as(u64, @bitCast(other));
return @as(@This(), @bitCast(lhs | rhs));
}
pub fn clear(self: *@This(), mask: @This()) void {
const lhs = @as(*u64, @bitCast(self));
const rhs = @as(u64, @bitCast(mask));
lhs.* &= ~rhs;
}
};
pub fn TableEntry(comptime Level: type) type {
_ = Level;
return struct {
raw: RawEntry,
pub const INVALID: @This() = .{ .raw = .{} };
pub fn address(self: @This()) PhysicalAddress {
if (self.raw.v) {
return .{ .raw = self.raw.address << 12 };
} else {
return PhysicalAddress.NULL;
}
}
pub fn bits(self: @This()) u64 {
return @as(u64, @bitCast(self.raw));
}
pub fn page(addr: PhysicalAddress, flags: RawEntry) @This() {
return .{
.raw = flags.makeUnion(.{
.address = @as(u39, @intCast(addr.raw >> 12)),
.r = true,
.v = true,
.d = true,
.a = true,
}),
};
}
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
flags.clear(.{ .r = true, .w = true, .x = true });
return .{
.raw = flags.makeUnion(.{
.address = @as(u39, @intCast(addr.raw >> 12)),
.v = true,
})
};
}
};
}
pub fn Table(comptime Level: type) type {
return struct {
pub const Entry = TableEntry(Level);
entries: [512]Entry align(4096),
pub fn empty() @This() {
return .{
.entries = [_]Entry{.INVALID} ** 512
};
}
pub inline fn entry(self: *@This(), index: usize) *Entry {
return &self.entries[index];
}
};
}
pub var fixed = Table(L1).empty();
+42
View File
@@ -0,0 +1,42 @@
const std = @import("std");
fn dummyWrite(_: u8) void {}
pub const log = struct {
pub const Level = enum {
debug,
info,
warn,
err,
};
var writeFn: *const fn(u8) void = dummyWrite;
const writer: std.io.GenericWriter(u0, error{}, writeWrapperFn) = .{
.context = 0
};
fn writeWrapperFn(context: u0, data: []const u8) error{}!usize {
_ = context;
for (data) |byte| {
writeFn(byte);
}
return data.len;
}
pub fn setWriteFn(f: *const fn(u8) void) void {
writeFn = f;
}
pub fn info(comptime format: []const u8, args: anytype) void {
write(.info, format ++ "\r\n", args);
}
pub fn writeRaw(data: []const u8) void {
writeWrapperFn(void, data);
}
pub fn write(level: Level, comptime format: []const u8, args: anytype) void {
_ = level;
writer.print(format, args) catch return;
}
};
+7
View File
@@ -0,0 +1,7 @@
// export const _ = @import("arch/riscv64/boot.zig").rv64BspLowerEntry;
pub const arch = @import("arch.zig").arch();
pub const mem = @import("mem.zig");
export fn kernel_main() callconv(.C) void {
arch.halt();
}
+9
View File
@@ -0,0 +1,9 @@
pub const PhysicalAddress = packed struct(u64) {
raw: u64,
pub const NULL: @This() = .{ .raw = 0 };
pub fn add(self: @This(), offset: usize) @This() {
return .{ .raw = self.raw + @as(u64, @intCast(offset)) };
}
};
+6
View File
@@ -0,0 +1,6 @@
pub fn IrqSafeSpinlock(comptime T: type) type {
return struct {
inner: T,
};
}