summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2024-12-25 02:55:41 +0100
committerJohannes Stoelp <johannes.stoelp@gmail.com>2024-12-25 21:25:16 +0100
commita2ce17753fa4a727f082bfb9bd515dc3fde56f02 (patch)
tree9b5d3c309a32c483e4d9cec34d1fe57a476da578
parent14845124cf5d4fb42a04cb1262b32bd3d00f45f8 (diff)
downloadzig-playground-a2ce17753fa4a727f082bfb9bd515dc3fde56f02.tar.gz
zig-playground-a2ce17753fa4a727f082bfb9bd515dc3fde56f02.zip
mbr: zig only real mode 16 bit
-rw-r--r--x86-bare-metal/mbr-pure-zig/.gitignore1
-rw-r--r--x86-bare-metal/mbr-pure-zig/Makefile29
-rw-r--r--x86-bare-metal/mbr-pure-zig/bios.zig65
-rw-r--r--x86-bare-metal/mbr-pure-zig/mbr.ld24
-rw-r--r--x86-bare-metal/mbr-pure-zig/mbr.zig106
-rw-r--r--x86-bare-metal/mbr-pure-zig/x220.pngbin0 -> 438430 bytes
6 files changed, 225 insertions, 0 deletions
diff --git a/x86-bare-metal/mbr-pure-zig/.gitignore b/x86-bare-metal/mbr-pure-zig/.gitignore
new file mode 100644
index 0000000..a92a67f
--- /dev/null
+++ b/x86-bare-metal/mbr-pure-zig/.gitignore
@@ -0,0 +1 @@
+BUILD/ \ No newline at end of file
diff --git a/x86-bare-metal/mbr-pure-zig/Makefile b/x86-bare-metal/mbr-pure-zig/Makefile
new file mode 100644
index 0000000..41d2653
--- /dev/null
+++ b/x86-bare-metal/mbr-pure-zig/Makefile
@@ -0,0 +1,29 @@
+O := BUILD
+
+COMMON_FLAGS := -fno-unwind-tables -fno-strip -target x86-freestanding-code16 -O ReleaseSmall
+
+$(O)/boot: $(O)/boot.elf | check_ep dump_info
+ objcopy -O binary $< $@
+
+$(O)/boot.elf: mbr.ld mbr.zig | $(O)
+ zig build-exe -femit-bin=$@ $(COMMON_FLAGS) -fno-entry --script $^
+
+clean:
+ $(RM) -r $(O)
+
+$(O):
+ mkdir -p $(O)
+
+# Check entry point is exactly at 0x7c00.
+check_ep: $(O)/boot.elf
+ readelf -h $< | awk '/Entry point address:/ { print $$4 }' | grep 0x7c00 >& /dev/null
+
+# Dump some debug info (disasm, elf load segments, code size).
+dump_info: $(O)/boot.elf
+ objdump -Mintel -m i8086 --disassemble --visualize-jumps=extended-color $<
+ readelf -W -l $<
+ size $<
+ size $< | awk '/$(notdir $<)/ { print "MBR utilization " $$1/512 "%" }'
+
+run: $(O)/boot
+ qemu-system-i386 -hda $< $(QEMU_ARGS)
diff --git a/x86-bare-metal/mbr-pure-zig/bios.zig b/x86-bare-metal/mbr-pure-zig/bios.zig
new file mode 100644
index 0000000..b96b312
--- /dev/null
+++ b/x86-bare-metal/mbr-pure-zig/bios.zig
@@ -0,0 +1,65 @@
+/// BIOS starts with video mode 0h.
+/// * 80x25 text mode
+/// * 16 colors (4bit)
+/// http://www.ctyme.com/intr/rb-0069.htm
+pub const COLS = 80;
+pub const ROWS = 25;
+
+/// BIOS CALL - video teletype output.
+/// http://www.ctyme.com/intr/rb-0106.htm
+pub fn putc(c: u8) void {
+ // ah=0eh (call nr)
+ // al=c (ascii char to write)
+ const ax = (@as(u16, 0xe) << 8) | c;
+
+ _ = asm volatile (
+ \\int $0x10
+ : // No outputs.
+ : [a] "{ax}" (ax), // BIOS call number + char to write.
+ : "cc"
+ );
+
+ // On linefeed also write a carriage return to move to column 0.
+ if (c == '\n') {
+ putc('\r');
+ }
+}
+
+/// BIOS CALL - video set cursor position.
+/// http://www.ctyme.com/intr/rb-0087.htm
+pub fn set_pos(r: u8) void {
+ // dh=r (cursor row)
+ // dl=0 (cursor col)
+ const dx = @as(u16, r) << 8 | 0;
+
+ _ = asm volatile (
+ \\int $0x10
+ : // No outputs.
+ : [a] "{ax}" (0x200), // BIOS call number.
+ [b] "{bx}" (0x0), // Page number.
+ [d] "{dx}" (dx), // Position.
+ : "cc"
+ );
+}
+
+/// BIOS CALL - video scroll up
+/// http://www.ctyme.com/intr/rb-0096.htm
+pub fn scroll_up() void {
+ // ah=06h (call nr)
+ // al=1 (rows to scroll)
+ const ax = (@as(u16, 0x6) << 8) | 1;
+
+ // dh=ROWS-1 (row lower right corner)
+ // dl=COLS-1 (col lower right corner)
+ const dx = (@as(u16, ROWS - 1) << 8) | (COLS - 1);
+
+ _ = asm volatile (
+ \\int $0x10
+ : // No outputs.
+ : [a] "{ax}" (ax), // BIOS call number + rows to scroll.
+ [p] "{dx}" (dx), // Row + col lower right corner.
+ [b] "{bx}" (0x0700), // Attribute, dark bg, white fg.
+ [c] "{cx}" (0), // Row + col upper left corner.
+ : "cc"
+ );
+}
diff --git a/x86-bare-metal/mbr-pure-zig/mbr.ld b/x86-bare-metal/mbr-pure-zig/mbr.ld
new file mode 100644
index 0000000..9ada8c4
--- /dev/null
+++ b/x86-bare-metal/mbr-pure-zig/mbr.ld
@@ -0,0 +1,24 @@
+/*OUTPUT_FORMAT(binary)*/
+OUTPUT_FORMAT(elf32-i386)
+OUTPUT_ARCH(i386)
+ENTRY(_entry)
+
+SECTIONS {
+ . = 0x7c00;
+ .boot : { *(.boot) }
+ .text : { *(.text) }
+ .data : { *(.data) }
+ .rodata : { *(.rodata) *(.rodata.str*) }
+ _boot_end = .;
+
+ . = 0x7c00 + 510;
+ .mbr.magic : {
+ BYTE(0x55);
+ BYTE(0xaa);
+ }
+
+ /*/DISCARD/ : { *(.*) }*/
+ /*rest : { *(.*) }*/
+
+ ASSERT(_boot_end - 0x7c00 < 510, "boot sector must fit in 510 bytes")
+}
diff --git a/x86-bare-metal/mbr-pure-zig/mbr.zig b/x86-bare-metal/mbr-pure-zig/mbr.zig
new file mode 100644
index 0000000..666aa6b
--- /dev/null
+++ b/x86-bare-metal/mbr-pure-zig/mbr.zig
@@ -0,0 +1,106 @@
+const bios = @import("bios.zig");
+
+// -- ENTRY POINT ---------------------------------------------------------------
+
+export fn _entry() linksection(".boot") callconv(.naked) noreturn {
+ asm volatile (
+ // Disable interrupts.
+ \\cli
+ // Clear segment selectors.
+ \\xor %%ax, %%ax
+ \\mov %%ax, %%ds
+ \\mov %%ax, %%es
+ \\mov %%ax, %%ss
+ \\mov %%ax, %%fs
+ \\mov %%ax, %%gs
+ \\mov $0x7c00, %sp
+ // Long jump to set cs to 0x0000, as some BIOSes load the MBR
+ // to either 07c0:0000 or 0000:7c000.
+ \\ljmp $0x0, $main
+ );
+}
+
+// -- MAIN ----------------------------------------------------------------------
+
+// main should be "callconv(.naked)", once issue is fixed.
+// https://github.com/ziglang/zig/issues/18183
+export fn main() noreturn {
+ for (0..255) |i| {
+ if (i < bios.ROWS) {
+ bios.set_pos(@truncate(i));
+ } else {
+ // Once we reach the bottom of the screen start scrolling.
+ bios.scroll_up();
+ bios.set_pos(bios.ROWS - 1);
+ }
+ puts_dec(i + 1);
+ bios.putc(' ');
+ puts_hex(i + 1);
+ bios.putc(' ');
+ bios.putc(@truncate('a' + (i % 26)));
+ bios.putc(@truncate('a' + (i % 26)));
+ waste_time();
+ }
+ puts("\ndone");
+
+ while (true) {
+ asm volatile ("hlt");
+ }
+}
+
+// -- UTILS ---------------------------------------------------------------------
+
+fn puts(str: []const u8) void {
+ for (str) |c| {
+ bios.putc(c);
+ }
+}
+
+fn puts_dec(init_val: u32) void {
+ var val = init_val;
+ var buf: [32]u8 = undefined;
+ var idx = buf.len;
+
+ while (val > 0 and idx != 0) : (val /= 10) {
+ idx -= 1;
+ buf[idx] = @as(u8, @truncate(val % 10)) + '0';
+ }
+ puts(buf[idx..]);
+}
+
+fn puts_hex(init_val: u32) void {
+ var val = init_val;
+ var buf: [32]u8 = undefined;
+ var idx = buf.len;
+
+ while (val > 0 and idx != 0) : (val /= 16) {
+ idx -= 1;
+ const digit = switch (@as(u8, @truncate(val % 16))) {
+ 0...9 => |d| '0' + d,
+ 10...15 => |d| 'a' + d - 10,
+ else => '?',
+ };
+ buf[idx] = digit;
+ }
+ puts(buf[idx..]);
+}
+
+fn rdtsc() u32 {
+ return asm volatile ("rdtsc"
+ : [eax] "={eax}" (-> u32),
+ :
+ : "edx"
+ );
+}
+
+fn waste_time() void {
+ const start = rdtsc();
+ var now = start;
+ var idx: usize = 0;
+ while (now - start < (1 << 28)) : (idx += 1) {
+ if (idx == 10_000) {
+ now = rdtsc();
+ idx = 0;
+ }
+ }
+}
diff --git a/x86-bare-metal/mbr-pure-zig/x220.png b/x86-bare-metal/mbr-pure-zig/x220.png
new file mode 100644
index 0000000..ac3a4e8
--- /dev/null
+++ b/x86-bare-metal/mbr-pure-zig/x220.png
Binary files differ