mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
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:
committed by
Greg Kroah-Hartman
parent
f6bb3b67be
commit
8c13a7323b
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user