From 6a4d59302073a85e2343952850cedf9833d93a2f Mon Sep 17 00:00:00 2001 From: Matt Knight Date: Sun, 25 Jun 2023 11:14:12 -0700 Subject: [PATCH] update to master, use zig package manager (#11) --- tools/uf2/README.md | 20 +++- tools/uf2/build.zig | 18 +-- tools/uf2/build.zig.zon | 4 + tools/uf2/src/elf2uf2.zig | 86 ++++++++++++++ tools/uf2/src/example.zig | 8 +- tools/uf2/src/{main.zig => uf2.zig} | 167 +++------------------------- 6 files changed, 139 insertions(+), 164 deletions(-) create mode 100644 tools/uf2/build.zig.zon create mode 100644 tools/uf2/src/elf2uf2.zig rename tools/uf2/src/{main.zig => uf2.zig} (71%) diff --git a/tools/uf2/README.md b/tools/uf2/README.md index 8817720..4c09697 100644 --- a/tools/uf2/README.md +++ b/tools/uf2/README.md @@ -1,4 +1,22 @@ # uf2 + USB Flashing Format (UF2) for your build.zig -This is currently WIP and untested. +This package is for assembling uf2 files from ELF binaries. This format is used for flashing a microcontroller over a mass storage interface, such as the Pi Pico. + +See https://github.com/microsoft/uf2#file-containers for how we're going to embed file source into the format. + +For use in a build.zig: + +```zig +const uf2 = @import("uf2"); + +pub fn build(b: *Build) void { + // ... + + const uf2_file = uf2.from_elf(b, exe, .{ .family_id = .RP2040 }); + b.installFile(uf2_file_source); + + // ... +} +``` diff --git a/tools/uf2/build.zig b/tools/uf2/build.zig index 2795742..f72e906 100644 --- a/tools/uf2/build.zig +++ b/tools/uf2/build.zig @@ -4,16 +4,20 @@ pub fn build(b: *std.build.Builder) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); - const lib = b.addStaticLibrary(.{ - .name = "uf2", - .root_source_file = .{ .path = "src/main.zig" }, + const elf2uf2 = b.addExecutable(.{ + .name = "elf2uf2", + .root_source_file = .{ .path = "src/elf2uf2.zig" }, .target = target, .optimize = optimize, }); - b.installArtifact(lib); + b.installArtifact(elf2uf2); + + _ = b.addModule("uf2", .{ + .source_file = .{ .path = "src/uf2.zig" }, + }); const main_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = .{ .path = "src/uf2.zig" }, }); const test_step = b.step("test", "Run library tests"); @@ -27,9 +31,9 @@ pub fn build(b: *std.build.Builder) void { const gen_step = b.step("gen", "Generate family id enum"); gen_step.dependOn(&gen_run_step.step); - const exe = b.addExecutable(.{ + const example = b.addExecutable(.{ .name = "example", .root_source_file = .{ .path = "src/example.zig" }, }); - b.installArtifact(exe); + b.installArtifact(example); } diff --git a/tools/uf2/build.zig.zon b/tools/uf2/build.zig.zon new file mode 100644 index 0000000..b324423 --- /dev/null +++ b/tools/uf2/build.zig.zon @@ -0,0 +1,4 @@ +.{ + .name = "uf2", + .version = "0.0.0", +} diff --git a/tools/uf2/src/elf2uf2.zig b/tools/uf2/src/elf2uf2.zig new file mode 100644 index 0000000..96e5d9f --- /dev/null +++ b/tools/uf2/src/elf2uf2.zig @@ -0,0 +1,86 @@ +const std = @import("std"); +const uf2 = @import("uf2.zig"); + +// command line options: +// --output-path +// --family-id +// --elf-path +const usage = + \\elf2uf2 [--family-id ] --elf-path --output-path + \\ --help Show this message + \\ --elf-path Path of the ELF file to convert + \\ --output-path Path to save the generated UF2 file + \\ --family-id Family id of the microcontroller + \\ +; + +fn find_arg(args: []const []const u8, key: []const u8) !?[]const u8 { + const key_idx = for (args, 0..) |arg, i| { + if (std.mem.eql(u8, key, arg)) + break i; + } else return null; + + if (key_idx >= args.len - 1) { + std.log.err("missing value for {s}", .{key}); + return error.MissingArgValue; + } + + const value = args[key_idx + 1]; + if (std.mem.startsWith(u8, value, "--")) { + std.log.err("missing value for {s}", .{key}); + return error.MissingArgValue; + } + + return value; +} + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + var args = try std.process.argsAlloc(gpa.allocator()); + defer std.process.argsFree(gpa.allocator(), args); + + for (args) |arg| if (std.mem.eql(u8, "--help", arg)) { + try std.io.getStdOut().writeAll(usage); + return; + }; + + const elf_path = (try find_arg(args, "--elf-path")) orelse { + std.log.err("missing arg: --elf-path", .{}); + return error.MissingArg; + }; + + const output_path = (try find_arg(args, "--output-path")) orelse { + std.log.err("missing arg: --output-path", .{}); + return error.MissingArg; + }; + + const family_id: ?uf2.FamilyId = if (try find_arg(args, "--family-id")) |family_id_str| + if (std.mem.startsWith(u8, family_id_str, "0x")) + @enumFromInt(uf2.FamilyId, try std.fmt.parseInt(u32, family_id_str, 0)) + else + std.meta.stringToEnum(uf2.FamilyId, family_id_str) orelse { + std.log.err("invalid family id: {s}, valid family names are:", .{family_id_str}); + inline for (@typeInfo(uf2.FamilyId).Enum.fields) |field| + std.log.err(" - {s}", .{field.name}); + + return error.InvalidFamilyId; + } + else + null; + + var archive = uf2.Archive.init(gpa.allocator()); + defer archive.deinit(); + + try archive.add_elf(elf_path, .{ + .family_id = family_id, + }); + + const dest_file = try std.fs.cwd().createFile(output_path, .{}); + defer dest_file.close(); + + var buffered = std.io.bufferedWriter(dest_file.writer()); + try archive.write_to(buffered.writer()); + try buffered.flush(); +} diff --git a/tools/uf2/src/example.zig b/tools/uf2/src/example.zig index 6e07789..66ff018 100644 --- a/tools/uf2/src/example.zig +++ b/tools/uf2/src/example.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const uf2 = @import("main.zig"); +const uf2 = @import("uf2.zig"); pub fn main() !void { var args = try std.process.argsAlloc(std.heap.page_allocator); @@ -9,13 +9,13 @@ pub fn main() !void { var archive = uf2.Archive.init(std.heap.page_allocator); defer archive.deinit(); - try archive.addElf(args[1], .{ + try archive.add_elf(args[1], .{ .family_id = .RP2040, }); const out_file = try std.fs.cwd().createFile(args[2], .{}); defer out_file.close(); - try archive.writeTo(out_file.writer()); + try archive.write_to(out_file.writer()); } else if (args.len == 2) { const file = try std.fs.cwd().openFile(args[1], .{}); defer file.close(); @@ -24,7 +24,7 @@ pub fn main() !void { defer blocks.deinit(); while (true) { - const block = uf2.Block.fromReader(file.reader()) catch |err| switch (err) { + const block = uf2.Block.from_reader(file.reader()) catch |err| switch (err) { error.EndOfStream => break, else => return err, }; diff --git a/tools/uf2/src/main.zig b/tools/uf2/src/uf2.zig similarity index 71% rename from tools/uf2/src/main.zig rename to tools/uf2/src/uf2.zig index 715d244..20e08e8 100644 --- a/tools/uf2/src/main.zig +++ b/tools/uf2/src/uf2.zig @@ -1,21 +1,3 @@ -//! This package is for assembling uf2 files from ELF binaries. This format is -//! used for flashing a microcontroller over a mass storage interface, such as -//! the pi pico. -//! -//! See https://github.com/microsoft/uf2#file-containers for how we're going to -//! embed file source into the format -//! -//! For use in a build.zig: -//! -//! ```zig -//! const uf2_file = uf2.Uf2Step.create(exe, .{}); -//! const flash_op = uf2_file.addFlashOperation("/mnt/something"); -//! const flash_step = builder.addStep("flash"); -//! flash_step.dependOn(&flash_op.step); -//! ``` -//! -//! TODO: allow for multiple builds in one archive - const std = @import("std"); const testing = std.testing; const assert = std.debug.assert; @@ -32,125 +14,6 @@ pub const Options = struct { family_id: ?FamilyId = null, }; -pub const Uf2Step = struct { - step: std.build.Step, - exe: *LibExeObjStep, - opts: Options, - output_file: GeneratedFile, - - pub fn create(exe: *LibExeObjStep, opts: Options) *Uf2Step { - assert(exe.kind == .exe); - var ret = exe.step.owner.allocator.create(Uf2Step) catch - @panic("failed to allocate"); - ret.* = .{ - .step = std.build.Step.init(.{ - .id = .custom, - .name = "uf2", - .owner = exe.step.owner, - .makeFn = make, - }), - .exe = exe, - .opts = opts, - .output_file = .{ .step = &ret.step }, - }; - - ret.step.dependOn(&exe.step); - return ret; - } - - /// uf2 is typically used to flash via a mass storage device, this step - /// writes the file contents to the mounted directory - pub fn addFlashOperation(self: *Uf2Step, path: []const u8) *FlashOpStep { - return FlashOpStep.create(self, path); - } - - pub fn install(uf2_step: *Uf2Step) void { - const owner = uf2_step.exe.step.owner; - const name = std.mem.join(owner.allocator, "", &.{ uf2_step.exe.name, ".uf2" }) catch @panic("failed to join"); - const install_step = owner.addInstallFileWithDir(.{ - .generated = &uf2_step.output_file, - }, .bin, name); - owner.getInstallStep().dependOn(&uf2_step.step); - owner.getInstallStep().dependOn(&install_step.step); - } - - fn make(step: *std.build.Step, node: *std.Progress.Node) anyerror!void { - _ = node; - const uf2_step = @fieldParentPtr(Uf2Step, "step", step); - const file_source = uf2_step.exe.getOutputSource(); - const exe_path = file_source.getPath(uf2_step.exe.step.owner); - const dest_path = try std.mem.join(uf2_step.exe.step.owner.allocator, "", &.{ - exe_path, - ".uf2", - }); - - var archive = Archive.init(uf2_step.exe.step.owner.allocator); - errdefer archive.deinit(); - - try archive.addElf(exe_path, uf2_step.opts); - - const dest_file = try std.fs.cwd().createFile(dest_path, .{}); - defer dest_file.close(); - - try archive.writeTo(dest_file.writer()); - uf2_step.output_file.path = dest_path; - } -}; - -/// for uf2, a flash op is just copying a file to a directory. -pub const FlashOpStep = struct { - step: std.build.Step, - uf2_step: *Uf2Step, - mass_storage_path: []const u8, - - pub fn create(uf2_step: *Uf2Step, mass_storage_path: []const u8) *FlashOpStep { - var ret = uf2_step.exe.builder.allocator.create(FlashOpStep) catch - @panic("failed to allocate flash operation step"); - ret.* = .{ - .step = std.build.Step.init( - .custom, - "flash_op", - uf2_step.exe.builder.allocator, - make, - ), - .uf2_step = uf2_step, - .mass_storage_path = mass_storage_path, - }; - - ret.step.dependOn(&uf2_step.step); - return ret; - } - - fn openMassStorage(self: FlashOpStep) !std.fs.Dir { - return if (std.fs.path.isAbsolute(self.mass_storage_path)) - try std.fs.openDirAbsolute(self.mass_storage_path, .{}) - else - try std.fs.cwd().openDir(self.mass_storage_path, .{}); - } - - fn make(step: *std.build.Step) !void { - const self = @fieldParentPtr(FlashOpStep, "step", step); - - var mass_storage = self.openMassStorage() catch |err| switch (err) { - error.FileNotFound => { - std.log.err("failed to open mass storage device: '{s}'", .{ - self.mass_storage_path, - }); - return err; - }, - else => return err, - }; - defer mass_storage.close(); - - try std.fs.cwd().copyFile( - self.uf2_step.path.?, - mass_storage, - std.fs.path.basename(self.uf2_step.path.?), - .{}, - ); - } -}; - pub const Archive = struct { allocator: Allocator, blocks: std.ArrayList(Block), @@ -172,7 +35,7 @@ pub const Archive = struct { self.families.deinit(); } - pub fn addElf(self: *Self, path: []const u8, opts: Options) !void { + pub fn add_elf(self: *Self, path: []const u8, opts: Options) !void { // TODO: ensures this reports an error if there is a collision if (opts.family_id) |family_id| try self.families.putNoClobber(family_id, {}); @@ -219,7 +82,7 @@ pub const Archive = struct { }; var segment_idx: usize = 0; - var addr = std.mem.alignBackwardGeneric(u32, segments.items[0].addr, prog_page_size); + var addr = std.mem.alignBackward(u32, segments.items[0].addr, prog_page_size); while (addr < last_segment_end and segment_idx < segments.items.len) { const segment = &segments.items[segment_idx]; const segment_end = segment.addr + segment.size; @@ -233,7 +96,7 @@ pub const Archive = struct { const block_end = block.target_addr + prog_page_size; if (segment.addr < block_end) { - const n_bytes = std.math.min(segment.size, block_end - segment.addr); + const n_bytes = @min(segment.size, block_end - segment.addr); try file.seekTo(segment.file_offset); const block_offset = segment.addr - block.target_addr; const n_read = try file.reader().readAll(block.data[block_offset .. block_offset + n_bytes]); @@ -251,7 +114,7 @@ pub const Archive = struct { } } else { block.payload_size = prog_page_size; - addr = std.mem.alignBackwardGeneric(u32, segment.addr, prog_page_size); + addr = std.mem.alignBackward(u32, segment.addr, prog_page_size); } } @@ -264,14 +127,14 @@ pub const Archive = struct { .extension_tags_present = false, }, .target_addr = addr, - .payload_size = std.math.min(prog_page_size, segment_end - addr), + .payload_size = @min(prog_page_size, segment_end - addr), .block_number = undefined, .total_blocks = undefined, .file_size_or_family_id = .{ .family_id = if (opts.family_id) |family_id| family_id else - @intToEnum(FamilyId, 0), + @enumFromInt(FamilyId, 0), }, .data = std.mem.zeroes([476]u8), }); @@ -307,15 +170,15 @@ pub const Archive = struct { @panic("TODO"); } - pub fn writeTo(self: *Self, writer: anytype) !void { + pub fn write_to(self: *Self, writer: anytype) !void { for (self.blocks.items, 0..) |*block, i| { block.block_number = @intCast(u32, i); block.total_blocks = @intCast(u32, self.blocks.items.len); - try block.writeTo(writer); + try block.write_to(writer); } } - pub fn addFile(self: *Self, path: []const u8) !void { + pub fn add_file(self: *Self, path: []const u8) !void { const file = if (std.fs.path.isAbsolute(path)) try std.fs.openFileAbsolute(path, .{}) else @@ -359,7 +222,7 @@ pub const Archive = struct { block.payload_size = n_read; if (n_read != @sizeOf(block.data)) { std.mem.copy(u8, block.data[n_read..], path); - path_pos = std.math.min(block.len - n_read, path.len); + path_pos = @min(block.len - n_read, path.len); if (n_read + path_pos < block.data.len) { // write null terminator too and we're done block.data[n_read + path_pos] = 0; @@ -370,7 +233,7 @@ pub const Archive = struct { // copying null terminated path into block, likely crossing a // block boundary std.mem.copy(u8, &block.data, path[path_pos..]); - const n_copied = std.math.min(block.data.len, path[path_pos..].len); + const n_copied = @min(block.data.len, path[path_pos..].len); path_pos += n_copied; if (n_copied < block.data.len) { // write null terminator and peace out @@ -426,7 +289,7 @@ pub const Block = extern struct { assert(512 == @sizeOf(Block)); } - pub fn fromReader(reader: anytype) !Block { + pub fn from_reader(reader: anytype) !Block { var block: Block = undefined; inline for (std.meta.fields(Block)) |field| { switch (field.type) { @@ -447,7 +310,7 @@ pub const Block = extern struct { return block; } - fn writeTo(self: Block, writer: anytype) !void { + pub fn write_to(self: Block, writer: anytype) !void { inline for (std.meta.fields(Block)) |field| { switch (field.type) { u32 => try writer.writeIntLittle(u32, @field(self, field.name)), @@ -504,11 +367,11 @@ test "Block loopback" { }; rand.bytes(&expected.data); - try expected.writeTo(fbs.writer()); + try expected.write_to(fbs.writer()); // needs to be reset for reader fbs.reset(); - const actual = try Block.fromReader(fbs.reader()); + const actual = try Block.from_reader(fbs.reader()); try expectEqualBlock(expected, actual); }