rp2040-flasher inital commit (#72)

Co-authored-by: Felix (xq) Queißner <git@random-projects.net>
wch-ch32v003
Felix Queißner 1 year ago committed by GitHub
parent 35e9757bdb
commit d6b3be320f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -39,6 +39,10 @@ pub fn addPiPicoExecutable(
// project requires multiple HALs, it accepts microzig as a param // project requires multiple HALs, it accepts microzig as a param
pub fn build(b: *Builder) !void { pub fn build(b: *Builder) !void {
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const args_dep = b.dependency("args", .{});
const args_mod = args_dep.module("args");
var examples = Examples.init(b, optimize); var examples = Examples.init(b, optimize);
examples.install(b); examples.install(b);
@ -52,6 +56,16 @@ pub fn build(b: *Builder) !void {
const test_step = b.step("test", "run unit tests"); const test_step = b.step("test", "run unit tests");
test_step.dependOn(&b.addRunArtifact(pio_tests).step); test_step.dependOn(&b.addRunArtifact(pio_tests).step);
const flash_tool = b.addExecutable(.{
.name = "rp2040-flash",
.optimize = .Debug,
.target = .{},
.root_source_file = .{ .path = "tools/rp2040-flash.zig" },
});
flash_tool.addModule("args", args_mod);
b.installArtifact(flash_tool);
} }
fn root() []const u8 { fn root() []const u8 {

@ -6,6 +6,9 @@
.url = "https://github.com/ZigEmbeddedGroup/microzig/archive/9392fe0f7bddde26155c181ab80b70097b49c791.tar.gz", .url = "https://github.com/ZigEmbeddedGroup/microzig/archive/9392fe0f7bddde26155c181ab80b70097b49c791.tar.gz",
.hash = "1220326148075cd017425e04356329c33c3e794d31a54bdafa46521fcfcc55b422a3", .hash = "1220326148075cd017425e04356329c33c3e794d31a54bdafa46521fcfcc55b422a3",
}, },
.args = .{
.url = "https://github.com/MasterQ32/zig-args/archive/91d1e89fb89a4d01dec7c9aec95b0a324080ebcc.tar.gz",
.hash = "12203d04cafc97f952d74cdb077e74c0ab3414f9f6b5fbd159112c62bfa584a0dbed",
},
}, },
} }

@ -0,0 +1,331 @@
const std = @import("std");
const args_parser = @import("args");
const builtin = @import("builtin");
const CliOptions = struct {
help: bool = false,
device: ?[]const u8 = null,
wait: bool = false,
pub const shorthands = .{
.h = "help",
.d = "device",
.w = "wait",
};
};
const wait_device_ready_timeout = 60 * std.time.ns_per_s; // timeout until a device is found
const wait_device_avail_timeout = 60 * std.time.ns_per_s; // timeout until a device is found
const access_denied_limit = 20; // try that many times with AccessDenied before the user is informed
fn print_usage(file: std.fs.File, exe: ?[]const u8) !void {
try file.writer().writeAll(exe orelse "rp2040-flash");
try file.writer().writeAll(
\\ [-h] [-d <device>] <uf2-file>
\\Flash your RP2040 devices easily via UF2 interface.
\\
\\Options:
\\ -h, --help Shows this help text.
\\ -d, --device <path> Uses <path> as the UF2 device. Otherwise tries to auto-guess the correct device.
\\ -w, --wait Waits 60 seconds until a device appears.
\\
);
}
pub fn main() !u8 {
const stderr = std.io.getStdErr();
const stdout = std.io.getStdOut();
const stdin = std.io.getStdIn();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer _ = arena.deinit();
const allocator = arena.allocator();
var cli = args_parser.parseForCurrentProcess(CliOptions, allocator, .print) catch return 1;
defer cli.deinit();
if (cli.options.help) {
try print_usage(stdout, cli.executable_name);
return 0;
}
if (cli.positionals.len != 1) {
try print_usage(stderr, cli.executable_name);
return 1;
}
const uf2_file_path = cli.positionals[0];
var uf2_file = try std.fs.cwd().openFile(uf2_file_path, .{});
defer uf2_file.close();
const uf2_stat = try uf2_file.stat();
if ((uf2_stat.size % 512) != 0) {
std.log.warn("{s} does not have a size multiple of 512. might be corrupt!", .{uf2_file_path});
}
const file_valid = blk: {
try uf2_file.seekTo(0);
var file_valid = true;
while (file_valid) {
var block: [512]u8 = undefined;
const len = try uf2_file.read(&block);
if (len == 0)
break;
// 0 4 First magic number, 0x0A324655 ("UF2\n")
// 4 4 Second magic number, 0x9E5D5157
// 8 4 Flags
// 12 4 Address in flash where the data should be written
// 16 4 Number of bytes used in data (often 256)
// 20 4 Sequential block number; starts at 0
// 24 4 Total number of blocks in file
// 28 4 File size or board family ID or zero
// 32 476 Data, padded with zeros
// 508 4 Final magic number, 0x0AB16F30
const first_magic_number = std.mem.readIntLittle(u32, block[0..][0..4]);
const second_magic_number = std.mem.readIntLittle(u32, block[4..][0..4]);
const final_magic_number = std.mem.readIntLittle(u32, block[508..][0..4]);
file_valid = file_valid and (first_magic_number == 0x0A324655);
file_valid = file_valid and (second_magic_number == 0x9E5D5157);
file_valid = file_valid and (final_magic_number == 0x0AB16F30);
}
break :blk file_valid;
};
if (file_valid == false) {
std.log.warn("{s} does not seem to be a valid UF2 file. Do you really want to flash it?", .{uf2_file_path});
while (true) {
try stderr.writer().writeAll("Flash? [jN]: ");
var buffer: [64]u8 = undefined;
const selection_or_null = try stdin.reader().readUntilDelimiterOrEof(&buffer, '\n');
const selection_str = std.mem.trim(u8, selection_or_null orelse "", "\r\n\t ");
if (selection_str.len == 0)
return 1;
if (std.ascii.eqlIgnoreCase(selection_str, "j"))
break;
if (std.ascii.eqlIgnoreCase(selection_str, "n"))
return 1;
}
}
try uf2_file.seekTo(0);
const detect_timeout = std.time.nanoTimestamp() + wait_device_avail_timeout;
var first_run = true;
const device_path = if (cli.options.device) |devname|
try allocator.dupe(u8, devname)
else while (true) {
if (std.time.nanoTimestamp() >= detect_timeout) {
try stderr.writeAll("failed to detect any RP2040 devices :(\n");
return 1;
}
const maybe_device = try autoDetectPico(allocator);
if (maybe_device) |device|
break device;
if (!cli.options.wait) {
try stderr.writeAll("failed to detect any RP2040 devices :(\n");
return 1;
}
if (first_run) {
try stderr.writeAll("failed to detect any RP2040 devices, waiting...\n");
first_run = false;
}
std.time.sleep(250 * std.time.ns_per_ms);
};
const connect_timeout = std.time.nanoTimestamp() + wait_device_ready_timeout;
var first_attempt = true;
var access_denied_counter: u32 = 0;
var last_err: anyerror = error.Unknown;
var device_file: std.fs.File = blk: while (std.time.nanoTimestamp() < connect_timeout) {
var device = std.fs.cwd().openFile(device_path, .{ .mode = .write_only }) catch |err| {
last_err = err;
switch (err) {
error.FileNotFound => {}, // just waiting for the device
error.AccessDenied => {
access_denied_counter += 1;
if (access_denied_counter >= access_denied_limit) {
try stderr.writer().print("Could not open {s}: Access denied. Do you have write-access to the device?\n", .{device_path});
return 1;
}
},
else => |e| return e,
}
if (first_attempt) {
try stderr.writer().print("Waiting for {s}.", .{device_path});
first_attempt = false;
} else {
try stderr.writeAll(".");
}
std.time.sleep(250 * std.time.ns_per_ms);
continue;
};
try stderr.writeAll("\n");
break :blk device;
} else {
try stderr.writer().print("\nfailed to connect to {s}: {s}\n", .{ device_path, @errorName(last_err) });
return 1;
};
defer device_file.close();
try stderr.writeAll("Flashing");
{
try uf2_file.seekTo(0);
var block_num: u64 = 0;
while (true) {
try stderr.writeAll(".");
var block: [512]u8 = undefined;
const rd_len = try uf2_file.read(&block);
if (rd_len == 0)
break;
if (rd_len != block.len) {
try stderr.writer().print("\nFailed to read block {}: Only {} bytes read!\n", .{ block_num, rd_len });
return 1;
}
const wr_len = try device_file.write(&block);
if (wr_len != block.len) {
try stderr.writer().print("\nFailed to write block {}: Only {} bytes written!\n", .{ block_num, wr_len });
return 1;
}
block_num += 1;
}
}
try stderr.writeAll("\nDone.\n");
return 0;
}
fn autoDetectPico(allocator: std.mem.Allocator) !?[]const u8 {
switch (builtin.os.tag) {
.linux => {
const stdin = std.io.getStdIn();
const stderr = std.io.getStdErr();
const Device = struct {
name: []const u8,
path: []const u8,
};
var picos = std.ArrayList(Device).init(allocator);
defer picos.deinit();
var base_dir = try std.fs.openIterableDirAbsolute("/sys/block/", .{});
defer base_dir.close();
var iter = base_dir.iterate();
while (try iter.next()) |entry| {
var device_dir = try base_dir.dir.openDir(entry.name, .{});
defer device_dir.close();
const H = struct {
fn isPicoDevice(dir: std.fs.Dir, allo: std.mem.Allocator) !?[]const u8 {
// "/sys/block/*/removable" => "1"
// "/sys/block/*/device/model" => "RP2"
// "/sys/block/*/device/vendor" => "RPI"
var buffer: [64]u8 = undefined;
const removable = std.mem.trim(u8, try dir.readFile("removable", &buffer), "\r\n\t ");
if (!std.mem.eql(u8, removable, "1"))
return null;
const device_model = std.mem.trim(u8, try dir.readFile("device/model", &buffer), "\r\n\t ");
if (!std.mem.eql(u8, device_model, "RP2"))
return null;
const device_vendor = std.mem.trim(u8, try dir.readFile("device/vendor", &buffer), "\r\n\t ");
if (!std.mem.eql(u8, device_vendor, "RPI"))
return null;
const device_id = std.mem.trim(u8, try dir.readFile("dev", &buffer), "\r\n\t ");
return try std.fs.path.join(allo, &.{
"/dev/block", device_id,
});
}
};
const maybe_device = H.isPicoDevice(device_dir, allocator) catch |err| {
if (err != error.FileNotFound and err != error.AccessDenied) {
std.log.err("failed to scan /sys/block/{s}: {s}", .{ entry.name, @errorName(err) });
}
continue;
};
if (maybe_device) |device_path| {
try picos.append(Device{
.name = try allocator.dupe(u8, entry.name),
.path = device_path,
});
}
}
if (picos.items.len == 0) {
return null;
}
var default_selection: usize = 0;
try stderr.writer().writeAll("Select your device:\n");
for (picos.items, 1..) |pico_dev, index| {
try stderr.writer().print("#{d: <2} {s}\n", .{ index, pico_dev.name });
if (default_selection == 0) {
default_selection = index;
}
}
const selection = while (true) {
try stderr.writer().print("Select port [{}]: ", .{default_selection});
var buffer: [64]u8 = undefined;
const selection_or_null = try stdin.reader().readUntilDelimiterOrEof(&buffer, '\n');
const selection_str = std.mem.trim(u8, selection_or_null orelse break default_selection, "\r\n\t ");
if (selection_str.len == 0)
break default_selection;
const selection = std.fmt.parseInt(usize, selection_str, 10) catch continue;
if (selection < 1 or selection > picos.items.len) {
continue;
}
break selection;
};
return picos.items[selection - 1].path;
},
else => {
std.log.warn("Device auto-detection not implemented for {s}", .{@tagName(builtin.os.tag)});
return null;
},
}
}
Loading…
Cancel
Save