/* * Linux host USB redirector * * Copyright (c) 2005 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "vl.h" #if defined(__linux__) #include #include #include #include /* We redefine it to avoid version problems */ struct usb_ctrltransfer { uint8_t bRequestType; uint8_t bRequest; uint16_t wValue; uint16_t wIndex; uint16_t wLength; uint32_t timeout; void *data; }; //#define DEBUG #define MAX_DEVICES 8 #define USBDEVFS_PATH "/proc/bus/usb" typedef struct USBHostDevice { USBDevice dev; int fd; } USBHostDevice; typedef struct USBHostHubState { USBDevice *hub_dev; USBPort *hub_ports[MAX_DEVICES]; USBDevice *hub_devices[MAX_DEVICES]; } USBHostHubState; 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 */ ioctl(s->fd, USBDEVFS_RESET); #endif } static int usb_host_handle_control(USBDevice *dev, int request, int value, int index, int length, uint8_t *data) { USBHostDevice *s = (USBHostDevice *)dev; struct usb_ctrltransfer ct; int ret; if (request == (DeviceOutRequest | USB_REQ_SET_ADDRESS)) { /* specific SET_ADDRESS support */ dev->addr = value; return 0; } else { ct.bRequestType = request >> 8; ct.bRequest = request; ct.wValue = value; ct.wIndex = index; ct.wLength = length; ct.timeout = 50; ct.data = data; ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); if (ret < 0) { switch(errno) { case ETIMEDOUT: return USB_RET_NAK; default: return USB_RET_STALL; } } else { return ret; } } } static int usb_host_handle_data(USBDevice *dev, int pid, uint8_t devep, uint8_t *data, int len) { USBHostDevice *s = (USBHostDevice *)dev; struct usbdevfs_bulktransfer bt; int ret; /* XXX: optimize and handle all data types by looking at the config descriptor */ if (pid == USB_TOKEN_IN) devep |= 0x80; bt.ep = devep; bt.len = len; bt.timeout = 50; bt.data = data; ret = ioctl(s->fd, USBDEVFS_BULK, &bt); if (ret < 0) { switch(errno) { case ETIMEDOUT: return USB_RET_NAK; case EPIPE: default: #ifdef DEBUG printf("handle_data: errno=%d\n", errno); #endif return USB_RET_STALL; } } else { return ret; } } static int usb_host_handle_packet(USBDevice *dev, int pid, uint8_t devaddr, uint8_t devep, uint8_t *data, int len) { return usb_generic_handle_packet(dev, pid, devaddr, devep, data, len); } /* XXX: exclude high speed devices or implement EHCI */ static void scan_host_device(USBHostHubState *s, const char *filename) { int fd, interface, ret, i; USBHostDevice *dev; struct usbdevfs_connectinfo ci; uint8_t descr[1024]; int descr_len, dev_descr_len, config_descr_len, nb_interfaces; #ifdef DEBUG printf("scanning %s\n", filename); #endif fd = open(filename, O_RDWR); if (fd < 0) { perror(filename); return; } /* read the config description */ descr_len = read(fd, descr, sizeof(descr)); if (descr_len <= 0) { perror("read descr"); goto fail; } i = 0; dev_descr_len = descr[0]; if (dev_descr_len > descr_len) goto fail; i += dev_descr_len; config_descr_len = descr[i]; if (i + config_descr_len > descr_len) goto fail; nb_interfaces = descr[i + 4]; if (nb_interfaces != 1) { /* NOTE: currently we grab only one interface */ goto fail; } /* XXX: only grab if all interfaces are free */ interface = 0; ret = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface); if (ret < 0) { if (errno == EBUSY) { #ifdef DEBUG printf("%s already grabbed\n", filename); #endif } else { perror("USBDEVFS_CLAIMINTERFACE"); } fail: close(fd); return; } ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci); if (ret < 0) { perror("USBDEVFS_CONNECTINFO"); goto fail; } #ifdef DEBUG printf("%s grabbed\n", filename); #endif /* find a free slot */ for(i = 0; i < MAX_DEVICES; i++) { if (!s->hub_devices[i]) break; } if (i == MAX_DEVICES) { #ifdef DEBUG printf("too many host devices\n"); goto fail; #endif } dev = qemu_mallocz(sizeof(USBHostDevice)); if (!dev) goto fail; dev->fd = fd; if (ci.slow) dev->dev.speed = USB_SPEED_LOW; else dev->dev.speed = USB_SPEED_HIGH; dev->dev.handle_packet = usb_host_handle_packet; dev->dev.handle_reset = usb_host_handle_reset; dev->dev.handle_control = usb_host_handle_control; dev->dev.handle_data = usb_host_handle_data; s->hub_devices[i] = (USBDevice *)dev; /* activate device on hub */ usb_attach(s->hub_ports[i], s->hub_devices[i]); } static void scan_host_devices(USBHostHubState *s, const char *bus_path) { DIR *d; struct dirent *de; char buf[1024]; d = opendir(bus_path); if (!d) return; for(;;) { de = readdir(d); if (!de) break; if (de->d_name[0] != '.') { snprintf(buf, sizeof(buf), "%s/%s", bus_path, de->d_name); scan_host_device(s, buf); } } closedir(d); } static void scan_host_buses(USBHostHubState *s) { DIR *d; struct dirent *de; char buf[1024]; d = opendir(USBDEVFS_PATH); if (!d) return; for(;;) { de = readdir(d); if (!de) break; if (isdigit(de->d_name[0])) { snprintf(buf, sizeof(buf), "%s/%s", USBDEVFS_PATH, de->d_name); scan_host_devices(s, buf); } } closedir(d); } /* virtual hub containing the USB devices of the host */ USBDevice *usb_host_hub_init(void) { USBHostHubState *s; s = qemu_mallocz(sizeof(USBHostHubState)); if (!s) return NULL; s->hub_dev = usb_hub_init(s->hub_ports, MAX_DEVICES); if (!s->hub_dev) { free(s); return NULL; } scan_host_buses(s); return s->hub_dev; } #else /* XXX: modify configure to compile the right host driver */ USBDevice *usb_host_hub_init(void) { return NULL; } #endif