Skip to content

Commit

Permalink
usb: host: add xhci hooks for USB offload
Browse files Browse the repository at this point in the history
To enable supporting for USB offload, define "offload" in usb controller
node of device tree. "offload" value can be used to determine which type
of offload was been enabled in the SoC.

For example:

&usbdrd_dwc3 {
	...
	/* support usb offloading, 0: disabled, 1: audio */
	offload = <1>;
	...
};

There are several vendor_ops introduced by this patch:

struct xhci_vendor_ops - function callbacks for vendor specific operations
{
	@vendor_init:
		- called for vendor init process during xhci-plat-hcd
		  probe.
	@vendor_cleanup:
		- called for vendor cleanup process during xhci-plat-hcd
		  remove.
	@is_usb_offload_enabled:
		- called to check if usb offload enabled.
	@queue_irq_work:
		- called to queue vendor specific irq work.
	@alloc_dcbaa:
		- called when allocating vendor specific dcbaa during
		  memory initializtion.
	@free_dcbaa:
		- called to free vendor specific dcbaa when cleanup the
		  memory.
	@alloc_transfer_ring:
		- called when vendor specific transfer ring allocation is required
	@free_transfer_ring:
		- called to free vendor specific transfer ring
	@sync_dev_ctx:
		- called when synchronization for device context is required
}

The xhci hooks with prefix "xhci_vendor_" on the ops in xhci_vendor_ops.
For example, vendor_init ops will be invoked by xhci_vendor_init() hook,
is_usb_offload_enabled ops will be invoked by
xhci_vendor_is_usb_offload_enabled(), and so on.

Signed-off-by: Daehwan Jung <dh10.jung@samsung.com>
  • Loading branch information
Daehwan Jung authored and intel-lab-lkp committed Feb 3, 2022
1 parent 7bbb0fd commit 507c80f
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 17 deletions.
5 changes: 5 additions & 0 deletions drivers/usb/host/xhci-hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -535,8 +535,13 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend)
cmd->status == COMP_COMMAND_RING_STOPPED) {
xhci_warn(xhci, "Timeout while waiting for stop endpoint command\n");
ret = -ETIME;
goto cmd_cleanup;
}

ret = xhci_vendor_sync_dev_ctx(xhci, slot_id);
if (ret)
xhci_warn(xhci, "Sync device context failed, ret=%d\n", ret);

cmd_cleanup:
xhci_free_command(xhci, cmd);
return ret;
Expand Down
131 changes: 116 additions & 15 deletions drivers/usb/host/xhci-mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,54 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
return 0;
}

static void xhci_vendor_free_container_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (ops && ops->free_container_ctx)
ops->free_container_ctx(xhci, ctx);
}

static void xhci_vendor_alloc_container_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx,
int type, gfp_t flags)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (ops && ops->alloc_container_ctx)
ops->alloc_container_ctx(xhci, ctx, type, flags);
}

static struct xhci_ring *xhci_vendor_alloc_transfer_ring(struct xhci_hcd *xhci,
u32 endpoint_type, enum xhci_ring_type ring_type,
unsigned int max_packet, gfp_t mem_flags)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (ops && ops->alloc_transfer_ring)
return ops->alloc_transfer_ring(xhci, endpoint_type, ring_type,
max_packet, mem_flags);
return 0;
}

void xhci_vendor_free_transfer_ring(struct xhci_hcd *xhci,
struct xhci_ring *ring, unsigned int ep_index)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (ops && ops->free_transfer_ring)
ops->free_transfer_ring(xhci, ring, ep_index);
}

bool xhci_vendor_is_usb_offload_enabled(struct xhci_hcd *xhci,
struct xhci_virt_device *virt_dev, unsigned int ep_index)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (ops && ops->is_usb_offload_enabled)
return ops->is_usb_offload_enabled(xhci, virt_dev, ep_index);
return false;
}

/*
* Create a new ring with zero or more segments.
*
Expand Down Expand Up @@ -419,7 +467,11 @@ void xhci_free_endpoint_ring(struct xhci_hcd *xhci,
struct xhci_virt_device *virt_dev,
unsigned int ep_index)
{
xhci_ring_free(xhci, virt_dev->eps[ep_index].ring);
if (xhci_vendor_is_usb_offload_enabled(xhci, virt_dev, ep_index))
xhci_vendor_free_transfer_ring(xhci, virt_dev->eps[ep_index].ring, ep_index);
else
xhci_ring_free(xhci, virt_dev->eps[ep_index].ring);

virt_dev->eps[ep_index].ring = NULL;
}

Expand Down Expand Up @@ -478,6 +530,7 @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci,
{
struct xhci_container_ctx *ctx;
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if ((type != XHCI_CTX_TYPE_DEVICE) && (type != XHCI_CTX_TYPE_INPUT))
return NULL;
Expand All @@ -491,7 +544,12 @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci,
if (type == XHCI_CTX_TYPE_INPUT)
ctx->size += CTX_SIZE(xhci->hcc_params);

ctx->bytes = dma_pool_zalloc(xhci->device_pool, flags, &ctx->dma);
if (xhci_vendor_is_usb_offload_enabled(xhci, NULL, 0) &&
(ops && ops->alloc_container_ctx))
xhci_vendor_alloc_container_ctx(xhci, ctx, type, flags);
else
ctx->bytes = dma_pool_zalloc(xhci->device_pool, flags, &ctx->dma);

if (!ctx->bytes) {
kfree(ctx);
return NULL;
Expand All @@ -502,9 +560,16 @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci,
void xhci_free_container_ctx(struct xhci_hcd *xhci,
struct xhci_container_ctx *ctx)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (!ctx)
return;
dma_pool_free(xhci->device_pool, ctx->bytes, ctx->dma);
if (xhci_vendor_is_usb_offload_enabled(xhci, NULL, 0) &&
(ops && ops->free_container_ctx))
xhci_vendor_free_container_ctx(xhci, ctx);
else
dma_pool_free(xhci->device_pool, ctx->bytes, ctx->dma);

kfree(ctx);
}

Expand Down Expand Up @@ -897,7 +962,7 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id)

for (i = 0; i < 31; i++) {
if (dev->eps[i].ring)
xhci_ring_free(xhci, dev->eps[i].ring);
xhci_free_endpoint_ring(xhci, dev, i);
if (dev->eps[i].stream_info)
xhci_free_stream_info(xhci,
dev->eps[i].stream_info);
Expand Down Expand Up @@ -1495,8 +1560,16 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
mult = 0;

/* Set up the endpoint ring */
virt_dev->eps[ep_index].new_ring =
xhci_ring_alloc(xhci, 2, 1, ring_type, max_packet, mem_flags);
if (xhci_vendor_is_usb_offload_enabled(xhci, virt_dev, ep_index) &&
usb_endpoint_xfer_isoc(&ep->desc)) {
virt_dev->eps[ep_index].new_ring =
xhci_vendor_alloc_transfer_ring(xhci, endpoint_type, ring_type,
max_packet, mem_flags);
} else {
virt_dev->eps[ep_index].new_ring =
xhci_ring_alloc(xhci, 2, 1, ring_type, max_packet, mem_flags);
}

if (!virt_dev->eps[ep_index].new_ring)
return -ENOMEM;

Expand Down Expand Up @@ -1844,6 +1917,24 @@ void xhci_free_erst(struct xhci_hcd *xhci, struct xhci_erst *erst)
}
EXPORT_SYMBOL_GPL(xhci_free_erst);

static struct xhci_device_context_array *xhci_vendor_alloc_dcbaa(
struct xhci_hcd *xhci, gfp_t flags)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (ops && ops->alloc_dcbaa)
return ops->alloc_dcbaa(xhci, flags);
return 0;
}

static void xhci_vendor_free_dcbaa(struct xhci_hcd *xhci)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (ops && ops->free_dcbaa)
ops->free_dcbaa(xhci);
}

void xhci_mem_cleanup(struct xhci_hcd *xhci)
{
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
Expand Down Expand Up @@ -1898,9 +1989,13 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Freed medium stream array pool");

if (xhci->dcbaa)
dma_free_coherent(dev, sizeof(*xhci->dcbaa),
xhci->dcbaa, xhci->dcbaa->dma);
if (xhci_vendor_is_usb_offload_enabled(xhci, NULL, 0)) {
xhci_vendor_free_dcbaa(xhci);
} else {
if (xhci->dcbaa)
dma_free_coherent(dev, sizeof(*xhci->dcbaa),
xhci->dcbaa, xhci->dcbaa->dma);
}
xhci->dcbaa = NULL;

scratchpad_free(xhci);
Expand Down Expand Up @@ -2441,15 +2536,21 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
* xHCI section 5.4.6 - doorbell array must be
* "physically contiguous and 64-byte (cache line) aligned".
*/
xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma,
flags);
if (!xhci->dcbaa)
goto fail;
xhci->dcbaa->dma = dma;
if (xhci_vendor_is_usb_offload_enabled(xhci, NULL, 0)) {
xhci->dcbaa = xhci_vendor_alloc_dcbaa(xhci, flags);
if (!xhci->dcbaa)
goto fail;
} else {
xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma,
flags);
if (!xhci->dcbaa)
goto fail;
xhci->dcbaa->dma = dma;
}
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"// Device context base array address = 0x%llx (DMA), %p (virt)",
(unsigned long long)xhci->dcbaa->dma, xhci->dcbaa);
xhci_write_64(xhci, dma, &xhci->op_regs->dcbaa_ptr);
xhci_write_64(xhci, xhci->dcbaa->dma, &xhci->op_regs->dcbaa_ptr);

/*
* Initialize the ring segment pool. The ring must be a contiguous
Expand Down
43 changes: 42 additions & 1 deletion drivers/usb/host/xhci-plat.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,41 @@ static const struct of_device_id usb_xhci_of_match[] = {
MODULE_DEVICE_TABLE(of, usb_xhci_of_match);
#endif

static struct xhci_plat_priv_overwrite xhci_plat_vendor_overwrite;

int xhci_plat_register_vendor_ops(struct xhci_vendor_ops *vendor_ops)
{
if (vendor_ops == NULL)
return -EINVAL;

xhci_plat_vendor_overwrite.vendor_ops = vendor_ops;

return 0;
}
EXPORT_SYMBOL_GPL(xhci_plat_register_vendor_ops);

static int xhci_vendor_init(struct xhci_hcd *xhci)
{
struct xhci_vendor_ops *ops = NULL;

if (xhci_plat_vendor_overwrite.vendor_ops)
ops = xhci->vendor_ops = xhci_plat_vendor_overwrite.vendor_ops;

if (ops && ops->vendor_init)
return ops->vendor_init(xhci);
return 0;
}

static void xhci_vendor_cleanup(struct xhci_hcd *xhci)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (ops && ops->vendor_cleanup)
ops->vendor_cleanup(xhci);

xhci->vendor_ops = NULL;
}

static int xhci_plat_probe(struct platform_device *pdev)
{
const struct xhci_plat_priv *priv_match;
Expand Down Expand Up @@ -332,6 +367,10 @@ static int xhci_plat_probe(struct platform_device *pdev)
goto put_usb3_hcd;
}

ret = xhci_vendor_init(xhci);
if (ret)
goto disable_usb_phy;

hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
xhci->shared_hcd->tpl_support = hcd->tpl_support;

Expand Down Expand Up @@ -411,8 +450,10 @@ static int xhci_plat_remove(struct platform_device *dev)
usb_phy_shutdown(hcd->usb_phy);

usb_remove_hcd(hcd);
usb_put_hcd(shared_hcd);

xhci_vendor_cleanup(xhci);

usb_put_hcd(shared_hcd);
clk_disable_unprepare(clk);
clk_disable_unprepare(reg_clk);
usb_put_hcd(hcd);
Expand Down
8 changes: 8 additions & 0 deletions drivers/usb/host/xhci-plat.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
struct xhci_plat_priv {
const char *firmware_name;
unsigned long long quirks;
struct xhci_vendor_data *vendor_data;
int (*plat_setup)(struct usb_hcd *);
void (*plat_start)(struct usb_hcd *);
int (*init_quirk)(struct usb_hcd *);
Expand All @@ -22,4 +23,11 @@ struct xhci_plat_priv {

#define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv)
#define xhci_to_priv(x) ((struct xhci_plat_priv *)(x)->priv)

struct xhci_plat_priv_overwrite {
struct xhci_vendor_ops *vendor_ops;
};

int xhci_plat_register_vendor_ops(struct xhci_vendor_ops *vendor_ops);

#endif /* _XHCI_PLAT_H */
13 changes: 13 additions & 0 deletions drivers/usb/host/xhci-ring.c
Original file line number Diff line number Diff line change
Expand Up @@ -3075,6 +3075,15 @@ void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
}
EXPORT_SYMBOL_GPL(xhci_update_erst_dequeue);

static irqreturn_t xhci_vendor_queue_irq_work(struct xhci_hcd *xhci)
{
struct xhci_vendor_ops *ops = xhci_vendor_get_ops(xhci);

if (ops && ops->queue_irq_work)
return ops->queue_irq_work(xhci);
return IRQ_NONE;
}

/*
* xHCI spec says we can get an interrupt, and if the HC has an error condition,
* we might get bad data out of the event ring. Section 4.10.2.7 has a list of
Expand Down Expand Up @@ -3108,6 +3117,10 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
goto out;
}

ret = xhci_vendor_queue_irq_work(xhci);
if (ret == IRQ_HANDLED)
goto out;

/*
* Clear the op reg interrupt status first,
* so we can receive interrupts from other MSI-X interrupters.
Expand Down
Loading

0 comments on commit 507c80f

Please sign in to comment.