diff options
author | aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162> | 2008-08-21 19:31:10 +0000 |
---|---|---|
committer | aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162> | 2008-08-21 19:31:10 +0000 |
commit | 64838171c464b642d68160ba78b6bfd9c7281516 (patch) | |
tree | 44b244063fbdc5e89985a0d4de609bb2a7ad62e4 | |
parent | 54f254f973a1b2ed0f3571390f4de060adfe23e8 (diff) |
husb: rewrite Linux host USB layer, fully async operation (Max Krasnyansky)
This is a follow up to the async UHCI patch. Both BULK and ISOC transactions
are now fully asynchrounous. I left CONTROL synchronous for now, ideally
we want it to be async too and it should not be that hard to do now.
This patch obviously requires UHCI patch. The combo has been tested with
various devices. See the UHCI patch description for list of the devices.
Most of the testing was done with the KVM flavor of QEMU.
Signed-off-by: Max Krasnyansky <maxk@kernel.org>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@5051 c046a42c-6fe2-441c-8c8c-71466251a162
-rw-r--r-- | usb-linux.c | 460 |
1 files changed, 182 insertions, 278 deletions
diff --git a/usb-linux.c b/usb-linux.c index 622255c9e2..786ef1bd68 100644 --- a/usb-linux.c +++ b/usb-linux.c @@ -3,8 +3,9 @@ * * Copyright (c) 2005 Fabrice Bellard * - * Support for host device auto connect & disconnect - * Copyright (c) 2008 Max Krasnyansky + * Copyright (c) 2008 Max Krasnyansky + * Support for host device auto connect & disconnect + * Magor rewrite to support fully async operation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -55,12 +56,15 @@ static int usb_host_find_device(int *pbus_num, int *paddr, const char *devname); //#define DEBUG -//#define DEBUG_ISOCH -//#define USE_ASYNCIO + +#ifdef DEBUG +#define dprintf printf +#else +#define dprintf(...) +#endif #define USBDEVFS_PATH "/proc/bus/usb" #define PRODUCT_NAME_SZ 32 -#define SIG_ISOCOMPLETE (SIGRTMIN+7) #define MAX_ENDPOINTS 16 struct sigaction sigact; @@ -68,21 +72,18 @@ struct sigaction sigact; /* endpoint association data */ struct endp_data { uint8_t type; + uint8_t halted; }; - - -/* FIXME: move USBPacket to PendingURB */ typedef struct USBHostDevice { USBDevice dev; - int fd; - int pipe_fds[2]; - USBPacket *packet; + int fd; + + uint8_t descr[1024]; + int descr_len; + int configuration; + struct endp_data endp_table[MAX_ENDPOINTS]; - int configuration; - uint8_t descr[1024]; - int descr_len; - int urbs_ready; QEMUTimer *timer; @@ -93,6 +94,26 @@ typedef struct USBHostDevice { struct USBHostDevice *next; } USBHostDevice; +static int is_isoc(USBHostDevice *s, int ep) +{ + return s->endp_table[ep - 1].type == USBDEVFS_URB_TYPE_ISO; +} + +static int is_halted(USBHostDevice *s, int ep) +{ + return s->endp_table[ep - 1].halted; +} + +static void clear_halt(USBHostDevice *s, int ep) +{ + s->endp_table[ep - 1].halted = 0; +} + +static void set_halt(USBHostDevice *s, int ep) +{ + s->endp_table[ep - 1].halted = 1; +} + static USBHostDevice *hostdev_list; static void hostdev_link(USBHostDevice *dev) @@ -128,64 +149,94 @@ static USBHostDevice *hostdev_find(int bus_num, int addr) return NULL; } -typedef struct PendingURB { - struct usbdevfs_urb *urb; - int status; - struct PendingURB *next; -} PendingURB; +/* + * Async URB state. + * We always allocate one isoc descriptor even for bulk transfers + * to simplify allocation and casts. + */ +typedef struct AsyncURB +{ + struct usbdevfs_urb urb; + struct usbdevfs_iso_packet_desc isocpd; -static PendingURB *pending_urbs = NULL; + USBPacket *packet; + USBHostDevice *hdev; +} AsyncURB; -static int add_pending_urb(struct usbdevfs_urb *urb) +static AsyncURB *async_alloc(void) { - PendingURB *purb = qemu_mallocz(sizeof(PendingURB)); - if (purb) { - purb->urb = urb; - purb->status = 0; - purb->next = pending_urbs; - pending_urbs = purb; - return 1; - } - return 0; + return (AsyncURB *) qemu_mallocz(sizeof(AsyncURB)); } -static int del_pending_urb(struct usbdevfs_urb *urb) +static void async_free(AsyncURB *aurb) { - PendingURB *purb = pending_urbs; - PendingURB *prev = NULL; + qemu_free(aurb); +} - while (purb && purb->urb != urb) { - prev = purb; - purb = purb->next; - } +static void async_complete(void *opaque) +{ + USBHostDevice *s = opaque; + AsyncURB *aurb; + + while (1) { + USBPacket *p; - if (purb && purb->urb == urb) { - if (prev) { - prev->next = purb->next; - } else { - pending_urbs = purb->next; + int r = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &aurb); + if (r < 0) { + if (errno == EAGAIN) + return; + + if (errno == ENODEV) { + printf("husb: device %d.%d disconnected\n", s->bus_num, s->addr); + usb_device_del_addr(0, s->dev.addr); + return; + } + + dprintf("husb: async. reap urb failed errno %d\n", errno); + return; } - qemu_free(purb); - return 1; + + p = aurb->packet; + + dprintf("husb: async completed. aurb %p status %d alen %d\n", + aurb, aurb->urb.status, aurb->urb.actual_length); + + if (p) { + switch (aurb->urb.status) { + case 0: + p->len = aurb->urb.actual_length; + break; + + case -EPIPE: + set_halt(s, p->devep); + /* fall through */ + default: + p->len = USB_RET_NAK; + break; + } + + usb_packet_complete(p); + } + + async_free(aurb); } - return 0; } -#ifdef USE_ASYNCIO -static PendingURB *get_pending_urb(struct usbdevfs_urb *urb) +static void async_cancel(USBPacket *unused, void *opaque) { - PendingURB *purb = pending_urbs; + AsyncURB *aurb = opaque; + USBHostDevice *s = aurb->hdev; - while (purb && purb->urb != urb) { - purb = purb->next; - } + dprintf("husb: async cancel. aurb %p\n", aurb); + + /* Mark it as dead (see async_complete above) */ + aurb->packet = NULL; - if (purb && purb->urb == urb) { - return purb; + int r = ioctl(s->fd, USBDEVFS_DISCARDURB, aurb); + if (r < 0) { + dprintf("husb: async. discard urb failed errno %d\n", errno); } - return NULL; } -#endif static int usb_host_update_interfaces(USBHostDevice *dev, int configuration) { @@ -204,19 +255,16 @@ static int usb_host_update_interfaces(USBHostDevice *dev, int configuration) i += dev_descr_len; while (i < dev->descr_len) { -#ifdef DEBUG - printf("i is %d, descr_len is %d, dl %d, dt %d\n", i, dev->descr_len, + dprintf("husb: i is %d, descr_len is %d, dl %d, dt %d\n", i, dev->descr_len, dev->descr[i], dev->descr[i+1]); -#endif + if (dev->descr[i+1] != USB_DT_CONFIG) { i += dev->descr[i]; continue; } config_descr_len = dev->descr[i]; -#ifdef DEBUG - printf("config #%d need %d\n", dev->descr[i + 5], configuration); -#endif + printf("husb: config #%d need %d\n", dev->descr[i + 5], configuration); if (configuration < 0 || configuration == dev->descr[i + 5]) break; @@ -225,7 +273,7 @@ static int usb_host_update_interfaces(USBHostDevice *dev, int configuration) } if (i >= dev->descr_len) { - printf("usb_host: error - device has no matching configuration\n"); + printf("husb: update iface failed. no matching configuration\n"); goto fail; } nb_interfaces = dev->descr[i + 4]; @@ -251,32 +299,29 @@ static int usb_host_update_interfaces(USBHostDevice *dev, int configuration) ret = ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &interface); if (ret < 0) { if (errno == EBUSY) { - fprintf(stderr, - "usb_host: warning - device already grabbed\n"); + printf("husb: update iface. device already grabbed\n"); } else { - perror("USBDEVFS_CLAIMINTERFACE"); + perror("husb: failed to claim interface"); } fail: return 0; } } -#ifdef DEBUG - printf("usb_host: %d interfaces claimed for configuration %d\n", + printf("husb: %d interfaces claimed for configuration %d\n", nb_interfaces, configuration); -#endif return 1; } static void usb_host_handle_reset(USBDevice *dev) { -#if 0 USBHostDevice *s = (USBHostDevice *)dev; - /* USBDEVFS_RESET, but not the first time as it has already be - done by the host OS */ + + dprintf("husb: reset device %u.%u\n", s->bus_num, s->addr); + ioctl(s->fd, USBDEVFS_RESET); -#endif + usb_host_update_interfaces(s, s->configuration); } static void usb_host_handle_destroy(USBDevice *dev) @@ -284,9 +329,12 @@ static void usb_host_handle_destroy(USBDevice *dev) USBHostDevice *s = (USBHostDevice *)dev; qemu_del_timer(s->timer); + qemu_set_fd_handler(s->fd, NULL, NULL, NULL); hostdev_unlink(s); + async_complete(s); + if (s->fd >= 0) close(s->fd); @@ -320,10 +368,7 @@ static int usb_host_handle_control(USBDevice *dev, ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si); usb_linux_update_endp_table(s); } else if (request == (DeviceOutRequest | USB_REQ_SET_CONFIGURATION)) { -#ifdef DEBUG - printf("usb_host_handle_control: SET_CONFIGURATION request - " - "config %d\n", value & 0xff); -#endif + dprintf("husb: ctrl set config %d\n", value & 0xff); if (s->configuration != (value & 0xff)) { s->configuration = (value & 0xff); intf_update_required = 1; @@ -339,6 +384,9 @@ static int usb_host_handle_control(USBDevice *dev, ct.timeout = 50; ct.data = data; ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); + + dprintf("husb: ctrl req 0x%x val 0x%x index %u len %u ret %d\n", + ct.bRequest, ct.wValue, ct.wIndex, ct.wLength, ret); } if (ret < 0) { @@ -350,160 +398,69 @@ static int usb_host_handle_control(USBDevice *dev, } } else { if (intf_update_required) { -#ifdef DEBUG - printf("usb_host_handle_control: updating interfaces\n"); -#endif + dprintf("husb: updating interfaces\n"); usb_host_update_interfaces(s, value & 0xff); } return ret; } } -static int usb_host_handle_isoch(USBDevice *dev, USBPacket *p); - static int usb_host_handle_data(USBDevice *dev, USBPacket *p) { - USBHostDevice *s = (USBHostDevice *)dev; - struct usbdevfs_bulktransfer bt; + USBHostDevice *s = (USBHostDevice *) dev; + AsyncURB *aurb; + struct usbdevfs_urb *urb; int ret; - uint8_t devep = p->devep; - if (s->endp_table[p->devep - 1].type == USBDEVFS_URB_TYPE_ISO) { - return usb_host_handle_isoch(dev, p); + aurb = async_alloc(); + if (!aurb) { + dprintf("husb: async malloc failed\n"); + return USB_RET_NAK; } + aurb->hdev = s; + aurb->packet = p; + + urb = &aurb->urb; - /* XXX: optimize and handle all data types by looking at the - config descriptor */ if (p->pid == USB_TOKEN_IN) - devep |= 0x80; - bt.ep = devep; - bt.len = p->len; - bt.timeout = 50; - bt.data = p->data; - ret = ioctl(s->fd, USBDEVFS_BULK, &bt); - if (ret < 0) { - switch(errno) { - case ETIMEDOUT: + urb->endpoint = p->devep | 0x80; + else + urb->endpoint = p->devep; + + if (is_halted(s, p->devep)) { + ret = ioctl(s->fd, USBDEVFS_CLEAR_HALT, &urb->endpoint); + if (ret < 0) { + dprintf("husb: failed to clear halt. ep 0x%x errno %d\n", + urb->endpoint, errno); return USB_RET_NAK; - case EPIPE: - default: -#ifdef DEBUG - printf("handle_data: errno=%d\n", errno); -#endif - return USB_RET_STALL; } - } else { - return ret; - } -} - -#ifdef USE_ASYNCIO -static void urb_completion_pipe_read(void *opaque) -{ - USBHostDevice *s = opaque; - USBPacket *p = s->packet; - PendingURB *pending_urb = NULL; - struct usbdevfs_urb *purb = NULL; - int len, ret; - - len = read(s->pipe_fds[0], &pending_urb, sizeof(pending_urb)); - if (len != sizeof(pending_urb)) { - printf("urb_completion: error reading pending_urb, len=%d\n", len); - return; - } - - /* FIXME: handle pending_urb->status */ - del_pending_urb(pending_urb->urb); - - if (!p) { - s->urbs_ready++; - return; + clear_halt(s, p->devep); } - ret = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &purb); - if (ret < 0) { - printf("urb_completion: REAPURBNDELAY ioctl=%d errno=%d\n", - ret, errno); - return; - } - -#ifdef DEBUG_ISOCH - if (purb == pending_urb->urb) { - printf("urb_completion: urb mismatch reaped=%p pending=%p\n", - purb, urb); - } -#endif - - p->len = purb->actual_length; - usb_packet_complete(p); - qemu_free(purb); - s->packet = NULL; -} - -static void isoch_done(int signum, siginfo_t *info, void *context) -{ - struct usbdevfs_urb *urb = (struct usbdevfs_urb *)info->si_addr; - USBHostDevice *s = (USBHostDevice *)urb->usercontext; - PendingURB *purb; + urb->buffer = p->data; + urb->buffer_length = p->len; - if (info->si_code != SI_ASYNCIO || - info->si_signo != SIG_ISOCOMPLETE) { - return; + if (is_isoc(s, p->devep)) { + /* Setup ISOC transfer */ + urb->type = USBDEVFS_URB_TYPE_ISO; + urb->flags = USBDEVFS_URB_ISO_ASAP; + urb->number_of_packets = 1; + urb->iso_frame_desc[0].length = p->len; + } else { + /* Setup bulk transfer */ + urb->type = USBDEVFS_URB_TYPE_BULK; } - purb = get_pending_urb(urb); - if (purb) { - purb->status = info->si_errno; - write(s->pipe_fds[1], &purb, sizeof(purb)); - } -} -#endif + urb->usercontext = s; -static int usb_host_handle_isoch(USBDevice *dev, USBPacket *p) -{ - USBHostDevice *s = (USBHostDevice *)dev; - struct usbdevfs_urb *urb, *purb = NULL; - int ret; - uint8_t devep = p->devep; + ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); - if (p->pid == USB_TOKEN_IN) - devep |= 0x80; + dprintf("husb: data submit. ep 0x%x len %u aurb %p\n", urb->endpoint, p->len, aurb); - urb = qemu_mallocz(sizeof(struct usbdevfs_urb) + - sizeof(struct usbdevfs_iso_packet_desc)); - if (!urb) { - printf("usb_host_handle_isoch: malloc failed\n"); - return 0; - } + if (ret < 0) { + dprintf("husb: submit failed. errno %d\n", errno); + async_free(aurb); - urb->type = USBDEVFS_URB_TYPE_ISO; - urb->endpoint = devep; - urb->status = 0; - urb->flags = USBDEVFS_URB_ISO_ASAP; - urb->buffer = p->data; - urb->buffer_length = p->len; - urb->actual_length = 0; - urb->start_frame = 0; - urb->error_count = 0; -#ifdef USE_ASYNCIO - urb->signr = SIG_ISOCOMPLETE; -#else - urb->signr = 0; -#endif - urb->usercontext = s; - urb->number_of_packets = 1; - urb->iso_frame_desc[0].length = p->len; - urb->iso_frame_desc[0].actual_length = 0; - urb->iso_frame_desc[0].status = 0; - ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); - if (ret == 0) { - if (!add_pending_urb(urb)) { - printf("usb_host_handle_isoch: add_pending_urb failed %p\n", urb); - } - } else { - printf("usb_host_handle_isoch: SUBMITURB ioctl=%d errno=%d\n", - ret, errno); - qemu_free(urb); switch(errno) { case ETIMEDOUT: return USB_RET_NAK; @@ -512,37 +469,9 @@ static int usb_host_handle_isoch(USBDevice *dev, USBPacket *p) return USB_RET_STALL; } } -#ifdef USE_ASYNCIO - /* FIXME: handle urbs_ready together with sync io - * workaround for injecting the signaled urbs into current frame */ - if (s->urbs_ready > 0) { - ret = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &purb); - if (ret == 0) { - ret = purb->actual_length; - qemu_free(purb); - s->urbs_ready--; - } - return ret; - } - s->packet = p; + + usb_defer_packet(p, async_cancel, aurb); return USB_RET_ASYNC; -#else - ret = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &purb); - if (ret == 0) { - if (del_pending_urb(purb)) { - ret = purb->actual_length; - qemu_free(purb); - } else { - printf("usb_host_handle_isoch: del_pending_urb failed %p\n", purb); - } - } else { -#ifdef DEBUG_ISOCH - printf("usb_host_handle_isoch: REAPURBNDELAY ioctl=%d errno=%d\n", - ret, errno); -#endif - } - return ret; -#endif } /* returns 1 on problem encountered or 0 for success */ @@ -579,7 +508,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) if (descriptors[i + 1] != USB_DT_CONFIG || descriptors[i + 5] != configuration) { - printf("invalid descriptor data - configuration\n"); + dprintf("invalid descriptor data - configuration\n"); return 1; } i += descriptors[i]; @@ -641,10 +570,11 @@ static int usb_linux_update_endp_table(USBHostDevice *s) type = USBDEVFS_URB_TYPE_INTERRUPT; break; default: - printf("usb_host: malformed endpoint type\n"); + dprintf("usb_host: malformed endpoint type\n"); type = USBDEVFS_URB_TYPE_BULK; } s->endp_table[(devep & 0xf) - 1].type = type; + s->endp_table[(devep & 0xf) - 1].halted = 0; i += descriptors[i]; } @@ -660,7 +590,7 @@ static void usb_host_device_check(void *priv) err = ioctl(s->fd, USBDEVFS_CONNECTINFO, &ci); if (err < 0) { - printf("usb device %d.%d disconnected\n", 0, s->dev.addr); + printf("husb: device %d.%d disconnected\n", s->bus_num, s->addr); usb_device_del_addr(0, s->dev.addr); return; } @@ -686,9 +616,7 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p if (!dev->timer) goto fail; -#ifdef DEBUG - printf("usb_host_device_open %d.%d\n", bus_num, addr); -#endif + printf("husb: open device %d.%d\n", bus_num, addr); snprintf(buf, sizeof(buf), USBDEVFS_PATH "/%03d/%03d", bus_num, addr); @@ -701,7 +629,7 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p /* read the device description */ dev->descr_len = read(fd, dev->descr, sizeof(dev->descr)); if (dev->descr_len <= 0) { - perror("usb_host_device_open: reading device data failed"); + perror("husb: reading device data failed"); goto fail; } @@ -728,9 +656,7 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p goto fail; } -#ifdef DEBUG - printf("host USB device %d.%d grabbed\n", bus_num, addr); -#endif + printf("husb: grabbed usb device %d.%d\n", bus_num, addr); ret = usb_linux_update_endp_table(dev); if (ret) @@ -754,34 +680,15 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p pstrcpy(dev->dev.devname, sizeof(dev->dev.devname), prod_name); -#ifdef USE_ASYNCIO - /* set up the signal handlers */ - sigemptyset(&sigact.sa_mask); - sigact.sa_sigaction = isoch_done; - sigact.sa_flags = SA_SIGINFO; - sigact.sa_restorer = 0; - ret = sigaction(SIG_ISOCOMPLETE, &sigact, NULL); - if (ret < 0) { - perror("usb_host_device_open: sigaction failed"); - goto fail; - } - - if (pipe(dev->pipe_fds) < 0) { - perror("usb_host_device_open: pipe creation failed"); - goto fail; - } - fcntl(dev->pipe_fds[0], F_SETFL, O_NONBLOCK | O_ASYNC); - fcntl(dev->pipe_fds[1], F_SETFL, O_NONBLOCK); - qemu_set_fd_handler(dev->pipe_fds[0], urb_completion_pipe_read, NULL, dev); -#endif + /* USB devio uses 'write' flag to check for async completions */ + qemu_set_fd_handler(dev->fd, NULL, async_complete, dev); /* Start the timer to detect disconnect */ qemu_mod_timer(dev->timer, qemu_get_clock(rt_clock) + 1000); hostdev_link(dev); - dev->urbs_ready = 0; - return (USBDevice *)dev; + return (USBDevice *) dev; fail: if (dev) { @@ -804,7 +711,7 @@ USBDevice *usb_host_device_open(const char *devname) return NULL; if (hostdev_find(bus_num, addr)) { - printf("host usb device %d.%d is already open\n", bus_num, addr); + term_printf("husb: host usb device %d.%d is already open\n", bus_num, addr); return NULL; } @@ -844,7 +751,7 @@ static int usb_host_scan(void *opaque, USBScanFunc *func) f = fopen(USBDEVFS_PATH "/devices", "r"); if (!f) { - term_printf("Could not open %s\n", USBDEVFS_PATH "/devices"); + term_printf("husb: could not open %s\n", USBDEVFS_PATH "/devices"); return 0; } device_count = 0; @@ -954,7 +861,7 @@ static int usb_host_auto_scan(void *opaque, int bus_num, int addr, if (hostdev_find(bus_num, addr)) return 0; - printf("Auto open: bus_num %d addr %d\n", bus_num, addr); + dprintf("husb: auto open: bus_num %d addr %d\n", bus_num, addr); dev = usb_host_device_open_addr(bus_num, addr, product_name); if (dev) @@ -978,7 +885,7 @@ static void usb_host_auto_add(int bus_num, int addr, int vendor_id, int product_ { struct USBAutoFilter *f = qemu_mallocz(sizeof(*f)); if (!f) { - printf("Failed to allocate auto filter\n"); + printf("husb: failed to allocate auto filter\n"); return; } @@ -996,7 +903,7 @@ static void usb_host_auto_add(int bus_num, int addr, int vendor_id, int product_ */ usb_auto_timer = qemu_new_timer(rt_clock, usb_host_auto_timer, NULL); if (!usb_auto_timer) { - printf("Failed to allocate timer\n"); + printf("husb: failed to allocate timer\n"); qemu_free(f); return; } @@ -1005,7 +912,7 @@ static void usb_host_auto_add(int bus_num, int addr, int vendor_id, int product_ qemu_mod_timer(usb_auto_timer, qemu_get_clock(rt_clock) + 2000); } - printf("Auto filter: bus_num %d addr %d vid %d pid %d\n", + dprintf("husb: auto filter: bus_num %d addr %d vid %d pid %d\n", bus_num, addr, vendor_id, product_id); f->next = usb_auto_filter; @@ -1174,9 +1081,6 @@ void usb_host_info(void) usb_host_scan(NULL, usb_host_info_device); } - - - #else void usb_host_info(void) |