diff options
| -rw-r--r-- | .dockerignore | 10 | ||||
| -rw-r--r-- | .gitignore | 15 | ||||
| -rw-r--r-- | .gitmodules | 6 | ||||
| -rw-r--r-- | Dockerfile | 29 | ||||
| -rw-r--r-- | Makefile | 56 | ||||
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | boot/boot.zig | 22 | ||||
| -rw-r--r-- | boot/limine.conf | 5 | ||||
| -rw-r--r-- | boot/limine.zig | 27 | ||||
| -rw-r--r-- | boot/linker.ld | 52 | ||||
| -rw-r--r-- | build.zig | 46 | ||||
| -rw-r--r-- | kernel/.gitkeep | 0 | ||||
| m--------- | limine | 0 | ||||
| m--------- | revo | 0 |
14 files changed, 273 insertions, 1 deletions
diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5c42f8f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +revo +limine +iso_root +zig-out +.zig-cache +*.iso +*.ppm +*.png +*.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f770585 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# zig build output +/zig-out/ +/.zig-cache/ + +# ISO staging + image +/iso_root/ +/*.iso + +# local boot scratch (screendumps, qemu logs) +/*.ppm +/*.png +/*.log + +# clang + sanitization stuff +/*.dSYM diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a2e24b3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "revo"] + path = revo + url = https://github.com/hachem-wtf/revo.git +[submodule "limine"] + path = limine + url = https://github.com/limine-bootloader/limine.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7a633d3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# This is so that if something breaks, lung doesn't tell at me +# +# docker build -t revel . +# docker run --rm -v "$PWD":/revel revel make run +FROM debian:bookworm-slim + +ARG ZIG_VERSION=0.16.0 +ARG TARGETARCH +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl xz-utils git make gcc \ + xorriso qemu-system-x86 \ + && rm -rf /var/lib/apt/lists/* + +RUN case "${TARGETARCH}" in \ + amd64) ZARCH=x86_64 ;; \ + arm64) ZARCH=aarch64 ;; \ + *) echo "unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZARCH}-linux-${ZIG_VERSION}.tar.xz" \ + -o /tmp/zig.tar.xz \ + && mkdir -p /opt/zig \ + && tar -xJf /tmp/zig.tar.xz -C /opt/zig --strip-components=1 \ + && ln -s /opt/zig/zig /usr/local/bin/zig \ + && rm /tmp/zig.tar.xz + +WORKDIR /revel +CMD ["make", "run"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6252139 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +# Revel — build the kernel, assemble a Limine ISO, boot it in QEMU. +# +# make build the ISO (default) +# make run build and boot it headless (serial on stdout) +# make kernel just compile the kernel ELF +# make clean remove build output (kernel, ISO, caches) +# make distclean also remove Limine's built host tool + +ZIG ?= zig +QEMU ?= qemu-system-x86_64 + +KERNEL := zig-out/bin/revel +ISO := revel.iso + +.PHONY: all kernel iso run clean distclean + +all: iso + +kernel: + $(ZIG) build + +limine/limine: + $(MAKE) -C limine + +iso: kernel limine/limine + rm -rf iso_root + mkdir -p iso_root/boot/limine iso_root/EFI/BOOT + cp $(KERNEL) iso_root/boot/revel + cp boot/limine.conf iso_root/boot/limine/ + cp limine/limine-bios.sys \ + limine/limine-bios-cd.bin \ + limine/limine-uefi-cd.bin iso_root/boot/limine/ + cp limine/BOOTX64.EFI \ + limine/BOOTIA32.EFI iso_root/EFI/BOOT/ + xorriso -as mkisofs -R -r -J \ + -b boot/limine/limine-bios-cd.bin \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -hfsplus -apm-block-size 2048 \ + --efi-boot boot/limine/limine-uefi-cd.bin \ + -efi-boot-part --efi-boot-image --protective-msdos-label \ + iso_root -o $(ISO) + ./limine/limine bios-install $(ISO) + +# for now this is just going to be a headless run +# all I need this for is to check for kernel panics +# and limine related failures. +run: iso + $(QEMU) -M q35 -m 256M -cdrom $(ISO) -boot d \ + -display none -serial stdio -no-reboot \ + -no-shutdown + +clean: + rm -rf iso_root zig-out .zig-cache $(ISO) + +distclean: clean + rm -f limine/limine @@ -1,2 +1,6 @@ # revel -kernel made in revo +A Unix-like kernel, written in revo! + +## License + +MIT, see [LICENSE](LICENSE). diff --git a/boot/boot.zig b/boot/boot.zig new file mode 100644 index 0000000..0f6c92c --- /dev/null +++ b/boot/boot.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const limine = @import("limine.zig"); + +// We need a panic handler otherwise zig won't be happy, +// std usually formats a message through std.Io.Writer which +// emits SSE instructions which I haven't enabled. For now +// we just halt. +pub const panic = std.debug.FullPanic(struct { + fn halt(_: []const u8, _: ?usize) noreturn { + while (true) asm volatile ("hlt"); + } +}.halt); + +// Check `linker.ld` +export var base_revision: limine.BaseRevision linksection(".limine_requests") = limine.BaseRevision.init(3); +export var requests_start: limine.RequestsStartMarker linksection(".limine_requests_start") = .{}; +export var requests_end: limine.RequestsEndMarker linksection(".limine_requests_end") = .{}; + +// ENTRY(_start) +export fn _start() callconv(.c) noreturn { + while (true) asm volatile ("hlt"); +} diff --git a/boot/limine.conf b/boot/limine.conf new file mode 100644 index 0000000..7398dca --- /dev/null +++ b/boot/limine.conf @@ -0,0 +1,5 @@ +timeout: 0 + +/Revel + protocol: limine + path: boot():/boot/revel diff --git a/boot/limine.zig b/boot/limine.zig new file mode 100644 index 0000000..e9aac9c --- /dev/null +++ b/boot/limine.zig @@ -0,0 +1,27 @@ +// Limine scans the loaded image for these magic numbers, +// BaseRevision is a handshake, essentially we ask limine +// for rev 3, and if it supports it, limine zeros it out +pub const BaseRevision = extern struct { + magic0: u64 = 0xf9562b2d5c95a6c8, + magic1: u64 = 0x6a7b384944536bdc, + revision: u64, + + pub fn init(revision: u64) BaseRevision { + return .{ .revision = revision }; + } +}; + +// Bracketing markers, they are pretty optional but when +// we add proper limine requests, having these here comes +// handy +pub const RequestsStartMarker = extern struct { + m0: u64 = 0xf6b8f4b39de7d1ae, + m1: u64 = 0xfab91a6940fcb9cf, + m2: u64 = 0x785c6ed015d3e316, + m3: u64 = 0x181e920a7852b9d9, +}; + +pub const RequestsEndMarker = extern struct { + m0: u64 = 0xadc0e0531bb10d03, + m1: u64 = 0x9572709f31764c62, +}; diff --git a/boot/linker.ld b/boot/linker.ld new file mode 100644 index 0000000..e15ed10 --- /dev/null +++ b/boot/linker.ld @@ -0,0 +1,52 @@ +/* the higher half of the kernel link maps + * at the top 2 GiB is where Limine maps us. */ + +OUTPUT_FORMAT(elf64-x86-64) +ENTRY(_start) + +PHDRS +{ + limine_requests PT_LOAD; + text PT_LOAD; + rodata PT_LOAD; + data PT_LOAD; +} + +SECTIONS +{ + . = 0xffffffff80000000; + + /* The loader hunts for the magic numbers here + * these are referenced in `boot/` */ + .limine_requests : { + KEEP(*(.limine_requests_start)) + KEEP(*(.limine_requests)) + KEEP(*(.limine_requests_end)) + } :limine_requests + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + .text : { + *(.text .text.*) + } :text + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + .rodata : { + *(.rodata .rodata.*) + } :rodata + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + .data : { + *(.data .data.*) + } :data + + .bss : { + *(.bss .bss.*) + *(COMMON) + } :data + + /* no unwind tables or note sections. */ + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + } +} diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..f49eb58 --- /dev/null +++ b/build.zig @@ -0,0 +1,46 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // Default to ReleaseSafe because Debug pulls Zig's + // UBSan runtime and other sanitization, which needs + // f128/SSE ops, which i don't provide. + const optimize = b.option(std.builtin.OptimizeMode, "optimize", "optimize mode") orelse .ReleaseSafe; + + // The kernel must NOT touch SSE/MMX/AVX until it has + // enabled them itself, so I strip those features and + // let the compiler use a soft-float ABI. + // `code_model = .kernel` keeps relocations valid for + // the higher-half load address in boot/linker.ld. + // + // In other words. Going raw baby + var query: std.Target.Query = .{ + .cpu_arch = .x86_64, + .os_tag = .freestanding, + .abi = .none, + }; + const Feature = std.Target.x86.Feature; + query.cpu_features_sub.addFeature(@intFromEnum(Feature.mmx)); + query.cpu_features_sub.addFeature(@intFromEnum(Feature.sse)); + query.cpu_features_sub.addFeature(@intFromEnum(Feature.sse2)); + query.cpu_features_sub.addFeature(@intFromEnum(Feature.avx)); + query.cpu_features_sub.addFeature(@intFromEnum(Feature.avx2)); + query.cpu_features_add.addFeature(@intFromEnum(Feature.soft_float)); + + const kernel = b.addExecutable(.{ + .name = "revel", + .root_module = b.createModule(.{ + .root_source_file = b.path("boot/boot.zig"), + .target = b.resolveTargetQuery(query), + .optimize = optimize, + .code_model = .kernel, + .red_zone = false, + .single_threaded = true, + .link_libc = false, + }), + }); + kernel.entry = .{ .symbol_name = "_start" }; + kernel.pie = false; + kernel.setLinkerScript(b.path("boot/linker.ld")); + + b.installArtifact(kernel); +} diff --git a/kernel/.gitkeep b/kernel/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/kernel/.gitkeep diff --git a/limine b/limine new file mode 160000 +Subproject 5be26a73d7b7b4d4477d18be94e1d16e615adf5 diff --git a/revo b/revo new file mode 160000 +Subproject de1a5c8bdca6c0cc7bb60378dfd7c9e64f12dcf |
