/* handle standard types: * - command completion event * - port status change event * - transfer event * - host controller event */ static void xhci_handle_event(xhci_t *const xhci) { const trb_t *const ev = xhci->er.cur; const int trb_type = TRB_GET(TT, ev); switch (trb_type) { /* Either pass along the event or advance event ring */ case TRB_EV_TRANSFER: xhci_handle_transfer_event(xhci); break; case TRB_EV_CMD_CMPL: xhci_handle_command_completion_event(xhci); break; case TRB_EV_PORTSC: xhci_debug("Port Status Change Event for %d: %d\n", TRB_GET(PORT, ev), TRB_GET(CC, ev)); /* We ignore the event as we look for the PORTSC registers instead, at a time when it suits _us_. */ xhci_advance_event_ring(xhci); break; case TRB_EV_HOST: xhci_handle_host_controller_event(xhci); break; default: xhci_debug("Warning: Spurious event: %d, Completion Code: %d\n", trb_type, TRB_GET(CC, ev)); xhci_advance_event_ring(xhci); break; } }
static trb_t * xhci_next_trb(trb_t *cur, int *const pcs) { ++cur; while (TRB_GET(TT, cur) == TRB_LINK) { if (pcs && TRB_GET(TC, cur)) *pcs ^= 1; cur = phys_to_virt(cur->ptr_low); } return cur; }
static void xhci_handle_command_completion_event(xhci_t *const xhci) { const trb_t *const ev = xhci->er.cur; xhci_debug("Warning: Spurious command completion event:\n" " Pointer: 0x%08x%08x\n" " CC: %d\n" " Slot ID: %d\n" " Cycle: %d\n", ev->ptr_high, ev->ptr_low, TRB_GET(CC, ev), TRB_GET(ID, ev), ev->control & TRB_CYCLE); xhci_advance_event_ring(xhci); }
/* * returns cc of command in question (pointed to by `address`) * caller should abort command if cc is TIMEOUT */ int xhci_wait_for_command_done(xhci_t *const xhci, const trb_t *const address, const int clear_event) { /* * The Address Device Command should take most time, as it has to * communicate with the USB device. Set address processing shouldn't * take longer than 50ms (at the slave). Let's take a timeout of * 100ms. */ unsigned long timeout_us = 100 * 1000; /* 100ms */ int cc = TIMEOUT; while (xhci_wait_for_event_type(xhci, TRB_EV_CMD_CMPL, &timeout_us)) { if ((xhci->er.cur->ptr_low == virt_to_phys(address)) && (xhci->er.cur->ptr_high == 0)) { cc = TRB_GET(CC, xhci->er.cur); break; } xhci_handle_command_completion_event(xhci); } if (!timeout_us) { xhci_debug("Warning: Timed out waiting for TRB_EV_CMD_CMPL.\n"); } else if (clear_event) { xhci_advance_event_ring(xhci); } xhci_update_event_dq(xhci); return cc; }
static void xhci_handle_host_controller_event(xhci_t *const xhci) { const trb_t *const ev = xhci->er.cur; const int cc = TRB_GET(CC, ev); switch (cc) { case CC_EVENT_RING_FULL_ERROR: xhci_debug("Event ring full! (@%p)\n", xhci->er.cur); /* * If we get here, we have processed the whole queue: * xHC pushes this event, when it sees the ring full, * full of other events. * IMO it's save and necessary to update the dequeue * pointer here. */ xhci_advance_event_ring(xhci); xhci_update_event_dq(xhci); break; default: xhci_debug("Warning: Spurious host controller event: %d\n", cc); xhci_advance_event_ring(xhci); break; } }
static const trb_t * xhci_next_trb(const trb_t *const cur) { if (TRB_GET(TT, cur) == TRB_LINK) return (!cur->ptr_low) ? NULL : phys_to_virt(cur->ptr_low); else return cur + 1; }
static void xhci_enqueue_trb(transfer_ring_t *const tr) { const int chain = TRB_GET(CH, tr->cur); TRB_SET(C, tr->cur, tr->pcs); ++tr->cur; while (TRB_GET(TT, tr->cur) == TRB_LINK) { xhci_spew("Handling LINK pointer\n"); const int tc = TRB_GET(TC, tr->cur); TRB_SET(CH, tr->cur, chain); TRB_SET(C, tr->cur, tr->pcs); tr->cur = phys_to_virt(tr->cur->ptr_low); if (tc) tr->pcs ^= 1; } }
/* returns cc of command in question (pointed to by `address`) */ int xhci_wait_for_command_aborted(xhci_t *const xhci, const trb_t *const address) { /* * Specification says that something might be seriously wrong, if * we don't get a response after 5s. Still, let the caller decide, * what to do then. */ unsigned long timeout_us = 5 * 1000 * 1000; /* 5s */ int cc = TIMEOUT; /* * Expects two command completion events: * The first with CC == COMMAND_ABORTED should point to address, * the second with CC == COMMAND_RING_STOPPED should point to new dq. */ while (xhci_wait_for_event_type(xhci, TRB_EV_CMD_CMPL, &timeout_us)) { if ((xhci->er.cur->ptr_low == virt_to_phys(address)) && (xhci->er.cur->ptr_high == 0)) { cc = TRB_GET(CC, xhci->er.cur); xhci_advance_event_ring(xhci); break; } xhci_handle_command_completion_event(xhci); } if (!timeout_us) xhci_debug("Warning: Timed out waiting for COMMAND_ABORTED.\n"); while (xhci_wait_for_event_type(xhci, TRB_EV_CMD_CMPL, &timeout_us)) { if (TRB_GET(CC, xhci->er.cur) == CC_COMMAND_RING_STOPPED) { xhci->cr.cur = phys_to_virt(xhci->er.cur->ptr_low); xhci_advance_event_ring(xhci); break; } xhci_handle_command_completion_event(xhci); } if (!timeout_us) xhci_debug("Warning: Timed out " "waiting for COMMAND_RING_STOPPED.\n"); xhci_update_event_dq(xhci); return cc; }
/* read one intr-packet from queue, if available. extend the queue for new input. return NULL if nothing new available. Recommended use: while (data=poll_intr_queue(q)) process(data); */ static u8 * xhci_poll_intr_queue(void *const q) { if (!q) return NULL; intrq_t *const intrq = (intrq_t *)q; endpoint_t *const ep = intrq->ep; xhci_t *const xhci = XHCI_INST(ep->dev->controller); /* TODO: Reset interrupt queue if it gets halted? */ xhci_handle_events(xhci); u8 *reqdata = NULL; while (!reqdata && intrq->ready) { const int ep_id = xhci_ep_id(ep); transfer_ring_t *const tr = xhci->dev[ep->dev->address].transfer_rings[ep_id]; /* Fetch the request's buffer */ reqdata = phys_to_virt(intrq->next->ptr_low); /* Enqueue the last (spare) TRB and ring doorbell */ xhci_enqueue_trb(tr); xhci->dbreg[ep->dev->address] = ep_id; /* Reuse the current buffer for the next spare TRB */ xhci_clear_trb(tr->cur, tr->pcs); tr->cur->ptr_low = virt_to_phys(reqdata); tr->cur->ptr_high = 0; TRB_SET(TL, tr->cur, intrq->size); TRB_SET(TT, tr->cur, TRB_NORMAL); TRB_SET(ISP, tr->cur, 1); TRB_SET(IOC, tr->cur, 1); /* Check if anything was transferred */ const size_t read = TRB_GET(TL, intrq->next); if (!read) reqdata = NULL; else if (read < intrq->size) /* At least zero it, poll interface is rather limited */ memset(reqdata + read, 0x00, intrq->size - read); /* Advance the interrupt queue */ if (intrq->ready == intrq->next) /* This was last TRB being ready */ intrq->ready = NULL; intrq->next = xhci_next_trb(intrq->next, NULL); } return reqdata; }
static void xhci_handle_transfer_event(xhci_t *const xhci) { const trb_t *const ev = xhci->er.cur; const int cc = TRB_GET(CC, ev); const int id = TRB_GET(ID, ev); const int ep = TRB_GET(EP, ev); devinfo_t *di; intrq_t *intrq; if (id && id <= xhci->max_slots_en && (di = DEVINFO_FROM_XHCI(xhci, id)) && (intrq = di->interrupt_queues[ep])) { /* It's a running interrupt endpoint */ intrq->ready = phys_to_virt(ev->ptr_low); if (cc == CC_SUCCESS || cc == CC_SHORT_PACKET) { TRB_SET(TL, intrq->ready, intrq->size - TRB_GET(EVTL, ev)); } else { xhci_debug("Interrupt Transfer failed: %d\n", cc); TRB_SET(TL, intrq->ready, 0); } } else if (cc == CC_STOPPED || cc == CC_STOPPED_LENGTH_INVALID) { /* Ignore 'Forced Stop Events' */ } else { xhci_debug("Warning: " "Spurious transfer event for ID %d, EP %d:\n" " Pointer: 0x%08x%08x\n" " TL: 0x%06x\n" " CC: %d\n", id, ep, ev->ptr_high, ev->ptr_low, TRB_GET(EVTL, ev), cc); } xhci_advance_event_ring(xhci); }
/* returns cc of transfer for given slot/endpoint pair */ int xhci_wait_for_transfer(xhci_t *const xhci, const int slot_id, const int ep_id) { xhci_spew("Waiting for transfer on ID %d EP %d\n", slot_id, ep_id); /* 2s for all types of transfers */ /* TODO: test, wait longer? */ unsigned long timeout_us = 2 * 1000 * 1000; int cc = TIMEOUT; while (xhci_wait_for_event_type(xhci, TRB_EV_TRANSFER, &timeout_us)) { if (TRB_GET(ID, xhci->er.cur) == slot_id && TRB_GET(EP, xhci->er.cur) == ep_id) { cc = TRB_GET(CC, xhci->er.cur); xhci_advance_event_ring(xhci); break; } xhci_handle_transfer_event(xhci); } if (!timeout_us) xhci_debug("Warning: Timed out waiting for TRB_EV_TRANSFER.\n"); xhci_update_event_dq(xhci); return cc; }
static unsigned long xhci_wait_for_event_type(xhci_t *const xhci, const int trb_type, unsigned long *const timeout_us) { while (xhci_wait_for_event(&xhci->er, timeout_us)) { if (TRB_GET(TT, xhci->er.cur) == trb_type) break; xhci_handle_event(xhci); } return *timeout_us; }
void xhci_post_command(xhci_t *const xhci) { xhci_debug("Command %d (@%p)\n", TRB_GET(TT, xhci->cr.cur), xhci->cr.cur); TRB_SET(C, xhci->cr.cur, xhci->cr.pcs); ++xhci->cr.cur; /* pass command trb to hardware */ wmb(); /* Ring the doorbell */ xhci->dbreg[0] = 0; while (TRB_GET(TT, xhci->cr.cur) == TRB_LINK) { xhci_debug("Handling LINK pointer (@%p)\n", xhci->cr.cur); const int tc = TRB_GET(TC, xhci->cr.cur); TRB_SET(C, xhci->cr.cur, xhci->cr.pcs); xhci->cr.cur = phys_to_virt(xhci->cr.cur->ptr_low); if (tc) xhci->cr.pcs ^= 1; } }
int xhci_cmd_enable_slot(xhci_t *const xhci, int *const slot_id) { trb_t *const cmd = xhci_next_command_trb(xhci); TRB_SET(TT, cmd, TRB_CMD_ENABLE_SLOT); xhci_post_command(xhci); int cc = xhci_wait_for_command(xhci, cmd, 0); if (cc >= 0) { if (cc == CC_SUCCESS) { *slot_id = TRB_GET(ID, xhci->er.cur); if (*slot_id > xhci->max_slots_en) cc = CONTROLLER_ERROR; } xhci_advance_event_ring(xhci); xhci_handle_events(xhci); } return cc; }
/* create and hook-up an intr queue into device schedule */ static void * xhci_create_intr_queue(endpoint_t *const ep, const int reqsize, const int reqcount, const int reqtiming) { /* reqtiming: We ignore it and use the interval from the endpoint descriptor configured earlier. */ xhci_t *const xhci = XHCI_INST(ep->dev->controller); const int slot_id = ep->dev->address; const int ep_id = xhci_ep_id(ep); transfer_ring_t *const tr = xhci->dev[slot_id].transfer_rings[ep_id]; if (reqcount > (TRANSFER_RING_SIZE - 2)) { xhci_debug("reqcount is too high, at most %d supported\n", TRANSFER_RING_SIZE - 2); return NULL; } if (reqsize > 0x10000) { xhci_debug("reqsize is too large, at most 64KiB supported\n"); return NULL; } if (xhci->dev[slot_id].interrupt_queues[ep_id]) { xhci_debug("Only one interrupt queue per endpoint supported\n"); return NULL; } /* Allocate intrq structure and reqdata chunks */ intrq_t *const intrq = malloc(sizeof(*intrq)); if (!intrq) { xhci_debug("Out of memory\n"); return NULL; } int i; int pcs = tr->pcs; trb_t *cur = tr->cur; for (i = 0; i < reqcount; ++i) { if (TRB_GET(C, cur) == pcs) { xhci_debug("Not enough empty TRBs\n"); goto _free_return; } void *const reqdata = xhci_align(1, reqsize); if (!reqdata) { xhci_debug("Out of memory\n"); goto _free_return; } xhci_clear_trb(cur, pcs); cur->ptr_low = virt_to_phys(reqdata); cur->ptr_high = 0; TRB_SET(TL, cur, reqsize); TRB_SET(TT, cur, TRB_NORMAL); TRB_SET(ISP, cur, 1); TRB_SET(IOC, cur, 1); cur = xhci_next_trb(cur, &pcs); } intrq->size = reqsize; intrq->count = reqcount; intrq->next = tr->cur; intrq->ready = NULL; intrq->ep = ep; xhci->dev[slot_id].interrupt_queues[ep_id] = intrq; /* Now enqueue all the prepared TRBs but the last and ring the doorbell. */ for (i = 0; i < (reqcount - 1); ++i) xhci_enqueue_trb(tr); xhci->dbreg[slot_id] = ep_id; return intrq; _free_return: cur = tr->cur; for (--i; i >= 0; --i) { free(phys_to_virt(cur->ptr_low)); cur = xhci_next_trb(cur, NULL); } free(intrq); return NULL; }