sylveos

Toy Operating System
Log | Files | Refs

commit 84ee98bde8cbb1ae93be63a2559ce4cc0f4c056b
parent da6ca5a829f07c4bfcf9ffafb5eece2828ee4c09
Author: Sylvia Ivory <git@sivory.net>
Date:   Fri, 30 Jan 2026 20:41:16 -0800

Bootloader local side

Diffstat:
Mbuild.zig | 3+++
Mbuild.zig.zon | 33++++-----------------------------
Ainclude/setup-tty.c | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/setup-tty.h | 2++
Ainstaller/bootloader.zig | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minstaller/net.zig | 12+++---------
Ainstaller/root.zig | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 270 insertions(+), 38 deletions(-)

diff --git a/build.zig b/build.zig @@ -195,6 +195,9 @@ pub fn build(b: *std.Build) !void { installer.addCSourceFile(.{ .file = b.path("include/setup-tty.c") }); installer.addIncludePath(b.path("include/")); + const clap = b.dependency("clap", .{}); + installer.addImport("clap", clap.module("clap")); + const exe = b.addExecutable(.{ .name = "pi-install", .linkage = .dynamic, diff --git a/build.zig.zon b/build.zig.zon @@ -32,35 +32,10 @@ // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ - // See `zig fetch --save <url>` for a command-line interface for adding dependencies. - //.example = .{ - // // When updating this field to a new URL, be sure to delete the corresponding - // // `hash`, otherwise you are communicating that you expect to find the old hash at - // // the new URL. If the contents of a URL change this will result in a hash mismatch - // // which will prevent zig from using it. - // .url = "https://example.com/foo.tar.gz", - // - // // This is computed from the file contents of the directory of files that is - // // obtained after fetching `url` and applying the inclusion rules given by - // // `paths`. - // // - // // This field is the source of truth; packages do not come from a `url`; they - // // come from a `hash`. `url` is just one of many possible mirrors for how to - // // obtain a package matching this `hash`. - // // - // // Uses the [multihash](https://multiformats.io/multihash/) format. - // .hash = "...", - // - // // When this is provided, the package is found in a directory relative to the - // // build root. In this case the package's hash is irrelevant and therefore not - // // computed. This field and `url` are mutually exclusive. - // .path = "foo", - // - // // When this is set to `true`, a package is declared to be lazily - // // fetched. This makes the dependency only get fetched if it is - // // actually used. - // .lazy = false, - //}, + .clap = .{ + .url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.11.0.tar.gz", + .hash = "clap-0.11.0-oBajB-HnAQDPCKYzwF7rO3qDFwRcD39Q0DALlTSz5H7e", + }, }, // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that diff --git a/include/setup-tty.c b/include/setup-tty.c @@ -0,0 +1,83 @@ +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <termios.h> + +#include "setup-tty.h" + +unsigned parse_baud(unsigned rate) { + switch(rate) { + case 9600: return B9600; + case 115200: return B115200; + case 230400: return B230400; + case 460800: return B460800; + #ifdef B576000 + case 576000: return B576000; + #endif + #ifdef B921600 + case 921600: return B921600; + #endif + #ifdef B1000000 + case 1000000: return B1000000; + #endif + default: + return B115200; + } +} + +// XXX: if anyone figures out a cleaner way to do this, lmk. I don't +// have a mac, so stopped as soon as we had something that worked on +// linux & macos. +// params: +// - <timeout> is in seconds (< 1 ok) +// - <speed> is baud rate. +int set_tty_to_8n1(int fd, unsigned speed, double timeout) { + struct termios tty; + memset(&tty, 0, sizeof tty); + if (tcgetattr (fd, &tty) != 0) + return -1; + memset (&tty, 0, sizeof tty); + + // https://github.com/rlcheng/raspberry_pi_workshop + cfsetspeed(&tty, speed); + + // disable IGNBRK for mismatched speed tests; otherwise receive break + // as \000 chars + + // XXX: wait, does this disable break or ignore-ignore break?? + tty.c_iflag &= ~IGNBRK; // disable break processing + tty.c_lflag = 0; // no signaling chars, no echo, + // no canonical processing + tty.c_oflag = 0; // no remapping, no delays + tty.c_cc[VMIN] = 0; // read doesn't block + // VTIME is in .1 seconds, so have to multiply by 10. + tty.c_cc[VTIME] = (int)(timeout *10); // this seems to cause issues? + + /* + * Setup TTY for 8n1 mode, used by the pi UART. + */ + + // Disables the Parity Enable bit(PARENB),So No Parity + tty.c_cflag &= ~PARENB; + // CSTOPB = 2 Stop bits,here it is cleared so 1 Stop bit + tty.c_cflag &= ~CSTOPB; + // Clears the mask for setting the data size + tty.c_cflag &= ~CSIZE; + // Set the data bits = 8 + tty.c_cflag |= CS8; + // No Hardware flow Control + tty.c_cflag &= ~CRTSCTS; + // Enable receiver,Ignore Modem Control lines + tty.c_cflag |= CREAD | CLOCAL; + + // Disable XON/XOFF flow control both i/p and o/p + tty.c_iflag &= ~(IXON | IXOFF | IXANY); + // Non Cannonical mode + tty.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG); + // No Output Processing + tty.c_oflag &= ~OPOST; + + if(tcsetattr (fd, TCSANOW, &tty) != 0) + return -1; + return fd; +} diff --git a/include/setup-tty.h b/include/setup-tty.h @@ -0,0 +1,2 @@ +unsigned parse_baud(unsigned rate); +int set_tty_to_8n1(int fd, unsigned speed, double timeout); diff --git a/installer/bootloader.zig b/installer/bootloader.zig @@ -0,0 +1,82 @@ +const std = @import("std"); + +const net = @import("./net.zig"); + +const Message = net.Message; +const Error = net.Error; + +// w - UART (unsafe) +// r - UART (unsafe) +pub fn boot(w: *std.Io.Writer, r: *std.Io.Reader, code: []u8, base_addr: u32) !void { + const checksum = std.hash.Crc32.hash(code); + + std.debug.print("crc32={x}", .{checksum}); + + const parent_progress = std.Progress.start(.{}); + + const header: net.Header = .{ + .arm_base = base_addr, + .checksum = checksum, + .n_bytes = @truncate(code.len), + }; + + parent_progress.setName("waiting for a start"); + + while (try net.get_u32(r) != @intFromEnum(Message.GET_PROG_INFO)) { + const byte = try net.get_u8(r); + std.debug.print("got invalid GET_PROG_INFO: 0x{X}\n", .{byte}); + } + + parent_progress.setName("sending header"); + + try net.transmit_header(w, header); + + parent_progress.setName("waiting for response"); + + while (true) { + switch (try net.get_u32(r)) { + @intFromEnum(Message.GET_CODE) => break, + @intFromEnum(Message.BAD_CODE_ADDR) => return Error.BadAddress, + else => continue, + } + } + + const checksum_return = try net.get_u32(r); + + if (checksum != checksum_return) { + return Error.BadChecksum; + } + + parent_progress.setName("got response, sending code"); + + const transmit_progress = parent_progress.start("uploading", code.len); + try net.transmit_code(w, transmit_progress, code); + + parent_progress.setName("waiting for response"); + + while (true) { + const b = try net.get_u32(r); + switch (b) { + @intFromEnum(Message.BOOT_SUCCESS) => break, + @intFromEnum(Message.BAD_CODE_CKSUM) => return Error.BadChecksum, + else => { + var buffer: [4]u8 = undefined; + std.mem.writeInt(u32, &buffer, b, .little); + std.debug.print("{s}", .{buffer}); + }, + } + } + + var stdout_buffer: [1024]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&stdout_buffer); + const stdout_writer = &stdout.interface; + + parent_progress.setName("got response, booting"); + parent_progress.end(); + + while (true) { + const b = try net.get_u8(r); + try stdout_writer.writeByte(b); + try stdout_writer.flush(); + } +} diff --git a/installer/net.zig b/installer/net.zig @@ -29,10 +29,6 @@ pub fn get_u32(r: *std.Io.Reader) !u32 { try r.readSliceAll(&buffer); const v = std.mem.readInt(u32, &buffer, .little); - if (@import("builtin").abi.isGnu()) { - std.debug.print("GET32:0x{X}\n", .{v}); - } - return v; } @@ -43,10 +39,6 @@ pub fn get_u8(r: *std.Io.Reader) !u8 { } pub fn put_u32(w: *std.Io.Writer, v: u32) !void { - if (@import("builtin").abi.isGnu()) { - std.debug.print("PUT32:0x{X}\n", .{v}); - } - var buffer: [@sizeOf(u32)]u8 = undefined; std.mem.writeInt(u32, &buffer, v, .little); try w.writeAll(&buffer); @@ -82,10 +74,12 @@ pub fn receive_header(r: *std.Io.Reader, max_size: u32) !Header { return .{ .arm_base = arm_base, .checksum = checksum, .n_bytes = n_bytes }; } -pub fn transmit_code(w: *std.Io.Writer, code: []const u8) !void { +pub fn transmit_code(w: *std.Io.Writer, progress: std.Progress.Node, code: []const u8) !void { + defer progress.end(); try put_message(w, .PUT_CODE); for (code) |byte| { try put_u8(w, byte); + progress.completeOne(); } } diff --git a/installer/root.zig b/installer/root.zig @@ -0,0 +1,93 @@ +const std = @import("std"); +const clap = @import("clap"); + +const bootloader = @import("./bootloader.zig"); +const c = @cImport({ + @cInclude("setup-tty.h"); +}); + +pub fn main() !void { + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; + const allocator = gpa.allocator(); + defer _ = gpa.deinit(); + + const params = comptime clap.parseParamsComptime( + \\-h, --help display this help and exit + \\-b, --baud <baud> baud rate + \\-d, --device <device> device path + \\-t, --timeout <float> UART timeout + \\-b, --arm-base <addr> executable base address + \\<file> binary to run + \\ + ); + + const parsers = .{ + .baud = clap.parsers.int(u32, 10), + .file = clap.parsers.string, + .device = clap.parsers.string, + .float = clap.parsers.float(f64), + .addr = clap.parsers.int(u32, 16), + }; + + var diagnostics: clap.Diagnostic = .{}; + var res = clap.parse(clap.Help, &params, parsers, .{ + .diagnostic = &diagnostics, + .allocator = allocator, + }) catch |e| { + try diagnostics.reportToFile(.stderr(), e); + return e; + }; + defer res.deinit(); + + if (res.args.help != 0) { + std.debug.print("pi-install ", .{}); + try clap.usageToFile(.stderr(), clap.Help, &params); + std.debug.print("\n", .{}); + + return clap.helpToFile(.stderr(), clap.Help, &params, .{ + .description_on_new_line = false, + .spacing_between_parameters = 0, + }); + } + + const tty_path = res.args.device orelse "/dev/ttyUSB0"; + const baud = c.parse_baud(res.args.baud orelse 115200); + const path = res.positionals[0] orelse return error.MissingFile; + const base_addr = res.args.@"arm-base" orelse 0x8000; + const timeout = res.args.timeout orelse 10.0; + + std.debug.print("tty-usb=<{s}> baud=<{d}> program=<{s}> base-addr=<0x{X}> timeout=<{d}s>\n", .{ tty_path, baud, path, base_addr, timeout }); + + const tty = try std.fs.openFileAbsolute(tty_path, .{ .allow_ctty = true, .mode = .read_write }); + + if (c.set_tty_to_8n1(tty.handle, baud, timeout) == -1) { + return error.SetTTY; + } + + var code: []u8 = undefined; + + { + const cwd = std.fs.cwd(); + + var reader_buffer: [1024]u8 = undefined; + var code_file = try cwd.openFile(path, .{ .mode = .read_only }); + var code_reader = code_file.reader(&reader_buffer); + var r = &code_reader.interface; + + const size = (try code_file.stat()).size; + + code = try r.readAlloc(allocator, size); + } + + defer allocator.free(code); + + var reader_buffer: [1024]u8 = undefined; + var file_reader = tty.reader(&reader_buffer); + const r = &file_reader.interface; + + var writer_buffer: [1024]u8 = undefined; + var file_writer = tty.writer(&writer_buffer); + const w = &file_writer.interface; + + try bootloader.boot(w, r, code, base_addr); +}