Fix save-to-USB fails after drive hotplug or replug

Validate the cached MSD and rescan if stale, detect a replug via CCS+PED on xHCI,
retry msd_init once via port-reset re-enumeration, clear unit attention with REQUEST SENSE
This commit is contained in:
Sam Demeulemeester authored and Sam Demeulemeester committed 2026-09-08 14:45:45 +02:00
1 parent e18aa349ff
commit 269fbcceea
6 files changed
+88 -11

No files matched your search

+9
View File
@@ -405,9 +405,12 @@ static int slog_pct(int ticks, int total)
void save_results_to_usb(void)
{
bool reenumerated = false;
// The drive may have been plugged in after boot - scan for it now. This must be
// done before the heap mark below is recorded, so that anything allocated for a
// newly found drive is not freed when the save completes.
restart:
if (!usb_mass_storage_found) {
prints(POP_R+14, POP_LI, "Scanning for USB drive... ");
(void)usb_scan_for_msd();
@@ -429,6 +432,12 @@ void save_results_to_usb(void)
prints(POP_R+14, POP_LI, "Initializing USB drive... ");
if (!msd_init(&msd)) {
// A port reset + re-enumeration is the last-resort recovery for a wedged drive, so retry once.
if (!reenumerated && msd.hcd->methods->scan_for_msd != NULL) {
reenumerated = true;
usb_forget_msd();
goto restart;
}
prints(POP_R+14, POP_LI, "USB drive init failed. ");
usleep(2000 * MILLISEC);
goto cleanup;
+10 -2
View File
@@ -238,6 +238,7 @@ typedef struct {
// State needed to rescan the root ports after initialisation.
int num_hs_devices;
uint8_t num_ports;
uint8_t msd_port; // 1-based root port owning the found drive, 0 if none
bool i_have_companions;
bool port_in_use[EHCI_MAX_PORTS];
} workspace_t __attribute__ ((aligned (256)));
@@ -626,9 +627,11 @@ static bool scan_for_msd(const usb_hcd_t *hcd)
// Check the port is powered up.
if (~port_status & EHCI_PORT_SC_PP) continue;
// Skip ports owned by a device found during a previous scan, unless it was unplugged.
// Skip ports owned by a previous scan, unless unplugged or hosting a since-forgotten drive.
if (ws->port_in_use[port_idx]) {
if ((port_status & (EHCI_PORT_SC_CCS | EHCI_PORT_SC_PED)) == (EHCI_PORT_SC_CCS | EHCI_PORT_SC_PED)) {
if (ws->msd_port == 1 + port_idx) {
ws->msd_port = 0;
} else if ((port_status & (EHCI_PORT_SC_CCS | EHCI_PORT_SC_PED)) == (EHCI_PORT_SC_CCS | EHCI_PORT_SC_PED)) {
continue;
}
ws->port_in_use[port_idx] = false;
@@ -668,6 +671,7 @@ static bool scan_for_msd(const usb_hcd_t *hcd)
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, USB_SPEED_HIGH, ws->num_hs_devices,
&ws->num_hs_devices, keyboards, 0, &num_keyboards)) {
ws->port_in_use[port_idx] = true;
ws->msd_port = 1 + port_idx;
return true;
}
@@ -877,9 +881,13 @@ bool ehci_probe(uintptr_t base_addr, usb_hcd_t *hcd)
num_hs_devices++;
// Look for keyboards and USB drives attached directly or indirectly to this port.
bool port_msd_before = usb_mass_storage_found;
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, USB_SPEED_HIGH, num_hs_devices,
&num_hs_devices, keyboards, MAX_KEYBOARDS, &num_keyboards)) {
ws->port_in_use[port_idx] = true;
if (usb_mass_storage_found && !port_msd_before) {
ws->msd_port = 1 + port_idx;
}
continue;
}
+18 -1
View File
@@ -1284,10 +1284,27 @@ bool usb_hcd_available(void)
return num_hcd > 0;
}
void usb_forget_msd(void)
{
usb_mass_storage_found = false;
usb_msd_hcd_idx = -1;
usb_msd_name[0] = '\0';
}
bool usb_scan_for_msd(void)
{
if (usb_mass_storage_found) {
return true;
// An unplug or replug makes the stored device state stale, so check it still responds.
const usb_hcd_t *hcd = &hcd_list[usb_msd_hcd_idx];
usb_setup_pkt_t setup_pkt;
build_setup_packet(&setup_pkt, USB_REQ_FROM_DEVICE, USB_GET_DESCRIPTOR,
USB_DESC_DEVICE << 8, 0, sizeof(usb_device_desc_t));
if (hcd->methods->get_data_request(hcd, &usb_msd_info.ep0, &setup_pkt,
hcd->ws->data_buffer, sizeof(usb_device_desc_t))
&& valid_usb_device_descriptor(hcd->ws->data_buffer)) {
return true;
}
usb_forget_msd();
}
usb_runtime_scan = true;
for (int i = 0; i < num_hcd && !usb_mass_storage_found; i++) {
+6
View File
@@ -373,6 +373,12 @@ extern bool usb_mass_storage_found;
*/
extern char usb_msd_name[64];
/**
* Discards the stored mass storage device so the next usb_scan_for_msd() call
* performs a full re-enumeration. Used to recover a wedged drive by port reset.
*/
void usb_forget_msd(void);
/**
* Returns the mass storage device discovered during the initial USB scan.
* If found, populates msd and returns true.
+33 -6
View File
@@ -65,6 +65,13 @@ typedef struct __attribute__((packed)) {
uint8_t status;
} usb_csw_t;
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
// Set when the device processed the last command but rejected it (CHECK CONDITION).
static bool cmd_rejected = false;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
@@ -109,6 +116,8 @@ static bool msd_bot_command(usb_msd_t *msd, const uint8_t *cdb, int cdb_len,
{
const usb_hcd_t *hcd = msd->hcd;
cmd_rejected = false;
// Build Command Block Wrapper. cb[] not in the initializer is zero-padded.
usb_cbw_t cbw = {
.signature = CBW_SIGNATURE,
@@ -153,7 +162,22 @@ static bool msd_bot_command(usb_msd_t *msd, const uint8_t *cdb, int cdb_len,
return false;
}
return data_ok && csw.status == CSW_STATUS_PASSED;
if (data_ok && csw.status == CSW_STATUS_PASSED) {
return true;
}
cmd_rejected = true;
return false;
}
// Discard pending sense data: a post-reset unit attention fails every command until collected.
static void msd_request_sense(usb_msd_t *msd)
{
if (!cmd_rejected) {
return;
}
uint8_t cdb[6] = { SCSI_REQUEST_SENSE, 0, 0, 0, 18, 0 };
uint8_t sense_data[18];
(void)msd_bot_command(msd, cdb, 6, sense_data, sizeof(sense_data), true);
}
// Retry transient bus errors. The command is reissued whole, so a partial op just repeats.
@@ -164,6 +188,7 @@ static bool msd_bot_command_retry(usb_msd_t *msd, const uint8_t *cdb, int cdb_le
if (msd_bot_command(msd, cdb, cdb_len, data, data_len, data_in)) {
return true;
}
msd_request_sense(msd);
usleep(20 * MILLISEC);
}
return false;
@@ -211,13 +236,15 @@ bool msd_init(usb_msd_t *msd)
// TEST UNIT READY — retry a few times since the device may need time to spin up.
uint8_t cdb_tur[6] = { SCSI_TEST_UNIT_READY };
for (int retry = 0; retry < 5; retry++) {
if (msd_bot_command(msd, cdb_tur, 6, NULL, 0, false)) {
break;
bool ready = false;
for (int retry = 0; retry < 5 && !ready; retry++) {
ready = msd_bot_command(msd, cdb_tur, 6, NULL, 0, false);
if (!ready) {
msd_request_sense(msd);
usleep(500 * MILLISEC);
}
usleep(500 * MILLISEC);
if (retry == 4) return false;
}
if (!ready) return false;
// READ CAPACITY (10) — returns 8 bytes: last LBA (4 bytes BE) + block size (4 bytes BE).
uint8_t cdb_cap[10] = { SCSI_READ_CAPACITY_10 };
+12 -2
View File
@@ -371,6 +371,7 @@ typedef struct {
// State needed to rescan the root ports after initialisation.
uint8_t num_ports;
uint8_t msd_port; // 1-based root port owning the found drive, 0 if none
uint8_t port_type[XHCI_MAX_PORTS];
} workspace_t __attribute__ ((aligned (64)));
@@ -1155,9 +1156,13 @@ static bool scan_for_msd(const usb_hcd_t *hcd)
uint32_t port_status = read32(&op_regs->port_regs[port_idx].sc);
// Skip ports owned by a device found during a previous scan, unless it was unplugged.
// Skip ports owned by a previous scan, unless unplugged/replugged or hosting a since-forgotten drive
if (ws->port_type[port_idx] & PORT_TYPE_IN_USE) {
if (port_status & XHCI_PORT_SC_CCS) continue;
if (ws->msd_port == 1 + port_idx) {
ws->msd_port = 0;
} else if ((port_status & (XHCI_PORT_SC_CCS | XHCI_PORT_SC_PED)) == (XHCI_PORT_SC_CCS | XHCI_PORT_SC_PED)) {
continue;
}
ws->port_type[port_idx] &= ~PORT_TYPE_IN_USE;
}
@@ -1190,6 +1195,7 @@ static bool scan_for_msd(const usb_hcd_t *hcd)
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, device_speed, slot_id,
&num_devices, keyboards, 0, &num_keyboards)) {
ws->port_type[port_idx] |= PORT_TYPE_IN_USE;
ws->msd_port = 1 + port_idx;
return true;
}
@@ -1492,9 +1498,13 @@ bool xhci_probe(uintptr_t base_addr, usb_hcd_t *hcd)
if (slot_id == 0) break;
// Look for keyboards attached directly or indirectly to this port.
bool port_msd_before = usb_mass_storage_found;
if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, device_speed, slot_id,
&num_devices, keyboards, MAX_KEYBOARDS, &num_keyboards)) {
ws->port_type[port_idx] |= PORT_TYPE_IN_USE;
if (usb_mass_storage_found && !port_msd_before) {
ws->msd_port = 1 + port_idx;
}
continue;
}