1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
# MSC host: bound the Test Unit Ready retry loop and act on sense data
> Split out of PR #3851 (`etmtrace-rp2350`, rp2350 ETM trace + stock clocks):
> a host-stack MSC bug with no relation to that branch's scope.
**Goal:** stop `msch_open`'s enumeration retry from spinning forever when a
device answers Test Unit Ready with CHECK CONDITION, and use the sense data the
driver already fetches to decide whether to keep waiting, give up, or report.
---
## What is already established
### The loop is unbounded, and the source says so
`src/class/msc/msc_host.c:445-472` is a two-function cycle with no counter:
```c
static bool config_test_unit_ready_complete(...) {
if (csw->status == 0) {
... tuh_msc_read_capacity(...); // ready -> proceed to mount
} else {
// Note: During enumeration, some device fails Test Unit Ready and require a few retries
// with Request Sense to start working !!
// TODO limit number of retries <-- :459, pre-existing
TU_LOG_DRV("SCSI Request Sense\r\n");
TU_ASSERT(tuh_msc_request_sense(dev_addr, cbw->lun, enum_buf,
config_request_sense_complete, 0));
}
return true;
}
static bool config_request_sense_complete(...) {
TU_ASSERT(csw->status == 0);
TU_ASSERT(tuh_msc_test_unit_ready(dev_addr, cbw->lun,
config_test_unit_ready_complete, 0)); // :472
return true;
}
```
Two defects, independent of each other:
1. **No bound.** TUR fail -> Request Sense -> TUR -> ... forever. `tuh_msc_mount_cb()`
is never called and the application is never told anything; the device sits
enumerated-but-unmounted indefinitely.
2. **Sense data is fetched and discarded.** `config_request_sense_complete`
checks only the CSW status. `enum_buf` holds a `scsi_sense_fixed_resp_t`
whose `sense_key` / ASC / ASCQ distinguish "Not Ready — becoming ready"
(retry is correct) from "Not Ready — medium not present" (a card reader with
no card; retrying can never succeed) from a hard error. The driver cannot
currently tell these apart because it never looks.
### Measured on hardware (2026-08-25)
Rig: `raspberry_pi_pico` (RP2040) + Pico-PIO-USB host on GP20/21, probe
`E6614103E719612F`, console over the probe's CDC. Build:
`-DCFG_TUH_RPI_PIO_USB=1 -DLOG=2`.
- `examples/host/msc_file_explorer` never mounts. Debug log over ~25 s:
**1× `SCSI Test Unit Ready`, 350× `SCSI Request Sense`**, zero
`SCSI Read Capacity`, zero mount callbacks. `dd` reports
`no MSC device mounted`.
- **The transfers themselves all succeed** — every CBW/CSW pair logs `OK`
(`Queue EP 02 with 31 bytes ... OK`, `Queue EP 81 with 13 bytes ... OK`), so
this is a SCSI-state-machine problem, not a bulk-transfer or PIO-USB timing
problem.
- Reproduced with **two different drives** (`24a9:1802` "STORAGE DEVICE" and the
drive swapped in after it), so it is not one device's quirk.
- Control transfers on the same target are fine: `examples/host/device_info`
reads full descriptors from the same drive on the same board
(`bcdUSB 0210`, `bMaxPacketSize0 64`, i.e. full-speed).
- **The very same drive mounts and sustains I/O on RP2350**
(`pico2_etm_trace` carrier): `msc_file_explorer` + `dd` returns
`dd: 524288 bytes in 8448 ms = 62 KB/s`. Confirmed by the maintainer at the
bench, so the device is healthy and the "not ready" answer is provoked by
something specific to the RP2040 setup.
- **Bumping Pico-PIO-USB does not fix it.** Retested with upstream HEAD
`5a37a66` (10 commits ahead of the pinned `675543b`, including
`512d3a2` "Place calc_usb_crc16 in RAM like calc_usb_crc5 and the CRC
tables", which looked like a promising RP2040 timing fix, and `cbf055d`
transaction-length clamp) via `-DPICO_PIO_USB_PATH=<clone>`: identical
failure, no mount.
- Clock is **not** a factor: identical failure at 120 MHz, 133 MHz and
156 MHz on RP2040 (and on RP2350 all of 120/125/126/138/150/156/162/174/186/240 MHz
behave identically).
### What is NOT established
- The actual sense key/ASC/ASCQ the failing drives return — the driver never
logs it. **Task 1 below exists to capture it**, and its answer decides whether
a bounded retry is sufficient or a "medium not present" path is also needed.
- **Why the RP2040 setup provokes the not-ready state.** Leading suspect is
VBUS quality rather than firmware: the RP2350 carrier feeds J5 through a
proper load switch, while the RP2040 rig is a bare Pico whose GP22 "VBUS
enable" drives nothing (no load switch on a bare Pico), so the drive is fed
directly off the VBUS pin through hookup wire. A bus-powered drive that
cannot spin up answers exactly this "not ready" forever. Measure VBUS at the
device under load, or retest with a powered hub / self-powered device,
BEFORE attributing the stall to the host stack.
- The actual sense key (Task 1) — still the gate for any policy change.
---
## What remains
### Task 1: Log the sense response (diagnostic, ship-able on its own)
**Files:** `src/class/msc/msc_host.c` (`config_request_sense_complete`, ~:467)
Add a `TU_LOG_DRV` of `sense_key`, `add_sense_code`, `add_sense_qualifier` from
the fixed-format response in `usbh_get_enum_buf()`. `scsi_sense_fixed_resp_t` is
already declared in `src/class/msc/msc.h`.
Verify on the rig above: rebuild `msc_file_explorer` with `-DLOG=2`, flash, read
the probe CDC, and record the triple. Expected candidates:
`0x02/0x04/0x01` (becoming ready) or `0x02/0x3A/0x00` (medium not present).
### Task 2: Bound the retry
**Files:** `src/class/msc/msc_host.c`, `msch_interface_t` (add a retry counter),
`src/class/msc/msc_host.h` (a `CFG_TUH_MSC_TUR_RETRY_COUNT`-style knob with a
sane default; follow the existing `CFG_TUH_MSC_*` naming in
`src/tusb_option.h`).
On exhaustion, stop the cycle and surface the failure rather than silently
looping — the application currently has no way to learn the device is stuck.
### Task 3: Decide behaviour per sense key
Gated on Task 1's measurement. At minimum: keep retrying on "becoming ready",
stop immediately on "medium not present". Do not invent policy for sense keys
that were not observed.
### Task 4: Regression coverage
`test/unit-test/` has no MSC host suite today; adding one means mocking
`tuh_msc_*` completions. Confirm with the maintainer whether a unit test or a
HIL case on a known not-ready device (an empty card reader is the cheap
reproducer) is the wanted evidence before building either.
---
## Why it was split out
Found while sweeping PIO-USB clocks on the `etmtrace-rp2350` branch, which
touches only rp2040/rp2350 clock pinning and ETM trace config. This bug is in
the class-driver layer, affects every MCU running the MSC host, and predates
that branch (the `// TODO limit number of retries` is already in master). It
deserves its own PR and its own hardware evidence.
|