arch/arm/sama5: Use a recursive mutex to fix OHCI deadlock.

Unplugging a USB device from an OHCI root hub will cause
a deadlock if DRVR_EPFREE is called from sam_rhsc_bottomhalf. A
typical call chain looks like this: sam_rhsc_bottomhalf->
CLASS_DISCONNECTED->usbhost_destroy->DRVR_EPFREE. In this case
DRVR_EPFREE tries to lock a locked mutex. A recursive mutex
prevents this deadlock.
This commit is contained in:
Lwazi Dube 2023-05-02 21:16:36 -04:00 committed by Xiang Xiao
parent 79574c18d8
commit e2083354f1

View File

@ -256,7 +256,7 @@ struct sam_ohci_s
uint8_t outinterval; /* Minimum periodic IN EP polling interval: 2, 4, 6, 16, or 32 */
#endif
mutex_t lock; /* Support mutually exclusive access */
rmutex_t lock; /* Support mutually exclusive access */
sem_t pscsem; /* Semaphore to wait Writeback Done Head event */
struct work_s work; /* Supports interrupt bottom half */
@ -461,7 +461,7 @@ static void sam_disconnect(struct usbhost_driver_s *drvr,
static struct sam_ohci_s g_ohci =
{
.lock = NXMUTEX_INITIALIZER,
.lock = NXRMUTEX_INITIALIZER,
.pscsem = SEM_INITIALIZER(0),
};
@ -1871,7 +1871,7 @@ static int sam_ctrltd(struct sam_rhport_s *rhport,
* wakes this thread up needs the lock).
*/
#warning REVISIT
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
/* Wait for the Writeback Done Head interrupt Loop to handle any false
* alarm semaphore counts.
@ -1886,7 +1886,7 @@ static int sam_ctrltd(struct sam_rhport_s *rhport,
* this upon return.
*/
ret2 = nxmutex_lock(&g_ohci.lock);
ret2 = nxrmutex_lock(&g_ohci.lock);
if (ret2 < 0)
{
ret = ret2;
@ -2242,7 +2242,7 @@ static void sam_ohci_bottomhalf(void *arg)
* real option (other than to reschedule and delay).
*/
nxmutex_lock(&g_ohci.lock);
nxrmutex_lock(&g_ohci.lock);
/* Root hub status change interrupt */
@ -2295,7 +2295,7 @@ static void sam_ohci_bottomhalf(void *arg)
/* Now re-enable interrupts */
sam_putreg(OHCI_INT_MIE, SAM_USBHOST_INTEN);
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
}
/****************************************************************************
@ -2591,7 +2591,7 @@ static int sam_ep0configure(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
/* We must have exclusive access to EP0 and the control list */
ret = nxmutex_lock(&g_ohci.lock);
ret = nxrmutex_lock(&g_ohci.lock);
if (ret < 0)
{
return ret;
@ -2614,7 +2614,7 @@ static int sam_ep0configure(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
up_clean_dcache((uintptr_t)edctrl,
(uintptr_t)edctrl + sizeof(struct ohci_ed_s));
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
usbhost_vtrace2(OHCI_VTRACE2_EP0CTRLED, RHPORT(rhport),
(uint16_t)edctrl->hw.ctrl);
@ -2683,7 +2683,7 @@ static int sam_epalloc(struct usbhost_driver_s *drvr,
* periodic list, and the interrupt table.
*/
ret = nxmutex_lock(&g_ohci.lock);
ret = nxrmutex_lock(&g_ohci.lock);
if (ret < 0)
{
goto errout_with_eplist;
@ -2814,7 +2814,7 @@ static int sam_epalloc(struct usbhost_driver_s *drvr,
/* Success.. return an opaque reference to the endpoint list container */
*ep = (usbhost_ep_t)eplist;
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
return OK;
errout_with_td:
@ -2822,7 +2822,7 @@ errout_with_td:
errout_with_ed:
sam_edfree(ed);
errout_with_lock:
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
errout_with_eplist:
kmm_free(eplist);
errout:
@ -2869,7 +2869,7 @@ static int sam_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep)
* periodic list and the interrupt table.
*/
ret2 = nxmutex_lock(&g_ohci.lock);
ret2 = nxrmutex_lock(&g_ohci.lock);
/* Remove the ED to the correct list depending on the transfer type */
@ -2905,7 +2905,7 @@ static int sam_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep)
nxsem_destroy(&eplist->wdhsem);
kmm_free(eplist);
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
return ret < 0 ? ret : ret2;
}
@ -2951,7 +2951,7 @@ static int sam_alloc(struct usbhost_driver_s *drvr,
/* We must have exclusive access to the transfer buffer pool */
ret = nxmutex_lock(&g_ohci.lock);
ret = nxrmutex_lock(&g_ohci.lock);
if (ret < 0)
{
return ret;
@ -2966,7 +2966,7 @@ static int sam_alloc(struct usbhost_driver_s *drvr,
ret = OK;
}
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
return ret;
}
@ -3002,9 +3002,9 @@ static int sam_free(struct usbhost_driver_s *drvr, uint8_t *buffer)
/* We must have exclusive access to the transfer buffer pool */
ret = nxmutex_lock(&g_ohci.lock);
ret = nxrmutex_lock(&g_ohci.lock);
sam_tbfree(buffer);
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
return ret;
}
@ -3140,7 +3140,7 @@ static int sam_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
/* We must have exclusive access to EP0 and the control list */
ret = nxmutex_lock(&g_ohci.lock);
ret = nxrmutex_lock(&g_ohci.lock);
if (ret < 0)
{
return ret;
@ -3166,7 +3166,7 @@ static int sam_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
* it to be reloaded from RAM after the DMA.
*/
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
up_invalidate_dcache((uintptr_t)buffer, (uintptr_t)buffer + len);
return ret;
}
@ -3194,7 +3194,7 @@ static int sam_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
/* We must have exclusive access to EP0 and the control list */
ret = nxmutex_lock(&g_ohci.lock);
ret = nxrmutex_lock(&g_ohci.lock);
if (ret < 0)
{
return ret;
@ -3217,7 +3217,7 @@ static int sam_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
}
}
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
return ret;
}
@ -3366,7 +3366,7 @@ static ssize_t sam_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
* table.
*/
ret = nxmutex_lock(&g_ohci.lock);
ret = nxrmutex_lock(&g_ohci.lock);
if (ret < 0)
{
return (ssize_t)ret;
@ -3403,7 +3403,7 @@ static ssize_t sam_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
*/
#warning REVISIT
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
/* Wait for the Writeback Done Head interrupt Loop to handle any false
* alarm semaphore counts.
@ -3418,7 +3418,7 @@ static ssize_t sam_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
* this upon return.
*/
ret2 = nxmutex_lock(&g_ohci.lock);
ret2 = nxrmutex_lock(&g_ohci.lock);
if (ret2 < 0)
{
ret = ret2;
@ -3451,7 +3451,7 @@ static ssize_t sam_transfer(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
nbytes = eplist->xfrd;
DEBUGASSERT(nbytes >= 0 && nbytes <= buflen);
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
return nbytes;
}
@ -3478,7 +3478,7 @@ errout:
/* Make sure that there is no outstanding request on this endpoint */
eplist->wdhwait = false;
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
return (ssize_t)ret;
}
@ -3634,7 +3634,7 @@ static int sam_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
* table.
*/
ret = nxmutex_lock(&g_ohci.lock);
ret = nxrmutex_lock(&g_ohci.lock);
if (ret < 0)
{
return ret;
@ -3664,7 +3664,7 @@ static int sam_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
* when the transfer completes.
*/
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
return OK;
errout:
@ -3673,7 +3673,7 @@ errout:
eplist->callback = NULL;
eplist->arg = NULL;
nxmutex_unlock(&g_ohci.lock);
nxrmutex_unlock(&g_ohci.lock);
return ret;
}
#endif /* CONFIG_USBHOST_ASYNCH */