update to master, use zig package manager (#11)
This commit is contained in:
parent
7985fb7ff9
commit
bc9801a9c6
20
README.md
20
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);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
|
18
build.zig
18
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);
|
||||
}
|
||||
|
|
4
build.zig.zon
Normal file
4
build.zig.zon
Normal file
|
@ -0,0 +1,4 @@
|
|||
.{
|
||||
.name = "uf2",
|
||||
.version = "0.0.0",
|
||||
}
|
86
src/elf2uf2.zig
Normal file
86
src/elf2uf2.zig
Normal file
|
@ -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 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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user