Something went wrong. Try again.
Completely reimplimented in zig (eventually)
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231//! Shared tool discovery across transports: Microchip programmers speak//! jtagice3 over raw USB bulk (usbfs); Atmel/CMSIS-DAP tools speak AVR-over-//! HID (hidraw). Listing and selection live here so every command sees the//! same view of attached hardware.
const std = @import("std");const Io = std.Io;const Allocator = std.mem.Allocator;const mem = std.mem;const usb = @import("usb.zig");const style = @import("style.zig");
pub const Transport = enum { /// jtagice3 frames over vendor-specific bulk endpoints (/dev/bus/usb). usb_bulk, /// AVR-over-CMSIS-DAP fragmentation over hidraw. hidraw,};
pub const default_report_size: u16 = 512;
pub const Tool = struct { vid: u16, pid: u16, name: []const u8, report_size: u16 = default_report_size,};
pub const tools = [_]Tool{ // Microchip: scripting interface is bulk USB, not HID. .{ .vid = 0x04D8, .pid = 0x9036, .name = "MPLAB PICkit 5" }, .{ .vid = 0x04D8, .pid = 0x9037, .name = "MPLAB ICD 5" }, .{ .vid = 0x04D8, .pid = 0x9012, .name = "MPLAB PICkit 4" }, .{ .vid = 0x04D8, .pid = 0x9015, .name = "MPLAB ICD 4" }, .{ .vid = 0x04D8, .pid = 0x9018, .name = "MPLAB SNAP" }, .{ .vid = 0x04D8, .pid = 0x9004, .name = "MPLAB REAL ICE" },};
pub fn findTool(vid: u16, pid: u16) ?Tool { for (tools) |tool| { if (tool.vid == vid and tool.pid == pid) return tool; } return null;}
/// One attached programming tool, normalised across transports.pub const AttachedTool = struct { /// Node to open: /dev/bus/usb/... or /dev/hidrawN. node: []u8, vid: u16, pid: u16, name: []u8, serial: []u8, tool: Tool,
pub fn deinit(self: AttachedTool, allocator: Allocator) void { allocator.free(self.node); allocator.free(self.name); allocator.free(self.serial); }};
/// All attached programming tools, sorted by name then serial for stable/// output.pub fn listAttached(io: Io, gpa: Allocator) ![]AttachedTool { var list: std.ArrayList(AttachedTool) = .empty; errdefer { for (list.items) |t| t.deinit(gpa); list.deinit(gpa); }
const usb_devices = try usb.listDevices(io, gpa, µchip_ids); defer { for (usb_devices) |dev| dev.deinit(gpa); gpa.free(usb_devices); } for (usb_devices) |dev| { const tool = findTool(dev.vid, dev.pid) orelse continue; try list.append(gpa, .{ .node = try gpa.dupe(u8, dev.node), .vid = dev.vid, .pid = dev.pid, .name = try gpa.dupe(u8, dev.name), .serial = try gpa.dupe(u8, dev.serial), .tool = tool, }); }
// const hid_devices = try hid.listDevices(io, gpa); // defer { // for (hid_devices) |dev| dev.deinit(gpa); // gpa.free(hid_devices); // } // for (hid_devices) |dev| { // const tool = findTool(dev.vid, dev.pid) orelse continue; // if (tool.transport != .hidraw) continue; // try list.append(gpa, .{ // .node = try gpa.dupe(u8, dev.node), // .vid = dev.vid, // .pid = dev.pid, // .name = try gpa.dupe(u8, dev.name), // .serial = try gpa.dupe(u8, dev.serial), // .tool = tool, // }); // }
const items = try list.toOwnedSlice(gpa); sortAttached(items); return items;}
const microchip_ids = [_]usb.Id{ .{ .vid = 0x04D8, .pid = 0x9036 }, .{ .vid = 0x04D8, .pid = 0x9037 }, .{ .vid = 0x04D8, .pid = 0x9012 }, .{ .vid = 0x04D8, .pid = 0x9015 }, .{ .vid = 0x04D8, .pid = 0x9018 }, .{ .vid = 0x04D8, .pid = 0x9004 },};
fn lessAttached(_: void, a: AttachedTool, b: AttachedTool) bool { const by_name = mem.order(u8, a.name, b.name); if (by_name != .eq) return by_name == .lt; return mem.order(u8, a.serial, b.serial) == .lt;}
fn sortAttached(items: []AttachedTool) void { mem.sort(AttachedTool, items, {}, lessAttached);}
pub fn die(init: std.process.Init, comptime fmt: []const u8, args: anytype) noreturn { var buf: [512]u8 = undefined; var w = Io.File.stderr().writer(init.io, &buf); style.errPrefix(init.io, &w.interface) catch {}; w.interface.print(fmt, args) catch {}; w.interface.flush() catch {}; std.process.exit(2);}
/// Find a PICkit 5 by optional serial; prints a helpful message (including/// attached alternatives) and exits when nothing matches./// Find a PICkit 5 by serial. When `serial` is null, exactly one PK5 must be/// attached — with several connected the caller must disambiguate.pub fn findPickit5(init: std.process.Init, serial: ?[]const u8) !AttachedTool { const gpa = init.gpa; const attached = try listAttached(init.io, gpa); defer { for (attached) |t| t.deinit(gpa); gpa.free(attached); }
var pk5_count: usize = 0; for (attached) |t| { if (!mem.endsWith(u8, t.tool.name, "PICkit 5")) continue; pk5_count += 1; }
if (pk5_count == 0) { if (serial != null) { die( init, "no PICkit 5 found with serial {s}\n{s}", .{ serial.?, attachedSummary(attached, gpa) }, ); } die(init, "no PICkit 5 found\n", .{}); }
if (pk5_count > 1 and serial == null) { die( init, "multiple PICkit 5 tools connected\n{s}Specify one with --serial\n", .{attachedSummary(attached, gpa)}, ); }
for (attached) |t| { if (!mem.endsWith(u8, t.tool.name, "PICkit 5")) continue; if (serial) |s| { if (!mem.eql(u8, t.serial, s)) continue; } return .{ .node = try gpa.dupe(u8, t.node), .vid = t.vid, .pid = t.pid, .name = try gpa.dupe(u8, t.name), .serial = try gpa.dupe(u8, t.serial), .tool = t.tool, }; } die( init, "no PICkit 5 found with serial {s}\n{s}", .{ serial.?, attachedSummary(attached, gpa) }, );}
/// One line per attached tool: " <name> serial=<s> (<node>)"; or a/// "no tools attached" hint when the list is empty.fn attachedSummary(attached: []const AttachedTool, gpa: Allocator) []const u8 { if (attached.len == 0) return "No supported tools are attached.\n"; var buf: std.ArrayList(u8) = .empty; for (attached) |t| { buf.print(gpa, " {s} serial={s} ({s})\n", .{ t.name, t.serial, t.node }) catch break; } return buf.toOwnedSlice(gpa) catch "See `mplabx tools list`.\n";}
test "findTool knows both transports" { const pk5 = findTool(0x04D8, 0x9036).?; try std.testing.expectEqualStrings("MPLAB PICkit 5", pk5.name); try std.testing.expectEqual(Transport.usb_bulk, pk5.transport);
const atice = findTool(0x03EB, 0x2141).?; try std.testing.expectEqual(Transport.hidraw, atice.transport);
try std.testing.expect(findTool(0x1234, 0x5678) == null);}
test "sortAttached orders by name then serial" { var items = [_]AttachedTool{ .{ .node = @constCast("b"), .vid = 1, .pid = 1, .name = @constCast("MPLAB SNAP"), .serial = @constCast("2"), .tool = undefined }, .{ .node = @constCast("c"), .vid = 1, .pid = 1, .name = @constCast("MPLAB SNAP"), .serial = @constCast("10"), .tool = undefined }, .{ .node = @constCast("a"), .vid = 1, .pid = 1, .name = @constCast("MPLAB ICD 5"), .serial = @constCast("x"), .tool = undefined }, }; sortAttached(&items); try std.testing.expectEqualStrings("MPLAB ICD 5", items[0].name); try std.testing.expectEqualStrings("10", items[1].serial); try std.testing.expectEqualStrings("2", items[2].serial);}