Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/zig-out
|
||||
/.zig-cache
|
||||
@@ -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.
@@ -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 = .);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
});
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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)) };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
pub fn IrqSafeSpinlock(comptime T: type) type {
|
||||
return struct {
|
||||
inner: T,
|
||||
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user