diff options
-rw-r--r-- | example-network/.gitignore | 2 | ||||
-rw-r--r-- | example-network/Makefile | 3 | ||||
-rw-r--r-- | example-network/pong.zig | 136 | ||||
-rw-r--r-- | flake.nix | 19 | ||||
-rw-r--r-- | x86-bare-metal/mbr-disk-lba/.gitignore | 1 | ||||
-rw-r--r-- | x86-bare-metal/mbr-disk-lba/Makefile | 40 | ||||
-rw-r--r-- | x86-bare-metal/mbr-disk-lba/mbr.S | 125 | ||||
-rw-r--r-- | x86-bare-metal/mbr-disk-lba/mbr.ld | 26 | ||||
-rw-r--r-- | x86-bare-metal/mbr-disk-lba/zmbr.zig | 66 | ||||
-rw-r--r-- | x86-bare-metal/mbr-e820/Makefile | 2 | ||||
-rw-r--r-- | x86-bare-metal/mbr-textmode/.gitignore | 1 | ||||
-rw-r--r-- | x86-bare-metal/mbr-textmode/Makefile | 28 | ||||
-rw-r--r-- | x86-bare-metal/mbr-textmode/mbr.S | 98 | ||||
-rw-r--r-- | x86-bare-metal/mbr-textmode/mbr.ld | 25 | ||||
-rw-r--r-- | x86-bare-metal/mbr-textmode/zmbr.zig | 39 |
15 files changed, 610 insertions, 1 deletions
diff --git a/example-network/.gitignore b/example-network/.gitignore new file mode 100644 index 0000000..5263b7f --- /dev/null +++ b/example-network/.gitignore @@ -0,0 +1,2 @@ +pong +pong.o*
\ No newline at end of file diff --git a/example-network/Makefile b/example-network/Makefile index 8fc8a1c..7b51314 100644 --- a/example-network/Makefile +++ b/example-network/Makefile @@ -2,3 +2,6 @@ s: zig run basic.zig -- -s c: zig run basic.zig + +pong: pong.zig + zig build-exe $^ -O ReleaseFast diff --git a/example-network/pong.zig b/example-network/pong.zig new file mode 100644 index 0000000..3ef861c --- /dev/null +++ b/example-network/pong.zig @@ -0,0 +1,136 @@ +const std = @import("std"); + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +const default_opts = struct { + /// Whether to start the server or the client side. + is_server: bool, + /// IPv4 address to listen (server) on or connect (client) to. + addr: [:0]const u8, + /// Message size send between client and server. + msg_size: usize, +}{ .is_server = true, .addr = "127.0.0.1", .msg_size = 4096 }; + +/// Parse command line arguments. +fn parse_args() !?@TypeOf(default_opts) { + var opts = default_opts; + var args = std.process.args(); + + // Skip program name. + _ = args.skip(); + + return while (args.next()) |arg| { + if (std.mem.eql(u8, arg, "-c")) { + opts.is_server = false; + } else if (std.mem.eql(u8, arg, "-a")) { + opts.addr = args.next() orelse break error.MissingAddressValue; + } else if (std.mem.eql(u8, arg, "-m")) { + const bytes = args.next() orelse break error.MissingMessageSizeBytesValue; + opts.msg_size = try std.fmt.parseInt(usize, bytes, 10); + } else if (std.mem.eql(u8, arg, "-h")) { + std.debug.print("Usage: pong [args]\n", .{}); + std.debug.print(" -c start client, rather than server\n", .{}); + std.debug.print(" -a <addr> listen/connect IPv4 addr (default: {s})\n", .{default_opts.addr}); + std.debug.print(" -m <bytes> message size in bytes, client only (default: {})\n", .{default_opts.msg_size}); + std.debug.print(" -h this help message\n", .{}); + break null; + } else { + std.log.err("Unknown argument '{s}'", .{arg}); + break null; + } + } else opts; +} + +fn run_server(alloc: std.mem.Allocator, addr: std.net.Address) !void { + // Start a new server. + var server = try addr.listen(.{ .reuse_address = true }); + defer server.deinit(); + + while (true) { + // Accept a new client. + std.log.info("server listening on {}", .{addr}); + const client = (try server.accept()).stream; + defer client.close(); + + // Allocate message buffer from client provided message size. + const buf = blk: { + // (1) Read message size from client (format: "<bytes>\n"). + var msg_buf: [16]u8 = undefined; + const msg = try client.reader().readUntilDelimiter(&msg_buf, '\n'); + const msg_size = try std.fmt.parseInt(usize, msg, 10); + + // (2) Allocate actual message buffer. + std.log.info("allocate message buffer with {} bytes", .{msg_size}); + break :blk try alloc.alloc(u8, msg_size); + }; + defer alloc.free(buf); + + // Function to receive and loop back message buffer. + const loop_message = struct { + fn call(c: std.net.Stream, b: []u8) !void { + _ = try c.readAll(b); + try c.writeAll(b); + } + }.call; + + // Send message buffer in a loop, on error wait for a new client. + while (true) { + loop_message(client, buf) catch break; + } + } +} + +fn run_client(alloc: std.mem.Allocator, addr: std.net.Address, msg_size: usize) !void { + // Connect to a server. + std.log.info("client connecting to {}", .{addr}); + var con = try std.net.tcpConnectToAddress(addr); + defer con.close(); + + // Send message size and allocate message buffer. + const buf = blk: { + // (1) Send message size to server (format: "<bytes>\n"). + var msg_buf: [16]u8 = undefined; + const msg = try std.fmt.bufPrint(&msg_buf, "{}\n", .{msg_size}); + try con.writeAll(msg); + + // (2) Allocate actual message buffer. + std.log.info("allocate message buffer with {} bytes", .{msg_size}); + break :blk try alloc.alloc(u8, msg_size); + }; + defer alloc.free(buf); + + // Send message buffer in a loop, and periodically report throughput. + while (true) { + var count: usize = 0; + const start = try std.time.Instant.now(); + var delta: u64 = 0; + + while (delta < std.time.ns_per_s) : (delta = (try std.time.Instant.now()).since(start)) { + try con.writeAll(buf); + _ = try con.readAll(buf); + count += 1; + } + + const bytes_per_sec = count * 2 * buf.len * (delta / std.time.ns_per_s); + std.log.info("{:.2}/sec ping-pong messages", .{std.fmt.fmtIntSizeBin(bytes_per_sec)}); + } +} + +pub fn main() !void { + // Command line options. + const opts = try parse_args() orelse return; + const addr = try std.net.Address.parseIp4(opts.addr, 8080); + + // Allocator. + var gpa = std.heap.GeneralPurposeAllocator(.{}).init; + defer std.debug.assert(gpa.deinit() == .ok); + const alloc = gpa.allocator(); + + // Run server or client. + try if (opts.is_server) + run_server(alloc, addr) + else + run_client(alloc, addr, opts.msg_size); +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ab93306 --- /dev/null +++ b/flake.nix @@ -0,0 +1,19 @@ +{ + description = "Zig development environment"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = { self, nixpkgs, ... }: let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + in { + devShells.${system}.default = pkgs.mkShell { + buildInputs = [ + pkgs.zig + pkgs.zls + ]; + }; + }; +} diff --git a/x86-bare-metal/mbr-disk-lba/.gitignore b/x86-bare-metal/mbr-disk-lba/.gitignore new file mode 100644 index 0000000..a92a67f --- /dev/null +++ b/x86-bare-metal/mbr-disk-lba/.gitignore @@ -0,0 +1 @@ +BUILD/
\ No newline at end of file diff --git a/x86-bare-metal/mbr-disk-lba/Makefile b/x86-bare-metal/mbr-disk-lba/Makefile new file mode 100644 index 0000000..f897450 --- /dev/null +++ b/x86-bare-metal/mbr-disk-lba/Makefile @@ -0,0 +1,40 @@ +O := BUILD + +all: dump_elf dump_bin + +$(O)/boot: $(O)/boot.elf + # MBR 512 bytes (sector 1 - lba 0) + objcopy -O binary $< $@ + # Craft 512 bytes (sector 2 - lba 1) + printf "aaaa" >> $@ + dd if=/dev/zero bs=1 count=508 >> $@ + # Craft 512 bytes (sector 3 - lba 2) + printf "bbbb" >> $@ + dd if=/dev/zero bs=1 count=508 >> $@ + +$(O)/boot.elf: mbr.ld $(O)/mbr.o $(O)/zmbr.o + ld -o $@ -nostdlib -T $^ + +$(O)/mbr.o: mbr.S | $(O) + gcc -m32 -c -o $@ -ffreestanding mbr.S + +$(O)/zmbr.o: zmbr.zig | $(O) + zig build-obj -fno-strip -femit-bin=$@ -target x86-freestanding-none -O ReleaseSmall $< + +clean: + $(RM) -r $(O) + +$(O): + mkdir -p $(O) + +dump_elf: $(O)/boot.elf + @#objdump -Mintel --disassemble=kmain --visualize-jumps=extended-color $< + readelf -W -l $< + size $< + size $< | awk '/$(notdir $<)/ { print "MBR utilization " $$1/512 "%" }' + +dump_bin: $(O)/boot + hexdump -C $< + +run: $(O)/boot + qemu-system-i386 -hda $< $(QEMU_ARGS) diff --git a/x86-bare-metal/mbr-disk-lba/mbr.S b/x86-bare-metal/mbr-disk-lba/mbr.S new file mode 100644 index 0000000..df2cef1 --- /dev/null +++ b/x86-bare-metal/mbr-disk-lba/mbr.S @@ -0,0 +1,125 @@ +// -- BOOT TEXT SECTION --------------------------------------------------------- + +.code16 +.intel_syntax noprefix + +.section .boot, "ax", @progbits + // Disable interrupts. + cli + + // Clear segment selectors. + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + // Set cs to 0x0000, as some BIOSes load the MBR to either 07c0:0000 or 0000:7c000. + jmp 0x0000:entry_rm16 + +// LBA - disk address packet. +lba_pkt: + .byte 0x10 // Size of this disk packet in bytes (16). + .byte 0 // Reserved. + .2byte 2 // Number of blocks to read (sectors 512 bytes?) to read. + .4byte 0x7e00 // Destination address. + .8byte 1 // Starting lba block number (0 indexed, MBR is at 0). + +entry_rm16: + // Disk extended read. + // ah = 42h + // dl = drive number + // ds:si = address of disk packet + // Return + // cf = 0 (success) 1 (failed) + // ah = 0 (success) error code (failed) + + // [1] http://www.ctyme.com/intr/rb-0708.htm + mov ah, 0x42 + //mov dl, #drive // bios puts boot disk into dl + lea si, [lba_pkt] + int 0x13 + + jnc 2f +1: + hlt + jmp 1b +2: + // Get current video mode [1]. + // Return: + // ah number of columns + // al display mode (see table in [2]) + // + // [1] http://www.ctyme.com/intr/rb-0108.htm + // [2] http://www.ctyme.com/intr/rb-0069.htm + mov ah, 0xf + int 0x10 + + // Execpt that the bios initializes text mode 0x3. + // * 80x25 text mode (cols x rows) + // * 2 byte per character + // [15] blink [14:12] bg color [11:8] fg color [7:0] char + // * 0xB80000 screen address + cmp al, 0x3 + // Else we indicate an error with a blue screen. + je 2f + // Set background color [1]. + // + // [1] http://www.ctyme.com/intr/rb-0101.htm + mov ah, 0xb + mov bx, 1 + int 0x10 +1: + hlt + jmp 1b +2: + + // Enable A20 address line. + in al, 0x92 + or al, 2 + out 0x92, al + + // Load GDT descriptor. + lgdt [gdt_desc] + + // Enable protected mode (set CR0.PE bit). + mov eax, cr0 + or eax, (1 << 0) + mov cr0, eax + + // Far jump which loads segment selector (0x0008) into cs. + // 0x0008 -> RPL=0, TI=0(GDT), I=1 + jmp 0x0008:entry_pm32 + +.code32 +entry_pm32: + // Select data segment selector (0x0010) for ds. + mov ax, gdt_data - gdt + mov ds, ax + + // Initialize stack pointer. + // Real Mode memory (https://wiki.osdev.org/Memory_Map_(x86) + // 0x00000500 - 0x00007BFF | 29.75 KiB | conventional memory + mov esp, 0x7c00 + + // Enter zmbr.zig:kmain. + // Should not return, but for safety we emit a call rather than a jmp. + call kmain + +1: + hlt + jmp 1b + +// -- RODATA SECTION ------------------------------------------------------------ + +.section .rodata, "a", @progbits +.balign 8 +gdt: + .8byte 0x0000000000000000 // 0x00 | null descriptor + .8byte 0x00cf9a000000ffff // 0x08 | 32 bit, code (rx), present, dpl=0, g=4K, base=0, limit=fffff +gdt_data: + .8byte 0x00cf92000000ffff // 0x10 | 32 bit, data (rw), present, dpl=0, g=4K, base=0, limit=fffff +gdt_desc: + .2byte (. - gdt - 1) // size + .4byte gdt // address diff --git a/x86-bare-metal/mbr-disk-lba/mbr.ld b/x86-bare-metal/mbr-disk-lba/mbr.ld new file mode 100644 index 0000000..b93543a --- /dev/null +++ b/x86-bare-metal/mbr-disk-lba/mbr.ld @@ -0,0 +1,26 @@ +/*OUTPUT_FORMAT(binary)*/ +OUTPUT_FORMAT(elf32-i386) +OUTPUT_ARCH(i386) + +SECTIONS { + . = 0x7c00; + .boot : { + *(.boot) + } + .text : { *(.text) } + .data : { *(.data) } + .bss : { *(.bss) } + .rodata : { *(.rodata) } + _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-disk-lba/zmbr.zig b/x86-bare-metal/mbr-disk-lba/zmbr.zig new file mode 100644 index 0000000..e5b06ea --- /dev/null +++ b/x86-bare-metal/mbr-disk-lba/zmbr.zig @@ -0,0 +1,66 @@ +// Frambuffer limits. +const COLS = 80; +const ROWS = 25; + +// Frambuffer cursor. +var col: u16 = 0; +var row: u16 = 0; + +// Frambuffer. +const fb: []u16 = @as([*]u16, @ptrFromInt(0xB8000))[0 .. COLS * ROWS]; + +/// Clear screen (all black). +fn clear_screen() void { + for (fb) |*ch| { + ch.* = 0; + } +} + +/// Draw string to current cursor position. +fn puts(str: []const u8) void { + // Each framebuffer entry in text mode is 16bit wide. + // [15] blink + // [14:12] bg color (3 bit) + // [11: 8] fg color (4 bit) + // [ 7: 0] ascii character + // https://en.wikipedia.org/wiki/VGA_text_mode + for (str) |ch| { + if (ch == '\n') { + col = 0; + row += 1; + } else { + const pos = (row * COLS + col); + // bg - black; fg - white; + fb[pos] = @as(u16, 15) << 8 | ch; + col += 1; + } + if (col == COLS) { + row += 1; + if (row == ROWS) { + row = 0; + } + } + } +} + +// kmain should be "callconv(.naked)", once issue is fixed. +// https://github.com/ziglang/zig/issues/18183 +export fn kmain() noreturn { + clear_screen(); + + // Print first bytes of LBA block 1 we loaded from disk (sector 2). + const lba1: []const u8 = @as([*]const u8, @ptrFromInt(0x7e00))[0..4]; + puts("lba1: "); + puts(lba1); + puts("\n"); + + // Print first bytes of LBA block 2 we loaded from disk (sector 3). + const lba2: []const u8 = @as([*]const u8, @ptrFromInt(0x8000))[0..4]; + puts("lba2: "); + puts(lba2); + puts("\n"); + + while (true) { + asm volatile ("hlt"); + } +} diff --git a/x86-bare-metal/mbr-e820/Makefile b/x86-bare-metal/mbr-e820/Makefile index 5dc465f..99ed9d8 100644 --- a/x86-bare-metal/mbr-e820/Makefile +++ b/x86-bare-metal/mbr-e820/Makefile @@ -1,6 +1,6 @@ O := BUILD -COMMON_FLAGS := -fno-unwind-tables -fno-strip -target x86-freestanding-code16 -O ReleaseSmall -mcpu=i386 +COMMON_FLAGS := -fno-unwind-tables -fno-strip -target x86-freestanding-code16 -O ReleaseSmall -mcpu=i386 -fomit-frame-pointer $(O)/boot: $(O)/boot.elf | check_ep dump_info objcopy -O binary $< $@ diff --git a/x86-bare-metal/mbr-textmode/.gitignore b/x86-bare-metal/mbr-textmode/.gitignore new file mode 100644 index 0000000..a92a67f --- /dev/null +++ b/x86-bare-metal/mbr-textmode/.gitignore @@ -0,0 +1 @@ +BUILD/
\ No newline at end of file diff --git a/x86-bare-metal/mbr-textmode/Makefile b/x86-bare-metal/mbr-textmode/Makefile new file mode 100644 index 0000000..7642791 --- /dev/null +++ b/x86-bare-metal/mbr-textmode/Makefile @@ -0,0 +1,28 @@ +O := BUILD + +$(O)/boot: $(O)/boot.elf | dump_info + objcopy -O binary $< $@ + +$(O)/boot.elf: mbr.ld $(O)/mbr.o $(O)/zmbr.o + ld -o $@ -nostdlib -T $^ + +$(O)/mbr.o: mbr.S | $(O) + gcc -m32 -c -o $@ -ffreestanding mbr.S + +$(O)/zmbr.o: zmbr.zig | $(O) + zig build-obj -femit-bin=$@ -target x86-freestanding-none -O ReleaseSmall $< + +clean: + $(RM) -r $(O) + +$(O): + mkdir -p $(O) + +dump_info: $(O)/boot.elf + objdump -Mintel --disassemble=kmain --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-textmode/mbr.S b/x86-bare-metal/mbr-textmode/mbr.S new file mode 100644 index 0000000..be41bed --- /dev/null +++ b/x86-bare-metal/mbr-textmode/mbr.S @@ -0,0 +1,98 @@ +// -- BOOT TEXT SECTION --------------------------------------------------------- + +.code16 +.intel_syntax noprefix + +.section .boot, "ax", @progbits + // Disable interrupts. + cli + + // Clear segment selectors. + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + // Set cs to 0x0000, as some BIOSes load the MBR to either 07c0:0000 or 0000:7c000. + jmp 0x0000:entry_rm16 + +entry_rm16: + // Get current video mode [1]. + // Return: + // ah number of columns + // al display mode (see table in [2]) + // + // [1] http://www.ctyme.com/intr/rb-0108.htm + // [2] http://www.ctyme.com/intr/rb-0069.htm + mov ah, 0xf + int 0x10 + + // Execpt that the bios initializes text mode 0x3. + // * 80x25 text mode (cols x rows) + // * 2 byte per character + // [15] blink [14:12] bg color [11:8] fg color [7:0] char + // * 0xB80000 screen address + cmp al, 0x3 + // Else we indicate an error with a blue screen. + je 2f + // Set background color [1]. + // + // [1] http://www.ctyme.com/intr/rb-0101.htm + mov ah, 0xb + mov bx, 1 + int 0x10 +1: + hlt + jmp 1b +2: + + // Enable A20 address line. + in al, 0x92 + or al, 2 + out 0x92, al + + // Load GDT descriptor. + lgdt [gdt_desc] + + // Enable protected mode (set CR0.PE bit). + mov eax, cr0 + or eax, (1 << 0) + mov cr0, eax + + // Far jump which loads segment selector (0x0008) into cs. + // 0x0008 -> RPL=0, TI=0(GDT), I=1 + jmp 0x0008:entry_pm32 + +.code32 +entry_pm32: + // Select data segment selector (0x0010) for ds. + mov ax, gdt_data - gdt + mov ds, ax + + // Initialize stack pointer. + // Real Mode memory (https://wiki.osdev.org/Memory_Map_(x86) + // 0x00000500 - 0x00007BFF | 29.75 KiB | conventional memory + mov esp, 0x7c00 + + // Enter zmbr.zig:kmain. + // Should not return, but for safety we emit a call rather than a jmp. + call kmain + +1: + hlt + jmp 1b + +// -- RODATA SECTION ------------------------------------------------------------ + +.section .rodata, "a", @progbits +.balign 8 +gdt: + .8byte 0x0000000000000000 // 0x00 | null descriptor + .8byte 0x00cf9a000000ffff // 0x08 | 32 bit, code (rx), present, dpl=0, g=4K, base=0, limit=fffff +gdt_data: + .8byte 0x00cf92000000ffff // 0x10 | 32 bit, data (rw), present, dpl=0, g=4K, base=0, limit=fffff +gdt_desc: + .2byte (. - gdt - 1) // size + .4byte gdt // address diff --git a/x86-bare-metal/mbr-textmode/mbr.ld b/x86-bare-metal/mbr-textmode/mbr.ld new file mode 100644 index 0000000..372ea42 --- /dev/null +++ b/x86-bare-metal/mbr-textmode/mbr.ld @@ -0,0 +1,25 @@ +/*OUTPUT_FORMAT(binary)*/ +OUTPUT_FORMAT(elf32-i386) +OUTPUT_ARCH(i386) + +SECTIONS { + . = 0x7c00; + .boot : { + *(.boot) + } + .text : { *(.text) } + .data : { *(.data) } + .rodata : { *(.rodata) } + _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-textmode/zmbr.zig b/x86-bare-metal/mbr-textmode/zmbr.zig new file mode 100644 index 0000000..2d95249 --- /dev/null +++ b/x86-bare-metal/mbr-textmode/zmbr.zig @@ -0,0 +1,39 @@ +const COLS = 80; +const ROWS = 25; + +/// Clear screen (all black). +fn clear_screen(video: []u16) void { + for (video) |*ch| { + ch.* = 0; + } +} + +/// Draw the color palette. +fn draw_palette(video: []u16) void { + // Each framebuffer entry in text mode is 16bit wide. + // [15] blink + // [14:12] bg color (3 bit) + // [11: 8] fg color (4 bit) + // [ 7: 0] ascii character + // + // https://en.wikipedia.org/wiki/VGA_text_mode + + // Print each bg / fg combination once. + for (video, 0..0x80) |*ch, i| { + ch.* = @as(u16, @truncate(i)) << 8 | 'a'; + } +} + +// kmain should be "callconv(.naked)", once issue is fixed. +// https://github.com/ziglang/zig/issues/18183 +export fn kmain() noreturn { + // Take a slice to VGA video memory (mode 3h text mode 80x25). + const video: []u16 = @as([*]u16, @ptrFromInt(0xB8000))[0 .. COLS * ROWS]; + + clear_screen(video); + draw_palette(video); + + while (true) { + asm volatile ("hlt"); + } +} |