update to master, use zig package manager (#11)

wch-ch32v003
Matt Knight 1 year ago committed by Matt Knight
parent be93629fc9
commit 6a4d593020

@ -1,4 +1,22 @@
# uf2 # uf2
USB Flashing Format (UF2) for your build.zig 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);
// ...
}
```

@ -4,16 +4,20 @@ pub fn build(b: *std.build.Builder) void {
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const lib = b.addStaticLibrary(.{ const elf2uf2 = b.addExecutable(.{
.name = "uf2", .name = "elf2uf2",
.root_source_file = .{ .path = "src/main.zig" }, .root_source_file = .{ .path = "src/elf2uf2.zig" },
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
b.installArtifact(lib); b.installArtifact(elf2uf2);
_ = b.addModule("uf2", .{
.source_file = .{ .path = "src/uf2.zig" },
});
const main_tests = b.addTest(.{ 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"); 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"); const gen_step = b.step("gen", "Generate family id enum");
gen_step.dependOn(&gen_run_step.step); gen_step.dependOn(&gen_run_step.step);
const exe = b.addExecutable(.{ const example = b.addExecutable(.{
.name = "example", .name = "example",
.root_source_file = .{ .path = "src/example.zig" }, .root_source_file = .{ .path = "src/example.zig" },
}); });
b.installArtifact(exe); b.installArtifact(example);
} }

@ -0,0 +1,4 @@
.{
.name = "uf2",
.version = "0.0.0",
}

@ -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 <id>] --elf-path <path> --output-path <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();
}

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const uf2 = @import("main.zig"); const uf2 = @import("uf2.zig");
pub fn main() !void { pub fn main() !void {
var args = try std.process.argsAlloc(std.heap.page_allocator); 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); var archive = uf2.Archive.init(std.heap.page_allocator);
defer archive.deinit(); defer archive.deinit();
try archive.addElf(args[1], .{ try archive.add_elf(args[1], .{
.family_id = .RP2040, .family_id = .RP2040,
}); });
const out_file = try std.fs.cwd().createFile(args[2], .{}); const out_file = try std.fs.cwd().createFile(args[2], .{});
defer out_file.close(); defer out_file.close();
try archive.writeTo(out_file.writer()); try archive.write_to(out_file.writer());
} else if (args.len == 2) { } else if (args.len == 2) {
const file = try std.fs.cwd().openFile(args[1], .{}); const file = try std.fs.cwd().openFile(args[1], .{});
defer file.close(); defer file.close();
@ -24,7 +24,7 @@ pub fn main() !void {
defer blocks.deinit(); defer blocks.deinit();
while (true) { 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, error.EndOfStream => break,
else => return err, else => return err,
}; };

@ -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 std = @import("std");
const testing = std.testing; const testing = std.testing;
const assert = std.debug.assert; const assert = std.debug.assert;
@ -32,125 +14,6 @@ pub const Options = struct {
family_id: ?FamilyId = null, 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 { pub const Archive = struct {
allocator: Allocator, allocator: Allocator,
blocks: std.ArrayList(Block), blocks: std.ArrayList(Block),
@ -172,7 +35,7 @@ pub const Archive = struct {
self.families.deinit(); 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 // TODO: ensures this reports an error if there is a collision
if (opts.family_id) |family_id| if (opts.family_id) |family_id|
try self.families.putNoClobber(family_id, {}); try self.families.putNoClobber(family_id, {});
@ -219,7 +82,7 @@ pub const Archive = struct {
}; };
var segment_idx: usize = 0; 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) { while (addr < last_segment_end and segment_idx < segments.items.len) {
const segment = &segments.items[segment_idx]; const segment = &segments.items[segment_idx];
const segment_end = segment.addr + segment.size; const segment_end = segment.addr + segment.size;
@ -233,7 +96,7 @@ pub const Archive = struct {
const block_end = block.target_addr + prog_page_size; const block_end = block.target_addr + prog_page_size;
if (segment.addr < block_end) { 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); try file.seekTo(segment.file_offset);
const block_offset = segment.addr - block.target_addr; const block_offset = segment.addr - block.target_addr;
const n_read = try file.reader().readAll(block.data[block_offset .. block_offset + n_bytes]); const n_read = try file.reader().readAll(block.data[block_offset .. block_offset + n_bytes]);
@ -251,7 +114,7 @@ pub const Archive = struct {
} }
} else { } else {
block.payload_size = prog_page_size; 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, .extension_tags_present = false,
}, },
.target_addr = addr, .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, .block_number = undefined,
.total_blocks = undefined, .total_blocks = undefined,
.file_size_or_family_id = .{ .file_size_or_family_id = .{
.family_id = if (opts.family_id) |family_id| .family_id = if (opts.family_id) |family_id|
family_id family_id
else else
@intToEnum(FamilyId, 0), @enumFromInt(FamilyId, 0),
}, },
.data = std.mem.zeroes([476]u8), .data = std.mem.zeroes([476]u8),
}); });
@ -307,15 +170,15 @@ pub const Archive = struct {
@panic("TODO"); @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| { for (self.blocks.items, 0..) |*block, i| {
block.block_number = @intCast(u32, i); block.block_number = @intCast(u32, i);
block.total_blocks = @intCast(u32, self.blocks.items.len); 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)) const file = if (std.fs.path.isAbsolute(path))
try std.fs.openFileAbsolute(path, .{}) try std.fs.openFileAbsolute(path, .{})
else else
@ -359,7 +222,7 @@ pub const Archive = struct {
block.payload_size = n_read; block.payload_size = n_read;
if (n_read != @sizeOf(block.data)) { if (n_read != @sizeOf(block.data)) {
std.mem.copy(u8, block.data[n_read..], path); 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) { if (n_read + path_pos < block.data.len) {
// write null terminator too and we're done // write null terminator too and we're done
block.data[n_read + path_pos] = 0; block.data[n_read + path_pos] = 0;
@ -370,7 +233,7 @@ pub const Archive = struct {
// copying null terminated path into block, likely crossing a // copying null terminated path into block, likely crossing a
// block boundary // block boundary
std.mem.copy(u8, &block.data, path[path_pos..]); 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; path_pos += n_copied;
if (n_copied < block.data.len) { if (n_copied < block.data.len) {
// write null terminator and peace out // write null terminator and peace out
@ -426,7 +289,7 @@ pub const Block = extern struct {
assert(512 == @sizeOf(Block)); assert(512 == @sizeOf(Block));
} }
pub fn fromReader(reader: anytype) !Block { pub fn from_reader(reader: anytype) !Block {
var block: Block = undefined; var block: Block = undefined;
inline for (std.meta.fields(Block)) |field| { inline for (std.meta.fields(Block)) |field| {
switch (field.type) { switch (field.type) {
@ -447,7 +310,7 @@ pub const Block = extern struct {
return block; 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| { inline for (std.meta.fields(Block)) |field| {
switch (field.type) { switch (field.type) {
u32 => try writer.writeIntLittle(u32, @field(self, field.name)), u32 => try writer.writeIntLittle(u32, @field(self, field.name)),
@ -504,11 +367,11 @@ test "Block loopback" {
}; };
rand.bytes(&expected.data); rand.bytes(&expected.data);
try expected.writeTo(fbs.writer()); try expected.write_to(fbs.writer());
// needs to be reset for reader // needs to be reset for reader
fbs.reset(); fbs.reset();
const actual = try Block.fromReader(fbs.reader()); const actual = try Block.from_reader(fbs.reader());
try expectEqualBlock(expected, actual); try expectEqualBlock(expected, actual);
} }
Loading…
Cancel
Save