From 1abb68471812482dd51cab88d9019d1e53e341e8 Mon Sep 17 00:00:00 2001 From: "William A. Kennington III" Date: Tue, 5 May 2026 06:12:49 +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 | 42 +++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/transports/libhoth_usb_fifo.c b/transports/libhoth_usb_fifo.c index b41f3f9..6548440 100644 --- a/transports/libhoth_usb_fifo.c +++ b/transports/libhoth_usb_fifo.c @@ -29,31 +29,52 @@ static int libhoth_usb_fifo_run_transfers(struct libhoth_usb_device* dev, bool out, bool in) { + int status; struct libhoth_usb_fifo* drvdata = &dev->driver_data.fifo; - drvdata->all_transfers_completed = 0; - drvdata->out_transfer_completed = !out; - drvdata->in_transfer_completed = !in; + drvdata->all_transfers_completed = 1; + drvdata->out_transfer_completed = true; + drvdata->in_transfer_completed = true; if (in) { - int status = libusb_submit_transfer(drvdata->in_transfer); + status = libusb_submit_transfer(drvdata->in_transfer); if (status != LIBUSB_SUCCESS) { - return status; + goto out; } + drvdata->all_transfers_completed = 0; + drvdata->in_transfer_completed = false; } if (out) { int status = libusb_submit_transfer(drvdata->out_transfer); if (status != LIBUSB_SUCCESS) { - return status; + goto out; } + drvdata->all_transfers_completed = 0; + drvdata->out_transfer_completed = false; } 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) { + goto out; } } - return LIBHOTH_OK; + drvdata->in_transfer_completed = true; + drvdata->out_transfer_completed = true; + status = LIBHOTH_OK; + +out: + if (!drvdata->in_transfer_completed) { + libusb_cancel_transfer(drvdata->in_transfer); + drvdata->in_transfer_completed = true; + } + if (!drvdata->out_transfer_completed) { + libusb_cancel_transfer(drvdata->out_transfer); + drvdata->out_transfer_completed = true; + } + while (drvdata->all_transfers_completed == 0) { + libusb_handle_events_completed(dev->ctx, &drvdata->all_transfers_completed); + } + return status; } static void fifo_transfer_callback(struct libusb_transfer* transfer) { @@ -159,6 +180,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);