diff --git a/source/usb.c b/source/usb.c index 547c3f7..8fb9a3d 100644 --- a/source/usb.c +++ b/source/usb.c @@ -101,9 +101,9 @@ static bool g_usbDeviceInterfaceInitialized = false; static Event *g_usbStateChangeEvent = NULL; static thrd_t g_usbDetectionThread; -static UEvent g_usbTimeoutEvent = {0}; -static bool g_usbDetectionThreadCreated = false, g_usbHostAvailable = false, g_usbSessionStarted = false; -static atomic_bool g_usbDetectionThreadExit = false; +static UEvent g_usbDetectionThreadExitEvent = {0}, g_usbTimeoutEvent = {0}; +static bool g_usbHostAvailable = false, g_usbSessionStarted = false, g_usbDetectionThreadExitFlag = false; +static atomic_bool g_usbDetectionThreadCreated = false; static u8 *g_usbTransferBuffer = NULL; static u64 g_usbTransferRemainingSize = 0; @@ -167,11 +167,15 @@ bool usbInitialize(void) goto exit; } + /* Create usermode exit event. */ + ueventCreate(&g_usbDetectionThreadExitEvent, true); + /* Create usermode USB timeout event. */ ueventCreate(&g_usbTimeoutEvent, true); /* Create USB detection thread. */ - if (!(g_usbDetectionThreadCreated = usbCreateDetectionThread())) goto exit; + atomic_store(&g_usbDetectionThreadCreated, usbCreateDetectionThread()); + if (!atomic_load(&g_usbDetectionThreadCreated)) goto exit; ret = true; @@ -183,15 +187,14 @@ exit: void usbExit(void) { - /* Destroy USB detection thread. */ - if (g_usbDetectionThreadCreated && !atomic_load(&g_usbDetectionThreadExit)) + /* Destroy USB detection thread before attempting to lock. */ + if (atomic_load(&g_usbDetectionThreadCreated)) { - atomic_store(&g_usbDetectionThreadExit, true); usbDestroyDetectionThread(); - g_usbDetectionThreadCreated = false; - atomic_store(&g_usbDetectionThreadExit, false); + atomic_store(&g_usbDetectionThreadCreated, false); } + /* Now we can safely lock. */ rwlockWriteLock(&g_usbDeviceLock); /* Clear USB state change kernel event. */ @@ -346,6 +349,9 @@ static bool usbCreateDetectionThread(void) static void usbDestroyDetectionThread(void) { + /* Signal the exit event to terminate the USB detection thread */ + ueventSignal(&g_usbDetectionThreadExitEvent); + /* Wait for the USB detection thread to exit. */ thrd_join(g_usbDetectionThread, NULL); } @@ -359,28 +365,39 @@ static int usbDetectionThreadFunc(void *arg) Waiter usb_change_event_waiter = waiterForEvent(g_usbStateChangeEvent); Waiter usb_timeout_event_waiter = waiterForUEvent(&g_usbTimeoutEvent); + Waiter exit_event_waiter = waiterForUEvent(&g_usbDetectionThreadExitEvent); while(true) { - /* Check if the thread exit flag has been enabled. */ - if (atomic_load(&g_usbDetectionThreadExit)) break; - - /* Wait until an event is triggered (100 ms). */ - rc = waitMulti(&idx, (u64)100000000, usb_change_event_waiter, usb_timeout_event_waiter); + /* Wait until an event is triggered. */ + rc = waitMulti(&idx, -1, usb_change_event_waiter, usb_timeout_event_waiter, exit_event_waiter); if (R_FAILED(rc)) continue; rwlockWriteLock(&g_usbDeviceLock); rwlockWriteLock(&(g_usbDeviceInterface.lock)); + /* Exit event triggered. */ + if (idx == 2) break; + /* Retrieve current USB connection status. */ /* Only proceed if we're dealing with a status change. */ g_usbHostAvailable = usbIsHostAvailable(); g_usbSessionStarted = false; g_usbTransferRemainingSize = 0; - /* Start a USB session if we're connected to a host device and if the status change event was triggered. */ - /* This will essentially hang this thread and all other threads that call USB-related functions until a session is established. */ - if (g_usbHostAvailable) g_usbSessionStarted = usbStartSession(); + /* Start an USB session if we're connected to a host device. */ + /* This will essentially hang this thread and all other threads that call USB-related functions until: */ + /* a) A session is established. */ + /* b) The console is disconnected. */ + /* c) The thread exit event is triggered. */ + if (g_usbHostAvailable) + { + /* Wait until a session is established. */ + g_usbSessionStarted = usbStartSession(); + + /* Check if the exit event was triggered while waiting for a session to be established. */ + if (!g_usbSessionStarted && g_usbDetectionThreadExitFlag) break; + } rwlockWriteUnlock(&(g_usbDeviceInterface.lock)); rwlockWriteUnlock(&g_usbDeviceLock); @@ -388,9 +405,12 @@ static int usbDetectionThreadFunc(void *arg) /* Close USB session if needed. */ if (g_usbHostAvailable && g_usbSessionStarted) usbEndSession(); - g_usbHostAvailable = g_usbSessionStarted = false; + g_usbHostAvailable = g_usbSessionStarted = g_usbDetectionThreadExitFlag = false; g_usbTransferRemainingSize = 0; + rwlockWriteUnlock(&(g_usbDeviceInterface.lock)); + rwlockWriteUnlock(&g_usbDeviceLock); + return 0; } @@ -1014,20 +1034,16 @@ static bool usbTransferData(void *buf, u64 size, UsbDsEndpoint *endpoint) rc = eventWait(&(endpoint->CompletionEvent), USB_TRANSFER_TIMEOUT * (u64)1000000000); } else { /* If we're starting an USB transfer session, wait indefinitely inside a loop to let the user start the companion app. */ - while(true) + int idx = 0; + Waiter completion_event_waiter = waiterForEvent(&(endpoint->CompletionEvent)); + Waiter exit_event_waiter = waiterForUEvent(&g_usbDetectionThreadExitEvent); + + rc = waitMulti(&idx, -1, completion_event_waiter, exit_event_waiter); + if (R_SUCCEEDED(rc) && idx == 1) { - /* Check if the thread exit flag has been enabled. */ - if (atomic_load(&g_usbDetectionThreadExit)) - { - rc = MAKERESULT(Module_Kernel, KernelError_TimedOut); - thread_exit = true; - break; - } - - /* Wait for the endpoint completion event (100 ms). */ - /* Break from this loop if the wait function succeeded, or if we got an result code different than TimedOut. */ - rc = eventWait(&(endpoint->CompletionEvent), (u64)100000000); - if (R_SUCCEEDED(rc) || R_VALUE(rc) != KERNELRESULT(TimedOut)) break; + /* Exit event triggered. */ + rc = MAKERESULT(Module_Kernel, KernelError_TimedOut); + g_usbDetectionThreadExitFlag = thread_exit = true; } }