Skip to content

Commit

Permalink
USB: xhci: No-op command queueing and irq handler.
Browse files Browse the repository at this point in the history
xHCI host controllers can optionally implement a no-op test.  This
simple test ensures the OS has correctly setup all basic data structures
and can correctly respond to interrupts from the host controller
hardware.

There are two rings exercised by the no-op test:  the command ring, and
the event ring.

The host controller driver writes a no-op command TRB to the command
ring, and rings the doorbell for the command ring (the first entry in
the doorbell array).  The hardware receives this event, places a command
completion event on the event ring, and fires an interrupt.

The host controller driver sees the interrupt, and checks the event ring
for TRBs it can process, and sees the command completion event.  (See
the rules in xhci-ring.c for who "owns" a TRB.  This is a simplified set
of rules, and may not contain all the details that are in the xHCI 0.95
spec.)

A timer fires every 60 seconds to debug the state of the hardware and
command and event rings.  This timer only runs if
CONFIG_USB_XHCI_HCD_DEBUGGING is 'y'.

Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
Sarah Sharp authored and Greg Kroah-Hartman committed Jun 16, 2009
1 parent a74588f commit 7f84eef
Show file tree
Hide file tree
Showing 6 changed files with 647 additions and 13 deletions.
76 changes: 76 additions & 0 deletions drivers/usb/host/xhci-dbg.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,64 @@ void xhci_print_registers(struct xhci_hcd *xhci)
xhci_print_op_regs(xhci);
}

void xhci_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb)
{
int i;
for (i = 0; i < 4; ++i)
xhci_dbg(xhci, "Offset 0x%x = 0x%x\n",
i*4, trb->generic.field[i]);
}

/**
* Debug a transfer request block (TRB).
*/
void xhci_debug_trb(struct xhci_hcd *xhci, union xhci_trb *trb)
{
u64 address;
u32 type = xhci_readl(xhci, &trb->link.control) & TRB_TYPE_BITMASK;

switch (type) {
case TRB_TYPE(TRB_LINK):
xhci_dbg(xhci, "Link TRB:\n");
xhci_print_trb_offsets(xhci, trb);

address = trb->link.segment_ptr[0] +
(((u64) trb->link.segment_ptr[1]) << 32);
xhci_dbg(xhci, "Next ring segment DMA address = 0x%llx\n", address);

xhci_dbg(xhci, "Interrupter target = 0x%x\n",
GET_INTR_TARGET(trb->link.intr_target));
xhci_dbg(xhci, "Cycle bit = %u\n",
(unsigned int) (trb->link.control & TRB_CYCLE));
xhci_dbg(xhci, "Toggle cycle bit = %u\n",
(unsigned int) (trb->link.control & LINK_TOGGLE));
xhci_dbg(xhci, "No Snoop bit = %u\n",
(unsigned int) (trb->link.control & TRB_NO_SNOOP));
break;
case TRB_TYPE(TRB_TRANSFER):
address = trb->trans_event.buffer[0] +
(((u64) trb->trans_event.buffer[1]) << 32);
/*
* FIXME: look at flags to figure out if it's an address or if
* the data is directly in the buffer field.
*/
xhci_dbg(xhci, "DMA address or buffer contents= %llu\n", address);
break;
case TRB_TYPE(TRB_COMPLETION):
address = trb->event_cmd.cmd_trb[0] +
(((u64) trb->event_cmd.cmd_trb[1]) << 32);
xhci_dbg(xhci, "Command TRB pointer = %llu\n", address);
xhci_dbg(xhci, "Completion status = %u\n",
(unsigned int) GET_COMP_CODE(trb->event_cmd.status));
xhci_dbg(xhci, "Flags = 0x%x\n", (unsigned int) trb->event_cmd.flags);
break;
default:
xhci_dbg(xhci, "Unknown TRB with TRB type ID %u\n",
(unsigned int) type>>10);
xhci_print_trb_offsets(xhci, trb);
break;
}
}

/**
* Debug a segment with an xHCI ring.
Expand Down Expand Up @@ -261,6 +319,20 @@ void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg)
}
}

void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring)
{
xhci_dbg(xhci, "Ring deq = 0x%x (virt), 0x%x (dma)\n",
(unsigned int) ring->dequeue,
trb_virt_to_dma(ring->deq_seg, ring->dequeue));
xhci_dbg(xhci, "Ring deq updated %u times\n",
ring->deq_updates);
xhci_dbg(xhci, "Ring enq = 0x%x (virt), 0x%x (dma)\n",
(unsigned int) ring->enqueue,
trb_virt_to_dma(ring->enq_seg, ring->enqueue));
xhci_dbg(xhci, "Ring enq updated %u times\n",
ring->enq_updates);
}

/**
* Debugging for an xHCI ring, which is a queue broken into multiple segments.
*
Expand All @@ -277,6 +349,10 @@ void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring)
struct xhci_segment *first_seg = ring->first_seg;
xhci_debug_segment(xhci, first_seg);

if (!ring->enq_updates && !ring->deq_updates) {
xhci_dbg(xhci, " Ring has not been updated\n");
return;
}
for (seg = first_seg->next; seg != first_seg; seg = seg->next)
xhci_debug_segment(xhci, seg);
}
Expand Down
152 changes: 151 additions & 1 deletion drivers/usb/host/xhci-hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,120 @@ int xhci_init(struct usb_hcd *hcd)
return retval;
}

/*
* Called in interrupt context when there might be work
* queued on the event ring
*
* xhci->lock must be held by caller.
*/
static void xhci_work(struct xhci_hcd *xhci)
{
u32 temp;

/*
* Clear the op reg interrupt status first,
* so we can receive interrupts from other MSI-X interrupters.
* Write 1 to clear the interrupt status.
*/
temp = xhci_readl(xhci, &xhci->op_regs->status);
temp |= STS_EINT;
xhci_writel(xhci, temp, &xhci->op_regs->status);
/* FIXME when MSI-X is supported and there are multiple vectors */
/* Clear the MSI-X event interrupt status */

/* Acknowledge the interrupt */
temp = xhci_readl(xhci, &xhci->ir_set->irq_pending);
temp |= 0x3;
xhci_writel(xhci, temp, &xhci->ir_set->irq_pending);
/* Flush posted writes */
xhci_readl(xhci, &xhci->ir_set->irq_pending);

/* FIXME this should be a delayed service routine that clears the EHB */
handle_event(xhci);

/* Clear the event handler busy flag; the event ring should be empty. */
temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[0]);
xhci_writel(xhci, temp & ~ERST_EHB, &xhci->ir_set->erst_dequeue[0]);
/* Flush posted writes -- FIXME is this necessary? */
xhci_readl(xhci, &xhci->ir_set->irq_pending);
}

/*-------------------------------------------------------------------------*/

/*
* 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
* indicators of an event TRB error, but we check the status *first* to be safe.
*/
irqreturn_t xhci_irq(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
u32 temp, temp2;

spin_lock(&xhci->lock);
/* Check if the xHC generated the interrupt, or the irq is shared */
temp = xhci_readl(xhci, &xhci->op_regs->status);
temp2 = xhci_readl(xhci, &xhci->ir_set->irq_pending);
if (!(temp & STS_EINT) && !ER_IRQ_PENDING(temp2)) {
spin_unlock(&xhci->lock);
return IRQ_NONE;
}

temp = xhci_readl(xhci, &xhci->op_regs->status);
if (temp & STS_FATAL) {
xhci_warn(xhci, "WARNING: Host System Error\n");
xhci_halt(xhci);
xhci_to_hcd(xhci)->state = HC_STATE_HALT;
return -ESHUTDOWN;
}

xhci_work(xhci);
spin_unlock(&xhci->lock);

return IRQ_HANDLED;
}

#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING
void event_ring_work(unsigned long arg)
{
unsigned long flags;
int temp;
struct xhci_hcd *xhci = (struct xhci_hcd *) arg;
int i, j;

xhci_dbg(xhci, "Poll event ring: %lu\n", jiffies);

spin_lock_irqsave(&xhci->lock, flags);
temp = xhci_readl(xhci, &xhci->op_regs->status);
xhci_dbg(xhci, "op reg status = 0x%x\n", temp);
temp = xhci_readl(xhci, &xhci->ir_set->irq_pending);
xhci_dbg(xhci, "ir_set 0 pending = 0x%x\n", temp);
xhci_dbg(xhci, "No-op commands handled = %d\n", xhci->noops_handled);
xhci_dbg(xhci, "HC error bitmask = 0x%x\n", xhci->error_bitmask);
xhci->error_bitmask = 0;
xhci_dbg(xhci, "Event ring:\n");
xhci_debug_segment(xhci, xhci->event_ring->deq_seg);
xhci_dbg_ring_ptrs(xhci, xhci->event_ring);
temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[0]);
temp &= ERST_PTR_MASK;
xhci_dbg(xhci, "ERST deq = 0x%x\n", temp);
xhci_dbg(xhci, "Command ring:\n");
xhci_debug_segment(xhci, xhci->cmd_ring->deq_seg);
xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring);
xhci_dbg_cmd_ptrs(xhci);

if (xhci->noops_submitted != NUM_TEST_NOOPS)
if (setup_one_noop(xhci))
ring_cmd_db(xhci);
spin_unlock_irqrestore(&xhci->lock, flags);

if (!xhci->zombie)
mod_timer(&xhci->event_ring_timer, jiffies + POLL_TIMEOUT * HZ);
else
xhci_dbg(xhci, "Quit polling the event ring.\n");
}
#endif

/*
* Start the HC after it was halted.
*
Expand All @@ -233,8 +347,9 @@ int xhci_run(struct usb_hcd *hcd)
{
u32 temp;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
xhci_dbg(xhci, "xhci_run\n");
void (*doorbell)(struct xhci_hcd *) = NULL;

xhci_dbg(xhci, "xhci_run\n");
#if 0 /* FIXME: MSI not setup yet */
/* Do this at the very last minute */
ret = xhci_setup_msix(xhci);
Expand All @@ -243,6 +358,17 @@ int xhci_run(struct usb_hcd *hcd)

return -ENOSYS;
#endif
#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING
init_timer(&xhci->event_ring_timer);
xhci->event_ring_timer.data = (unsigned long) xhci;
xhci->event_ring_timer.function = event_ring_work;
/* Poll the event ring */
xhci->event_ring_timer.expires = jiffies + POLL_TIMEOUT * HZ;
xhci->zombie = 0;
xhci_dbg(xhci, "Setting event ring polling timer\n");
add_timer(&xhci->event_ring_timer);
#endif

xhci_dbg(xhci, "// Set the interrupt modulation register\n");
temp = xhci_readl(xhci, &xhci->ir_set->irq_control);
temp &= 0xffff;
Expand All @@ -266,10 +392,24 @@ int xhci_run(struct usb_hcd *hcd)
&xhci->ir_set->irq_pending);
xhci_print_ir_set(xhci, xhci->ir_set, 0);

if (NUM_TEST_NOOPS > 0)
doorbell = setup_one_noop(xhci);

xhci_dbg(xhci, "Command ring memory map follows:\n");
xhci_debug_ring(xhci, xhci->cmd_ring);
xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring);
xhci_dbg_cmd_ptrs(xhci);

xhci_dbg(xhci, "ERST memory map follows:\n");
xhci_dbg_erst(xhci, &xhci->erst);
xhci_dbg(xhci, "Event ring:\n");
xhci_debug_ring(xhci, xhci->event_ring);
xhci_dbg_ring_ptrs(xhci, xhci->event_ring);
temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[1]);
xhci_dbg(xhci, "ERST deq upper = 0x%x\n", temp);
temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[0]);
temp &= ERST_PTR_MASK;
xhci_dbg(xhci, "ERST deq = 0x%x\n", temp);

temp = xhci_readl(xhci, &xhci->op_regs->command);
temp |= (CMD_RUN);
Expand All @@ -280,6 +420,8 @@ int xhci_run(struct usb_hcd *hcd)
temp = xhci_readl(xhci, &xhci->op_regs->command);
xhci_dbg(xhci, "// @%x = 0x%x\n",
(unsigned int) &xhci->op_regs->command, temp);
if (doorbell)
(*doorbell)(xhci);

xhci_dbg(xhci, "Finished xhci_run\n");
return 0;
Expand Down Expand Up @@ -309,6 +451,12 @@ void xhci_stop(struct usb_hcd *hcd)
#if 0 /* No MSI yet */
xhci_cleanup_msix(xhci);
#endif
#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING
/* Tell the event ring poll function not to reschedule */
xhci->zombie = 1;
del_timer_sync(&xhci->event_ring_timer);
#endif

xhci_dbg(xhci, "// Disabling event ring interrupts\n");
temp = xhci_readl(xhci, &xhci->op_regs->status);
xhci_writel(xhci, temp & ~STS_EINT, &xhci->op_regs->status);
Expand Down Expand Up @@ -346,6 +494,8 @@ void xhci_shutdown(struct usb_hcd *hcd)
xhci_readl(xhci, &xhci->op_regs->status));
}

/*-------------------------------------------------------------------------*/

int xhci_get_frame(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
Expand Down
11 changes: 3 additions & 8 deletions drivers/usb/host/xhci-mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci,
}
/* The ring is empty, so the enqueue pointer == dequeue pointer */
ring->enqueue = ring->first_seg->trbs;
ring->enq_seg = ring->first_seg;
ring->dequeue = ring->enqueue;
ring->deq_seg = ring->first_seg;
/* The ring is initialized to 0. The producer must write 1 to the cycle
* bit to handover ownership of the TRB, so PCS = 1. The consumer must
* compare CCS to the cycle bit to check ownership, so CCS = 1.
Expand Down Expand Up @@ -374,14 +376,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
xhci_writel(xhci, val, &xhci->ir_set->erst_base[0]);

/* Set the event ring dequeue address */
xhci_dbg(xhci, "// Set ERST dequeue address for ir_set 0 = 0x%x%x\n",
xhci->erst.entries[0].seg_addr[1], xhci->erst.entries[0].seg_addr[0]);
val = xhci_readl(xhci, &xhci->run_regs->ir_set[0].erst_dequeue[0]);
val &= ERST_PTR_MASK;
val |= (xhci->erst.entries[0].seg_addr[0] & ~ERST_PTR_MASK);
xhci_writel(xhci, val, &xhci->run_regs->ir_set[0].erst_dequeue[0]);
xhci_writel(xhci, xhci->erst.entries[0].seg_addr[1],
&xhci->run_regs->ir_set[0].erst_dequeue[1]);
set_hc_event_deq(xhci);
xhci_dbg(xhci, "Wrote ERST address to ir_set 0.\n");
xhci_print_ir_set(xhci, xhci->ir_set, 0);

Expand Down
1 change: 1 addition & 0 deletions drivers/usb/host/xhci-pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ static const struct hc_driver xhci_pci_hc_driver = {
/*
* generic hardware linkage
*/
.irq = xhci_irq,
.flags = HCD_MEMORY | HCD_USB3,

/*
Expand Down
Loading

0 comments on commit 7f84eef

Please sign in to comment.