| /* |
| * BSD host USB redirector |
| * |
| * Copyright (c) 2006 Lonnie Mendez |
| * Portions of code and concepts borrowed from |
| * usb-linux.c and libusb's bsd.c and are copyright their respective owners. |
| * |
| * 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 "qemu-common.h" |
| #include "monitor.h" |
| #include "hw/usb.h" |
| |
| /* usb.h declares these */ |
| #undef USB_SPEED_HIGH |
| #undef USB_SPEED_FULL |
| #undef USB_SPEED_LOW |
| |
| #include <sys/ioctl.h> |
| #ifndef __DragonFly__ |
| #include <dev/usb/usb.h> |
| #else |
| #include <bus/usb/usb.h> |
| #endif |
| |
| /* This value has maximum potential at 16. |
| * You should also set hw.usb.debug to gain |
| * more detailed view. |
| */ |
| //#define DEBUG |
| #define UGEN_DEBUG_LEVEL 0 |
| |
| |
| typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id, |
| int vendor_id, int product_id, |
| const char *product_name, int speed); |
| static int usb_host_find_device(int *pbus_num, int *paddr, |
| const char *devname); |
| |
| typedef struct USBHostDevice { |
| USBDevice dev; |
| int ep_fd[USB_MAX_ENDPOINTS]; |
| int devfd; |
| char devpath[32]; |
| } USBHostDevice; |
| |
| |
| static int ensure_ep_open(USBHostDevice *dev, int ep, int mode) |
| { |
| char buf[32]; |
| int fd; |
| |
| /* Get the address for this endpoint */ |
| ep = UE_GET_ADDR(ep); |
| |
| if (dev->ep_fd[ep] < 0) { |
| #if defined(__FreeBSD__) || defined(__DragonFly__) |
| snprintf(buf, sizeof(buf) - 1, "%s.%d", dev->devpath, ep); |
| #else |
| snprintf(buf, sizeof(buf) - 1, "%s.%02d", dev->devpath, ep); |
| #endif |
| /* Try to open it O_RDWR first for those devices which have in and out |
| * endpoints with the same address (eg 0x02 and 0x82) |
| */ |
| fd = open(buf, O_RDWR); |
| if (fd < 0 && errno == ENXIO) |
| fd = open(buf, mode); |
| if (fd < 0) { |
| #ifdef DEBUG |
| printf("ensure_ep_open: failed to open device endpoint %s: %s\n", |
| buf, strerror(errno)); |
| #endif |
| } |
| dev->ep_fd[ep] = fd; |
| } |
| |
| return dev->ep_fd[ep]; |
| } |
| |
| static void ensure_eps_closed(USBHostDevice *dev) |
| { |
| int epnum = 1; |
| |
| if (!dev) |
| return; |
| |
| while (epnum < USB_MAX_ENDPOINTS) { |
| if (dev->ep_fd[epnum] >= 0) { |
| close(dev->ep_fd[epnum]); |
| dev->ep_fd[epnum] = -1; |
| } |
| epnum++; |
| } |
| } |
| |
| static void usb_host_handle_reset(USBDevice *dev) |
| { |
| #if 0 |
| USBHostDevice *s = (USBHostDevice *)dev; |
| #endif |
| } |
| |
| /* XXX: |
| * -check device states against transfer requests |
| * and return appropriate response |
| */ |
| static int usb_host_handle_control(USBDevice *dev, |
| USBPacket *p, |
| int request, |
| int value, |
| int index, |
| int length, |
| uint8_t *data) |
| { |
| USBHostDevice *s = (USBHostDevice *)dev; |
| struct usb_ctl_request req; |
| struct usb_alt_interface aiface; |
| int ret, timeout = 50; |
| |
| if ((request >> 8) == UT_WRITE_DEVICE && |
| (request & 0xff) == UR_SET_ADDRESS) { |
| |
| /* specific SET_ADDRESS support */ |
| dev->addr = value; |
| return 0; |
| } else if ((request >> 8) == UT_WRITE_DEVICE && |
| (request & 0xff) == UR_SET_CONFIG) { |
| |
| ensure_eps_closed(s); /* can't do this without all eps closed */ |
| |
| ret = ioctl(s->devfd, USB_SET_CONFIG, &value); |
| if (ret < 0) { |
| #ifdef DEBUG |
| printf("handle_control: failed to set configuration - %s\n", |
| strerror(errno)); |
| #endif |
| return USB_RET_STALL; |
| } |
| |
| return 0; |
| } else if ((request >> 8) == UT_WRITE_INTERFACE && |
| (request & 0xff) == UR_SET_INTERFACE) { |
| |
| aiface.uai_interface_index = index; |
| aiface.uai_alt_no = value; |
| |
| ensure_eps_closed(s); /* can't do this without all eps closed */ |
| ret = ioctl(s->devfd, USB_SET_ALTINTERFACE, &aiface); |
| if (ret < 0) { |
| #ifdef DEBUG |
| printf("handle_control: failed to set alternate interface - %s\n", |
| strerror(errno)); |
| #endif |
| return USB_RET_STALL; |
| } |
| |
| return 0; |
| } else { |
| req.ucr_request.bmRequestType = request >> 8; |
| req.ucr_request.bRequest = request & 0xff; |
| USETW(req.ucr_request.wValue, value); |
| USETW(req.ucr_request.wIndex, index); |
| USETW(req.ucr_request.wLength, length); |
| req.ucr_data = data; |
| req.ucr_flags = USBD_SHORT_XFER_OK; |
| |
| ret = ioctl(s->devfd, USB_SET_TIMEOUT, &timeout); |
| #if defined(__NetBSD__) || defined(__OpenBSD__) |
| if (ret < 0 && errno != EINVAL) { |
| #else |
| if (ret < 0) { |
| #endif |
| #ifdef DEBUG |
| printf("handle_control: setting timeout failed - %s\n", |
| strerror(errno)); |
| #endif |
| } |
| |
| ret = ioctl(s->devfd, USB_DO_REQUEST, &req); |
| /* ugen returns EIO for usbd_do_request_ no matter what |
| * happens with the transfer */ |
| if (ret < 0) { |
| #ifdef DEBUG |
| printf("handle_control: error after request - %s\n", |
| strerror(errno)); |
| #endif |
| return USB_RET_NAK; // STALL |
| } else { |
| return req.ucr_actlen; |
| } |
| } |
| } |
| |
| static int usb_host_handle_data(USBDevice *dev, USBPacket *p) |
| { |
| USBHostDevice *s = (USBHostDevice *)dev; |
| int ret, fd, mode; |
| int one = 1, shortpacket = 0, timeout = 50; |
| sigset_t new_mask, old_mask; |
| uint8_t devep = p->devep; |
| |
| /* protect data transfers from SIGALRM signal */ |
| sigemptyset(&new_mask); |
| sigaddset(&new_mask, SIGALRM); |
| sigprocmask(SIG_BLOCK, &new_mask, &old_mask); |
| |
| if (p->pid == USB_TOKEN_IN) { |
| devep |= 0x80; |
| mode = O_RDONLY; |
| shortpacket = 1; |
| } else { |
| mode = O_WRONLY; |
| } |
| |
| fd = ensure_ep_open(s, devep, mode); |
| if (fd < 0) { |
| sigprocmask(SIG_SETMASK, &old_mask, NULL); |
| return USB_RET_NODEV; |
| } |
| |
| if (ioctl(fd, USB_SET_TIMEOUT, &timeout) < 0) { |
| #ifdef DEBUG |
| printf("handle_data: failed to set timeout - %s\n", |
| strerror(errno)); |
| #endif |
| } |
| |
| if (shortpacket) { |
| if (ioctl(fd, USB_SET_SHORT_XFER, &one) < 0) { |
| #ifdef DEBUG |
| printf("handle_data: failed to set short xfer mode - %s\n", |
| strerror(errno)); |
| #endif |
| sigprocmask(SIG_SETMASK, &old_mask, NULL); |
| } |
| } |
| |
| if (p->pid == USB_TOKEN_IN) |
| ret = readv(fd, p->iov.iov, p->iov.niov); |
| else |
| ret = writev(fd, p->iov.iov, p->iov.niov); |
| |
| sigprocmask(SIG_SETMASK, &old_mask, NULL); |
| |
| if (ret < 0) { |
| #ifdef DEBUG |
| printf("handle_data: error after %s data - %s\n", |
| pid == USB_TOKEN_IN ? "reading" : "writing", strerror(errno)); |
| #endif |
| switch(errno) { |
| case ETIMEDOUT: |
| case EINTR: |
| return USB_RET_NAK; |
| default: |
| return USB_RET_STALL; |
| } |
| } else { |
| return ret; |
| } |
| } |
| |
| static void usb_host_handle_destroy(USBDevice *opaque) |
| { |
| USBHostDevice *s = (USBHostDevice *)opaque; |
| int i; |
| |
| for (i = 0; i < USB_MAX_ENDPOINTS; i++) |
| if (s->ep_fd[i] >= 0) |
| close(s->ep_fd[i]); |
| |
| if (s->devfd < 0) |
| return; |
| |
| close(s->devfd); |
| |
| g_free(s); |
| } |
| |
| static int usb_host_initfn(USBDevice *dev) |
| { |
| return 0; |
| } |
| |
| USBDevice *usb_host_device_open(const char *devname) |
| { |
| struct usb_device_info bus_info, dev_info; |
| USBDevice *d = NULL, *ret = NULL; |
| USBHostDevice *dev; |
| char ctlpath[PATH_MAX + 1]; |
| char buspath[PATH_MAX + 1]; |
| int bfd, dfd, bus, address, i; |
| int ugendebug = UGEN_DEBUG_LEVEL; |
| |
| if (usb_host_find_device(&bus, &address, devname) < 0) { |
| goto fail; |
| } |
| |
| snprintf(buspath, PATH_MAX, "/dev/usb%d", bus); |
| |
| bfd = open(buspath, O_RDWR); |
| if (bfd < 0) { |
| #ifdef DEBUG |
| printf("usb_host_device_open: failed to open usb bus - %s\n", |
| strerror(errno)); |
| #endif |
| goto fail; |
| } |
| |
| bus_info.udi_addr = address; |
| if (ioctl(bfd, USB_DEVICEINFO, &bus_info) < 0) { |
| #ifdef DEBUG |
| printf("usb_host_device_open: failed to grab bus information - %s\n", |
| strerror(errno)); |
| #endif |
| goto fail_bfd; |
| } |
| |
| #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) |
| snprintf(ctlpath, PATH_MAX, "/dev/%s", bus_info.udi_devnames[0]); |
| #else |
| snprintf(ctlpath, PATH_MAX, "/dev/%s.00", bus_info.udi_devnames[0]); |
| #endif |
| |
| dfd = open(ctlpath, O_RDWR); |
| if (dfd < 0) { |
| dfd = open(ctlpath, O_RDONLY); |
| if (dfd < 0) { |
| #ifdef DEBUG |
| printf("usb_host_device_open: failed to open usb device %s - %s\n", |
| ctlpath, strerror(errno)); |
| #endif |
| } |
| goto fail_dfd; |
| } |
| |
| if (ioctl(dfd, USB_GET_DEVICEINFO, &dev_info) < 0) { |
| #ifdef DEBUG |
| printf("usb_host_device_open: failed to grab device info - %s\n", |
| strerror(errno)); |
| #endif |
| goto fail_dfd; |
| } |
| |
| d = usb_create(NULL /* FIXME */, "usb-host"); |
| dev = DO_UPCAST(USBHostDevice, dev, d); |
| |
| if (dev_info.udi_speed == 1) { |
| dev->dev.speed = USB_SPEED_LOW - 1; |
| dev->dev.speedmask = USB_SPEED_MASK_LOW; |
| } else { |
| dev->dev.speed = USB_SPEED_FULL - 1; |
| dev->dev.speedmask = USB_SPEED_MASK_FULL; |
| } |
| |
| if (strncmp(dev_info.udi_product, "product", 7) != 0) { |
| pstrcpy(dev->dev.product_desc, sizeof(dev->dev.product_desc), |
| dev_info.udi_product); |
| } else { |
| snprintf(dev->dev.product_desc, sizeof(dev->dev.product_desc), |
| "host:%s", devname); |
| } |
| |
| pstrcpy(dev->devpath, sizeof(dev->devpath), "/dev/"); |
| pstrcat(dev->devpath, sizeof(dev->devpath), dev_info.udi_devnames[0]); |
| |
| /* Mark the endpoints as not yet open */ |
| for (i = 0; i < USB_MAX_ENDPOINTS; i++) { |
| dev->ep_fd[i] = -1; |
| } |
| |
| ioctl(dfd, USB_SETDEBUG, &ugendebug); |
| |
| ret = (USBDevice *)dev; |
| |
| fail_dfd: |
| close(dfd); |
| fail_bfd: |
| close(bfd); |
| fail: |
| return ret; |
| } |
| |
| static struct USBDeviceInfo usb_host_dev_info = { |
| .product_desc = "USB Host Device", |
| .qdev.name = "usb-host", |
| .qdev.size = sizeof(USBHostDevice), |
| .init = usb_host_initfn, |
| .handle_packet = usb_generic_handle_packet, |
| .handle_reset = usb_host_handle_reset, |
| .handle_control = usb_host_handle_control, |
| .handle_data = usb_host_handle_data, |
| .handle_destroy = usb_host_handle_destroy, |
| }; |
| |
| static void usb_host_register_devices(void) |
| { |
| usb_qdev_register(&usb_host_dev_info); |
| } |
| device_init(usb_host_register_devices) |
| |
| static int usb_host_scan(void *opaque, USBScanFunc *func) |
| { |
| struct usb_device_info bus_info; |
| struct usb_device_info dev_info; |
| uint16_t vendor_id, product_id, class_id, speed; |
| int bfd, dfd, bus, address; |
| char busbuf[20], devbuf[20], product_name[256]; |
| int ret = 0; |
| |
| for (bus = 0; bus < 10; bus++) { |
| |
| snprintf(busbuf, sizeof(busbuf) - 1, "/dev/usb%d", bus); |
| bfd = open(busbuf, O_RDWR); |
| if (bfd < 0) |
| continue; |
| |
| for (address = 1; address < 127; address++) { |
| |
| bus_info.udi_addr = address; |
| if (ioctl(bfd, USB_DEVICEINFO, &bus_info) < 0) |
| continue; |
| |
| /* only list devices that can be used by generic layer */ |
| if (strncmp(bus_info.udi_devnames[0], "ugen", 4) != 0) |
| continue; |
| |
| #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) |
| snprintf(devbuf, sizeof(devbuf) - 1, "/dev/%s", bus_info.udi_devnames[0]); |
| #else |
| snprintf(devbuf, sizeof(devbuf) - 1, "/dev/%s.00", bus_info.udi_devnames[0]); |
| #endif |
| |
| dfd = open(devbuf, O_RDONLY); |
| if (dfd < 0) { |
| #ifdef DEBUG |
| printf("usb_host_scan: couldn't open device %s - %s\n", devbuf, |
| strerror(errno)); |
| #endif |
| continue; |
| } |
| |
| if (ioctl(dfd, USB_GET_DEVICEINFO, &dev_info) < 0) |
| printf("usb_host_scan: couldn't get device information for %s - %s\n", |
| devbuf, strerror(errno)); |
| |
| /* XXX: might need to fixup endianness of word values before copying over */ |
| |
| vendor_id = dev_info.udi_vendorNo; |
| product_id = dev_info.udi_productNo; |
| class_id = dev_info.udi_class; |
| speed = dev_info.udi_speed; |
| |
| if (strncmp(dev_info.udi_product, "product", 7) != 0) |
| pstrcpy(product_name, sizeof(product_name), |
| dev_info.udi_product); |
| else |
| product_name[0] = '\0'; |
| |
| ret = func(opaque, bus, address, class_id, vendor_id, |
| product_id, product_name, speed); |
| |
| close(dfd); |
| |
| if (ret) |
| goto the_end; |
| } |
| |
| close(bfd); |
| } |
| |
| the_end: |
| return ret; |
| } |
| |
| typedef struct FindDeviceState { |
| int vendor_id; |
| int product_id; |
| int bus_num; |
| int addr; |
| } FindDeviceState; |
| |
| static int usb_host_find_device_scan(void *opaque, int bus_num, int addr, |
| int class_id, |
| int vendor_id, int product_id, |
| const char *product_name, int speed) |
| { |
| FindDeviceState *s = opaque; |
| if (vendor_id == s->vendor_id && |
| product_id == s->product_id) { |
| s->bus_num = bus_num; |
| s->addr = addr; |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| |
| /* the syntax is : |
| 'bus.addr' (decimal numbers) or |
| 'vendor_id:product_id' (hexa numbers) */ |
| static int usb_host_find_device(int *pbus_num, int *paddr, |
| const char *devname) |
| { |
| const char *p; |
| int ret; |
| FindDeviceState fs; |
| |
| p = strchr(devname, '.'); |
| if (p) { |
| *pbus_num = strtoul(devname, NULL, 0); |
| *paddr = strtoul(p + 1, NULL, 0); |
| return 0; |
| } |
| p = strchr(devname, ':'); |
| if (p) { |
| fs.vendor_id = strtoul(devname, NULL, 16); |
| fs.product_id = strtoul(p + 1, NULL, 16); |
| ret = usb_host_scan(&fs, usb_host_find_device_scan); |
| if (ret) { |
| *pbus_num = fs.bus_num; |
| *paddr = fs.addr; |
| return 0; |
| } |
| } |
| return -1; |
| } |
| |
| /**********************/ |
| /* USB host device info */ |
| |
| struct usb_class_info { |
| int class; |
| const char *class_name; |
| }; |
| |
| static const struct usb_class_info usb_class_info[] = { |
| { USB_CLASS_AUDIO, "Audio"}, |
| { USB_CLASS_COMM, "Communication"}, |
| { USB_CLASS_HID, "HID"}, |
| { USB_CLASS_HUB, "Hub" }, |
| { USB_CLASS_PHYSICAL, "Physical" }, |
| { USB_CLASS_PRINTER, "Printer" }, |
| { USB_CLASS_MASS_STORAGE, "Storage" }, |
| { USB_CLASS_CDC_DATA, "Data" }, |
| { USB_CLASS_APP_SPEC, "Application Specific" }, |
| { USB_CLASS_VENDOR_SPEC, "Vendor Specific" }, |
| { USB_CLASS_STILL_IMAGE, "Still Image" }, |
| { USB_CLASS_CSCID, "Smart Card" }, |
| { USB_CLASS_CONTENT_SEC, "Content Security" }, |
| { -1, NULL } |
| }; |
| |
| static const char *usb_class_str(uint8_t class) |
| { |
| const struct usb_class_info *p; |
| for (p = usb_class_info; p->class != -1; p++) { |
| if (p->class == class) |
| break; |
| } |
| return p->class_name; |
| } |
| |
| static void usb_info_device(Monitor *mon, int bus_num, int addr, int class_id, |
| int vendor_id, int product_id, |
| const char *product_name, |
| int speed) |
| { |
| const char *class_str, *speed_str; |
| |
| switch(speed) { |
| case USB_SPEED_LOW: |
| speed_str = "1.5"; |
| break; |
| case USB_SPEED_FULL: |
| speed_str = "12"; |
| break; |
| case USB_SPEED_HIGH: |
| speed_str = "480"; |
| break; |
| default: |
| speed_str = "?"; |
| break; |
| } |
| |
| monitor_printf(mon, " Device %d.%d, speed %s Mb/s\n", |
| bus_num, addr, speed_str); |
| class_str = usb_class_str(class_id); |
| if (class_str) |
| monitor_printf(mon, " %s:", class_str); |
| else |
| monitor_printf(mon, " Class %02x:", class_id); |
| monitor_printf(mon, " USB device %04x:%04x", vendor_id, product_id); |
| if (product_name[0] != '\0') |
| monitor_printf(mon, ", %s", product_name); |
| monitor_printf(mon, "\n"); |
| } |
| |
| static int usb_host_info_device(void *opaque, |
| int bus_num, int addr, |
| int class_id, |
| int vendor_id, int product_id, |
| const char *product_name, |
| int speed) |
| { |
| Monitor *mon = opaque; |
| |
| usb_info_device(mon, bus_num, addr, class_id, vendor_id, product_id, |
| product_name, speed); |
| return 0; |
| } |
| |
| void usb_host_info(Monitor *mon) |
| { |
| usb_host_scan(mon, usb_host_info_device); |
| } |
| |
| /* XXX add this */ |
| int usb_host_device_close(const char *devname) |
| { |
| return 0; |
| } |