aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/usb.h3
-rw-r--r--hw/usb/core.c17
-rw-r--r--hw/usb/hcd-ehci.c84
-rw-r--r--hw/usb/host-linux.c15
-rw-r--r--hw/usb/redirect.c2
-rw-r--r--trace-events6
6 files changed, 86 insertions, 41 deletions
diff --git a/hw/usb.h b/hw/usb.h
index a5623d393f..7ed8fb8fcf 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -145,6 +145,8 @@
#define USB_ENDPOINT_XFER_INT 3
#define USB_ENDPOINT_XFER_INVALID 255
+#define USB_INTERFACE_INVALID 255
+
typedef struct USBBus USBBus;
typedef struct USBBusOps USBBusOps;
typedef struct USBPort USBPort;
@@ -363,6 +365,7 @@ void usb_packet_complete(USBDevice *dev, USBPacket *p);
void usb_cancel_packet(USBPacket * p);
void usb_ep_init(USBDevice *dev);
+void usb_ep_reset(USBDevice *dev);
void usb_ep_dump(USBDevice *dev);
struct USBEndpoint *usb_ep_get(USBDevice *dev, int pid, int ep);
uint8_t usb_ep_get_type(USBDevice *dev, int pid, int ep);
diff --git a/hw/usb/core.c b/hw/usb/core.c
index 0e02da7601..0614f76f4f 100644
--- a/hw/usb/core.c
+++ b/hw/usb/core.c
@@ -550,7 +550,7 @@ void usb_packet_cleanup(USBPacket *p)
qemu_iovec_destroy(&p->iov);
}
-void usb_ep_init(USBDevice *dev)
+void usb_ep_reset(USBDevice *dev)
{
int ep;
@@ -559,7 +559,6 @@ void usb_ep_init(USBDevice *dev)
dev->ep_ctl.ifnum = 0;
dev->ep_ctl.dev = dev;
dev->ep_ctl.pipeline = false;
- QTAILQ_INIT(&dev->ep_ctl.queue);
for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) {
dev->ep_in[ep].nr = ep + 1;
dev->ep_out[ep].nr = ep + 1;
@@ -567,12 +566,22 @@ void usb_ep_init(USBDevice *dev)
dev->ep_out[ep].pid = USB_TOKEN_OUT;
dev->ep_in[ep].type = USB_ENDPOINT_XFER_INVALID;
dev->ep_out[ep].type = USB_ENDPOINT_XFER_INVALID;
- dev->ep_in[ep].ifnum = 0;
- dev->ep_out[ep].ifnum = 0;
+ dev->ep_in[ep].ifnum = USB_INTERFACE_INVALID;
+ dev->ep_out[ep].ifnum = USB_INTERFACE_INVALID;
dev->ep_in[ep].dev = dev;
dev->ep_out[ep].dev = dev;
dev->ep_in[ep].pipeline = false;
dev->ep_out[ep].pipeline = false;
+ }
+}
+
+void usb_ep_init(USBDevice *dev)
+{
+ int ep;
+
+ usb_ep_reset(dev);
+ QTAILQ_INIT(&dev->ep_ctl.queue);
+ for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) {
QTAILQ_INIT(&dev->ep_in[ep].queue);
QTAILQ_INIT(&dev->ep_out[ep].queue);
}
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 1582c2c69e..080f62c00d 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)
@@ -775,7 +776,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;
@@ -787,7 +799,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);
@@ -893,10 +905,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[],
@@ -1246,6 +1259,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)
@@ -1293,8 +1323,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;
}
@@ -1600,23 +1629,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)
*/
@@ -1632,7 +1644,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++) {
@@ -1717,6 +1729,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);
@@ -1734,7 +1747,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);
@@ -2071,6 +2094,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 */
@@ -2078,8 +2102,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);
/*
@@ -2227,7 +2252,7 @@ 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);
@@ -2280,7 +2305,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:
@@ -2557,6 +2582,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);
diff --git a/hw/usb/host-linux.c b/hw/usb/host-linux.c
index 5479fb5987..d55be878ad 100644
--- a/hw/usb/host-linux.c
+++ b/hw/usb/host-linux.c
@@ -213,7 +213,7 @@ static int is_iso_started(USBHostDevice *s, int pid, int ep)
static void clear_iso_started(USBHostDevice *s, int pid, int ep)
{
- trace_usb_host_ep_stop_iso(s->bus_num, s->addr, ep);
+ trace_usb_host_iso_stop(s->bus_num, s->addr, ep);
get_endp(s, pid, ep)->iso_started = 0;
}
@@ -221,7 +221,7 @@ static void set_iso_started(USBHostDevice *s, int pid, int ep)
{
struct endp_data *e = get_endp(s, pid, ep);
- trace_usb_host_ep_start_iso(s->bus_num, s->addr, ep);
+ trace_usb_host_iso_start(s->bus_num, s->addr, ep);
if (!e->iso_started) {
e->iso_started = 1;
e->inflight = 0;
@@ -319,7 +319,8 @@ static void async_complete(void *opaque)
if (r < 0) {
if (errno == EAGAIN) {
if (urbs > 2) {
- fprintf(stderr, "husb: %d iso urbs finished at once\n", urbs);
+ /* indicates possible latency issues */
+ trace_usb_host_iso_many_urbs(s->bus_num, s->addr, urbs);
}
return;
}
@@ -352,7 +353,8 @@ static void async_complete(void *opaque)
urbs++;
inflight = change_iso_inflight(s, pid, ep, -1);
if (inflight == 0 && is_iso_started(s, pid, ep)) {
- fprintf(stderr, "husb: out of buffers for iso stream\n");
+ /* can be latency issues, or simply end of stream */
+ trace_usb_host_iso_out_of_bufs(s->bus_num, s->addr, ep);
}
continue;
}
@@ -1136,7 +1138,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s)
USBDescriptor *d;
bool active = false;
- usb_ep_init(&s->dev);
+ usb_ep_reset(&s->dev);
for (i = 0;; i += d->bLength) {
if (i+2 >= s->descr_len) {
@@ -1239,7 +1241,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s)
return 0;
error:
- usb_ep_init(&s->dev);
+ usb_ep_reset(&s->dev);
return 1;
}
@@ -1326,6 +1328,7 @@ static int usb_host_open(USBHostDevice *dev, int bus_num,
goto fail;
}
+ usb_ep_init(&dev->dev);
ret = usb_linux_update_endp_table(dev);
if (ret) {
goto fail;
diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c
index d949f040d5..10b4fbb3a7 100644
--- a/hw/usb/redirect.c
+++ b/hw/usb/redirect.c
@@ -1033,6 +1033,8 @@ static int usbredir_handle_status(USBRedirDevice *dev,
case usb_redir_inval:
WARNING("got invalid param error from usb-host?\n");
return USB_RET_NAK;
+ case usb_redir_babble:
+ return USB_RET_BABBLE;
case usb_redir_ioerror:
case usb_redir_timeout:
default:
diff --git a/trace-events b/trace-events
index bd0ca7e728..bf4fe46dff 100644
--- a/trace-events
+++ b/trace-events
@@ -368,8 +368,10 @@ usb_host_urb_complete(int bus, int addr, void *aurb, int status, int length, int
usb_host_urb_canceled(int bus, int addr, void *aurb) "dev %d:%d, aurb %p"
usb_host_ep_set_halt(int bus, int addr, int ep) "dev %d:%d, ep %d"
usb_host_ep_clear_halt(int bus, int addr, int ep) "dev %d:%d, ep %d"
-usb_host_ep_start_iso(int bus, int addr, int ep) "dev %d:%d, ep %d"
-usb_host_ep_stop_iso(int bus, int addr, int ep) "dev %d:%d, ep %d"
+usb_host_iso_start(int bus, int addr, int ep) "dev %d:%d, ep %d"
+usb_host_iso_stop(int bus, int addr, int ep) "dev %d:%d, ep %d"
+usb_host_iso_out_of_bufs(int bus, int addr, int ep) "dev %d:%d, ep %d"
+usb_host_iso_many_urbs(int bus, int addr, int count) "dev %d:%d, count %d"
usb_host_reset(int bus, int addr) "dev %d:%d"
usb_host_auto_scan_enabled(void)
usb_host_auto_scan_disabled(void)