From 6c3e0757f960376e4bf6d21d6e95959c7356717f Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sun, 12 Apr 2026 08:17:22 +0900 Subject: [PATCH] Boot reason via watchdog scratch register, reboot/picoboot CLI commands, enum msgpack support --- cmd/picomap/main.go | 53 ++++++++++++++++++++++++++++++++++--- firmware/firmware.cpp | 1 + firmware/include/handlers.h | 1 + firmware/include/msgpack.h | 14 ++++++++++ firmware/include/wire.h | 10 +++++-- firmware/lib/handlers.cpp | 17 +++++++++++- firmware/test.cpp | 1 + lib/client/types.go | 19 +++++++++++++ 8 files changed, 110 insertions(+), 6 deletions(-) diff --git a/cmd/picomap/main.go b/cmd/picomap/main.go index ed6083c..9dcc183 100644 --- a/cmd/picomap/main.go +++ b/cmd/picomap/main.go @@ -113,7 +113,7 @@ func closeTargets(targets []target) { func main() { if len(os.Args) < 2 { - slog.Error("usage: picomap [args...]") + fmt.Fprintf(os.Stderr, "usage: picomap [args...]\n\ncommands:\n info\n load\n log\n reboot\n picoboot\n test\n") os.Exit(1) } cmd := os.Args[1] @@ -127,10 +127,14 @@ func main() { err = cmdLoad(args) case "log": err = cmdLog(args) + case "reboot": + err = cmdReboot(args) + case "picoboot": + err = cmdPICOBOOT(args) case "test": err = cmdTestGroup(args) default: - slog.Error("usage: picomap [args...]") + fmt.Fprintf(os.Stderr, "usage: picomap [args...]\n\ncommands:\n info\n load\n log\n reboot\n picoboot\n test\n") os.Exit(1) } if err != nil { @@ -145,7 +149,8 @@ func printInfo(via string, info *client.ResponseInfo) { "board_id", hex.EncodeToString(info.BoardID[:]), "mac", net.HardwareAddr(info.MAC[:]).String(), "ip", net.IP(info.IP[:]).String(), - "firmware", info.FirmwareName) + "firmware", info.FirmwareName, + "boot", info.Boot.String()) } func cmdInfo(args []string) error { @@ -199,6 +204,48 @@ func cmdLog(args []string) error { return nil } +func cmdReboot(args []string) error { + fs := flag.NewFlagSet("reboot", flag.ExitOnError) + tf := addTargetFlags(fs) + fs.Parse(args) + + targets, err := tf.connect(500 * time.Millisecond) + if err != nil { + return err + } + defer closeTargets(targets) + + for _, t := range targets { + if err := t.client.Reboot(); err != nil { + slog.Error("reboot error", "via", t.name, "err", err) + continue + } + slog.Info("rebooted", "via", t.name) + } + return nil +} + +func cmdPICOBOOT(args []string) error { + fs := flag.NewFlagSet("picoboot", flag.ExitOnError) + tf := addTargetFlags(fs) + fs.Parse(args) + + targets, err := tf.connect(500 * time.Millisecond) + if err != nil { + return err + } + defer closeTargets(targets) + + for _, t := range targets { + if err := t.client.PICOBOOT(); err != nil { + slog.Error("picoboot error", "via", t.name, "err", err) + continue + } + slog.Info("picoboot", "via", t.name) + } + return nil +} + func boardSerial(id [8]byte) string { return fmt.Sprintf("%02X%02X%02X%02X%02X%02X%02X%02X", id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7]) diff --git a/firmware/firmware.cpp b/firmware/firmware.cpp index 4d7f8e8..1779f94 100644 --- a/firmware/firmware.cpp +++ b/firmware/firmware.cpp @@ -13,6 +13,7 @@ static constexpr handler_entry handlers[] = { }; int main() { + handlers_init(); dispatch_init(); dispatch_run(handlers); } diff --git a/firmware/include/handlers.h b/firmware/include/handlers.h index 1d19bc1..d75be63 100644 --- a/firmware/include/handlers.h +++ b/firmware/include/handlers.h @@ -6,6 +6,7 @@ extern std::string_view firmware_name; +void handlers_init(); std::optional handle_picoboot(const responder& resp, const RequestPICOBOOT&); std::optional handle_info(const responder& resp, const RequestInfo&); std::optional handle_log(const responder& resp, const RequestLog&); diff --git a/firmware/include/msgpack.h b/firmware/include/msgpack.h index 115fdce..ae3e331 100644 --- a/firmware/include/msgpack.h +++ b/firmware/include/msgpack.h @@ -373,6 +373,10 @@ public: requires std::is_integral_v && (!std::is_same_v) pack_result pack(T n) { return pack_integer(n); } + template + requires std::is_enum_v + pack_result pack(T v) { return pack_integer(static_cast>(v)); } + pack_result pack(bool v) { return pack_bool(v); } pack_result pack(float v) { return pack_float(v); } pack_result pack(double v) { return pack_double(v); } @@ -727,6 +731,16 @@ public: } }; +template + requires std::is_enum_v +result unpack(const parser &p, T &out) { + std::underlying_type_t v; + auto r = unpack(p, v); + if (!r) return r; + out = static_cast(v); + return r; +} + template requires std::is_integral_v && (!std::is_same_v) result unpack(const parser &p, T &out) { diff --git a/firmware/include/wire.h b/firmware/include/wire.h index bb0c47d..5f42521 100644 --- a/firmware/include/wire.h +++ b/firmware/include/wire.h @@ -44,14 +44,20 @@ struct RequestInfo { auto as_tuple() { return std::tie(); } }; +enum class boot_reason : uint8_t { + cold_boot = 0, + request_reboot = 1, +}; + struct ResponseInfo { static constexpr int8_t ext_id = 5; std::array board_id; std::array mac; std::array ip; std::string firmware_name; - auto as_tuple() const { return std::tie(board_id, mac, ip, firmware_name); } - auto as_tuple() { return std::tie(board_id, mac, ip, firmware_name); } + boot_reason boot; + auto as_tuple() const { return std::tie(board_id, mac, ip, firmware_name, boot); } + auto as_tuple() { return std::tie(board_id, mac, ip, firmware_name, boot); } }; struct RequestLog { diff --git a/firmware/lib/handlers.cpp b/firmware/lib/handlers.cpp index 0e36019..3acff80 100644 --- a/firmware/lib/handlers.cpp +++ b/firmware/lib/handlers.cpp @@ -9,6 +9,17 @@ static constexpr uint32_t XIP_BASE_ADDR = 0x10000000; static constexpr uint32_t FLASH_SIZE = 2 * 1024 * 1024; +static constexpr uint32_t REBOOT_MAGIC = 0x504D5242; + +static boot_reason detected_boot_reason; + +void handlers_init() { + if (watchdog_hw->scratch[0] == REBOOT_MAGIC) + detected_boot_reason = boot_reason::request_reboot; + else + detected_boot_reason = boot_reason::cold_boot; + watchdog_hw->scratch[0] = 0; +} std::optional handle_picoboot(const responder&, const RequestPICOBOOT&) { dispatch_schedule_ms(100, []{ reset_usb_boot(0, 1); }); @@ -24,6 +35,7 @@ std::optional handle_info(const responder&, const RequestInfo&) { resp.mac = ns.mac; resp.ip = ns.ip; resp.firmware_name = firmware_name; + resp.boot = detected_boot_reason; return resp; } @@ -67,7 +79,10 @@ std::optional handle_flash_write(const responder&, const Req } std::optional handle_reboot(const responder&, const RequestReboot&) { - dispatch_schedule_ms(100, []{ watchdog_reboot(0, 0, 0); }); + dispatch_schedule_ms(100, []{ + watchdog_hw->scratch[0] = REBOOT_MAGIC; + watchdog_reboot(0, 0, 0); + }); return ResponseReboot{}; } diff --git a/firmware/test.cpp b/firmware/test.cpp index 24583e9..5a30c25 100644 --- a/firmware/test.cpp +++ b/firmware/test.cpp @@ -25,6 +25,7 @@ static constexpr handler_entry handlers[] = { }; int main() { + handlers_init(); dispatch_init(); gpio_init(LED_PIN); diff --git a/lib/client/types.go b/lib/client/types.go index 9eac788..6dc3e5e 100644 --- a/lib/client/types.go +++ b/lib/client/types.go @@ -6,11 +6,30 @@ type RequestPICOBOOT struct{} type ResponsePICOBOOT struct{} type RequestInfo struct{} +type BootReason uint8 + +const ( + BootCold BootReason = 0 + BootReboot BootReason = 1 +) + +func (b BootReason) String() string { + switch b { + case BootCold: + return "cold" + case BootReboot: + return "reboot" + default: + return "unknown" + } +} + type ResponseInfo struct { BoardID [8]byte MAC [6]byte IP [4]byte FirmwareName string + Boot BootReason } type RequestLog struct{}