--- qemu/usb-linux.c 2006-08-11 20:04:27.000000000 -0500 +++ qemu/usb-linux.c 2006-11-18 20:40:52.000000000 -0600 @@ -29,6 +29,8 @@ #include #include #include +#include +#include /* We redefine it to avoid version problems */ struct usb_ctrltransfer { @@ -53,11 +55,225 @@ #define USBDEVFS_PATH "/proc/bus/usb" #define PRODUCT_NAME_SZ 32 +// endpoint association data +struct endp_data { + uint8_t type; +}; + typedef struct USBHostDevice { USBDevice dev; int fd; + + /* for async completion - the + * current controller model + * issues one packet per + * controller. + */ + struct usbdevfs_urb urb; + USBPacket *packet; + QEMUBH *bh; + int status; + + struct endp_data endp_table[16]; } USBHostDevice; +// returns 1 on problem encountered or 0 for success +static int usb_linux_update_endp_table(USBHostDevice *s) +{ + uint8_t descriptors[1024]; + uint8_t buf[3]; + uint8_t devep, type; + struct usb_ctrltransfer ct; + int configuration, interface, alt_interface; + int length, i, ret; + + ct.bRequestType = USB_DIR_IN; + ct.bRequest = USB_REQ_GET_CONFIGURATION; + ct.wValue = 0; + ct.wIndex = 0; + ct.wLength = 1; + ct.data = buf; + ct.timeout = 50; + + ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); + if (ret < 0) { + perror("usb_linux_update_endp_table"); + return 1; + } + configuration = buf[0]; + + // in address state + if (configuration == 0) + return 1; + + /* get the desired configuration, interface, and endpoint + * descriptors in one shot - could also re-read all data from + * open file descriptor, go through sysfs entries, etc. + */ + ct.bRequestType = USB_DIR_IN; + ct.bRequest = USB_REQ_GET_DESCRIPTOR; + ct.wValue = (USB_DT_CONFIG << 8) | (configuration - 1); + ct.wIndex = 0; + ct.wLength = 1024; + ct.data = descriptors; + ct.timeout = 50; + + ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); + if (ret < 0) { + perror("usb_linux_update_endp_table"); + return 1; + } + + length = ret; + i = 0; + + if (descriptors[i + 1] != USB_DT_CONFIG || + descriptors[i + 5] != configuration) { + printf("invalid descriptor data - configuration\n"); + return 1; + } + i += descriptors[i]; + + while (i < length) { + if (descriptors[i + 1] != USB_DT_INTERFACE || + (descriptors[i + 1] == USB_DT_INTERFACE && + descriptors[i + 4] == 0)) { + i += descriptors[i]; + continue; + } + + interface = descriptors[i + 2]; + ct.bRequestType = USB_DIR_IN | USB_RECIP_INTERFACE; + ct.bRequest = USB_REQ_GET_INTERFACE; + ct.wValue = 0; + ct.wIndex = interface; + ct.wLength = 1; + ct.data = buf; + ct.timeout = 50; + + ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); + if (ret < 0) { + perror("usb_linux_update_endp_table"); + return 1; + } + alt_interface = buf[0]; + + // the current interface descriptor is the active interface + // and has endpoints + if (descriptors[i + 3] != alt_interface) + continue; + + // advance to the endpoints + while (i < length && descriptors[i +1] != USB_DT_ENDPOINT) + i += descriptors[i]; + + if (i >= length) + break; + + while (i < length) { + if (descriptors[i + 1] != USB_DT_ENDPOINT) + break; + + devep = descriptors[i + 2]; + switch (descriptors[i + 3] & 0x3) { + case 0x00: + type = USBDEVFS_URB_TYPE_CONTROL; + break; + case 0x01: + type = USBDEVFS_URB_TYPE_ISO; + break; + case 0x02: + type = USBDEVFS_URB_TYPE_BULK; + break; + case 0x03: + type = USBDEVFS_URB_TYPE_INTERRUPT; + break; + default: + printf("usb_host: malformed endpoint type\n"); + type = USBDEVFS_URB_TYPE_BULK; + } + s->endp_table[(devep & 0xf) - 1].type = type; + + i += descriptors[i]; + } + + i += descriptors[i]; + } + + return 0; +} + +static void usb_linux_bh_cb(void *opaque) +{ + USBHostDevice *s = (USBHostDevice *)opaque; + struct usbdevfs_urb *context = NULL; + struct usbdevfs_urb *urb = &s->urb; + USBPacket *p = s->packet; + int ret; + + if (!s || !p) + return; + +#ifdef DEBUG + printf("completion: devaddr %d - devep 0x%02x\n", p->devaddr, p->devep); +#endif + + switch (s->status) { + case 0: + ret = ioctl(s->fd, USBDEVFS_REAPURB, &context); + if (ret < 0) { + perror("USBDEVFS_REAPURB"); + return; + } + + if (context != urb) { + printf("this is not our urb\n"); + return; + } + + p->len = urb->actual_length; + break; + // case : // timeout uhci ? + case -ETIMEDOUT: // timeout ohci + printf("usb_host: timeout status detected\n"); + p->len = USB_RET_NAK; + break; + case -EPIPE: + printf("usb_host: stall status detected\n"); + p->len = USB_RET_STALL; + break; + case -EOVERFLOW: + printf("usb_host: babble status detected\n"); + p->len = USB_RET_BABBLE; + break; + default: + printf("unhandled status code - urb status %d\n", + s->status); + p->len = urb->actual_length; + } + + s->packet = NULL; + usb_packet_complete(p); +} + +/* SI_ASYNCIO handler routine */ +void usb_linux_sig_handler(int signum, siginfo_t *info, void *context) +{ + struct usbdevfs_urb *urb = (struct usbdevfs_urb *)info->si_addr; + USBHostDevice *s = (USBHostDevice *)urb->usercontext; + + if (info->si_code != SI_ASYNCIO || + info->si_signo != USB_ASYNC_COMPLETION_SIGNAL) { + return; + } + + // si_errno is urb status upon completion (drivers/usb/core/devio.c) + // can find error codes for ohci host in usb/host/ohci.h (cc_to_error) + // can find error codes for uhci host in usb/host/uhci-q.c + s->status = info->si_errno; + qemu_bh_schedule(s->bh); +} + static void usb_host_handle_reset(USBDevice *dev) { #if 0 @@ -68,12 +284,31 @@ #endif } +static void usb_host_cancel_io(USBPacket *p, void *opaque) +{ + USBHostDevice *s = (USBHostDevice *)opaque; + +#ifdef DEBUG + printf("packet %p canceled\n", p); +#endif + + if (p->devep != 0) { + qemu_bh_cancel(s->bh); + s->packet = NULL; + ioctl(s->fd, USBDEVFS_DISCARDURB, &s->urb); + } +} + static void usb_host_handle_destroy(USBDevice *dev) { USBHostDevice *s = (USBHostDevice *)dev; - if (s->fd >= 0) + qemu_bh_delete(s->bh); + + if (s->fd >= 0) { close(s->fd); + } + qemu_free(s); } @@ -117,33 +352,39 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p) { USBHostDevice *s = (USBHostDevice *)dev; - struct usbdevfs_bulktransfer bt; - int ret; uint8_t devep = p->devep; + int ret; - /* 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); + + s->urb.type = s->endp_table[(devep & 0xf) - 1].type; + s->urb.endpoint = devep; + s->urb.flags = 0; + s->urb.buffer = p->data; + s->urb.buffer_length = p->len; + s->urb.actual_length = 0; + s->urb.signr = USB_ASYNC_COMPLETION_SIGNAL; + s->urb.number_of_packets = 0; // isochronous + s->urb.usercontext = s; + + ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &s->urb); if (ret < 0) { - switch(errno) { - case ETIMEDOUT: - return USB_RET_NAK; - case EPIPE: + printf("urb submission failed - errno %d\n", errno); + switch (errno) { + case ENODEV: + ret = USB_RET_NODEV; + break; default: -#ifdef DEBUG - printf("handle_data: errno=%d\n", errno); -#endif - return USB_RET_STALL; + ret = USB_RET_STALL; } - } else { return ret; } + + usb_defer_packet(p, usb_host_cancel_io, s); + s->packet = p; + + return USB_RET_ASYNC; } /* XXX: exclude high speed devices or implement EHCI */ @@ -165,7 +406,7 @@ snprintf(buf, sizeof(buf), USBDEVFS_PATH "/%03d/%03d", bus_num, addr); - fd = open(buf, O_RDWR); + fd = open(buf, O_RDWR | O_NONBLOCK); if (fd < 0) { perror(buf); return NULL; @@ -174,7 +415,7 @@ /* read the config description */ descr_len = read(fd, descr, sizeof(descr)); if (descr_len <= 0) { - perror("read descr"); + perror("device_open - read descr"); goto fail; } @@ -235,10 +476,23 @@ if (!dev) goto fail; dev->fd = fd; + + ret = usb_linux_update_endp_table(dev); + if (ret) { + qemu_free(dev); + goto fail; + } + + dev->bh = qemu_bh_new(usb_linux_bh_cb, dev); + if (!dev->bh) { + qemu_free(dev); + goto fail; + } + if (ci.slow) dev->dev.speed = USB_SPEED_LOW; else - dev->dev.speed = USB_SPEED_HIGH; + dev->dev.speed = USB_SPEED_FULL; dev->dev.handle_packet = usb_generic_handle_packet; dev->dev.handle_reset = usb_host_handle_reset; --- qemu/vl.c 2006-10-31 19:44:16.000000000 -0600 +++ qemu/vl.c 2006-11-18 20:32:20.000000000 -0600 @@ -3743,6 +3743,10 @@ static USBPort *used_usb_ports; static USBPort *free_usb_ports; +#if defined (__linux__) +void usb_linux_sig_handler(int, siginfo_t *, void *); +#endif + /* ??? Maybe change this to register a hub to keep track of the topology. */ void qemu_register_usb_port(USBPort *port, void *opaque, int index, usb_attachfn attach) @@ -6775,6 +6779,19 @@ socket_init(); #endif +#if defined (__linux__) +if (usb_enabled) { + struct sigaction usb_linux_sa; + usb_linux_sa.sa_sigaction = usb_linux_sig_handler; + sigfillset(&usb_linux_sa.sa_mask); + usb_linux_sa.sa_flags = SA_SIGINFO; +#if defined (TARGET_I386) && defined(USE_CODE_COPY) + usb_linux_sa.sa_flags |= SA_ONSTACK; +#endif + sigaction(USB_ASYNC_COMPLETION_SIGNAL, &usb_linux_sa, NULL); +} +#endif + /* init network clients */ if (nb_net_clients == 0) { /* if no clients, we use a default config */ --- qemu/vl.h 2006-09-24 13:49:43.000000000 -0500 +++ qemu/vl.h 2006-11-18 20:18:30.000000000 -0600 @@ -1181,6 +1181,10 @@ #include "hw/usb.h" +#if defined(__linux__) +#define USB_ASYNC_COMPLETION_SIGNAL (SIGRTMIN + 5) +#endif + /* usb ports of the VM */ void qemu_register_usb_port(USBPort *port, void *opaque, int index,