From 3200d1085df5f368885428e33d9439f55c7f1a47 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 26 Jan 2012 13:57:40 +0100 Subject: usb-uhci: implement bandwidth management The OS is allowed to make the UHCI Controller run in circles. That is usually done to serve multiple connected USB devices in a robin-round fashion, so the available USB bandwidth is evenly distributed between devices. The uhci emulation handles this in a very poor way though. When it figures it runs in circles it stops processing unconditionally, so it usually processes at most a single transfer desriptor per queue, even if there are multiple transfer descriptors are queued up. This patch makes uhci act in a more sophisticated way. It keeps track of successful processed transfer descriptors and transfered bytes. Then it will stop processing when there is nothing to do (no transfer descriptor was completed the last round) or when the transfered data reaches the usb bandwidth limit. Result is that the usb-storage devices connected to uhci are ten times faster, mkfs.vfat time for a 64M stick goes down from five seconds to a half second. Reason for this is that we are now processing up to 20 transfer descriptors (with 64 bytes each) per frame instead of a single one. Signed-off-by: Gerd Hoffmann --- hw/usb-uhci.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'hw/usb-uhci.c') diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c index cddcc8927..e0c7dbb71 100644 --- a/hw/usb-uhci.c +++ b/hw/usb-uhci.c @@ -73,7 +73,7 @@ #define FRAME_TIMER_FREQ 1000 -#define FRAME_MAX_LOOPS 100 +#define FRAME_MAX_LOOPS 256 #define NB_PORTS 2 @@ -942,7 +942,7 @@ static int qhdb_insert(QhDb *db, uint32_t addr) static void uhci_process_frame(UHCIState *s) { uint32_t frame_addr, link, old_td_ctrl, val, int_mask; - uint32_t curr_qh; + uint32_t curr_qh, td_count = 0, bytes_count = 0; int cnt, ret; UHCI_TD td; UHCI_QH qh; @@ -967,13 +967,26 @@ static void uhci_process_frame(UHCIState *s) if (qhdb_insert(&qhdb, link)) { /* * We're going in circles. Which is not a bug because - * HCD is allowed to do that as part of the BW management. - * In our case though it makes no sense to spin here. Sync transations - * are already done, and async completion handler will re-process - * the frame when something is ready. + * HCD is allowed to do that as part of the BW management. + * + * Stop processing here if + * (a) no transaction has been done since we've been + * here last time, or + * (b) we've reached the usb 1.1 bandwidth, which is + * 1280 bytes/frame. */ DPRINTF("uhci: detected loop. qh 0x%x\n", link); - break; + if (td_count == 0) { + DPRINTF("uhci: no transaction last round, stop\n"); + break; + } else if (bytes_count >= 1280) { + DPRINTF("uhci: bandwidth limit reached, stop\n"); + break; + } else { + td_count = 0; + qhdb_reset(&qhdb); + qhdb_insert(&qhdb, link); + } } pci_dma_read(&s->dev, link & ~0xf, &qh, sizeof(qh)); @@ -1033,6 +1046,8 @@ static void uhci_process_frame(UHCIState *s) link, td.link, td.ctrl, td.token, curr_qh); link = td.link; + td_count++; + bytes_count += (td.ctrl & 0x7ff) + 1; if (curr_qh) { /* update QH element link */ -- cgit v1.2.3 From d28f4e2d86317f7ef7398651e8a1d493e4bf8c88 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 6 Jan 2012 15:23:10 +0100 Subject: usb: kill USB_MSG_RESET The USB subsystem pipes internal reset notifications through usb_handle_packet() with a special magic PID. This indirection is a pretty pointless excercise as it ends up being handled by usb_generic_handle_packet anyway. Replace the USB_MSG_RESET with a usb_device_reset() function which can be called directly. Also rename the existing usb_reset() function to usb_port_reset() to avoid confusion. Signed-off-by: Gerd Hoffmann --- hw/usb-uhci.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'hw/usb-uhci.c') diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c index e0c7dbb71..407e0f3a5 100644 --- a/hw/usb-uhci.c +++ b/hw/usb-uhci.c @@ -342,7 +342,7 @@ static void uhci_reset(void *opaque) port = &s->ports[i]; port->ctrl = 0x0080; if (port->port.dev && port->port.dev->attached) { - usb_reset(&port->port); + usb_port_reset(&port->port); } } @@ -440,16 +440,12 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) } if (val & UHCI_CMD_GRESET) { UHCIPort *port; - USBDevice *dev; int i; /* send reset on the USB bus */ for(i = 0; i < NB_PORTS; i++) { port = &s->ports[i]; - dev = port->port.dev; - if (dev && dev->attached) { - usb_send_msg(dev, USB_MSG_RESET); - } + usb_device_reset(port->port.dev); } uhci_reset(s); return; @@ -491,7 +487,7 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) /* port reset */ if ( (val & UHCI_PORT_RESET) && !(port->ctrl & UHCI_PORT_RESET) ) { - usb_send_msg(dev, USB_MSG_RESET); + usb_device_reset(dev); } } port->ctrl &= UHCI_PORT_READ_ONLY; -- cgit v1.2.3 From 461700c1dc006600d7a955ee67184ced6084fd19 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 10 Jan 2012 17:34:24 +0100 Subject: usb-uhci: switch to usb_find_device() Switch over UHCI to use the new usb_find_device() function for device lookup. Signed-off-by: Gerd Hoffmann --- hw/usb-uhci.c | 45 +++++++++++++++------------------------------ 1 file changed, 15 insertions(+), 30 deletions(-) (limited to 'hw/usb-uhci.c') diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c index 407e0f3a5..a1f597ab8 100644 --- a/hw/usb-uhci.c +++ b/hw/usb-uhci.c @@ -94,15 +94,6 @@ static const char *pid2str(int pid) #define DPRINTF(...) #endif -#ifdef DEBUG_DUMP_DATA -static void dump_data(USBPacket *p, int ret) -{ - iov_hexdump(p->iov.iov, p->iov.niov, stderr, "uhci", ret); -} -#else -static void dump_data(USBPacket *p, int ret) {} -#endif - typedef struct UHCIState UHCIState; /* @@ -643,30 +634,22 @@ static void uhci_wakeup(USBPort *port1) } } -static int uhci_broadcast_packet(UHCIState *s, USBPacket *p) +static USBDevice *uhci_find_device(UHCIState *s, uint8_t addr) { - int i, ret; - - DPRINTF("uhci: packet enter. pid %s addr 0x%02x ep %d len %zd\n", - pid2str(p->pid), p->devaddr, p->devep, p->iov.size); - if (p->pid == USB_TOKEN_OUT || p->pid == USB_TOKEN_SETUP) - dump_data(p, 0); + USBDevice *dev; + int i; - ret = USB_RET_NODEV; - for (i = 0; i < NB_PORTS && ret == USB_RET_NODEV; i++) { + for (i = 0; i < NB_PORTS; i++) { UHCIPort *port = &s->ports[i]; - USBDevice *dev = port->port.dev; - - if (dev && dev->attached && (port->ctrl & UHCI_PORT_EN)) { - ret = usb_handle_packet(dev, p); + if (!(port->ctrl & UHCI_PORT_EN)) { + continue; + } + dev = usb_find_device(&port->port, addr); + if (dev != NULL) { + return dev; } } - - DPRINTF("uhci: packet exit. ret %d len %zd\n", ret, p->iov.size); - if (p->pid == USB_TOKEN_IN && ret > 0) - dump_data(p, ret); - - return ret; + return NULL; } static void uhci_async_complete(USBPort *port, USBPacket *packet); @@ -830,13 +813,15 @@ static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *in switch(pid) { case USB_TOKEN_OUT: case USB_TOKEN_SETUP: - len = uhci_broadcast_packet(s, &async->packet); + len = usb_handle_packet(uhci_find_device(s, async->packet.devaddr), + &async->packet); if (len >= 0) len = max_len; break; case USB_TOKEN_IN: - len = uhci_broadcast_packet(s, &async->packet); + len = usb_handle_packet(uhci_find_device(s, async->packet.devaddr), + &async->packet); break; default: -- cgit v1.2.3 From f53c398aa603cea135ee58fd15249aeff7b9c7ea Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 12 Jan 2012 12:51:48 +0100 Subject: usb: USBPacket: add status, rename owner -> ep Add enum to track the status of USBPackets, use that instead of the owner pointer to figure whenever a usb packet is currently in flight or not. Add some more packet status sanity checks. Also rename the USBEndpoint pointer from "owner" to "ep". Signed-off-by: Gerd Hoffmann --- hw/usb-uhci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'hw/usb-uhci.c') diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c index a1f597ab8..ef0814570 100644 --- a/hw/usb-uhci.c +++ b/hw/usb-uhci.c @@ -236,8 +236,8 @@ static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev) UHCIAsync *curr, *n; QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) { - if (curr->packet.owner == NULL || - curr->packet.owner->dev != dev) { + if (!usb_packet_is_inflight(&curr->packet) || + curr->packet.ep->dev != dev) { continue; } uhci_async_unlink(s, curr); -- cgit v1.2.3 From 079d0b7f1eedcc634c371fe05b617fdc55c8b762 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 12 Jan 2012 13:23:01 +0100 Subject: usb: Set USBEndpoint in usb_packet_setup(). With the separation of the device lookup (via usb_find_device) and packet processing we can lookup device and endpoint before setting up the usb packet. So we can initialize USBPacket->ep early and keep it valid for the whole lifecycle of the USBPacket. Also the devaddr and devep fields are not needed any more. Signed-off-by: Gerd Hoffmann --- hw/usb-uhci.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'hw/usb-uhci.c') diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c index ef0814570..ab64be604 100644 --- a/hw/usb-uhci.c +++ b/hw/usb-uhci.c @@ -761,6 +761,8 @@ static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *in int len = 0, max_len; uint8_t pid, isoc; uint32_t token; + USBDevice *dev; + USBEndpoint *ep; /* Is active ? */ if (!(td->ctrl & TD_CTRL_ACTIVE)) @@ -805,23 +807,22 @@ static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *in max_len = ((td->token >> 21) + 1) & 0x7ff; pid = td->token & 0xff; - usb_packet_setup(&async->packet, pid, (td->token >> 8) & 0x7f, - (td->token >> 15) & 0xf); + dev = uhci_find_device(s, (td->token >> 8) & 0x7f); + ep = usb_ep_get(dev, pid, (td->token >> 15) & 0xf); + usb_packet_setup(&async->packet, pid, ep); qemu_sglist_add(&async->sgl, td->buffer, max_len); usb_packet_map(&async->packet, &async->sgl); switch(pid) { case USB_TOKEN_OUT: case USB_TOKEN_SETUP: - len = usb_handle_packet(uhci_find_device(s, async->packet.devaddr), - &async->packet); + len = usb_handle_packet(dev, &async->packet); if (len >= 0) len = max_len; break; case USB_TOKEN_IN: - len = usb_handle_packet(uhci_find_device(s, async->packet.devaddr), - &async->packet); + len = usb_handle_packet(dev, &async->packet); break; default: -- cgit v1.2.3