diff options
Diffstat (limited to 'hw/usb/hcd-ehci.c')
| -rw-r--r-- | hw/usb/hcd-ehci.c | 246 |
1 files changed, 175 insertions, 71 deletions
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c index 5298204d9d..b043e7c23e 100644 --- a/hw/usb/hcd-ehci.c +++ b/hw/usb/hcd-ehci.c @@ -365,6 +365,7 @@ struct EHCIQueue { uint32_t seen; uint64_t ts; int async; + int revalidate; /* cached data from guest - needs to be flushed * when guest removes an entry (doorbell, handshake sequence) @@ -414,16 +415,18 @@ struct EHCIState { */ QEMUTimer *frame_timer; QEMUBH *async_bh; - int astate; // Current state in asynchronous schedule - int pstate; // Current state in periodic schedule + uint32_t astate; /* Current state in asynchronous schedule */ + uint32_t pstate; /* Current state in periodic schedule */ USBPort ports[NB_PORTS]; USBPort *companion_ports[NB_PORTS]; uint32_t usbsts_pending; + uint32_t usbsts_frindex; EHCIQueueHead aqueues; EHCIQueueHead pqueues; - uint32_t a_fetch_addr; // which address to look at next - uint32_t p_fetch_addr; // which address to look at next + /* which address to look at next */ + uint32_t a_fetch_addr; + uint32_t p_fetch_addr; USBPacket ipacket; QEMUSGList isgl; @@ -556,33 +559,45 @@ static inline void ehci_clear_usbsts(EHCIState *s, int mask) s->usbsts &= ~mask; } -static inline void ehci_set_interrupt(EHCIState *s, int intr) +/* update irq line */ +static inline void ehci_update_irq(EHCIState *s) { int level = 0; - // TODO honour interrupt threshold requests - - ehci_set_usbsts(s, intr); - if ((s->usbsts & USBINTR_MASK) & s->usbintr) { level = 1; } + trace_usb_ehci_irq(level, s->frindex, s->usbsts, s->usbintr); qemu_set_irq(s->irq, level); } -static inline void ehci_record_interrupt(EHCIState *s, int intr) +/* flag interrupt condition */ +static inline void ehci_raise_irq(EHCIState *s, int intr) { s->usbsts_pending |= intr; } -static inline void ehci_commit_interrupt(EHCIState *s) +/* + * Commit pending interrupts (added via ehci_raise_irq), + * at the rate allowed by "Interrupt Threshold Control". + */ +static inline void ehci_commit_irq(EHCIState *s) { + uint32_t itc; + if (!s->usbsts_pending) { return; } - ehci_set_interrupt(s, s->usbsts_pending); + if (s->usbsts_frindex > s->frindex) { + return; + } + + itc = (s->usbcmd >> 16) & 0xff; + s->usbsts |= s->usbsts_pending; s->usbsts_pending = 0; + s->usbsts_frindex = s->frindex + itc; + ehci_update_irq(s); } static void ehci_update_halt(EHCIState *s) @@ -773,7 +788,18 @@ static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr, return NULL; } -static void ehci_queues_rip_unused(EHCIState *ehci, int async, int flush) +static void ehci_queues_tag_unused_async(EHCIState *ehci) +{ + EHCIQueue *q; + + QTAILQ_FOREACH(q, &ehci->aqueues, next) { + if (!q->seen) { + q->revalidate = 1; + } + } +} + +static void ehci_queues_rip_unused(EHCIState *ehci, int async) { EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; uint64_t maxage = FRAME_TIMER_NS * ehci->maxframes * 4; @@ -785,7 +811,7 @@ static void ehci_queues_rip_unused(EHCIState *ehci, int async, int flush) q->ts = ehci->last_run_ns; continue; } - if (!flush && ehci->last_run_ns < q->ts + maxage) { + if (ehci->last_run_ns < q->ts + maxage) { continue; } ehci_free_queue(q); @@ -821,8 +847,9 @@ static void ehci_attach(USBPort *port) { EHCIState *s = port->opaque; uint32_t *portsc = &s->portsc[port->index]; + const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci"; - trace_usb_ehci_port_attach(port->index, port->dev->product_desc); + trace_usb_ehci_port_attach(port->index, owner, port->dev->product_desc); if (*portsc & PORTSC_POWNER) { USBPort *companion = s->companion_ports[port->index]; @@ -834,15 +861,17 @@ static void ehci_attach(USBPort *port) *portsc |= PORTSC_CONNECT; *portsc |= PORTSC_CSC; - ehci_set_interrupt(s, USBSTS_PCD); + ehci_raise_irq(s, USBSTS_PCD); + ehci_commit_irq(s); } static void ehci_detach(USBPort *port) { EHCIState *s = port->opaque; uint32_t *portsc = &s->portsc[port->index]; + const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci"; - trace_usb_ehci_port_detach(port->index); + trace_usb_ehci_port_detach(port->index, owner); if (*portsc & PORTSC_POWNER) { USBPort *companion = s->companion_ports[port->index]; @@ -862,7 +891,8 @@ static void ehci_detach(USBPort *port) *portsc &= ~(PORTSC_CONNECT|PORTSC_PED); *portsc |= PORTSC_CSC; - ehci_set_interrupt(s, USBSTS_PCD); + ehci_raise_irq(s, USBSTS_PCD); + ehci_commit_irq(s); } static void ehci_child_detach(USBPort *port, USBDevice *child) @@ -889,10 +919,11 @@ static void ehci_wakeup(USBPort *port) USBPort *companion = s->companion_ports[port->index]; if (companion->ops->wakeup) { companion->ops->wakeup(companion); - } else { - qemu_bh_schedule(s->async_bh); } + return; } + + qemu_bh_schedule(s->async_bh); } static int ehci_register_companion(USBBus *bus, USBPort *ports[], @@ -980,6 +1011,8 @@ static void ehci_reset(void *opaque) s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH; s->usbsts = USBSTS_HALT; + s->usbsts_pending = 0; + s->usbsts_frindex = 0; s->astate = EST_INACTIVE; s->pstate = EST_INACTIVE; @@ -1171,7 +1204,7 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) val &= USBSTS_RO_MASK; // bits 6 through 31 are RO ehci_clear_usbsts(s, val); // bits 0 through 5 are R/WC val = s->usbsts; - ehci_set_interrupt(s, 0); + ehci_update_irq(s); break; case USBINTR: @@ -1242,6 +1275,23 @@ static inline int put_dwords(EHCIState *ehci, uint32_t addr, return 1; } +/* + * Write the qh back to guest physical memory. This step isn't + * in the EHCI spec but we need to do it since we don't share + * physical memory with our guest VM. + * + * The first three dwords are read-only for the EHCI, so skip them + * when writing back the qh. + */ +static void ehci_flush_qh(EHCIQueue *q) +{ + uint32_t *qh = (uint32_t *) &q->qh; + uint32_t dwords = sizeof(EHCIqh) >> 2; + uint32_t addr = NLPTR_GET(q->qhaddr); + + put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3); +} + // 4.10.2 static int ehci_qh_do_overlay(EHCIQueue *q) @@ -1289,8 +1339,7 @@ static int ehci_qh_do_overlay(EHCIQueue *q) q->qh.bufptr[1] &= ~BUFPTR_CPROGMASK_MASK; q->qh.bufptr[2] &= ~BUFPTR_FRAMETAG_MASK; - put_dwords(q->ehci, NLPTR_GET(q->qhaddr), (uint32_t *) &q->qh, - sizeof(EHCIqh) >> 2); + ehci_flush_qh(q); return 0; } @@ -1386,18 +1435,18 @@ static void ehci_execute_complete(EHCIQueue *q) case USB_RET_NODEV: q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR); set_field(&q->qh.token, 0, QTD_TOKEN_CERR); - ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + ehci_raise_irq(q->ehci, USBSTS_ERRINT); break; case USB_RET_STALL: q->qh.token |= QTD_TOKEN_HALT; - ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + ehci_raise_irq(q->ehci, USBSTS_ERRINT); break; case USB_RET_NAK: set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT); return; /* We're not done yet with this transaction */ case USB_RET_BABBLE: q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE); - ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + ehci_raise_irq(q->ehci, USBSTS_ERRINT); break; default: /* should not be triggerable */ @@ -1408,7 +1457,7 @@ static void ehci_execute_complete(EHCIQueue *q) } else if ((p->usb_status > p->tbytes) && (p->pid == USB_TOKEN_IN)) { p->usb_status = USB_RET_BABBLE; q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE); - ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + ehci_raise_irq(q->ehci, USBSTS_ERRINT); } else { // TODO check 4.12 for splits @@ -1422,14 +1471,14 @@ static void ehci_execute_complete(EHCIQueue *q) set_field(&q->qh.token, p->tbytes, QTD_TOKEN_TBYTES); } ehci_finish_transfer(q, p->usb_status); + usb_packet_unmap(&p->packet, &p->sgl); qemu_sglist_destroy(&p->sgl); - usb_packet_unmap(&p->packet); q->qh.token ^= QTD_TOKEN_DTOGGLE; q->qh.token &= ~QTD_TOKEN_ACTIVE; if (q->qh.token & QTD_TOKEN_IOC) { - ehci_record_interrupt(q->ehci, USBSTS_INT); + ehci_raise_irq(q->ehci, USBSTS_INT); } } @@ -1547,7 +1596,7 @@ static int ehci_process_itd(EHCIState *ehci, usb_packet_map(&ehci->ipacket, &ehci->isgl); ret = usb_handle_packet(dev, &ehci->ipacket); assert(ret != USB_RET_ASYNC); - usb_packet_unmap(&ehci->ipacket); + usb_packet_unmap(&ehci->ipacket, &ehci->isgl); } else { DPRINTF("ISOCH: attempt to addess non-iso endpoint\n"); ret = USB_RET_NAK; @@ -1564,12 +1613,12 @@ static int ehci_process_itd(EHCIState *ehci, /* 3.3.2: XACTERR is only allowed on IN transactions */ if (dir) { itd->transact[i] |= ITD_XACT_XACTERR; - ehci_record_interrupt(ehci, USBSTS_ERRINT); + ehci_raise_irq(ehci, USBSTS_ERRINT); } break; case USB_RET_BABBLE: itd->transact[i] |= ITD_XACT_BABBLE; - ehci_record_interrupt(ehci, USBSTS_ERRINT); + ehci_raise_irq(ehci, USBSTS_ERRINT); break; case USB_RET_NAK: /* no data for us, so do a zero-length transfer */ @@ -1587,7 +1636,7 @@ static int ehci_process_itd(EHCIState *ehci, } } if (itd->transact[i] & ITD_XACT_IOC) { - ehci_record_interrupt(ehci, USBSTS_INT); + ehci_raise_irq(ehci, USBSTS_INT); } itd->transact[i] &= ~ITD_XACT_ACTIVE; } @@ -1596,23 +1645,6 @@ static int ehci_process_itd(EHCIState *ehci, } -/* - * Write the qh back to guest physical memory. This step isn't - * in the EHCI spec but we need to do it since we don't share - * physical memory with our guest VM. - * - * The first three dwords are read-only for the EHCI, so skip them - * when writing back the qh. - */ -static void ehci_flush_qh(EHCIQueue *q) -{ - uint32_t *qh = (uint32_t *) &q->qh; - uint32_t dwords = sizeof(EHCIqh) >> 2; - uint32_t addr = NLPTR_GET(q->qhaddr); - - put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3); -} - /* This state is the entry point for asynchronous schedule * processing. Entry here consitutes a EHCI start event state (4.8.5) */ @@ -1628,7 +1660,7 @@ static int ehci_state_waitlisthead(EHCIState *ehci, int async) ehci_set_usbsts(ehci, USBSTS_REC); } - ehci_queues_rip_unused(ehci, async, 0); + ehci_queues_rip_unused(ehci, async); /* Find the head of the list (4.9.1.1) */ for(i = 0; i < MAX_QH; i++) { @@ -1713,6 +1745,7 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) EHCIPacket *p; uint32_t entry, devaddr; EHCIQueue *q; + EHCIqh qh; entry = ehci_get_fetch_addr(ehci, async); q = ehci_find_queue_by_qh(ehci, entry, async); @@ -1730,7 +1763,17 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) } get_dwords(ehci, NLPTR_GET(q->qhaddr), - (uint32_t *) &q->qh, sizeof(EHCIqh) >> 2); + (uint32_t *) &qh, sizeof(EHCIqh) >> 2); + if (q->revalidate && (q->qh.epchar != qh.epchar || + q->qh.epcap != qh.epcap || + q->qh.current_qtd != qh.current_qtd)) { + ehci_free_queue(q); + q = ehci_alloc_queue(ehci, entry, async); + q->seen++; + p = NULL; + } + q->qh = qh; + q->revalidate = 0; ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &q->qh); devaddr = get_field(q->qh.epchar, QH_EPCHAR_DEVADDR); @@ -2067,6 +2110,7 @@ out: static int ehci_state_writeback(EHCIQueue *q) { EHCIPacket *p = QTAILQ_FIRST(&q->packets); + uint32_t *qtd, addr; int again = 0; /* Write back the QTD from the QH area */ @@ -2074,8 +2118,9 @@ static int ehci_state_writeback(EHCIQueue *q) assert(p->qtdaddr == q->qtdaddr); ehci_trace_qtd(q, NLPTR_GET(p->qtdaddr), (EHCIqtd *) &q->qh.next_qtd); - put_dwords(q->ehci, NLPTR_GET(p->qtdaddr), (uint32_t *) &q->qh.next_qtd, - sizeof(EHCIqtd) >> 2); + qtd = (uint32_t *) &q->qh.next_qtd; + addr = NLPTR_GET(p->qtdaddr); + put_dwords(q->ehci, addr + 2 * sizeof(uint32_t), qtd + 2, 2); ehci_free_packet(p); /* @@ -2179,8 +2224,6 @@ static void ehci_advance_state(EHCIState *ehci, int async) } } while (again); - - ehci_commit_interrupt(ehci); } static void ehci_advance_async_state(EHCIState *ehci) @@ -2223,10 +2266,10 @@ static void ehci_advance_async_state(EHCIState *ehci) */ if (ehci->usbcmd & USBCMD_IAAD) { /* Remove all unseen qhs from the async qhs queue */ - ehci_queues_rip_unused(ehci, async, 1); + ehci_queues_tag_unused_async(ehci); DPRINTF("ASYNC: doorbell request acknowledged\n"); ehci->usbcmd &= ~USBCMD_IAAD; - ehci_set_interrupt(ehci, USBSTS_IAA); + ehci_raise_irq(ehci, USBSTS_IAA); } break; @@ -2276,7 +2319,7 @@ static void ehci_advance_periodic_state(EHCIState *ehci) ehci_set_fetch_addr(ehci, async,entry); ehci_set_state(ehci, async, EST_FETCHENTRY); ehci_advance_state(ehci, async); - ehci_queues_rip_unused(ehci, async, 0); + ehci_queues_rip_unused(ehci, async); break; default: @@ -2299,12 +2342,17 @@ static void ehci_update_frindex(EHCIState *ehci, int frames) ehci->frindex += 8; if (ehci->frindex == 0x00002000) { - ehci_set_interrupt(ehci, USBSTS_FLR); + ehci_raise_irq(ehci, USBSTS_FLR); } if (ehci->frindex == 0x00004000) { - ehci_set_interrupt(ehci, USBSTS_FLR); + ehci_raise_irq(ehci, USBSTS_FLR); ehci->frindex = 0; + if (ehci->usbsts_frindex > 0x00004000) { + ehci->usbsts_frindex -= 0x00004000; + } else { + ehci->usbsts_frindex = 0; + } } } } @@ -2312,7 +2360,7 @@ static void ehci_update_frindex(EHCIState *ehci, int frames) static void ehci_frame_timer(void *opaque) { EHCIState *ehci = opaque; - int schedules = 0; + int need_timer = 0; int64_t expire_time, t_now; uint64_t ns_elapsed; int frames, skipped_frames; @@ -2323,8 +2371,8 @@ static void ehci_frame_timer(void *opaque) frames = ns_elapsed / FRAME_TIMER_NS; if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) { - schedules++; - expire_time = t_now + (get_ticks_per_sec() / FRAME_TIMER_FREQ); + need_timer++; + ehci->async_stepdown = 0; if (frames > ehci->maxframes) { skipped_frames = frames - ehci->maxframes; @@ -2343,8 +2391,6 @@ static void ehci_frame_timer(void *opaque) if (ehci->async_stepdown < ehci->maxframes / 2) { ehci->async_stepdown++; } - expire_time = t_now + (get_ticks_per_sec() - * ehci->async_stepdown / FRAME_TIMER_FREQ); ehci_update_frindex(ehci, frames); ehci->last_run_ns += FRAME_TIMER_NS * frames; } @@ -2353,11 +2399,19 @@ static void ehci_frame_timer(void *opaque) * called */ if (ehci_async_enabled(ehci) || ehci->astate != EST_INACTIVE) { - schedules++; - qemu_bh_schedule(ehci->async_bh); + need_timer++; + ehci_advance_async_state(ehci); } - if (schedules) { + ehci_commit_irq(ehci); + if (ehci->usbsts_pending) { + need_timer++; + ehci->async_stepdown = 0; + } + + if (need_timer) { + expire_time = t_now + (get_ticks_per_sec() + * (ehci->async_stepdown+1) / FRAME_TIMER_FREQ); qemu_mod_timer(ehci->frame_timer, expire_time); } } @@ -2390,9 +2444,58 @@ static USBBusOps ehci_bus_ops = { .register_companion = ehci_register_companion, }; +static int usb_ehci_post_load(void *opaque, int version_id) +{ + EHCIState *s = opaque; + int i; + + for (i = 0; i < NB_PORTS; i++) { + USBPort *companion = s->companion_ports[i]; + if (companion == NULL) { + continue; + } + if (s->portsc[i] & PORTSC_POWNER) { + companion->dev = s->ports[i].dev; + } else { + companion->dev = NULL; + } + } + + return 0; +} + static const VMStateDescription vmstate_ehci = { - .name = "ehci", - .unmigratable = 1, + .name = "ehci", + .version_id = 1, + .post_load = usb_ehci_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, EHCIState), + /* mmio registers */ + VMSTATE_UINT32(usbcmd, EHCIState), + VMSTATE_UINT32(usbsts, EHCIState), + VMSTATE_UINT32(usbintr, EHCIState), + VMSTATE_UINT32(frindex, EHCIState), + VMSTATE_UINT32(ctrldssegment, EHCIState), + VMSTATE_UINT32(periodiclistbase, EHCIState), + VMSTATE_UINT32(asynclistaddr, EHCIState), + VMSTATE_UINT32(configflag, EHCIState), + VMSTATE_UINT32(portsc[0], EHCIState), + VMSTATE_UINT32(portsc[1], EHCIState), + VMSTATE_UINT32(portsc[2], EHCIState), + VMSTATE_UINT32(portsc[3], EHCIState), + VMSTATE_UINT32(portsc[4], EHCIState), + VMSTATE_UINT32(portsc[5], EHCIState), + /* frame timer */ + VMSTATE_TIMER(frame_timer, EHCIState), + VMSTATE_UINT64(last_run_ns, EHCIState), + VMSTATE_UINT32(async_stepdown, EHCIState), + /* schedule state */ + VMSTATE_UINT32(astate, EHCIState), + VMSTATE_UINT32(pstate, EHCIState), + VMSTATE_UINT32(a_fetch_addr, EHCIState), + VMSTATE_UINT32(p_fetch_addr, EHCIState), + VMSTATE_END_OF_LIST() + } }; static Property ehci_properties[] = { @@ -2504,6 +2607,7 @@ static int usb_ehci_initfn(PCIDevice *dev) s->async_bh = qemu_bh_new(ehci_async_bh, s); QTAILQ_INIT(&s->aqueues); QTAILQ_INIT(&s->pqueues); + usb_packet_init(&s->ipacket); qemu_register_reset(ehci_reset, s); |