diff --git a/Dockerfile b/Dockerfile index 64b8cc4..1a20f53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,15 +6,20 @@ ARG DEBIAN_FRONTEND=noninteractive ARG ZIG_VERSION=0.15.2 ARG ZIG_TARBALL=zig-aarch64-linux-0.15.2.tar.xz ARG ZIG_URL=https://ziglang.org/download/0.15.2/zig-aarch64-linux-0.15.2.tar.xz +ARG OPENSBI_VERSION=1.8.1 +ARG OPENSBI_URL=https://github.com/riscv-software-src/opensbi/archive/refs/tags/v1.8.1.tar.gz RUN apt-get update \ && apt-get install -y --no-install-recommends \ bash \ ca-certificates \ + clang \ coreutils \ curl \ + device-tree-compiler \ file \ git \ + lld \ llvm \ make \ python3 \ @@ -33,4 +38,12 @@ RUN curl -fsSL "$ZIG_URL" -o /tmp/zig.tar.xz \ && ln -sf "/opt/zig-${ZIG_VERSION}/zig" /usr/local/bin/zig \ && rm -f /tmp/zig.tar.xz +RUN curl -fsSL "$OPENSBI_URL" -o /tmp/opensbi.tar.gz \ + && mkdir -p /tmp/opensbi-src \ + && tar -xzf /tmp/opensbi.tar.gz -C /tmp/opensbi-src --strip-components=1 \ + && make -C /tmp/opensbi-src PLATFORM=generic PLATFORM_RISCV_XLEN=32 LLVM=1 \ + && install -D /tmp/opensbi-src/build/platform/generic/firmware/fw_dynamic.bin /usr/share/qemu/opensbi-riscv32-generic-fw_dynamic.bin \ + && install -D /tmp/opensbi-src/build/platform/generic/firmware/fw_dynamic.elf /usr/share/qemu/opensbi-riscv32-generic-fw_dynamic.elf \ + && rm -rf /tmp/opensbi-src /tmp/opensbi.tar.gz + WORKDIR /workspaces/os-in-1000-lines-zig diff --git a/build.zig b/build.zig index bab2c3b..379de47 100644 --- a/build.zig +++ b/build.zig @@ -18,6 +18,9 @@ pub fn build(b: *std.Build) void { &.{ "llvm-readelf", "--version" }, &.{ "llvm-nm", "--version" }, &.{ "bash", "--version" }, + &.{ "clang", "--version" }, + &.{ "dtc", "--version" }, + &.{ "ld.lld", "--version" }, &.{ "make", "--version" }, &.{ "tar", "--version" }, &.{ "python3", "--version" }, @@ -38,6 +41,12 @@ pub fn build(b: *std.Build) void { }); status_step.dependOn(&status.step); + const common_module = b.createModule(.{ + .root_source_file = b.path("src/common.zig"), + .target = target, + .optimize = optimize, + }); + const kernel_module = b.createModule(.{ .root_source_file = b.path("src/kernel/main.zig"), .target = target, @@ -48,6 +57,7 @@ pub fn build(b: *std.Build) void { .omit_frame_pointer = true, .red_zone = false, }); + kernel_module.addImport("common", common_module); const kernel = b.addExecutable(.{ .name = "kernel", diff --git a/run.sh b/run.sh index 14ec017..91b0ef2 100755 --- a/run.sh +++ b/run.sh @@ -7,6 +7,7 @@ qemu_log="${QEMU_LOG:-${repo_root}/qemu.log}" qemu_timeout_seconds="${QEMU_TIMEOUT_SECONDS:-3}" qemu_bios="${QEMU_BIOS:-default}" qemu_default_bios_path="${QEMU_DEFAULT_BIOS_PATH:-/usr/share/qemu/opensbi-riscv32-generic-fw_dynamic.bin}" +qemu_allow_bios_none_fallback="${QEMU_ALLOW_BIOS_NONE_FALLBACK:-0}" required_tools=( qemu-system-riscv32 @@ -24,11 +25,20 @@ mkdir -p "$(dirname "${qemu_log}")" : >"${qemu_log}" if [[ "${qemu_bios}" == "default" && ! -f "${qemu_default_bios_path}" ]]; then - printf '%s\n' \ - "OpenSBI for riscv32 was not found at ${qemu_default_bios_path}." \ - "Falling back to -bios none for the Chapter 04 boot smoke test." | - tee -a "${qemu_log}" - qemu_bios="none" + if [[ "${qemu_allow_bios_none_fallback}" == "1" ]]; then + printf '%s\n' \ + "OpenSBI for riscv32 was not found at ${qemu_default_bios_path}." \ + "Falling back to -bios none as requested by QEMU_ALLOW_BIOS_NONE_FALLBACK=1." | + tee -a "${qemu_log}" + qemu_bios="none" + else + printf '%s\n' \ + "OpenSBI for riscv32 was not found at ${qemu_default_bios_path}." \ + "Skipping QEMU run because Chapter 05 SBI output requires OpenSBI." \ + "Set QEMU_DEFAULT_BIOS_PATH or QEMU_ALLOW_BIOS_NONE_FALLBACK=1 to override." | + tee -a "${qemu_log}" + exit 0 + fi fi if [[ ! -f "${kernel_elf}" ]]; then diff --git a/src/common.zig b/src/common.zig index e69de29..aec2f9d 100644 --- a/src/common.zig +++ b/src/common.zig @@ -0,0 +1,192 @@ +const std = @import("std"); + +pub const size_t = usize; +pub const paddr_t = u32; +pub const vaddr_t = u32; + +pub fn alignUp(value: usize, alignment: usize) usize { + return (value + alignment - 1) & ~(alignment - 1); +} + +pub fn isAligned(value: usize, alignment: usize) bool { + return (value & (alignment - 1)) == 0; +} + +pub fn offsetOf(comptime T: type, comptime field_name: []const u8) usize { + return @offsetOf(T, field_name); +} + +pub fn memset(buf: [*]u8, value: u8, len: size_t) [*]u8 { + @setRuntimeSafety(false); + + var index: size_t = 0; + while (index < len) : (index += 1) { + buf[index] = value; + } + + return buf; +} + +pub fn memcpy(dst: [*]u8, src: [*]const u8, len: size_t) [*]u8 { + @setRuntimeSafety(false); + + var index: size_t = 0; + while (index < len) : (index += 1) { + dst[index] = src[index]; + } + + return dst; +} + +pub fn strcpy(dst: [*]u8, src: [*:0]const u8) [*]u8 { + @setRuntimeSafety(false); + + var index: size_t = 0; + while (src[index] != 0) : (index += 1) { + dst[index] = src[index]; + } + dst[index] = 0; + + return dst; +} + +pub fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) i32 { + @setRuntimeSafety(false); + + var index: size_t = 0; + while (s1[index] != 0 and s2[index] != 0) : (index += 1) { + if (s1[index] != s2[index]) break; + } + + return @as(i32, s1[index]) - @as(i32, s2[index]); +} + +pub fn printf(comptime putchar: fn (u8) void, comptime fmt: []const u8, args: anytype) void { + comptime var index = 0; + comptime var arg_index = 0; + + inline while (index < fmt.len) : (index += 1) { + if (fmt[index] != '%') { + putchar(fmt[index]); + continue; + } + + index += 1; + if (index >= fmt.len) { + putchar('%'); + break; + } + + switch (fmt[index]) { + '%' => putchar('%'), + 's' => { + writeString(putchar, @field(args, tupleFieldName(@TypeOf(args), arg_index))); + arg_index += 1; + }, + 'd' => { + writeDecimal(putchar, @field(args, tupleFieldName(@TypeOf(args), arg_index))); + arg_index += 1; + }, + 'x' => { + writeHex(putchar, @field(args, tupleFieldName(@TypeOf(args), arg_index))); + arg_index += 1; + }, + else => @compileError("unsupported printf format specifier"), + } + } +} + +fn tupleFieldName(comptime Args: type, comptime index: usize) []const u8 { + const fields = std.meta.fields(Args); + if (index >= fields.len) @compileError("printf argument count mismatch"); + return fields[index].name; +} + +fn writeString(comptime putchar: fn (u8) void, value: anytype) void { + const T = @TypeOf(value); + switch (@typeInfo(T)) { + .pointer => |pointer| switch (pointer.size) { + .slice => for (value) |ch| putchar(ch), + .one => { + switch (@typeInfo(pointer.child)) { + .array => |array| { + const array_value = value.*; + if (array.sentinel()) |_| { + var index: usize = 0; + while (index < array.len and array_value[index] != 0) : (index += 1) { + putchar(array_value[index]); + } + } else { + for (array_value) |ch| putchar(ch); + } + }, + else => @compileError("expected string pointer"), + } + }, + else => @compileError("unsupported string type"), + }, + .array => for (value) |ch| putchar(ch), + else => @compileError("unsupported string type"), + } +} + +fn writeDecimal(comptime putchar: fn (u8) void, value: anytype) void { + const T = @TypeOf(value); + const info = @typeInfo(T); + if (info != .int and info != .comptime_int) @compileError("%d requires an integer"); + + const signed_value: i64 = switch (info) { + .int => |int_info| if (int_info.signedness == .signed) + @as(i64, @intCast(value)) + else + @as(i64, @intCast(@as(u64, value))), + .comptime_int => value, + else => unreachable, + }; + + var magnitude: u64 = undefined; + if (signed_value < 0) { + putchar('-'); + magnitude = @intCast(-signed_value); + } else { + magnitude = @intCast(signed_value); + } + + var divisor: u64 = 1; + while (magnitude / divisor > 9) { + divisor *= 10; + } + + while (divisor > 0) { + putchar('0' + @as(u8, @intCast(magnitude / divisor))); + magnitude %= divisor; + divisor /= 10; + } +} + +fn writeHex(comptime putchar: fn (u8) void, value: anytype) void { + const T = @TypeOf(value); + const info = @typeInfo(T); + if (info != .int and info != .comptime_int) @compileError("%x requires an integer"); + + const bits = switch (info) { + .int => |int_info| int_info.bits, + .comptime_int => 32, + else => unreachable, + }; + const nibble_count = @max(1, (bits + 3) / 4); + + const unsigned_value: u64 = switch (info) { + .int => @as(u64, @intCast(value)), + .comptime_int => @intCast(value), + else => unreachable, + }; + + comptime var nibble_index = nibble_count; + inline while (nibble_index > 0) { + nibble_index -= 1; + const shift = nibble_index * 4; + const nibble: u8 = @intCast((unsigned_value >> shift) & 0xf); + putchar("0123456789abcdef"[nibble]); + } +} diff --git a/src/kernel/main.zig b/src/kernel/main.zig index f92056c..9fad766 100644 --- a/src/kernel/main.zig +++ b/src/kernel/main.zig @@ -1,4 +1,8 @@ const boot = @import("boot.zig"); +const common = @import("common"); +const panic_mod = @import("panic.zig"); +const riscv = @import("riscv.zig"); +const sbi = @import("sbi.zig"); comptime { _ = boot; @@ -10,10 +14,7 @@ extern var __bss_end: u8; pub export fn kernel_main() noreturn { @setRuntimeSafety(false); zeroBss(); - - while (true) { - asm volatile ("wfi"); - } + panic_mod.PANIC(@src(), "booted!", .{}); } fn zeroBss() void { @@ -21,14 +22,12 @@ fn zeroBss() void { const start = @intFromPtr(&__bss); const end = @intFromPtr(&__bss_end); - var current: [*]u8 = @ptrFromInt(start); - while (@intFromPtr(current) < end) : (current += 1) { - current[0] = 0; - } + _ = common.memset(@ptrFromInt(start), 0, end - start); } pub fn panic(_: []const u8, _: ?*const anyopaque, _: ?usize) noreturn { + common.printf(sbi.consolePutchar, "PANIC: %s\n", .{"zig panic"}); while (true) { - asm volatile ("wfi"); + riscv.wfi(); } } diff --git a/src/kernel/panic.zig b/src/kernel/panic.zig new file mode 100644 index 0000000..e147dc0 --- /dev/null +++ b/src/kernel/panic.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const common = @import("common"); +const riscv = @import("riscv.zig"); +const sbi = @import("sbi.zig"); + +pub inline fn PANIC(comptime src: std.builtin.SourceLocation, comptime fmt: []const u8, args: anytype) noreturn { + common.printf(sbi.consolePutchar, "PANIC: %s:%d: ", .{ basename(src.file), src.line }); + common.printf(sbi.consolePutchar, fmt, args); + sbi.consolePutchar('\n'); + + while (true) { + riscv.wfi(); + } +} + +fn basename(path: []const u8) []const u8 { + var index = path.len; + while (index > 0) : (index -= 1) { + if (path[index - 1] == '/') return path[index..]; + } + return path; +} diff --git a/src/kernel/sbi.zig b/src/kernel/sbi.zig new file mode 100644 index 0000000..806eb73 --- /dev/null +++ b/src/kernel/sbi.zig @@ -0,0 +1,38 @@ +pub const Sbiret = extern struct { + @"error": isize, + value: isize, +}; + +pub fn call( + arg0: usize, + arg1: usize, + arg2: usize, + arg3: usize, + arg4: usize, + arg5: usize, + fid: usize, + eid: usize, +) Sbiret { + var value: isize = 0; + + const err_code = asm volatile ( + \\ ecall + \\ sw a1, 0(%[value_ptr]) + : [err] "={x10}" (-> isize), + : [arg0] "{x10}" (arg0), + [arg1] "{x11}" (arg1), + [arg2] "{x12}" (arg2), + [arg3] "{x13}" (arg3), + [arg4] "{x14}" (arg4), + [arg5] "{x15}" (arg5), + [fid] "{x16}" (fid), + [eid] "{x17}" (eid), + [value_ptr] "r" (&value), + : .{ .memory = true }); + + return .{ .@"error" = err_code, .value = value }; +} + +pub fn consolePutchar(ch: u8) void { + _ = call(ch, 0, 0, 0, 0, 0, 0, 1); +}