diff options
author | Gerd Hoffmann <kraxel@redhat.com> | 2012-01-12 14:26:13 +0100 |
---|---|---|
committer | Gerd Hoffmann <kraxel@redhat.com> | 2012-02-10 12:16:18 +0100 |
commit | db4be873d312576c6971da15a38e056017a406b8 (patch) | |
tree | f542f1e79cf24e89eb0900facc3291d387f47f48 | |
parent | 079d0b7f1eedcc634c371fe05b617fdc55c8b762 (diff) |
usb: maintain async packet list per endpoint
Maintain a list of async packets per endpoint. With the current code
the list will never receive more than a single item. I think you can
guess what the future plan is though ;)
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-rw-r--r-- | hw/usb.c | 127 | ||||
-rw-r--r-- | hw/usb.h | 9 |
2 files changed, 108 insertions, 28 deletions
@@ -279,6 +279,28 @@ USBDevice *usb_find_device(USBPort *port, uint8_t addr) return usb_device_find_device(dev, addr); } +static int usb_process_one(USBPacket *p) +{ + USBDevice *dev = p->ep->dev; + + if (p->ep->nr == 0) { + /* control pipe */ + switch (p->pid) { + case USB_TOKEN_SETUP: + return do_token_setup(dev, p); + case USB_TOKEN_IN: + return do_token_in(dev, p); + case USB_TOKEN_OUT: + return do_token_out(dev, p); + default: + return USB_RET_STALL; + } + } else { + /* data pipe */ + return usb_device_handle_data(dev, p); + } +} + /* Hand over a packet to a device for processing. Return value USB_RET_ASYNC indicates the processing isn't finished yet, the driver will call usb_packet_complete() when done processing it. */ @@ -292,30 +314,21 @@ int usb_handle_packet(USBDevice *dev, USBPacket *p) assert(dev == p->ep->dev); assert(dev->state == USB_STATE_DEFAULT); assert(p->state == USB_PACKET_SETUP); + assert(p->ep != NULL); - if (p->ep->nr == 0) { - /* control pipe */ - switch (p->pid) { - case USB_TOKEN_SETUP: - ret = do_token_setup(dev, p); - break; - case USB_TOKEN_IN: - ret = do_token_in(dev, p); - break; - case USB_TOKEN_OUT: - ret = do_token_out(dev, p); - break; - default: - ret = USB_RET_STALL; - break; + if (QTAILQ_EMPTY(&p->ep->queue)) { + ret = usb_process_one(p); + if (ret == USB_RET_ASYNC) { + usb_packet_set_state(p, USB_PACKET_ASYNC); + QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue); + } else { + p->result = ret; + usb_packet_set_state(p, USB_PACKET_COMPLETE); } } else { - /* data pipe */ - ret = usb_device_handle_data(dev, p); - } - - if (ret == USB_RET_ASYNC) { - p->state = USB_PACKET_ASYNC; + ret = USB_RET_ASYNC; + usb_packet_set_state(p, USB_PACKET_QUEUED); + QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue); } return ret; } @@ -325,9 +338,28 @@ int usb_handle_packet(USBDevice *dev, USBPacket *p) handle_packet. */ void usb_packet_complete(USBDevice *dev, USBPacket *p) { + USBEndpoint *ep = p->ep; + int ret; + assert(p->state == USB_PACKET_ASYNC); - p->state = USB_PACKET_COMPLETE; + assert(QTAILQ_FIRST(&ep->queue) == p); + usb_packet_set_state(p, USB_PACKET_COMPLETE); + QTAILQ_REMOVE(&ep->queue, p, queue); dev->port->ops->complete(dev->port, p); + + while (!QTAILQ_EMPTY(&ep->queue)) { + p = QTAILQ_FIRST(&ep->queue); + assert(p->state == USB_PACKET_QUEUED); + ret = usb_process_one(p); + if (ret == USB_RET_ASYNC) { + usb_packet_set_state(p, USB_PACKET_ASYNC); + break; + } + p->result = ret; + usb_packet_set_state(p, USB_PACKET_COMPLETE); + QTAILQ_REMOVE(&ep->queue, p, queue); + dev->port->ops->complete(dev->port, p); + } } /* Cancel an active packet. The packed must have been deferred by @@ -335,9 +367,13 @@ void usb_packet_complete(USBDevice *dev, USBPacket *p) completed. */ void usb_cancel_packet(USBPacket * p) { - assert(p->state == USB_PACKET_ASYNC); - p->state = USB_PACKET_CANCELED; - usb_device_cancel_packet(p->ep->dev, p); + bool callback = (p->state == USB_PACKET_ASYNC); + assert(usb_packet_is_inflight(p)); + usb_packet_set_state(p, USB_PACKET_CANCELED); + QTAILQ_REMOVE(&p->ep->queue, p, queue); + if (callback) { + usb_device_cancel_packet(p->ep->dev, p); + } } @@ -346,14 +382,50 @@ void usb_packet_init(USBPacket *p) qemu_iovec_init(&p->iov, 1); } +void usb_packet_set_state(USBPacket *p, USBPacketState state) +{ +#ifdef DEBUG + static const char *name[] = { + [USB_PACKET_UNDEFINED] = "undef", + [USB_PACKET_SETUP] = "setup", + [USB_PACKET_QUEUED] = "queued", + [USB_PACKET_ASYNC] = "async", + [USB_PACKET_COMPLETE] = "complete", + [USB_PACKET_CANCELED] = "canceled", + }; + static const char *rets[] = { + [-USB_RET_NODEV] = "NODEV", + [-USB_RET_NAK] = "NAK", + [-USB_RET_STALL] = "STALL", + [-USB_RET_BABBLE] = "BABBLE", + [-USB_RET_ASYNC] = "ASYNC", + }; + char add[16] = ""; + + if (state == USB_PACKET_COMPLETE) { + if (p->result < 0) { + snprintf(add, sizeof(add), " - %s", rets[-p->result]); + } else { + snprintf(add, sizeof(add), " - %d", p->result); + } + } + fprintf(stderr, "bus %s, port %s, dev %d, ep %d: packet %p: %s -> %s%s\n", + p->ep->dev->qdev.parent_bus->name, + p->ep->dev->port->path, + p->ep->dev->addr, p->ep->nr, + p, name[p->state], name[state], add); +#endif + p->state = state; +} + void usb_packet_setup(USBPacket *p, int pid, USBEndpoint *ep) { assert(!usb_packet_is_inflight(p)); - p->state = USB_PACKET_SETUP; p->pid = pid; p->ep = ep; p->result = 0; qemu_iovec_reset(&p->iov); + usb_packet_set_state(p, USB_PACKET_SETUP); } void usb_packet_addbuf(USBPacket *p, void *ptr, size_t len) @@ -404,6 +476,7 @@ void usb_ep_init(USBDevice *dev) dev->ep_ctl.type = USB_ENDPOINT_XFER_CONTROL; dev->ep_ctl.ifnum = 0; dev->ep_ctl.dev = dev; + 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; @@ -415,6 +488,8 @@ void usb_ep_init(USBDevice *dev) dev->ep_out[ep].ifnum = 0; dev->ep_in[ep].dev = dev; dev->ep_out[ep].dev = dev; + QTAILQ_INIT(&dev->ep_in[ep].queue); + QTAILQ_INIT(&dev->ep_out[ep].queue); } } @@ -177,6 +177,7 @@ struct USBEndpoint { uint8_t ifnum; int max_packet_size; USBDevice *dev; + QTAILQ_HEAD(, USBPacket) queue; }; /* definition of a USB device */ @@ -309,15 +310,16 @@ struct USBPort { typedef void USBCallback(USBPacket * packet, void *opaque); -/* Structure used to hold information about an active USB packet. */ typedef enum USBPacketState { USB_PACKET_UNDEFINED = 0, USB_PACKET_SETUP, + USB_PACKET_QUEUED, USB_PACKET_ASYNC, USB_PACKET_COMPLETE, USB_PACKET_CANCELED, } USBPacketState; +/* Structure used to hold information about an active USB packet. */ struct USBPacket { /* Data fields for use by the driver. */ int pid; @@ -326,9 +328,11 @@ struct USBPacket { int result; /* transfer length or USB_RET_* status code */ /* Internal use by the USB layer. */ USBPacketState state; + QTAILQ_ENTRY(USBPacket) queue; }; void usb_packet_init(USBPacket *p); +void usb_packet_set_state(USBPacket *p, USBPacketState state); void usb_packet_setup(USBPacket *p, int pid, USBEndpoint *ep); void usb_packet_addbuf(USBPacket *p, void *ptr, size_t len); int usb_packet_map(USBPacket *p, QEMUSGList *sgl); @@ -339,7 +343,8 @@ void usb_packet_cleanup(USBPacket *p); static inline bool usb_packet_is_inflight(USBPacket *p) { - return p->state == USB_PACKET_ASYNC; + return (p->state == USB_PACKET_QUEUED || + p->state == USB_PACKET_ASYNC); } USBDevice *usb_find_device(USBPort *port, uint8_t addr); |