#include <linux/usb/xhci-sideband.h>
#include <linux/dma-direct.h>
#include "xhci.h"
static struct sg_table *
xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring)
{
struct xhci_segment *seg;
struct sg_table *sgt;
unsigned int n_pages;
struct page **pages;
struct device *dev;
size_t sz;
int i;
dev = xhci_to_hcd(sb->xhci)->self.sysdev;
sz = ring->num_segs * TRB_SEGMENT_SIZE;
n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
pages = kvmalloc_objs(struct page *, n_pages);
if (!pages)
return NULL;
sgt = kzalloc_obj(*sgt);
if (!sgt) {
kvfree(pages);
return NULL;
}
seg = ring->first_seg;
if (!seg)
goto err;
for (i = 0; i < ring->num_segs; i++) {
dma_get_sgtable(dev, sgt, seg->trbs, seg->dma,
TRB_SEGMENT_SIZE);
pages[i] = sg_page(sgt->sgl);
sg_free_table(sgt);
seg = seg->next;
}
if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL))
goto err;
sg_dma_address(sgt->sgl) = ring->first_seg->dma;
return sgt;
err:
kvfree(pages);
kfree(sgt);
return NULL;
}
static void
__xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep)
{
lockdep_assert_held(&sb->mutex);
xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL);
ep->sideband = NULL;
sb->eps[ep->ep_index] = NULL;
}
static void
__xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
{
lockdep_assert_held(&sb->mutex);
if (!sb->ir)
return;
xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
sb->ir = NULL;
}
void xhci_sideband_notify_ep_ring_free(struct xhci_sideband *sb,
unsigned int ep_index)
{
struct xhci_sideband_event evt;
evt.type = XHCI_SIDEBAND_XFER_RING_FREE;
evt.evt_data = &ep_index;
if (sb->notify_client)
sb->notify_client(sb->intf, &evt);
}
EXPORT_SYMBOL_GPL(xhci_sideband_notify_ep_ring_free);
int
xhci_sideband_add_endpoint(struct xhci_sideband *sb,
struct usb_host_endpoint *host_ep)
{
struct xhci_virt_ep *ep;
unsigned int ep_index;
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)
return -EINVAL;
if (sb->eps[ep_index] || ep->sideband)
return -EBUSY;
ep->sideband = sb;
sb->eps[ep_index] = ep;
return 0;
}
EXPORT_SYMBOL_GPL(xhci_sideband_add_endpoint);
int
xhci_sideband_remove_endpoint(struct xhci_sideband *sb,
struct usb_host_endpoint *host_ep)
{
struct xhci_virt_ep *ep;
unsigned int ep_index;
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)
return -ENODEV;
__xhci_sideband_remove_endpoint(sb, ep);
return 0;
}
EXPORT_SYMBOL_GPL(xhci_sideband_remove_endpoint);
int
xhci_sideband_stop_endpoint(struct xhci_sideband *sb,
struct usb_host_endpoint *host_ep)
{
struct xhci_virt_ep *ep;
unsigned int ep_index;
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = sb->eps[ep_index];
if (!ep || !ep->sideband || ep->sideband != sb)
return -EINVAL;
return xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(xhci_sideband_stop_endpoint);
struct sg_table *
xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb,
struct usb_host_endpoint *host_ep)
{
struct xhci_virt_ep *ep;
unsigned int ep_index;
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = sb->eps[ep_index];
if (!ep || !ep->ring || !ep->sideband || ep->sideband != sb)
return NULL;
return xhci_ring_to_sgtable(sb, ep->ring);
}
EXPORT_SYMBOL_GPL(xhci_sideband_get_endpoint_buffer);
struct sg_table *
xhci_sideband_get_event_buffer(struct xhci_sideband *sb)
{
if (!sb || !sb->ir)
return NULL;
return xhci_ring_to_sgtable(sb, sb->ir->event_ring);
}
EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer);
bool xhci_sideband_check(struct usb_hcd *hcd)
{
struct usb_device *udev = hcd->self.root_hub;
bool active;
usb_lock_device(udev);
active = usb_offload_check(udev);
usb_unlock_device(udev);
return active;
}
EXPORT_SYMBOL_GPL(xhci_sideband_check);
int
xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
bool ip_autoclear, u32 imod_interval, int intr_num)
{
if (!sb || !sb->xhci)
return -ENODEV;
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)
return -ENOMEM;
sb->ir->ip_autoclear = ip_autoclear;
return 0;
}
EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);
void
xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
{
if (!sb)
return;
guard(mutex)(&sb->mutex);
__xhci_sideband_remove_interrupter(sb);
}
EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter);
int
xhci_sideband_interrupter_id(struct xhci_sideband *sb)
{
if (!sb || !sb->ir)
return -ENODEV;
return sb->ir->intr_num;
}
EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id);
struct xhci_sideband *
xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type,
int (*notify_client)(struct usb_interface *intf,
struct xhci_sideband_event *evt))
{
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct xhci_virt_device *vdev;
struct xhci_sideband *sb;
if (!udev->slot_id || type != XHCI_SIDEBAND_VENDOR)
return NULL;
sb = kzalloc_node(sizeof(*sb), GFP_KERNEL, dev_to_node(hcd->self.sysdev));
if (!sb)
return NULL;
mutex_init(&sb->mutex);
spin_lock_irq(&xhci->lock);
vdev = xhci->devs[udev->slot_id];
if (!vdev || vdev->sideband) {
xhci_warn(xhci, "XHCI sideband for slot %d already in use\n",
udev->slot_id);
spin_unlock_irq(&xhci->lock);
kfree(sb);
return NULL;
}
sb->xhci = xhci;
sb->vdev = vdev;
sb->intf = intf;
sb->type = type;
sb->notify_client = notify_client;
vdev->sideband = sb;
spin_unlock_irq(&xhci->lock);
return sb;
}
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;
if (!sb)
return;
xhci = sb->xhci;
scoped_guard(mutex, &sb->mutex) {
vdev = sb->vdev;
if (!vdev)
return;
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;
vdev->sideband = NULL;
spin_unlock_irq(&xhci->lock);
kfree(sb);
}
EXPORT_SYMBOL_GPL(xhci_sideband_unregister);
MODULE_DESCRIPTION("xHCI sideband driver for secondary interrupter management");
MODULE_LICENSE("GPL");