Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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
10 changes: 10 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand All @@ -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,
Expand All @@ -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",
Expand Down
20 changes: 15 additions & 5 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
192 changes: 192 additions & 0 deletions src/common.zig
Original file line number Diff line number Diff line change
@@ -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]);
}
}
17 changes: 8 additions & 9 deletions src/kernel/main.zig
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,25 +14,20 @@ 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 {
@setRuntimeSafety(false);
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();
}
}
22 changes: 22 additions & 0 deletions src/kernel/panic.zig
Original file line number Diff line number Diff line change
@@ -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;
}
38 changes: 38 additions & 0 deletions src/kernel/sbi.zig
Original file line number Diff line number Diff line change
@@ -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);
}
Loading