Code-review fixes for #60: USB MSC hardening + low-severity cleanups#69
Merged
Conversation
While the USB mass-storage drive is mounted the host PC owns the SD card, but the main loop kept running GPS_LOOP(), trackDetectionLoop(), checkAutoIdle() etc., so the firmware and host could both drive the FAT. Corruption was prevented only as a side effect of the SD-access policy denying those acquires, not by design. Add a usbMscActive early-return branch in loop() mirroring the existing bleActive branch: skip all GPS/tach/lap/SD processing, service only the Exit button (Select) and the status-page refresh, then return. The host now owns the card uncontested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gjbi1LXFAFetL5GPohypVu
The only exit from mass-storage mode was the on-device Exit button. Pulling the USB cable — the natural way to finish a drag-and-drop session — left the device stuck with usbMscActive true, holding SD_ACCESS_USB_MSC and the EMI-unsafe 8 MHz SD clock forever. A subsequent driving session would then be silently denied SD logging with no indication. The parked USB loop branch now checks isUsbConnected() (VBUS via NRF_POWER->USBREGSTATUS) each iteration and calls USB_MSC_DISABLE() (reboot) when the cable is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gjbi1LXFAFetL5GPohypVu
USB_MSC_ENABLE() ignored the bool returned by usb_msc.begin(); on a failed MSC interface registration it still set usbMscActive and re-enumerated, leaving the user on a "drive active" screen with no drive. Now bail cleanly on failure: drop media-ready, restore the normal SD clock, release the SD lock, and return false (the UI already shows the SD-busy warning on false). Also raise the detach()->attach() re-enumeration delay from 10 ms to 50 ms; 10 ms is below the USB spec's recommended detach settle window and some hosts would not re-mount the drive reliably. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gjbi1LXFAFetL5GPohypVu
msc_read_cb/msc_write_cb computed the sector count with bufsize / 512 and trusted the comment that bufsize is always a multiple of 512. A malformed host transfer with a non-multiple size would silently read/write fewer sectors than the host believed succeeded. Reject non-aligned/zero sizes with -1, and retry the SD op up to 3x to match the EMI-hardening retry pattern used elsewhere instead of aborting the whole transfer on one glitch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gjbi1LXFAFetL5GPohypVu
USB_MSC_DISABLE() went straight to delay(50) + NVIC_SystemReset(), relying entirely on the host having ejected first. If the user hit the on-device Exit (or pulled the cable) without safely ejecting, any buffered write was lost and the FAT could be left inconsistent across the hard reset. Now drop setUnitReady(false) and SD.card()->syncDevice() before resetting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gjbi1LXFAFetL5GPohypVu
The file-request callback copies at most 64 bytes into buffer[65], so a command longer than that was silently truncated and then run through filename validation and path building as a mangled-but-plausible name. The raw-data paths (track upload, OTA image chunks) already return earlier using data/len directly, so by the command-dispatch point anything len >= 65 is a malformed command, never a valid (FAT-short) filename command. Reject with ERROR rather than acting on the truncated string. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gjbi1LXFAFetL5GPohypVu
The lap_format refactor changed the big-font live pages from space-padded to zero-padded seconds once a lap passes one minute (1:05.007 vs the old 1: 5.007). Same field width, so the column stays stable, but the change was undocumented. Add an explicit column-stability test across the 1-minute boundary and call out the cosmetic change in the CHANGELOG. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gjbi1LXFAFetL5GPohypVu
Document the new usbMscActive loop-parking branch, VBUS-unplug exit, begin()-failure bailout, longer re-enumeration delay, and the flush/media-ready drop before the exit reboot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gjbi1LXFAFetL5GPohypVu
Coverage — host-testable units📂 Overall coverage
📄 File coverage
|
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.
Multi-agent code review of #60 surfaced two CRITICAL structural defects in the newly-added USB Mass Storage feature plus a handful of lower-severity issues. This branch fixes them, one commit per fix for auditability. All 121 host tests pass and the test build is clean.
Fixes (severity order)
loop()kept runningGPS_LOOP(),trackDetectionLoop(),checkAutoIdle(), etc., so the firmware and the host PC could both drive the FAT — corruption was prevented only as a side effect of the SD-access mutex denying those acquires, not by design.loop()now early-returns onusbMscActive(mirroring thebleActivebranch), servicing only the Exit button and the status page.usbMscActive, holdingSD_ACCESS_USB_MSCand the EMI-unsafe 8 MHz SD clock forever — a subsequent drive would then be silently denied logging. The parked branch now watches VBUS and reboots out of USB mode on disconnect.usb_msc.begin()result + lengthen re-enumeration delay.begin()'sboolwas ignored, so a failed MSC registration still flippedusbMscActiveand re-enumerated, stranding the user on a "drive active" screen with no drive. Now bails cleanly (restore clock, release lock, return false). Detach delay raised 10 ms → 50 ms (10 ms is below the USB spec's recommended settle window).bufsizesilently dropped a partial sector via integer division. Now reject zero/unaligned sizes and retry the SD op up to 3× (matching the EMI-hardening retry pattern used elsewhere).USB_MSC_DISABLE()went straight to reset, relying on the host having ejected first; an un-ejected exit could lose a buffered write / leave the FAT inconsistent. NowsetUnitReady(false)+syncDevice()before resetting.len >= 65command at dispatch is malformed — now rejected withERROR.kSpaceminutes>0 seconds zero-padding with a column-stability test + CHANGELOG note (cosmetic1: 5.007→1:05.007change from the lap_format refactor).CHANGELOG.mdandCLAUDE.mdupdated alongside the behavior changes.Lows deliberately left as-is
forceReleaseSDAccess()policy bypass — dead code (zero callers); left the exported API untouched.bleSendFileListlock-hold / TRACK_PARSE-preemptible invariant — accepted by review (recovered by the 1 Hz log-open retry / correct under current synchronous usage)."1.0"— can't useFIRMWARE_VERSION("2.2.3" is 5 chars; the MSC revision field caps at 4), so the current value is correct.🤖 Generated with Claude Code
Generated by Claude Code