diff options
Diffstat (limited to 'usb-linux.c')
-rw-r--r-- | usb-linux.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/usb-linux.c b/usb-linux.c new file mode 100644 index 0000000000..87f3d120b9 --- /dev/null +++ b/usb-linux.c @@ -0,0 +1,309 @@ +/* + * 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 <dirent.h> +#include <sys/ioctl.h> +#include <linux/usbdevice_fs.h> +#include <linux/version.h> + +/* 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 |