Merge "usb: xhci: Add helper API to issue stop endpoint command"

This commit is contained in:
qctecmdr 2019-12-17 16:36:53 -08:00 • committed by Gerrit - the friendly Code Review server
commit 96fb63ca26
4 changed files with 500 additions and 66 deletions

View file

@ -1831,6 +1831,147 @@ void xhci_free_erst(struct xhci_hcd *xhci, struct xhci_erst *erst)
erst->entries = NULL;
}
static void xhci_handle_sec_intr_events(struct xhci_hcd *xhci, int intr_num)
{
union xhci_trb *erdp_trb, *current_trb;
struct xhci_segment *seg;
u64 erdp_reg;
u32 iman_reg;
dma_addr_t deq;
unsigned long segment_offset;
/* disable irq, ack pending interrupt and ack all pending events */
iman_reg =
readl_relaxed(&xhci->sec_ir_set[intr_num]->irq_pending);
iman_reg &= ~IMAN_IE;
writel_relaxed(iman_reg,
&xhci->sec_ir_set[intr_num]->irq_pending);
iman_reg =
readl_relaxed(&xhci->sec_ir_set[intr_num]->irq_pending);
if (iman_reg & IMAN_IP)
writel_relaxed(iman_reg,
&xhci->sec_ir_set[intr_num]->irq_pending);
/* last acked event trb is in erdp reg */
erdp_reg =
xhci_read_64(xhci, &xhci->sec_ir_set[intr_num]->erst_dequeue);
deq = (dma_addr_t)(erdp_reg & ~ERST_PTR_MASK);
if (!deq) {
pr_debug("%s: event ring handling not required\n", __func__);
return;
}
seg = xhci->sec_event_ring[intr_num]->first_seg;
segment_offset = deq - seg->dma;
/* find out virtual address of the last acked event trb */
erdp_trb = current_trb = &seg->trbs[0] +
(segment_offset/sizeof(*current_trb));
/* read cycle state of the last acked trb to find out CCS */
xhci->sec_event_ring[intr_num]->cycle_state =
le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE;
while (1) {
/* last trb of the event ring: toggle cycle state */
if (current_trb == &seg->trbs[TRBS_PER_SEGMENT - 1]) {
xhci->sec_event_ring[intr_num]->cycle_state ^= 1;
current_trb = &seg->trbs[0];
} else {
current_trb++;
}
/* cycle state transition */
if ((le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE) !=
xhci->sec_event_ring[intr_num]->cycle_state)
break;
}
if (erdp_trb != current_trb) {
deq =
xhci_trb_virt_to_dma(xhci->sec_event_ring[intr_num]->deq_seg,
current_trb);
if (deq == 0)
xhci_warn(xhci,
"WARN invalid SW event ring dequeue ptr.\n");
/* Update HC event ring dequeue pointer */
erdp_reg &= ERST_PTR_MASK;
erdp_reg |= ((u64) deq & (u64) ~ERST_PTR_MASK);
}
/* Clear the event handler busy flag (RW1C); event ring is empty. */
erdp_reg |= ERST_EHB;
xhci_write_64(xhci, erdp_reg,
&xhci->sec_ir_set[intr_num]->erst_dequeue);
}
static int sec_event_ring_cleanup(struct xhci_hcd *xhci, unsigned int intr_num)
{
int size;
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
if (intr_num >= xhci->max_interrupters) {
xhci_err(xhci, "invalid secondary interrupter num %d\n",
intr_num);
return -EINVAL;
}
size =
sizeof(struct xhci_erst_entry)*(xhci->sec_erst[intr_num].num_entries);
if (xhci->sec_erst[intr_num].entries) {
xhci_handle_sec_intr_events(xhci, intr_num);
dma_free_coherent(dev, size, xhci->sec_erst[intr_num].entries,
xhci->sec_erst[intr_num].erst_dma_addr);
xhci->sec_erst[intr_num].entries = NULL;
}
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed SEC ERST#%d",
intr_num);
if (xhci->sec_event_ring[intr_num])
xhci_ring_free(xhci, xhci->sec_event_ring[intr_num]);
xhci->sec_event_ring[intr_num] = NULL;
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Freed sec event ring");
return 0;
}
int xhci_sec_event_ring_cleanup(struct usb_device *udev, unsigned int intr_num)
{
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
if (udev->state == USB_STATE_NOTATTACHED || !HCD_RH_RUNNING(hcd))
return 0;
return sec_event_ring_cleanup(hcd_to_xhci(hcd), intr_num);
}
EXPORT_SYMBOL(xhci_sec_event_ring_cleanup);
static void xhci_event_ring_cleanup(struct xhci_hcd *xhci)
{
unsigned int i;
/* sec event ring clean up */
for (i = 1; i < xhci->max_interrupters; i++)
sec_event_ring_cleanup(xhci, i);
kfree(xhci->sec_ir_set);
xhci->sec_ir_set = NULL;
kfree(xhci->sec_erst);
xhci->sec_erst = NULL;
kfree(xhci->sec_event_ring);
xhci->sec_event_ring = NULL;
/* primary event ring clean up */
xhci_free_erst(xhci, &xhci->erst);
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed primary ERST");
if (xhci->event_ring)
xhci_ring_free(xhci, xhci->event_ring);
xhci->event_ring = NULL;
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed priamry event ring");
}
void xhci_mem_cleanup(struct xhci_hcd *xhci)
{
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
@ -1838,12 +1979,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
cancel_delayed_work_sync(&xhci->cmd_timer);
xhci_free_erst(xhci, &xhci->erst);
if (xhci->event_ring)
xhci_ring_free(xhci, xhci->event_ring);
xhci->event_ring = NULL;
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed event ring");
xhci_event_ring_cleanup(xhci);
if (xhci->lpm_command)
xhci_free_command(xhci, xhci->lpm_command);
@ -2084,30 +2220,6 @@ static int xhci_check_trb_in_td_math(struct xhci_hcd *xhci)
return 0;
}
static void xhci_set_hc_event_deq(struct xhci_hcd *xhci)
{
u64 temp;
dma_addr_t deq;
deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg,
xhci->event_ring->dequeue);
if (deq == 0 && !in_interrupt())
xhci_warn(xhci, "WARN something wrong with SW event ring "
"dequeue ptr.\n");
/* Update HC event ring dequeue pointer */
temp = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue);
temp &= ERST_PTR_MASK;
/* Don't clear the EHB bit (which is RW1C) because
* there might be more events to service.
*/
temp &= ~ERST_EHB;
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"// Write event ring dequeue pointer, "
"preserving EHB bit");
xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp,
&xhci->ir_set->erst_dequeue);
}
static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports,
__le32 __iomem *addr, int max_caps)
{
@ -2344,6 +2456,159 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
return 0;
}
static int xhci_event_ring_setup(struct xhci_hcd *xhci, struct xhci_ring **er,
struct xhci_intr_reg __iomem *ir_set, struct xhci_erst *erst,
unsigned int intr_num, gfp_t flags)
{
dma_addr_t deq;
u64 val_64;
unsigned int val;
int ret;
*er = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT, 0, flags);
if (!*er)
return -ENOMEM;
ret = xhci_alloc_erst(xhci, *er, erst, flags);
if (ret)
return ret;
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"intr# %d: num segs = %i, virt addr = %pK, dma addr = 0x%llx",
intr_num,
erst->num_entries,
erst->entries,
(unsigned long long)erst->erst_dma_addr);
/* set ERST count with the number of entries in the segment table */
val = readl_relaxed(&ir_set->erst_size);
val &= ERST_SIZE_MASK;
val |= ERST_NUM_SEGS;
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Write ERST size = %i to ir_set %d (some bits preserved)", val,
intr_num);
writel_relaxed(val, &ir_set->erst_size);
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"intr# %d: Set ERST entries to point to event ring.",
intr_num);
/* set the segment table base address */
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Set ERST base address for ir_set %d = 0x%llx",
intr_num,
(unsigned long long)erst->erst_dma_addr);
val_64 = xhci_read_64(xhci, &ir_set->erst_base);
val_64 &= ERST_PTR_MASK;
val_64 |= (erst->erst_dma_addr & (u64) ~ERST_PTR_MASK);
xhci_write_64(xhci, val_64, &ir_set->erst_base);
/* Set the event ring dequeue address */
deq = xhci_trb_virt_to_dma((*er)->deq_seg, (*er)->dequeue);
if (deq == 0 && !in_interrupt())
xhci_warn(xhci,
"intr# %d:WARN something wrong with SW event ring deq ptr.\n",
intr_num);
/* Update HC event ring dequeue pointer */
val_64 = xhci_read_64(xhci, &ir_set->erst_dequeue);
val_64 &= ERST_PTR_MASK;
/* Don't clear the EHB bit (which is RW1C) because
* there might be more events to service.
*/
val_64 &= ~ERST_EHB;
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"intr# %d:Write event ring dequeue pointer, preserving EHB bit",
intr_num);
xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | val_64,
&ir_set->erst_dequeue);
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Wrote ERST address to ir_set %d.", intr_num);
return 0;
}
int xhci_sec_event_ring_setup(struct usb_device *udev, unsigned int intr_num)
{
int ret;
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
if (udev->state == USB_STATE_NOTATTACHED || !HCD_RH_RUNNING(hcd))
return 0;
if ((xhci->xhc_state & XHCI_STATE_HALTED) || !xhci->sec_ir_set
|| !xhci->sec_event_ring || !xhci->sec_erst ||
intr_num >= xhci->max_interrupters) {
xhci_err(xhci,
"%s:state %x ir_set %pK evt_ring %pK erst %pK intr# %d\n",
__func__, xhci->xhc_state, xhci->sec_ir_set,
xhci->sec_event_ring, xhci->sec_erst, intr_num);
return -EINVAL;
}
if (xhci->sec_event_ring && xhci->sec_event_ring[intr_num]
&& xhci->sec_event_ring[intr_num]->first_seg)
goto done;
xhci->sec_ir_set[intr_num] = &xhci->run_regs->ir_set[intr_num];
ret = xhci_event_ring_setup(xhci,
&xhci->sec_event_ring[intr_num],
xhci->sec_ir_set[intr_num],
&xhci->sec_erst[intr_num],
intr_num, GFP_KERNEL);
if (ret) {
xhci_err(xhci, "sec event ring setup failed inter#%d\n",
intr_num);
return ret;
}
done:
return 0;
}
EXPORT_SYMBOL(xhci_sec_event_ring_setup);
static int xhci_event_ring_init(struct xhci_hcd *xhci, gfp_t flags)
{
int ret = 0;
/* primary + secondary */
xhci->max_interrupters = HCS_MAX_INTRS(xhci->hcs_params1);
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"// Allocating primary event ring");
/* Set ir_set to interrupt register set 0 */
xhci->ir_set = &xhci->run_regs->ir_set[0];
ret = xhci_event_ring_setup(xhci, &xhci->event_ring, xhci->ir_set,
&xhci->erst, 0, flags);
if (ret) {
xhci_err(xhci, "failed to setup primary event ring\n");
goto fail;
}
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"// Allocating sec event ring related pointers");
xhci->sec_ir_set = kcalloc(xhci->max_interrupters,
sizeof(*xhci->sec_ir_set), flags);
if (!xhci->sec_ir_set) {
ret = -ENOMEM;
goto fail;
}
xhci->sec_event_ring = kcalloc(xhci->max_interrupters,
sizeof(*xhci->sec_event_ring), flags);
if (!xhci->sec_event_ring) {
ret = -ENOMEM;
goto fail;
}
xhci->sec_erst = kcalloc(xhci->max_interrupters,
sizeof(*xhci->sec_erst), flags);
if (!xhci->sec_erst)
ret = -ENOMEM;
fail:
return ret;
}
int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
{
dma_addr_t dma;
@ -2351,7 +2616,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
unsigned int val, val2;
u64 val_64;
u32 page_size, temp;
int i, ret;
int i;
INIT_LIST_HEAD(&xhci->cmd_list);
@ -2471,50 +2736,17 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
"// Doorbell array is located at offset 0x%x"
" from cap regs base addr", val);
xhci->dba = (void __iomem *) xhci->cap_regs + val;
/* Set ir_set to interrupt register set 0 */
xhci->ir_set = &xhci->run_regs->ir_set[0];
/*
* Event ring setup: Allocate a normal ring, but also setup
* the event ring segment table (ERST). Section 4.9.3.
*/
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Allocating event ring");
xhci->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT,
0, flags);
if (!xhci->event_ring)
if (xhci_event_ring_init(xhci, GFP_KERNEL))
goto fail;
if (xhci_check_trb_in_td_math(xhci) < 0)
goto fail;
ret = xhci_alloc_erst(xhci, xhci->event_ring, &xhci->erst, flags);
if (ret)
goto fail;
/* set ERST count with the number of entries in the segment table */
val = readl(&xhci->ir_set->erst_size);
val &= ERST_SIZE_MASK;
val |= ERST_NUM_SEGS;
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"// Write ERST size = %i to ir_set 0 (some bits preserved)",
val);
writel(val, &xhci->ir_set->erst_size);
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"// Set ERST entries to point to event ring.");
/* set the segment table base address */
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"// Set ERST base address for ir_set 0 = 0x%llx",
(unsigned long long)xhci->erst.erst_dma_addr);
val_64 = xhci_read_64(xhci, &xhci->ir_set->erst_base);
val_64 &= ERST_PTR_MASK;
val_64 |= (xhci->erst.erst_dma_addr & (u64) ~ERST_PTR_MASK);
xhci_write_64(xhci, val_64, &xhci->ir_set->erst_base);
/* Set the event ring dequeue address */
xhci_set_hc_event_deq(xhci);
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Wrote ERST address to ir_set 0.");
/*
* XXX: Might need to set the Interrupter Moderation Register to
* something other than the default (~1ms minimum between interrupts).

View file

@ -5300,6 +5300,146 @@ static void xhci_clear_tt_buffer_complete(struct usb_hcd *hcd,
spin_unlock_irqrestore(&xhci->lock, flags);
}
phys_addr_t xhci_get_sec_event_ring_phys_addr(struct usb_device *udev,
unsigned int intr_num, dma_addr_t *dma)
{
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct device *dev = hcd->self.sysdev;
struct sg_table sgt;
phys_addr_t pa;
if (udev->state == USB_STATE_NOTATTACHED || !HCD_RH_RUNNING(hcd))
return 0;
if (intr_num >= xhci->max_interrupters) {
xhci_err(xhci, "intr num %d >= max intrs %d\n", intr_num,
xhci->max_interrupters);
return 0;
}
if (!(xhci->xhc_state & XHCI_STATE_HALTED) &&
xhci->sec_event_ring && xhci->sec_event_ring[intr_num]
&& xhci->sec_event_ring[intr_num]->first_seg) {
dma_get_sgtable(dev, &sgt,
xhci->sec_event_ring[intr_num]->first_seg->trbs,
xhci->sec_event_ring[intr_num]->first_seg->dma,
TRB_SEGMENT_SIZE);
*dma = xhci->sec_event_ring[intr_num]->first_seg->dma;
pa = page_to_phys(sg_page(sgt.sgl));
sg_free_table(&sgt);
return pa;
}
return 0;
}
EXPORT_SYMBOL(xhci_get_sec_event_ring_phys_addr);
phys_addr_t xhci_get_xfer_ring_phys_addr(struct usb_device *udev,
struct usb_host_endpoint *ep, dma_addr_t *dma)
{
int ret;
unsigned int ep_index;
struct xhci_virt_device *virt_dev;
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
struct device *dev = hcd->self.sysdev;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct sg_table sgt;
phys_addr_t pa;
if (udev->state == USB_STATE_NOTATTACHED || !HCD_RH_RUNNING(hcd))
return 0;
ret = xhci_check_args(hcd, udev, ep, 1, true, __func__);
if (ret <= 0) {
xhci_err(xhci, "%s: invalid args\n", __func__);
return 0;
}
virt_dev = xhci->devs[udev->slot_id];
ep_index = xhci_get_endpoint_index(&ep->desc);
if (virt_dev->eps[ep_index].ring &&
virt_dev->eps[ep_index].ring->first_seg) {
dma_get_sgtable(dev, &sgt,
virt_dev->eps[ep_index].ring->first_seg->trbs,
virt_dev->eps[ep_index].ring->first_seg->dma,
TRB_SEGMENT_SIZE);
*dma = virt_dev->eps[ep_index].ring->first_seg->dma;
pa = page_to_phys(sg_page(sgt.sgl));
sg_free_table(&sgt);
return pa;
}
return 0;
}
EXPORT_SYMBOL(xhci_get_xfer_ring_phys_addr);
int xhci_stop_endpoint(struct usb_device *udev, struct usb_host_endpoint *ep)
{
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
unsigned int ep_index;
struct xhci_virt_device *virt_dev;
struct xhci_command *cmd;
unsigned long flags;
int ret = 0;
if (udev->state == USB_STATE_NOTATTACHED || !HCD_RH_RUNNING(hcd))
return 0;
cmd = xhci_alloc_command(xhci, true, GFP_NOIO);
if (!cmd)
return -ENOMEM;
spin_lock_irqsave(&xhci->lock, flags);
virt_dev = xhci->devs[udev->slot_id];
if (!virt_dev) {
ret = -ENODEV;
goto err;
}
ep_index = xhci_get_endpoint_index(&ep->desc);
if (virt_dev->eps[ep_index].ring &&
virt_dev->eps[ep_index].ring->dequeue) {
ret = xhci_queue_stop_endpoint(xhci, cmd, udev->slot_id,
ep_index, 0);
if (ret)
goto err;
xhci_ring_cmd_db(xhci);
spin_unlock_irqrestore(&xhci->lock, flags);
/* Wait for stop endpoint command to finish */
wait_for_completion(cmd->completion);
if (cmd->status == COMP_COMMAND_ABORTED ||
cmd->status == COMP_STOPPED) {
xhci_warn(xhci,
"stop endpoint command timeout for ep%d%s\n",
usb_endpoint_num(&ep->desc),
usb_endpoint_dir_in(&ep->desc) ? "in" : "out");
ret = -ETIME;
}
goto free_cmd;
}
err:
spin_unlock_irqrestore(&xhci->lock, flags);
free_cmd:
xhci_free_command(xhci, cmd);
return ret;
}
EXPORT_SYMBOL(xhci_stop_endpoint);
static const struct hc_driver xhci_hc_driver = {
.description = "xhci-hcd",
.product_desc = "xHCI Host Controller",

View file

@ -16,6 +16,7 @@
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/usb/hcd.h>
#include <linux/usb/xhci-sec.h>
#include <linux/io-64-nonatomic-lo-hi.h>
/* Code sharing between pci-quirks and xhci hcd */
@ -1735,6 +1736,8 @@ struct xhci_hcd {
struct xhci_doorbell_array __iomem *dba;
/* Our HCD's current interrupter register set */
struct xhci_intr_reg __iomem *ir_set;
/* secondary interrupter */
struct xhci_intr_reg __iomem **sec_ir_set;
/* Cached register copies of read-only HC data */
__u32 hcs_params1;
@ -1778,6 +1781,11 @@ struct xhci_hcd {
struct xhci_command *current_cmd;
struct xhci_ring *event_ring;
struct xhci_erst erst;
/* secondary event ring and erst */
struct xhci_ring **sec_event_ring;
struct xhci_erst *sec_erst;
/* Scratchpad */
struct xhci_scratchpad *scratchpad;
/* Store LPM test failed devices' information */

View file

@ -0,0 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* xHCI secondary ring APIs
*
* Copyright (c) 2019, The Linux Foundation. All rights reserved.
*/
#ifndef __LINUX_XHCI_SEC_H
#define __LINUX_XHCI_SEC_H
#include <linux/usb.h>
#if IS_ENABLED(CONFIG_USB_XHCI_HCD)
int xhci_sec_event_ring_setup(struct usb_device *udev, unsigned int intr_num);
int xhci_sec_event_ring_cleanup(struct usb_device *udev, unsigned int intr_num);
phys_addr_t xhci_get_sec_event_ring_phys_addr(struct usb_device *udev,
unsigned int intr_num, dma_addr_t *dma);
phys_addr_t xhci_get_xfer_ring_phys_addr(struct usb_device *udev,
struct usb_host_endpoint *ep, dma_addr_t *dma);
int xhci_stop_endpoint(struct usb_device *udev, struct usb_host_endpoint *ep);
#else
static inline int xhci_sec_event_ring_setup(struct usb_device *udev,
unsigned int intr_num);
{
return -ENODEV;
}
static inline int xhci_sec_event_ring_cleanup(struct usb_device *udev,
unsigned int intr_num)
{
return -ENODEV;
}
static inline phys_addr_t xhci_get_sec_event_ring_phys_addr(
struct usb_device *udev, unsigned int intr_num,
dma_addr_t *dma)
{
return NULL;
}
static inline phys_addr_t xhci_get_xfer_ring_phys_addr(struct usb_device *udev,
struct usb_host_endpoint *ep, dma_addr_t *dma)
{
return NULL;
}
static inline int xhci_stop_endpoint(struct usb_device *udev,
struct usb_host_endpoint *ep)
{
return -ENODEV;
}
#endif
#endif /* __LINUX_XHCI_SEC_H */