?? usb-linux.c
字號:
/* * 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 "qemu-common.h"#include "hw/usb.h"#include "console.h"#if defined(__linux__)#include <dirent.h>#include <sys/ioctl.h>#include <linux/usbdevice_fs.h>#include <linux/version.h>#include <signal.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;};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, char *product_name, int product_name_size, const char *devname);//#define DEBUG//#define DEBUG_ISOCH//#define USE_ASYNCIO#define USBDEVFS_PATH "/proc/bus/usb"#define PRODUCT_NAME_SZ 32#define SIG_ISOCOMPLETE (SIGRTMIN+7)#define MAX_ENDPOINTS 16struct sigaction sigact;/* endpoint association data */struct endp_data { uint8_t type;};/* FIXME: move USBPacket to PendingURB */typedef struct USBHostDevice { USBDevice dev; int fd; int pipe_fds[2]; USBPacket *packet; struct endp_data endp_table[MAX_ENDPOINTS]; int configuration; uint8_t descr[1024]; int descr_len; int urbs_ready;} USBHostDevice;typedef struct PendingURB { struct usbdevfs_urb *urb; int status; struct PendingURB *next;} PendingURB;static PendingURB *pending_urbs = NULL;static int add_pending_urb(struct usbdevfs_urb *urb){ 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;}static int del_pending_urb(struct usbdevfs_urb *urb){ PendingURB *purb = pending_urbs; PendingURB *prev = NULL; while (purb && purb->urb != urb) { prev = purb; purb = purb->next; } if (purb && purb->urb == urb) { if (prev) { prev->next = purb->next; } else { pending_urbs = purb->next; } qemu_free(purb); return 1; } return 0;}#ifdef USE_ASYNCIOstatic PendingURB *get_pending_urb(struct usbdevfs_urb *urb){ PendingURB *purb = pending_urbs; while (purb && purb->urb != urb) { purb = purb->next; } if (purb && purb->urb == urb) { return purb; } return NULL;}#endifstatic int usb_host_update_interfaces(USBHostDevice *dev, int configuration){ int dev_descr_len, config_descr_len; int interface, nb_interfaces, nb_configurations; int ret, i; if (configuration == 0) /* address state - ignore */ return 1; i = 0; dev_descr_len = dev->descr[0]; if (dev_descr_len > dev->descr_len) goto fail; nb_configurations = dev->descr[17]; 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, 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]; if (configuration == dev->descr[i + 5]) break; i += config_descr_len; } if (i >= dev->descr_len) { printf("usb_host: error - device has no matching configuration\n"); goto fail; } nb_interfaces = dev->descr[i + 4];#ifdef USBDEVFS_DISCONNECT /* earlier Linux 2.4 do not support that */ { struct usbdevfs_ioctl ctrl; for (interface = 0; interface < nb_interfaces; interface++) { ctrl.ioctl_code = USBDEVFS_DISCONNECT; ctrl.ifno = interface; ret = ioctl(dev->fd, USBDEVFS_IOCTL, &ctrl); if (ret < 0 && errno != ENODATA) { perror("USBDEVFS_DISCONNECT"); goto fail; } } }#endif /* XXX: only grab if all interfaces are free */ for (interface = 0; interface < nb_interfaces; interface++) { ret = ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &interface); if (ret < 0) { if (errno == EBUSY) { fprintf(stderr, "usb_host: warning - device already grabbed\n"); } else { perror("USBDEVFS_CLAIMINTERFACE"); } fail: return 0; } }#ifdef DEBUG printf("usb_host: %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 */ ioctl(s->fd, USBDEVFS_RESET);#endif}static void usb_host_handle_destroy(USBDevice *dev){ USBHostDevice *s = (USBHostDevice *)dev; if (s->fd >= 0) close(s->fd); qemu_free(s);}static int usb_linux_update_endp_table(USBHostDevice *s);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; struct usbdevfs_setinterface si; int intf_update_required = 0; int ret; if (request == (DeviceOutRequest | USB_REQ_SET_ADDRESS)) { /* specific SET_ADDRESS support */ dev->addr = value; return 0; } else if (request == ((USB_RECIP_INTERFACE << 8) | USB_REQ_SET_INTERFACE)) { /* set alternate setting for the interface */ si.interface = index; si.altsetting = value; 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 if (s->configuration != (value & 0xff)) { s->configuration = (value & 0xff); intf_update_required = 1; } goto do_request; } else { do_request: 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 { if (intf_update_required) {#ifdef DEBUG printf("usb_host_handle_control: updating interfaces\n");#endif 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; 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); } /* 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: 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_ASYNCIOstatic 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; } 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; if (info->si_code != SI_ASYNCIO || info->si_signo != SIG_ISOCOMPLETE) { return; } purb = get_pending_urb(urb); if (purb) { purb->status = info->si_errno; write(s->pipe_fds[1], &purb, sizeof(purb)); }}#endifstatic 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; if (p->pid == USB_TOKEN_IN) devep |= 0x80; 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; } 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; case EPIPE: default: 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; 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}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -