diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 5e6bf23a008b..484a5013d217 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2233,6 +2233,28 @@ int usb_hcd_get_frame_number (struct usb_device *udev) return hcd->driver->get_frame_number (hcd); } +int usb_hcd_sec_event_ring_setup(struct usb_device *udev, + unsigned int intr_num) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + if (!HCD_RH_RUNNING(hcd)) + return 0; + + return hcd->driver->sec_event_ring_setup(hcd, intr_num); +} + +int usb_hcd_sec_event_ring_cleanup(struct usb_device *udev, + unsigned int intr_num) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + if (!HCD_RH_RUNNING(hcd)) + return 0; + + return hcd->driver->sec_event_ring_cleanup(hcd, intr_num); +} + /*-------------------------------------------------------------------------*/ int usb_hcd_get_controller_id(struct usb_device *udev) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 8c23d3c4e9e0..090b2ce0cbcd 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -825,6 +825,23 @@ int usb_get_current_frame_number(struct usb_device *dev) } EXPORT_SYMBOL_GPL(usb_get_current_frame_number); +int usb_sec_event_ring_setup(struct usb_device *dev, + unsigned int intr_num) +{ + if (dev->state == USB_STATE_NOTATTACHED) + return 0; + + return usb_hcd_sec_event_ring_setup(dev, intr_num); +} +EXPORT_SYMBOL(usb_sec_event_ring_setup); + +int usb_sec_event_ring_cleanup(struct usb_device *dev, + unsigned int intr_num) +{ + return usb_hcd_sec_event_ring_cleanup(dev, intr_num); +} +EXPORT_SYMBOL(usb_sec_event_ring_cleanup); + /** * usb_get_controller_id - returns the host controller id. * @dev: the device whose host controller id is being queried. diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 9e87c282a743..5a27091f2524 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1837,6 +1837,137 @@ void xhci_free_erst(struct xhci_hcd *xhci, struct xhci_erst *erst) erst->entries = NULL; } +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 = + (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); +} + +int xhci_sec_event_ring_cleanup(struct usb_hcd *hcd, unsigned int intr_num) +{ + int size; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + 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; +} + +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++) + xhci_sec_event_ring_cleanup(xhci_to_hcd(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; @@ -1844,12 +1975,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); @@ -2094,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) { @@ -2381,6 +2483,154 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) return 0; } +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_hcd *hcd, unsigned int intr_num) +{ + int ret; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + 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; +} + +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; @@ -2388,7 +2638,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); @@ -2509,50 +2759,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). diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 65cc362717fc..84ec483be7ac 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -5243,6 +5243,8 @@ static const struct hc_driver xhci_hc_driver = { .enable_usb3_lpm_timeout = xhci_enable_usb3_lpm_timeout, .disable_usb3_lpm_timeout = xhci_disable_usb3_lpm_timeout, .find_raw_port_number = xhci_find_raw_port_number, + .sec_event_ring_setup = xhci_sec_event_ring_setup, + .sec_event_ring_cleanup = xhci_sec_event_ring_cleanup, }; void xhci_init_driver(struct hc_driver *drv, diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 07636dc419bb..ace381d0c1d4 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1741,6 +1741,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; int core_id; @@ -1786,6 +1788,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 */ @@ -2054,6 +2061,8 @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci, int type, gfp_t flags); void xhci_free_container_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); +int xhci_sec_event_ring_setup(struct usb_hcd *hcd, unsigned int intr_num); +int xhci_sec_event_ring_cleanup(struct usb_hcd *hcd, unsigned int intr_num); /* xHCI host controller glue */ typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); diff --git a/include/linux/usb.h b/include/linux/usb.h index 1f04fad23657..8e55b99b0567 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -824,6 +824,10 @@ static inline bool usb_device_no_sg_constraint(struct usb_device *udev) /* for drivers using iso endpoints */ extern int usb_get_current_frame_number(struct usb_device *usb_dev); +extern int usb_sec_event_ring_setup(struct usb_device *dev, + unsigned int intr_num); +extern int usb_sec_event_ring_cleanup(struct usb_device *dev, + unsigned int intr_num); extern int usb_get_controller_id(struct usb_device *dev); /* Sets up a group of bulk endpoints to support multiple stream IDs. */ diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index cca516cfa9da..67b9a6887032 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -406,6 +406,10 @@ struct hc_driver { int (*find_raw_port_number)(struct usb_hcd *, int); /* Call for power on/off the port if necessary */ int (*port_power)(struct usb_hcd *hcd, int portnum, bool enable); + + int (*sec_event_ring_setup)(struct usb_hcd *hcd, unsigned int intr_num); + int (*sec_event_ring_cleanup)(struct usb_hcd *hcd, + unsigned int intr_num); int (*get_core_id)(struct usb_hcd *hcd); }; @@ -446,6 +450,10 @@ extern int usb_hcd_alloc_bandwidth(struct usb_device *udev, struct usb_host_interface *old_alt, struct usb_host_interface *new_alt); extern int usb_hcd_get_frame_number(struct usb_device *udev); +extern int usb_hcd_sec_event_ring_setup(struct usb_device *udev, + unsigned int intr_num); +extern int usb_hcd_sec_event_ring_cleanup(struct usb_device *udev, + unsigned int intr_num); extern int usb_hcd_get_controller_id(struct usb_device *udev); struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver,