xhci: sideband: Fix race condition in sideband unregister

Uttkarsh Aggarwal observed a kernel panic during sideband un-register
and found it was caused by a race condition between sideband unregister,
and creating sideband interrupters.
The issue occurrs when thread T1 runs uaudio_disconnect() and released
sb->xhci via sideband_unregister, while thread T2 simultaneously accessed
the now-NULL sb->xhci in xhci_sideband_create_interrupter() resulting in
a crash.

Ensure new endpoints or interrupter can't be added to a sidenband after
xhci_sideband_unregister() cleared the existing ones, and unlocked the
sideband mutex.
Reorganize code so that mutex is only taken and released once in
xhci_sideband_unregister(), and clear sb->vdev while mutex is taken.

Use mutex guards to reduce human unlock errors in code

Refuse to add endpoints or interrupter if sb->vdev is not set.
sb->vdev is set when sideband is created and registered.

Reported-by: Uttkarsh Aggarwal <uttkarsh.aggarwal@oss.qualcomm.com>
Closes: https://lore.kernel.org/linux-usb/20251028080043.27760-1-uttkarsh.aggarwal@oss.qualcomm.com
Fixes: de66754e9f ("xhci: sideband: add initial api to register a secondary interrupter entity")
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://patch.msgid.link/20251107162819.1362579-4-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Mathias Nyman
2025-11-07 18:28:18 +02:00
committed by Greg Kroah-Hartman
parent f6bb3b67be
commit 8c13a7323b

View File

@@ -73,9 +73,12 @@ err:
return NULL;
}
/* Caller must hold sb->mutex */
static void
__xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep)
{
lockdep_assert_held(&sb->mutex);
/*
* Issue a stop endpoint command when an endpoint is removed.
* The stop ep cmd handler will handle the ring cleanup.
@@ -86,6 +89,25 @@ __xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *e
sb->eps[ep->ep_index] = NULL;
}
/* Caller must hold sb->mutex */
static void
__xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
{
struct usb_device *udev;
lockdep_assert_held(&sb->mutex);
if (!sb->ir)
return;
xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
sb->ir = NULL;
udev = sb->vdev->udev;
if (udev->state != USB_STATE_NOTATTACHED)
usb_offload_put(udev);
}
/* sideband api functions */
/**
@@ -131,14 +153,16 @@ xhci_sideband_add_endpoint(struct xhci_sideband *sb,
struct xhci_virt_ep *ep;
unsigned int ep_index;
mutex_lock(&sb->mutex);
guard(mutex)(&sb->mutex);
if (!sb->vdev)
return -ENODEV;
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = &sb->vdev->eps[ep_index];
if (ep->ep_state & EP_HAS_STREAMS) {
mutex_unlock(&sb->mutex);
if (ep->ep_state & EP_HAS_STREAMS)
return -EINVAL;
}
/*
* Note, we don't know the DMA mask of the audio DSP device, if its
@@ -148,14 +172,11 @@ xhci_sideband_add_endpoint(struct xhci_sideband *sb,
* and let this function add the endpoint and allocate the ring buffer
* with the smallest common DMA mask
*/
if (sb->eps[ep_index] || ep->sideband) {
mutex_unlock(&sb->mutex);
if (sb->eps[ep_index] || ep->sideband)
return -EBUSY;
}
ep->sideband = sb;
sb->eps[ep_index] = ep;
mutex_unlock(&sb->mutex);
return 0;
}
@@ -180,18 +201,16 @@ xhci_sideband_remove_endpoint(struct xhci_sideband *sb,
struct xhci_virt_ep *ep;
unsigned int ep_index;
mutex_lock(&sb->mutex);
guard(mutex)(&sb->mutex);
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = sb->eps[ep_index];
if (!ep || !ep->sideband || ep->sideband != sb) {
mutex_unlock(&sb->mutex);
if (!ep || !ep->sideband || ep->sideband != sb)
return -ENODEV;
}
__xhci_sideband_remove_endpoint(sb, ep);
xhci_initialize_ring_info(ep->ring);
mutex_unlock(&sb->mutex);
return 0;
}
@@ -316,28 +335,25 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
if (!sb || !sb->xhci)
return -ENODEV;
mutex_lock(&sb->mutex);
if (sb->ir) {
ret = -EBUSY;
goto out;
}
guard(mutex)(&sb->mutex);
if (!sb->vdev)
return -ENODEV;
if (sb->ir)
return -EBUSY;
sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci),
num_seg, imod_interval,
intr_num);
if (!sb->ir) {
ret = -ENOMEM;
goto out;
}
if (!sb->ir)
return -ENOMEM;
udev = sb->vdev->udev;
ret = usb_offload_get(udev);
sb->ir->ip_autoclear = ip_autoclear;
out:
mutex_unlock(&sb->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);
@@ -352,21 +368,12 @@ EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);
void
xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
{
struct usb_device *udev;
if (!sb || !sb->ir)
if (!sb)
return;
mutex_lock(&sb->mutex);
xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
guard(mutex)(&sb->mutex);
sb->ir = NULL;
udev = sb->vdev->udev;
if (udev->state != USB_STATE_NOTATTACHED)
usb_offload_put(udev);
mutex_unlock(&sb->mutex);
__xhci_sideband_remove_interrupter(sb);
}
EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter);
@@ -465,6 +472,7 @@ EXPORT_SYMBOL_GPL(xhci_sideband_register);
void
xhci_sideband_unregister(struct xhci_sideband *sb)
{
struct xhci_virt_device *vdev;
struct xhci_hcd *xhci;
int i;
@@ -473,17 +481,23 @@ xhci_sideband_unregister(struct xhci_sideband *sb)
xhci = sb->xhci;
mutex_lock(&sb->mutex);
for (i = 0; i < EP_CTX_PER_DEV; i++)
if (sb->eps[i])
__xhci_sideband_remove_endpoint(sb, sb->eps[i]);
mutex_unlock(&sb->mutex);
scoped_guard(mutex, &sb->mutex) {
vdev = sb->vdev;
if (!vdev)
return;
xhci_sideband_remove_interrupter(sb);
for (i = 0; i < EP_CTX_PER_DEV; i++)
if (sb->eps[i])
__xhci_sideband_remove_endpoint(sb, sb->eps[i]);
__xhci_sideband_remove_interrupter(sb);
sb->vdev = NULL;
}
spin_lock_irq(&xhci->lock);
sb->xhci = NULL;
sb->vdev->sideband = NULL;
vdev->sideband = NULL;
spin_unlock_irq(&xhci->lock);
kfree(sb);