diff options
Diffstat (limited to 'qemu/roms/SLOF/lib/libusb/usb-xhci.c')
-rw-r--r-- | qemu/roms/SLOF/lib/libusb/usb-xhci.c | 236 |
1 files changed, 205 insertions, 31 deletions
diff --git a/qemu/roms/SLOF/lib/libusb/usb-xhci.c b/qemu/roms/SLOF/lib/libusb/usb-xhci.c index 0c3d6e47f..858cd12f9 100644 --- a/qemu/roms/SLOF/lib/libusb/usb-xhci.c +++ b/qemu/roms/SLOF/lib/libusb/usb-xhci.c @@ -225,11 +225,11 @@ static void xhci_handle_cmd_completion(struct xhci_hcd *xhcd, xhcd->slot_id = 0; } -static struct xhci_event_trb *xhci_poll_event(struct xhci_hcd *xhcd, - uint32_t event_type) +static uint64_t xhci_poll_event(struct xhci_hcd *xhcd, + uint32_t event_type) { struct xhci_event_trb *event; - uint64_t val; + uint64_t val, retval = 0; uint32_t flags, time; int index; @@ -244,7 +244,7 @@ static struct xhci_event_trb *xhci_poll_event(struct xhci_hcd *xhcd, mb(); flags = le32_to_cpu(event->flags); if (time < SLOF_GetTimer()) - return NULL; + return 0; } mb(); @@ -273,6 +273,7 @@ static struct xhci_event_trb *xhci_poll_event(struct xhci_hcd *xhcd, break; } xhcd->ering.deq = (uint64_t) (event + 1); + retval = le64_to_cpu(event->addr); event->addr = 0; event->status = 0; @@ -289,7 +290,11 @@ static struct xhci_event_trb *xhci_poll_event(struct xhci_hcd *xhcd, dprintf("Update start %x deq %x index %d\n", xhcd->ering.trbs_dma, val, index/sizeof(*event)); write_reg64(&xhcd->run_regs->irs[0].erdp, val); - return event; + + if (retval == 0) + return (uint64_t)event; + else + return retval; } static void xhci_send_cmd(struct xhci_hcd *xhcd, uint32_t field1, @@ -388,10 +393,12 @@ static void xhci_init_seg(struct xhci_seg *seg, uint32_t size, uint32_t type) seg->deq = (uint64_t)seg->trbs; memset((void *)seg->trbs, 0, size); - link =(struct xhci_link_trb *) (seg->trbs + seg->size - 1); - link->addr = cpu_to_le64(seg->trbs_dma); - link->field2 = 0; - link->field3 = cpu_to_le32(0x1 | TRB_CMD_TYPE(TRB_LINK)); + if (type != TYPE_EVENT) { + link =(struct xhci_link_trb *) (seg->trbs + seg->size - 1); + link->addr = cpu_to_le64(seg->trbs_dma); + link->field2 = 0; + link->field3 = cpu_to_le32(0x1 | TRB_CMD_TYPE(TRB_LINK)); + } return; } @@ -601,8 +608,10 @@ static bool xhci_alloc_dev(struct xhci_hcd *xhcd, uint32_t slot_id, uint32_t por dev->port = newport; dev->priv = xdev; xdev->dev = dev; - if (setup_new_device(dev, newport)) + if (usb_setup_new_device(dev, newport)) { + usb_slof_populate_new_device(dev); return true; + } xhci_free_ctx(&xdev->out_ctx, XHCI_CTX_BUF_SIZE); fail_control_seg: @@ -616,6 +625,7 @@ static void xhci_free_dev(struct xhci_dev *xdev) { xhci_free_seg(&xdev->bulk_in, XHCI_DATA_TRBS_SIZE); xhci_free_seg(&xdev->bulk_out, XHCI_DATA_TRBS_SIZE); + xhci_free_seg(&xdev->intr, XHCI_INTR_TRBS_SIZE); xhci_free_seg(&xdev->control, XHCI_CONTROL_TRBS_SIZE); xhci_free_ctx(&xdev->in_ctx, XHCI_CTX_BUF_SIZE); xhci_free_ctx(&xdev->out_ctx, XHCI_CTX_BUF_SIZE); @@ -637,7 +647,25 @@ static bool usb3_dev_init(struct xhci_hcd *xhcd, uint32_t port) return true; } -static int xhci_hub_check_ports(struct xhci_hcd *xhcd) +static int xhci_device_present(uint32_t portsc, uint32_t usb_ver) +{ + if (usb_ver == USB_XHCI) { + /* Device present and enabled state */ + if ((portsc & PORTSC_CCS) && + (portsc & PORTSC_PP) && + (portsc & PORTSC_PED)) { + return true; + } + } else if (usb_ver == USB_EHCI) { + /* Device present and in disabled state */ + if ((portsc & PORTSC_CCS) && (portsc & PORTSC_CSC)) + return true; + } + return false; +} + +static int xhci_port_scan(struct xhci_hcd *xhcd, + uint32_t usb_ver) { uint32_t num_ports, portsc, i; struct xhci_op_regs *op; @@ -645,7 +673,7 @@ static int xhci_hub_check_ports(struct xhci_hcd *xhcd) struct xhci_cap_regs *cap; uint32_t xecp_off; uint32_t *xecp_addr, *base; - uint32_t port_off = 1, port_cnt; + uint32_t port_off = 0, port_cnt; dprintf("enter\n"); @@ -658,14 +686,14 @@ static int xhci_hub_check_ports(struct xhci_hcd *xhcd) base = (uint32_t *)cap; while (xecp_off > 0) { xecp_addr = base + xecp_off; - dprintf(stderr, "xecp_off %d %p %p \n", xecp_off, base, xecp_addr); + dprintf("xecp_off %d %p %p \n", xecp_off, base, xecp_addr); if (XHCI_XECP_CAP_ID(read_reg32(xecp_addr)) == XHCI_XECP_CAP_SP && - XHCI_XECP_CAP_SP_MJ(read_reg32(xecp_addr)) == 3 && + XHCI_XECP_CAP_SP_MJ(read_reg32(xecp_addr)) == usb_ver && XHCI_XECP_CAP_SP_MN(read_reg32(xecp_addr)) == 0) { port_cnt = XHCI_XECP_CAP_SP_PC(read_reg32(xecp_addr + 2)); port_off = XHCI_XECP_CAP_SP_PO(read_reg32(xecp_addr + 2)); - dprintf(stderr, "PortCount %d Portoffset %d\n", port_cnt, port_off); + dprintf("PortCount %d Portoffset %d\n", port_cnt, port_off); } base = xecp_addr; xecp_off = XHCI_XECP_NEXT_PTR(read_reg32(xecp_addr)); @@ -675,10 +703,8 @@ static int xhci_hub_check_ports(struct xhci_hcd *xhcd) for (i = (port_off - 1); i < (port_off + port_cnt - 1); i++) { prs = &op->prs[i]; portsc = read_reg32(&prs->portsc); - if ((portsc & PORTSC_CCS) && - (portsc & PORTSC_PP) && - (portsc & PORTSC_PED)) { - /* Device present and enabled */ + if (xhci_device_present(portsc, usb_ver)) { + /* Device present */ dprintf("Device present on port %d\n", i); /* Reset the port */ portsc = read_reg32(&prs->portsc); @@ -701,6 +727,11 @@ static int xhci_hub_check_ports(struct xhci_hcd *xhcd) return true; } +static int xhci_hub_check_ports(struct xhci_hcd *xhcd) +{ + return xhci_port_scan(xhcd, USB_XHCI) | xhci_port_scan(xhcd, USB_EHCI); +} + static bool xhci_hcd_init(struct xhci_hcd *xhcd) { struct xhci_op_regs *op; @@ -868,6 +899,18 @@ static bool xhci_hcd_exit(struct xhci_hcd *xhcd) SLOF_dma_map_out(xhcd->dcbaap_dma, (void *)xhcd->dcbaap, XHCI_DCBAAP_MAX_SIZE); SLOF_dma_free((void *)xhcd->dcbaap, XHCI_DCBAAP_MAX_SIZE); } + + /* + * QEMU implementation of XHCI doesn't implement halt + * properly. It basically says that it's halted immediately + * but doesn't actually terminate ongoing activities and + * DMAs. This needs to be fixed in QEMU. + * + * For now, wait for 50ms grace time till qemu stops using + * this device. + */ + SLOF_msleep(50); + return true; } @@ -1079,18 +1122,17 @@ static inline struct xhci_seg *xhci_pipe_get_seg(struct usb_pipe *pipe) static inline void *xhci_get_trb(struct xhci_seg *seg) { uint64_t val, enq; - uint32_t size; + int index; struct xhci_link_trb *link; enq = val = seg->enq; val = val + XHCI_TRB_SIZE; - size = seg->size * XHCI_TRB_SIZE; - /* TRBs being a cyclic buffer, here we cycle back to beginning. */ - if ((val % size) == 0) { + index = (enq - (uint64_t)seg->trbs) / XHCI_TRB_SIZE + 1; + dprintf("%s: enq %llx, val %llx %x\n", __func__, enq, val, index); + /* TRBs being a cyclic buffer, here we cycle back to beginning. */ + if (index == (seg->size - 1)) { + dprintf("%s: rounding \n", __func__); seg->enq = (uint64_t)seg->trbs; - enq = seg->enq; - seg->enq = seg->enq + XHCI_TRB_SIZE; - val = 0; seg->cycle_state ^= seg->cycle_state; link = (struct xhci_link_trb *) (seg->trbs + seg->size - 1); link->addr = cpu_to_le64(seg->trbs_dma); @@ -1105,6 +1147,12 @@ static inline void *xhci_get_trb(struct xhci_seg *seg) return (void *)enq; } +static uint64_t xhci_get_trb_phys(struct xhci_seg *seg, uint64_t trb) +{ + return seg->trbs_dma + (trb - (uint64_t)seg->trbs); +} + +static int usb_kb = false; static int xhci_transfer_bulk(struct usb_pipe *pipe, void *td, void *td_phys, void *data, int datalen) { @@ -1114,7 +1162,8 @@ static int xhci_transfer_bulk(struct usb_pipe *pipe, void *td, void *td_phys, struct xhci_transfer_trb *trb; struct xhci_db_regs *dbr; int ret = true; - uint32_t slot_id, epno; + uint32_t slot_id, epno, time; + uint64_t trb_phys, event_phys; if (!pipe->dev || !pipe->dev->hcidev) { dprintf(" NULL pointer\n"); @@ -1139,13 +1188,26 @@ static int xhci_transfer_bulk(struct usb_pipe *pipe, void *td, void *td_phys, } trb = xhci_get_trb(seg); + trb_phys = xhci_get_trb_phys(seg, (uint64_t)trb); fill_normal_trb(trb, (void *)data, datalen); epno = xhci_get_epno(pipe); write_reg32(&dbr->db[slot_id], epno); - if (!xhci_poll_event(xhcd, 0)) { - dprintf("Bulk failed\n"); - ret = false; + + time = SLOF_GetTimer() + USB_TIMEOUT; + while (1) { + event_phys = xhci_poll_event(xhcd, 0); + if (event_phys == trb_phys) { + break; + } else if (event_phys == 0) { /* polling timed out */ + ret = false; + break; + } else + usb_kb = true; + + /* transfer timed out */ + if (time < SLOF_GetTimer()) + return false; } trb->addr = 0; trb->len = 0; @@ -1214,7 +1276,8 @@ static void xhci_init_bulk_ep(struct usb_dev *dev, struct usb_pipe *pipe) if (!seg->trbs) { if (!xhci_alloc_seg(seg, XHCI_DATA_TRBS_SIZE, TYPE_BULK)) { - dprintf("Failed allocating seg\n"); + printf("usb-xhci: allocation failed for bulk endpoint\n"); + return; } } else { xhci_init_seg(seg, XHCI_DATA_TRBS_SIZE, TYPE_BULK); @@ -1235,6 +1298,61 @@ static void xhci_init_bulk_ep(struct usb_dev *dev, struct usb_pipe *pipe) xpipe->seg = seg; } +static int xhci_get_pipe_intr(struct usb_pipe *pipe, + struct xhci_hcd *xhcd, + char *buf, size_t len) +{ + struct xhci_dev *xdev; + struct xhci_seg *seg; + struct xhci_pipe *xpipe; + struct xhci_control_ctx *ctrl; + struct xhci_ep_ctx *ep; + uint32_t x_epno, val, type; + struct usb_dev *dev; + struct xhci_transfer_trb *trb; + + dev = pipe->dev; + if (dev->class != DEV_HID_KEYB) + return false; + + xdev = dev->priv; + pipe->mps = 8; + seg = xhci_pipe_get_seg(pipe); + xpipe = xhci_pipe_get_xpipe(pipe); + type = EP_INT_IN; + seg = &xdev->intr; + + if (!seg->trbs) { + if (!xhci_alloc_seg(seg, XHCI_INTR_TRBS_SIZE, TYPE_BULK)) { + printf("usb-xhci: allocation failed for interrupt endpoint\n"); + return false; + } + } else { + xhci_init_seg(seg, XHCI_EVENT_TRBS_SIZE, TYPE_BULK); + } + + xpipe->buf = buf; + xpipe->buf_phys = SLOF_dma_map_in(buf, len, false); + xpipe->buflen = len; + + ctrl = xhci_get_control_ctx(&xdev->in_ctx); + x_epno = xhci_get_epno(pipe); + ep = xhci_get_ep_ctx(&xdev->in_ctx, xdev->ctx_size, x_epno); + val = EP_TYPE(type) | MAX_BURST(0) | ERROR_COUNT(3) | + MAX_PACKET_SIZE(pipe->mps); + ep->field2 = cpu_to_le32(val); + ep->deq_addr = cpu_to_le64(seg->trbs_dma | seg->cycle_state); + ep->field4 = cpu_to_le32(8); + ctrl->a_flags = cpu_to_le32(BIT(x_epno) | 0x1); + ctrl->d_flags = 0; + xhci_configure_ep(xhcd, xdev->slot_id, xdev->in_ctx.dma_addr); + xpipe->seg = seg; + + trb = xhci_get_trb(seg); + fill_normal_trb(trb, (void *)xpipe->buf_phys, pipe->mps); + return true; +} + static struct usb_pipe* xhci_get_pipe(struct usb_dev *dev, struct usb_ep_descr *ep, char *buf, size_t len) { struct xhci_hcd *xhcd; @@ -1264,6 +1382,12 @@ static struct usb_pipe* xhci_get_pipe(struct usb_dev *dev, struct usb_ep_descr * new->dir = (ep->bEndpointAddress & 0x80) >> 7; new->epno = ep->bEndpointAddress & 0x0f; + if (new->type == USB_EP_TYPE_INTR) { + if (!xhci_get_pipe_intr(new, xhcd, buf, len)) { + printf("usb-xhci: %s alloc_intr failed %p\n", + __func__, new); + } + } if (new->type == USB_EP_TYPE_BULK) xhci_init_bulk_ep(dev, new); @@ -1284,6 +1408,10 @@ static void xhci_put_pipe(struct usb_pipe *pipe) if (pipe->type == USB_EP_TYPE_BULK) { xpipe = xhci_pipe_get_xpipe(pipe); xpipe->seg = NULL; + } else if (pipe->type == USB_EP_TYPE_INTR) { + xpipe = xhci_pipe_get_xpipe(pipe); + SLOF_dma_map_out(xpipe->buf_phys, xpipe->buf, xpipe->buflen); + xpipe->seg = NULL; } if (xhcd->end) xhcd->end->next = pipe; @@ -1298,6 +1426,51 @@ static void xhci_put_pipe(struct usb_pipe *pipe) dprintf("usb-xhci: %s exit\n", __func__); } +static int xhci_poll_intr(struct usb_pipe *pipe, uint8_t *data) +{ + struct xhci_transfer_trb *trb; + struct xhci_seg *seg; + struct xhci_pipe *xpipe; + struct xhci_dev *xdev; + struct xhci_hcd *xhcd; + struct xhci_db_regs *dbr; + uint32_t x_epno; + uint8_t *buf, ret = 1; + + if (!pipe || !pipe->dev || !pipe->dev->hcidev) + return 0; + xdev = pipe->dev->priv; + xhcd = (struct xhci_hcd *)pipe->dev->hcidev->priv; + x_epno = xhci_get_epno(pipe); + seg = xhci_pipe_get_seg(pipe); + xpipe = xhci_pipe_get_xpipe(pipe); + + if (usb_kb == true) { + /* This event was consumed by bulk transfer */ + usb_kb = false; + goto skip_poll; + } + buf = xpipe->buf; + memset(buf, 0, 8); + + mb(); + /* Ring the doorbell - x_epno */ + dbr = xhcd->db_regs; + write_reg32(&dbr->db[xdev->slot_id], x_epno); + if (!xhci_poll_event(xhcd, 0)) { + printf("poll intr failed\n"); + return 0; + } + mb(); + memcpy(data, buf, 8); + +skip_poll: + trb = xhci_get_trb(seg); + fill_normal_trb(trb, (void *)xpipe->buf_phys, pipe->mps); + mb(); + return ret; +} + struct usb_hcd_ops xhci_ops = { .name = "xhci-hcd", .init = xhci_init, @@ -1305,6 +1478,7 @@ struct usb_hcd_ops xhci_ops = { .usb_type = USB_XHCI, .get_pipe = xhci_get_pipe, .put_pipe = xhci_put_pipe, + .poll_intr = xhci_poll_intr, .send_ctrl = xhci_send_ctrl, .transfer_bulk = xhci_transfer_bulk, .next = NULL, |