From 8aa623a391d34241142cb6956571e85d876e379a Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Sat, 25 Apr 2026 05:46:07 +0000 Subject: [PATCH] [transport] usb: Fix htool crash on USB disconnect Previously, libhoth_usb_fifo_run_transfers() could return while one or more libusb transfers were still pending. This happened if the second transfer submission failed, or if the event handling loop was interrupted by a signal (e.g. SIGINT). When htool subsequently attempted to close and reopen the transport, it would call libusb_free_transfer() on these pending transfers, triggering an assertion failure in libusb: 'usbi_mutex_lock: Assertion pthread_mutex_lock(mutex) == 0 failed' This change ensures that: 1. All submitted transfers are completed (success, error, or cancel) before the function returns. 2. If a transfer submission fails, any other successfully submitted transfer is cancelled and waited for. 3. Signal interruptions do not cause an early return while transfers are pending. 4. Completion flags are correctly initialized on open. Verified on yutulis-ru4-bmc-01 with 1000 iterations of target reset spam without a crash. Signed-off-by: William A. Kennington III --- transports/libhoth_usb_fifo.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/transports/libhoth_usb_fifo.c b/transports/libhoth_usb_fifo.c index b41f3f9..6ef40b1 100644 --- a/transports/libhoth_usb_fifo.c +++ b/transports/libhoth_usb_fifo.c @@ -37,20 +37,40 @@ static int libhoth_usb_fifo_run_transfers(struct libhoth_usb_device* dev, if (in) { int status = libusb_submit_transfer(drvdata->in_transfer); if (status != LIBUSB_SUCCESS) { + drvdata->all_transfers_completed = 1; + drvdata->in_transfer_completed = true; + drvdata->out_transfer_completed = true; return status; } } if (out) { int status = libusb_submit_transfer(drvdata->out_transfer); if (status != LIBUSB_SUCCESS) { + if (in) { + libusb_cancel_transfer(drvdata->in_transfer); + // We must still wait for the IN transfer to complete/cancel + while (drvdata->all_transfers_completed == 0) { + libusb_handle_events_completed(dev->ctx, + &drvdata->all_transfers_completed); + } + } else { + drvdata->all_transfers_completed = 1; + drvdata->out_transfer_completed = true; + } return status; } } while (drvdata->all_transfers_completed == 0) { int status = libusb_handle_events_completed( dev->ctx, &drvdata->all_transfers_completed); - if (status == LIBUSB_ERROR_INTERRUPTED) { - return status; + if (status != LIBUSB_SUCCESS && status != LIBUSB_ERROR_INTERRUPTED) { + // On a real error, try to cancel everything to speed up completion + if (!drvdata->in_transfer_completed) { + libusb_cancel_transfer(drvdata->in_transfer); + } + if (!drvdata->out_transfer_completed) { + libusb_cancel_transfer(drvdata->out_transfer); + } } } return LIBHOTH_OK; @@ -159,6 +179,9 @@ int libhoth_usb_fifo_open(struct libhoth_usb_device* dev, goto err_out; } drvdata->prng_state = prng_seed; + drvdata->in_transfer_completed = true; + drvdata->out_transfer_completed = true; + drvdata->all_transfers_completed = 1; return LIBHOTH_OK; err_out: if (drvdata->in_buffer != NULL) free(drvdata->in_buffer);