usbmode: unblock the ota task when a USB OTA transfer times out - #337
Open
wmelonw wants to merge 1 commit into
Open
usbmode: unblock the ota task when a USB OTA transfer times out#337wmelonw wants to merge 1 commit into
wmelonw wants to merge 1 commit into
Conversation
When usbmode_ota_worker() times out waiting for an ota_data ack it breaks out of the transfer loop, skips post_ota_complete_message(), stops USB storage and exits. The ota task is still parked in its blocking input-message read and never wakes up, so the device is left on the firmware progress screen indefinitely with no error (Blockstream#287). Always send ota_complete before exiting. When it arrives early the ota task rejects it as bad data and goes through its normal error path, which replies to the worker, shows the user an error screen and lets them retry, instead of freezing until the device is power-cycled. This does not change the wait bounds: the unbounded waits for the first few acks are needed because the on-device hash confirmation happens while those chunks are being decompressed, and a user verifying the hash may take longer than the 10 s data timeout. Not addressed: read() on the MSC mount has no timeout, so a transfer that stalls at the USB layer can still hang the worker. That needs a cancellation path in the MSC driver and is left for a follow-up.
Author
|
Follow-up: I've since been able to build-verify this branch — it compiles cleanly for the Jade Plus target ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the "USB firmware update stalls at 2% with no error" behaviour reported in #287 (and seen by me updating a Jade Plus
noradioto 1.0.41 via USB storage).What happens today
usbmode_ota_worker()(main/usbhmsc/usbmode.c) streams the firmware file to the ota task in 4 KiBota_datamessages and waits for each ack. Once past the first few chunks that wait is bounded at 10 s (wait_for_ota_replies(),usbmode.c:569). When it times out the worker does this:i.e. on timeout it sends nothing to the ota task and exits. Meanwhile
ota_process()is parked in a blocking input read (main/process/ota.c:95):Nothing will ever arrive, so the ota task never reaches
ota_finalize()and its error screen. The device sits on the progress screen indefinitely with no error until it is power-cycled — exactly what #287 describes ("stalls permanently at 2% with no error information", "let it sit for 10 minutes").Why it is always in the first couple of percent: the timeout can first fire at
msgs_sent == 5, and the progress bar is integer-truncated uncompressed-bytes-written (ota.c:69,dialogs.c:742), so 3–4 chunks of a 1.18 MB image shows as 1–2%.The change
Always send
ota_completebefore the worker exits, including on timeout. When it arrives early the ota task rejects it (handle_in_bin_data()→OTA_ERR_BADDATA), which takes it through its existing error path inota_finalize(): it replies to the worker, callsawait_error(), and the user is back at a screen where they can retry.One hunk, no new state, no change to the wait bounds. It also retires the "Notify the user in the failed_wait == true case" TODO, since the ota task now does that through the code path it already has.
What this deliberately does not change
wait_forever = msgs_sent <= 4). I initially assumed that bound was the bug. It is not: the on-device hash confirmation happens inside decompression of the first data chunk (uncompressed_stream_writer()→ota_user_validate(),ota.c:26), so the ack for that chunk blocks for as long as the user takes to read and confirm the hash. Tightening the bound would make the update fail for anyone who takes more than 10 s to verify the hash — which is exactly the behaviour we want to encourage. Left as is.read()on the MSC mount has no timeout (usbmode.c:623). A transfer that stalls at the USB layer still hangs the worker insideread(), and this patch cannot help there (the worker never reaches the exit path). Bounding that needs a cancellation path in the MSC driver; I did not want to fake it with a wrapper task. Left for a follow-up.Verification
Not build-tested and not tested on hardware — I do not have the ESP-IDF toolchain set up on this machine. The change is small enough that I hope a maintainer can build it more easily than I can, but please treat it accordingly.
What I did do is port the worker / ota-task control flow (wait bounds, blocking receive, hash-confirmation point,
BADDATArejection,ota_finalizeerror path) to a host-side harness and drive it through the stall scenarios:<= 2(rejected)read()stalls at the USB layerThe second row is the #287 failure mode; the first row is why I dropped the bound change.
One cosmetic point for a follow-up: the error the user sees on the timeout path is the generic
ERR_BADDATAtext. A dedicatedota_cancelmessage (the existing TODO on that line) would let it say something more useful like "Update timed out".Unrelated observation from the same session
Before getting anywhere near 2%, the device refused to see the card at all, reporting it was not FAT32 through several reformats. The reader was a multi-slot USB-C card reader;
usb_host_msconly attaches LUN 0 (msc_host_install_device(),usbhmsc.c:160), and the microSD slot enumerated as LUN 1, so Jade was mounting the empty full-size slot. Moving the card into the full-size SD slot fixed it immediately. Probably worth a line in the USB update docs recommending a single-slot reader; happy to send that separately if useful.🤖 Generated with Claude Code