X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=usb-linux.c;h=1a2deb35c972cf3b8b107813e9d7bafe9a138fc9;hb=c54ed5bcdd8ed29f9cdfcfc0e456b6ec1f25d2c3;hp=2b7b09214e620ba40063ff1ff649b844391050c1;hpb=22f84e73d81a1b99a48b9bc4e3663fdfadfa7da1;p=qemu.git diff --git a/usb-linux.c b/usb-linux.c index 2b7b09214..1a2deb35c 100644 --- a/usb-linux.c +++ b/usb-linux.c @@ -33,10 +33,10 @@ #include "qemu-common.h" #include "qemu-timer.h" #include "monitor.h" +#include "sysemu.h" #include #include -#include #include #include @@ -53,33 +53,24 @@ struct usb_ctrltransfer { void *data; }; -struct usb_ctrlrequest { - uint8_t bRequestType; - uint8_t bRequest; - uint16_t wValue; - uint16_t wIndex; - uint16_t wLength; -}; - -typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id, - int vendor_id, int product_id, +typedef int USBScanFunc(void *opaque, int bus_num, int addr, char *port, + 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 #ifdef DEBUG -#define dprintf printf +#define DPRINTF printf #else -#define dprintf(...) +#define DPRINTF(...) #endif #define USBDBG_DEVOPENED "husb: opened %s/devices\n" #define USBPROCBUS_PATH "/proc/bus/usb" #define PRODUCT_NAME_SZ 32 -#define MAX_ENDPOINTS 16 +#define MAX_ENDPOINTS 15 +#define MAX_PORTLEN 16 #define USBDEVBUS_PATH "/dev/bus/usb" #define USBSYSBUS_PATH "/sys/bus/usb" @@ -93,237 +84,332 @@ static char *usb_host_device_path; static int usb_fs_type; /* endpoint association data */ +#define ISO_FRAME_DESC_PER_URB 32 +#define INVALID_EP_TYPE 255 + +/* devio.c limits single requests to 16k */ +#define MAX_USBFS_BUFFER_SIZE 16384 + +typedef struct AsyncURB AsyncURB; + struct endp_data { uint8_t type; uint8_t halted; + uint8_t iso_started; + AsyncURB *iso_urb; + int iso_urb_idx; + int iso_buffer_used; + int max_packet_size; + int inflight; }; -enum { - CTRL_STATE_IDLE = 0, - CTRL_STATE_SETUP, - CTRL_STATE_DATA, - CTRL_STATE_ACK -}; - -/* - * Control transfer state. - * Note that 'buffer' _must_ follow 'req' field because - * we need contigious buffer when we submit control URB. - */ -struct ctrl_struct { - uint16_t len; - uint16_t offset; - uint8_t state; - struct usb_ctrlrequest req; - uint8_t buffer[2048]; +struct USBAutoFilter { + uint32_t bus_num; + uint32_t addr; + char *port; + uint32_t vendor_id; + uint32_t product_id; }; typedef struct USBHostDevice { USBDevice dev; int fd; - uint8_t descr[1024]; + uint8_t descr[8192]; int descr_len; int configuration; int ninterfaces; int closing; + uint32_t iso_urb_count; + Notifier exit; - struct ctrl_struct ctrl; struct endp_data endp_table[MAX_ENDPOINTS]; + QLIST_HEAD(, AsyncURB) aurbs; /* Host side address */ int bus_num; int addr; + char port[MAX_PORTLEN]; + struct USBAutoFilter match; - struct USBHostDevice *next; + QTAILQ_ENTRY(USBHostDevice) next; } USBHostDevice; +static QTAILQ_HEAD(, USBHostDevice) hostdevs = QTAILQ_HEAD_INITIALIZER(hostdevs); + +static int usb_host_close(USBHostDevice *dev); +static int parse_filter(const char *spec, struct USBAutoFilter *f); +static void usb_host_auto_check(void *unused); +static int usb_host_read_file(char *line, size_t line_size, + const char *device_file, const char *device_name); + +static struct endp_data *get_endp(USBHostDevice *s, int ep) +{ + return s->endp_table + ep - 1; +} + static int is_isoc(USBHostDevice *s, int ep) { - return s->endp_table[ep - 1].type == USBDEVFS_URB_TYPE_ISO; + return get_endp(s, ep)->type == USBDEVFS_URB_TYPE_ISO; +} + +static int is_valid(USBHostDevice *s, int ep) +{ + return get_endp(s, ep)->type != INVALID_EP_TYPE; } static int is_halted(USBHostDevice *s, int ep) { - return s->endp_table[ep - 1].halted; + return get_endp(s, ep)->halted; } static void clear_halt(USBHostDevice *s, int ep) { - s->endp_table[ep - 1].halted = 0; + get_endp(s, ep)->halted = 0; } static void set_halt(USBHostDevice *s, int ep) { - s->endp_table[ep - 1].halted = 1; + get_endp(s, ep)->halted = 1; } -static USBHostDevice *hostdev_list; +static int is_iso_started(USBHostDevice *s, int ep) +{ + return get_endp(s, ep)->iso_started; +} + +static void clear_iso_started(USBHostDevice *s, int ep) +{ + get_endp(s, ep)->iso_started = 0; +} -static void hostdev_link(USBHostDevice *dev) +static void set_iso_started(USBHostDevice *s, int ep) { - dev->next = hostdev_list; - hostdev_list = dev; + struct endp_data *e = get_endp(s, ep); + if (!e->iso_started) { + e->iso_started = 1; + e->inflight = 0; + } } -static void hostdev_unlink(USBHostDevice *dev) +static int change_iso_inflight(USBHostDevice *s, int ep, int value) { - USBHostDevice *pdev = hostdev_list; - USBHostDevice **prev = &hostdev_list; + struct endp_data *e = get_endp(s, ep); - while (pdev) { - if (pdev == dev) { - *prev = dev->next; - return; - } + e->inflight += value; + return e->inflight; +} - prev = &pdev->next; - pdev = pdev->next; - } +static void set_iso_urb(USBHostDevice *s, int ep, AsyncURB *iso_urb) +{ + get_endp(s, ep)->iso_urb = iso_urb; +} + +static AsyncURB *get_iso_urb(USBHostDevice *s, int ep) +{ + return get_endp(s, ep)->iso_urb; +} + +static void set_iso_urb_idx(USBHostDevice *s, int ep, int i) +{ + get_endp(s, ep)->iso_urb_idx = i; } -static USBHostDevice *hostdev_find(int bus_num, int addr) +static int get_iso_urb_idx(USBHostDevice *s, int ep) { - USBHostDevice *s = hostdev_list; - while (s) { - if (s->bus_num == bus_num && s->addr == addr) - return s; - s = s->next; + return get_endp(s, ep)->iso_urb_idx; +} + +static void set_iso_buffer_used(USBHostDevice *s, int ep, int i) +{ + get_endp(s, ep)->iso_buffer_used = i; +} + +static int get_iso_buffer_used(USBHostDevice *s, int ep) +{ + return get_endp(s, ep)->iso_buffer_used; +} + +static void set_max_packet_size(USBHostDevice *s, int ep, uint8_t *descriptor) +{ + int raw = descriptor[4] + (descriptor[5] << 8); + int size, microframes; + + size = raw & 0x7ff; + switch ((raw >> 11) & 3) { + case 1: microframes = 2; break; + case 2: microframes = 3; break; + default: microframes = 1; break; } - return NULL; + get_endp(s, ep)->max_packet_size = size * microframes; } -/* +static int get_max_packet_size(USBHostDevice *s, int ep) +{ + return get_endp(s, ep)->max_packet_size; +} + +/* * Async URB state. - * We always allocate one isoc descriptor even for bulk transfers - * to simplify allocation and casts. + * We always allocate iso packet descriptors even for bulk transfers + * to simplify allocation and casts. */ -typedef struct AsyncURB +struct AsyncURB { struct usbdevfs_urb urb; - struct usbdevfs_iso_packet_desc isocpd; + struct usbdevfs_iso_packet_desc isocpd[ISO_FRAME_DESC_PER_URB]; + USBHostDevice *hdev; + QLIST_ENTRY(AsyncURB) next; + /* For regular async urbs */ USBPacket *packet; - USBHostDevice *hdev; -} AsyncURB; + int more; /* large transfer, more urbs follow */ -static AsyncURB *async_alloc(void) + /* For buffered iso handling */ + int iso_frame_idx; /* -1 means in flight */ +}; + +static AsyncURB *async_alloc(USBHostDevice *s) { - return (AsyncURB *) qemu_mallocz(sizeof(AsyncURB)); + AsyncURB *aurb = qemu_mallocz(sizeof(AsyncURB)); + aurb->hdev = s; + QLIST_INSERT_HEAD(&s->aurbs, aurb, next); + return aurb; } static void async_free(AsyncURB *aurb) { + QLIST_REMOVE(aurb, next); qemu_free(aurb); } -static void async_complete_ctrl(USBHostDevice *s, USBPacket *p) +static void do_disconnect(USBHostDevice *s) { - switch(s->ctrl.state) { - case CTRL_STATE_SETUP: - if (p->len < s->ctrl.len) - s->ctrl.len = p->len; - s->ctrl.state = CTRL_STATE_DATA; - p->len = 8; - break; - - case CTRL_STATE_ACK: - s->ctrl.state = CTRL_STATE_IDLE; - p->len = 0; - break; - - default: - break; - } + printf("husb: device %d.%d disconnected\n", + s->bus_num, s->addr); + usb_host_close(s); + usb_host_auto_check(NULL); } static void async_complete(void *opaque) { USBHostDevice *s = opaque; AsyncURB *aurb; + int urbs = 0; while (1) { - USBPacket *p; + USBPacket *p; - int r = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &aurb); + int r = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &aurb); if (r < 0) { - if (errno == EAGAIN) + if (errno == EAGAIN) { + if (urbs > 2) { + fprintf(stderr, "husb: %d iso urbs finished at once\n", urbs); + } return; - + } if (errno == ENODEV && !s->closing) { - printf("husb: device %d.%d disconnected\n", s->bus_num, s->addr); - usb_device_delete_addr(s->bus_num, s->dev.addr); + do_disconnect(s); return; } - dprintf("husb: async. reap urb failed errno %d\n", errno); + DPRINTF("husb: async. reap urb failed errno %d\n", errno); return; } - p = aurb->packet; - - dprintf("husb: async completed. aurb %p status %d alen %d\n", + DPRINTF("husb: async completed. aurb %p status %d alen %d\n", aurb, aurb->urb.status, aurb->urb.actual_length); - if (p) { + /* If this is a buffered iso urb mark it as complete and don't do + anything else (it is handled further in usb_host_handle_iso_data) */ + if (aurb->iso_frame_idx == -1) { + int inflight; + if (aurb->urb.status == -EPIPE) { + set_halt(s, aurb->urb.endpoint & 0xf); + } + aurb->iso_frame_idx = 0; + urbs++; + inflight = change_iso_inflight(s, aurb->urb.endpoint & 0xf, -1); + if (inflight == 0 && is_iso_started(s, aurb->urb.endpoint & 0xf)) { + fprintf(stderr, "husb: out of buffers for iso stream\n"); + } + continue; + } + + p = aurb->packet; + + if (p) { switch (aurb->urb.status) { case 0: - p->len = aurb->urb.actual_length; - if (aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL) - async_complete_ctrl(s, p); + p->len += aurb->urb.actual_length; break; case -EPIPE: set_halt(s, p->devep); - /* fall through */ + p->len = USB_RET_STALL; + break; + default: p->len = USB_RET_NAK; break; } - usb_packet_complete(p); - } + if (aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL) { + usb_generic_async_ctrl_complete(&s->dev, p); + } else if (!aurb->more) { + usb_packet_complete(&s->dev, p); + } + } async_free(aurb); } } -static void async_cancel(USBPacket *unused, void *opaque) +static void usb_host_async_cancel(USBDevice *dev, USBPacket *p) { - AsyncURB *aurb = opaque; - USBHostDevice *s = aurb->hdev; + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + AsyncURB *aurb; - dprintf("husb: async cancel. aurb %p\n", aurb); + QLIST_FOREACH(aurb, &s->aurbs, next) { + if (p != aurb->packet) { + continue; + } - /* Mark it as dead (see async_complete above) */ - aurb->packet = NULL; + DPRINTF("husb: async cancel: packet %p, aurb %p\n", p, aurb); - int r = ioctl(s->fd, USBDEVFS_DISCARDURB, aurb); - if (r < 0) { - dprintf("husb: async. discard urb failed errno %d\n", errno); + /* Mark it as dead (see async_complete above) */ + aurb->packet = NULL; + + int r = ioctl(s->fd, USBDEVFS_DISCARDURB, aurb); + if (r < 0) { + DPRINTF("husb: async. discard urb failed errno %d\n", errno); + } } } static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration) { + const char *op = NULL; int dev_descr_len, config_descr_len; - int interface, nb_interfaces, nb_configurations; + int interface, nb_interfaces; int ret, i; if (configuration == 0) /* address state - ignore */ return 1; - dprintf("husb: claiming interfaces. config %d\n", configuration); + DPRINTF("husb: claiming interfaces. config %d\n", configuration); i = 0; dev_descr_len = dev->descr[0]; - if (dev_descr_len > dev->descr_len) - goto fail; - nb_configurations = dev->descr[17]; + if (dev_descr_len > dev->descr_len) { + fprintf(stderr, "husb: update iface failed. descr too short\n"); + return 0; + } i += dev_descr_len; while (i < dev->descr_len) { - dprintf("husb: i is %d, descr_len is %d, dl %d, dt %d\n", i, dev->descr_len, + DPRINTF("husb: i is %d, descr_len is %d, dl %d, dt %d\n", + i, dev->descr_len, dev->descr[i], dev->descr[i+1]); if (dev->descr[i+1] != USB_DT_CONFIG) { @@ -332,7 +418,7 @@ static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration) } config_descr_len = dev->descr[i]; - printf("husb: config #%d need %d\n", dev->descr[i + 5], configuration); + printf("husb: config #%d need %d\n", dev->descr[i + 5], configuration); if (configuration < 0 || configuration == dev->descr[i + 5]) { configuration = dev->descr[i + 5]; @@ -343,8 +429,9 @@ static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration) } if (i >= dev->descr_len) { - fprintf(stderr, "husb: update iface failed. no matching configuration\n"); - goto fail; + fprintf(stderr, + "husb: update iface failed. no matching configuration\n"); + return 0; } nb_interfaces = dev->descr[i + 4]; @@ -355,9 +442,10 @@ static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration) for (interface = 0; interface < nb_interfaces; interface++) { ctrl.ioctl_code = USBDEVFS_DISCONNECT; ctrl.ifno = interface; + ctrl.data = 0; + op = "USBDEVFS_DISCONNECT"; ret = ioctl(dev->fd, USBDEVFS_IOCTL, &ctrl); if (ret < 0 && errno != ENODATA) { - perror("USBDEVFS_DISCONNECT"); goto fail; } } @@ -366,6 +454,7 @@ static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration) /* XXX: only grab if all interfaces are free */ for (interface = 0; interface < nb_interfaces; interface++) { + op = "USBDEVFS_CLAIMINTERFACE"; ret = ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &interface); if (ret < 0) { if (errno == EBUSY) { @@ -373,8 +462,7 @@ static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration) } else { perror("husb: failed to claim interface"); } - fail: - return 0; + goto fail; } } @@ -384,13 +472,20 @@ static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration) dev->ninterfaces = nb_interfaces; dev->configuration = configuration; return 1; + +fail: + if (errno == ENODEV) { + do_disconnect(dev); + } + perror(op); + return 0; } static int usb_host_release_interfaces(USBHostDevice *s) { int ret, i; - dprintf("husb: releasing interfaces\n"); + DPRINTF("husb: releasing interfaces\n"); for (i = 0; i < s->ninterfaces; i++) { ret = ioctl(s->fd, USBDEVFS_RELEASEINTERFACE, &i); @@ -405,9 +500,9 @@ static int usb_host_release_interfaces(USBHostDevice *s) static void usb_host_handle_reset(USBDevice *dev) { - USBHostDevice *s = (USBHostDevice *) dev; + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); - dprintf("husb: reset device %u.%u\n", s->bus_num, s->addr); + DPRINTF("husb: reset device %u.%u\n", s->bus_num, s->addr); ioctl(s->fd, USBDEVFS_RESET); @@ -418,97 +513,284 @@ static void usb_host_handle_destroy(USBDevice *dev) { USBHostDevice *s = (USBHostDevice *)dev; - s->closing = 1; + usb_host_close(s); + QTAILQ_REMOVE(&hostdevs, s, next); + qemu_remove_exit_notifier(&s->exit); +} - qemu_set_fd_handler(s->fd, NULL, NULL, NULL); +static int usb_linux_update_endp_table(USBHostDevice *s); - hostdev_unlink(s); +/* iso data is special, we need to keep enough urbs in flight to make sure + that the controller never runs out of them, otherwise the device will + likely suffer a buffer underrun / overrun. */ +static AsyncURB *usb_host_alloc_iso(USBHostDevice *s, uint8_t ep, int in) +{ + AsyncURB *aurb; + int i, j, len = get_max_packet_size(s, ep); + + aurb = qemu_mallocz(s->iso_urb_count * sizeof(*aurb)); + for (i = 0; i < s->iso_urb_count; i++) { + aurb[i].urb.endpoint = ep; + aurb[i].urb.buffer_length = ISO_FRAME_DESC_PER_URB * len; + aurb[i].urb.buffer = qemu_malloc(aurb[i].urb.buffer_length); + aurb[i].urb.type = USBDEVFS_URB_TYPE_ISO; + aurb[i].urb.flags = USBDEVFS_URB_ISO_ASAP; + aurb[i].urb.number_of_packets = ISO_FRAME_DESC_PER_URB; + for (j = 0 ; j < ISO_FRAME_DESC_PER_URB; j++) + aurb[i].urb.iso_frame_desc[j].length = len; + if (in) { + aurb[i].urb.endpoint |= 0x80; + /* Mark as fully consumed (idle) */ + aurb[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB; + } + } + set_iso_urb(s, ep, aurb); + + return aurb; +} + +static void usb_host_stop_n_free_iso(USBHostDevice *s, uint8_t ep) +{ + AsyncURB *aurb; + int i, ret, killed = 0, free = 1; + + aurb = get_iso_urb(s, ep); + if (!aurb) { + return; + } - async_complete(s); + for (i = 0; i < s->iso_urb_count; i++) { + /* in flight? */ + if (aurb[i].iso_frame_idx == -1) { + ret = ioctl(s->fd, USBDEVFS_DISCARDURB, &aurb[i]); + if (ret < 0) { + printf("husb: discard isoc in urb failed errno %d\n", errno); + free = 0; + continue; + } + killed++; + } + } + + /* Make sure any urbs we've killed are reaped before we free them */ + if (killed) { + async_complete(s); + } - if (s->fd >= 0) - close(s->fd); + for (i = 0; i < s->iso_urb_count; i++) { + qemu_free(aurb[i].urb.buffer); + } - qemu_free(s); + if (free) + qemu_free(aurb); + else + printf("husb: leaking iso urbs because of discard failure\n"); + set_iso_urb(s, ep, NULL); + set_iso_urb_idx(s, ep, 0); + clear_iso_started(s, ep); } -static int usb_linux_update_endp_table(USBHostDevice *s); +static int urb_status_to_usb_ret(int status) +{ + switch (status) { + case -EPIPE: + return USB_RET_STALL; + default: + return USB_RET_NAK; + } +} -static int usb_host_handle_data(USBHostDevice *s, USBPacket *p) +static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in) { - struct usbdevfs_urb *urb; AsyncURB *aurb; - int ret; + int i, j, ret, max_packet_size, offset, len = 0; - aurb = async_alloc(); - aurb->hdev = s; - aurb->packet = p; + max_packet_size = get_max_packet_size(s, p->devep); + if (max_packet_size == 0) + return USB_RET_NAK; - urb = &aurb->urb; + aurb = get_iso_urb(s, p->devep); + if (!aurb) { + aurb = usb_host_alloc_iso(s, p->devep, in); + } - if (p->pid == USB_TOKEN_IN) - urb->endpoint = p->devep | 0x80; - else - urb->endpoint = p->devep; + i = get_iso_urb_idx(s, p->devep); + j = aurb[i].iso_frame_idx; + if (j >= 0 && j < ISO_FRAME_DESC_PER_URB) { + if (in) { + /* Check urb status */ + if (aurb[i].urb.status) { + len = urb_status_to_usb_ret(aurb[i].urb.status); + /* Move to the next urb */ + aurb[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB - 1; + /* Check frame status */ + } else if (aurb[i].urb.iso_frame_desc[j].status) { + len = urb_status_to_usb_ret( + aurb[i].urb.iso_frame_desc[j].status); + /* Check the frame fits */ + } else if (aurb[i].urb.iso_frame_desc[j].actual_length > p->len) { + printf("husb: received iso data is larger then packet\n"); + len = USB_RET_NAK; + /* All good copy data over */ + } else { + len = aurb[i].urb.iso_frame_desc[j].actual_length; + memcpy(p->data, + aurb[i].urb.buffer + + j * aurb[i].urb.iso_frame_desc[0].length, + len); + } + } else { + len = p->len; + offset = (j == 0) ? 0 : get_iso_buffer_used(s, p->devep); + + /* Check the frame fits */ + if (len > max_packet_size) { + printf("husb: send iso data is larger then max packet size\n"); + return USB_RET_NAK; + } + + /* All good copy data over */ + memcpy(aurb[i].urb.buffer + offset, p->data, len); + aurb[i].urb.iso_frame_desc[j].length = len; + offset += len; + set_iso_buffer_used(s, p->devep, offset); + + /* Start the stream once we have buffered enough data */ + if (!is_iso_started(s, p->devep) && i == 1 && j == 8) { + set_iso_started(s, p->devep); + } + } + aurb[i].iso_frame_idx++; + if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) { + i = (i + 1) % s->iso_urb_count; + set_iso_urb_idx(s, p->devep, i); + } + } else { + if (in) { + set_iso_started(s, p->devep); + } else { + DPRINTF("hubs: iso out error no free buffer, dropping packet\n"); + } + } + + if (is_iso_started(s, p->devep)) { + /* (Re)-submit all fully consumed / filled urbs */ + for (i = 0; i < s->iso_urb_count; i++) { + if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) { + ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &aurb[i]); + if (ret < 0) { + printf("husb error submitting iso urb %d: %d\n", i, errno); + if (!in || len == 0) { + switch(errno) { + case ETIMEDOUT: + len = USB_RET_NAK; + break; + case EPIPE: + default: + len = USB_RET_STALL; + } + } + break; + } + aurb[i].iso_frame_idx = -1; + change_iso_inflight(s, p->devep, +1); + } + } + } + + return len; +} + +static int usb_host_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + struct usbdevfs_urb *urb; + AsyncURB *aurb; + int ret, rem; + uint8_t *pbuf; + uint8_t ep; + + if (!is_valid(s, p->devep)) { + return USB_RET_NAK; + } + + if (p->pid == USB_TOKEN_IN) { + ep = p->devep | 0x80; + } else { + ep = p->devep; + } if (is_halted(s, p->devep)) { - ret = ioctl(s->fd, USBDEVFS_CLEAR_HALT, &urb->endpoint); + ret = ioctl(s->fd, USBDEVFS_CLEAR_HALT, &ep); if (ret < 0) { - dprintf("husb: failed to clear halt. ep 0x%x errno %d\n", - urb->endpoint, errno); + DPRINTF("husb: failed to clear halt. ep 0x%x errno %d\n", + ep, errno); return USB_RET_NAK; } clear_halt(s, p->devep); } - urb->buffer = p->data; - urb->buffer_length = p->len; - if (is_isoc(s, p->devep)) { - /* Setup ISOC transfer */ - urb->type = USBDEVFS_URB_TYPE_ISO; - urb->flags = USBDEVFS_URB_ISO_ASAP; - urb->number_of_packets = 1; - urb->iso_frame_desc[0].length = p->len; - } else { - /* Setup bulk transfer */ - urb->type = USBDEVFS_URB_TYPE_BULK; + return usb_host_handle_iso_data(s, p, p->pid == USB_TOKEN_IN); } - urb->usercontext = s; + rem = p->len; + pbuf = p->data; + p->len = 0; + while (rem) { + aurb = async_alloc(s); + aurb->packet = p; + + urb = &aurb->urb; + urb->endpoint = ep; + urb->type = USBDEVFS_URB_TYPE_BULK; + urb->usercontext = s; + urb->buffer = pbuf; + + if (rem > MAX_USBFS_BUFFER_SIZE) { + urb->buffer_length = MAX_USBFS_BUFFER_SIZE; + aurb->more = 1; + } else { + urb->buffer_length = rem; + aurb->more = 0; + } + pbuf += urb->buffer_length; + rem -= urb->buffer_length; - ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); + ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); - dprintf("husb: data submit. ep 0x%x len %u aurb %p\n", urb->endpoint, p->len, aurb); + DPRINTF("husb: data submit: ep 0x%x, len %u, more %d, packet %p, aurb %p\n", + urb->endpoint, urb->buffer_length, aurb->more, p, aurb); - if (ret < 0) { - dprintf("husb: submit failed. errno %d\n", errno); - async_free(aurb); + if (ret < 0) { + DPRINTF("husb: submit failed. errno %d\n", errno); + async_free(aurb); - switch(errno) { - case ETIMEDOUT: - return USB_RET_NAK; - case EPIPE: - default: - return USB_RET_STALL; + switch(errno) { + case ETIMEDOUT: + return USB_RET_NAK; + case EPIPE: + default: + return USB_RET_STALL; + } } } - usb_defer_packet(p, async_cancel, aurb); return USB_RET_ASYNC; } static int ctrl_error(void) { - if (errno == ETIMEDOUT) + if (errno == ETIMEDOUT) { return USB_RET_NAK; - else + } else { return USB_RET_STALL; + } } static int usb_host_set_address(USBHostDevice *s, int addr) { - dprintf("husb: ctrl set addr %u\n", addr); + DPRINTF("husb: ctrl set addr %u\n", addr); s->dev.addr = addr; return 0; } @@ -518,12 +800,12 @@ static int usb_host_set_config(USBHostDevice *s, int config) usb_host_release_interfaces(s); int ret = ioctl(s->fd, USBDEVFS_SETCONFIGURATION, &config); - - dprintf("husb: ctrl set config %d ret %d errno %d\n", config, ret, errno); - - if (ret < 0) + + DPRINTF("husb: ctrl set config %d ret %d errno %d\n", config, ret, errno); + + if (ret < 0) { return ctrl_error(); - + } usb_host_claim_interfaces(s, config); return 0; } @@ -531,89 +813,89 @@ static int usb_host_set_config(USBHostDevice *s, int config) static int usb_host_set_interface(USBHostDevice *s, int iface, int alt) { struct usbdevfs_setinterface si; - int ret; + int i, ret; + + for (i = 1; i <= MAX_ENDPOINTS; i++) { + if (is_isoc(s, i)) { + usb_host_stop_n_free_iso(s, i); + } + } si.interface = iface; si.altsetting = alt; ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si); - - dprintf("husb: ctrl set iface %d altset %d ret %d errno %d\n", - iface, alt, ret, errno); - - if (ret < 0) - return ctrl_error(); + DPRINTF("husb: ctrl set iface %d altset %d ret %d errno %d\n", + iface, alt, ret, errno); + + if (ret < 0) { + return ctrl_error(); + } usb_linux_update_endp_table(s); return 0; } -static int usb_host_handle_control(USBHostDevice *s, USBPacket *p) +static int usb_host_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) { + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); struct usbdevfs_urb *urb; AsyncURB *aurb; - int ret, value, index; - int buffer_len; + int ret; - /* + /* * Process certain standard device requests. * These are infrequent and are processed synchronously. */ - value = le16_to_cpu(s->ctrl.req.wValue); - index = le16_to_cpu(s->ctrl.req.wIndex); - dprintf("husb: ctrl type 0x%x req 0x%x val 0x%x index %u len %u\n", - s->ctrl.req.bRequestType, s->ctrl.req.bRequest, value, index, - s->ctrl.len); + /* Note request is (bRequestType << 8) | bRequest */ + DPRINTF("husb: ctrl type 0x%x req 0x%x val 0x%x index %u len %u\n", + request >> 8, request & 0xff, value, index, length); - if (s->ctrl.req.bRequestType == 0) { - switch (s->ctrl.req.bRequest) { - case USB_REQ_SET_ADDRESS: - return usb_host_set_address(s, value); + switch (request) { + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + return usb_host_set_address(s, value); - case USB_REQ_SET_CONFIGURATION: - return usb_host_set_config(s, value & 0xff); - } - } + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + return usb_host_set_config(s, value & 0xff); - if (s->ctrl.req.bRequestType == 1 && - s->ctrl.req.bRequest == USB_REQ_SET_INTERFACE) + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: return usb_host_set_interface(s, index, value); + } /* The rest are asynchronous */ - buffer_len = 8 + s->ctrl.len; - if (buffer_len > sizeof(s->ctrl.buffer)) { - fprintf(stderr, "husb: ctrl buffer too small (%u > %zu)\n", - buffer_len, sizeof(s->ctrl.buffer)); + if (length > sizeof(dev->data_buf)) { + fprintf(stderr, "husb: ctrl buffer too small (%d > %zu)\n", + length, sizeof(dev->data_buf)); return USB_RET_STALL; } - aurb = async_alloc(); - aurb->hdev = s; + aurb = async_alloc(s); aurb->packet = p; - /* + /* * Setup ctrl transfer. * - * s->ctrl is layed out such that data buffer immediately follows + * s->ctrl is laid out such that data buffer immediately follows * 'req' struct which is exactly what usbdevfs expects. - */ + */ urb = &aurb->urb; urb->type = USBDEVFS_URB_TYPE_CONTROL; urb->endpoint = p->devep; - urb->buffer = &s->ctrl.req; - urb->buffer_length = buffer_len; + urb->buffer = &dev->setup_buf; + urb->buffer_length = length + 8; urb->usercontext = s; ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); - dprintf("husb: submit ctrl. len %u aurb %p\n", urb->buffer_length, aurb); + DPRINTF("husb: submit ctrl. len %u aurb %p\n", urb->buffer_length, aurb); if (ret < 0) { - dprintf("husb: submit failed. errno %d\n", errno); + DPRINTF("husb: submit failed. errno %d\n", errno); async_free(aurb); switch(errno) { @@ -625,161 +907,93 @@ static int usb_host_handle_control(USBHostDevice *s, USBPacket *p) } } - usb_defer_packet(p, async_cancel, aurb); return USB_RET_ASYNC; } -static int do_token_setup(USBDevice *dev, USBPacket *p) -{ - USBHostDevice *s = (USBHostDevice *) dev; - int ret = 0; - - if (p->len != 8) - return USB_RET_STALL; - - memcpy(&s->ctrl.req, p->data, 8); - s->ctrl.len = le16_to_cpu(s->ctrl.req.wLength); - s->ctrl.offset = 0; - s->ctrl.state = CTRL_STATE_SETUP; - - if (s->ctrl.req.bRequestType & USB_DIR_IN) { - ret = usb_host_handle_control(s, p); - if (ret < 0) - return ret; - - if (ret < s->ctrl.len) - s->ctrl.len = ret; - s->ctrl.state = CTRL_STATE_DATA; - } else { - if (s->ctrl.len == 0) - s->ctrl.state = CTRL_STATE_ACK; - else - s->ctrl.state = CTRL_STATE_DATA; - } - - return ret; -} - -static int do_token_in(USBDevice *dev, USBPacket *p) +static int usb_linux_get_configuration(USBHostDevice *s) { - USBHostDevice *s = (USBHostDevice *) dev; - int ret = 0; + uint8_t configuration; + struct usb_ctrltransfer ct; + int ret; - if (p->devep != 0) - return usb_host_handle_data(s, p); + if (usb_fs_type == USB_FS_SYS) { + char device_name[32], line[1024]; + int configuration; - switch(s->ctrl.state) { - case CTRL_STATE_ACK: - if (!(s->ctrl.req.bRequestType & USB_DIR_IN)) { - ret = usb_host_handle_control(s, p); - if (ret == USB_RET_ASYNC) - return USB_RET_ASYNC; + sprintf(device_name, "%d-%s", s->bus_num, s->port); - s->ctrl.state = CTRL_STATE_IDLE; - return ret > 0 ? 0 : ret; + if (!usb_host_read_file(line, sizeof(line), "bConfigurationValue", + device_name)) { + goto usbdevfs; } - - return 0; - - case CTRL_STATE_DATA: - if (s->ctrl.req.bRequestType & USB_DIR_IN) { - int len = s->ctrl.len - s->ctrl.offset; - if (len > p->len) - len = p->len; - memcpy(p->data, s->ctrl.buffer + s->ctrl.offset, len); - s->ctrl.offset += len; - if (s->ctrl.offset >= s->ctrl.len) - s->ctrl.state = CTRL_STATE_ACK; - return len; + if (sscanf(line, "%d", &configuration) != 1) { + goto usbdevfs; } + return configuration; + } - s->ctrl.state = CTRL_STATE_IDLE; - return USB_RET_STALL; +usbdevfs: + ct.bRequestType = USB_DIR_IN; + ct.bRequest = USB_REQ_GET_CONFIGURATION; + ct.wValue = 0; + ct.wIndex = 0; + ct.wLength = 1; + ct.data = &configuration; + ct.timeout = 50; - default: - return USB_RET_STALL; + ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); + if (ret < 0) { + perror("usb_linux_get_configuration"); + return -1; + } + + /* in address state */ + if (configuration == 0) { + return -1; } + + return configuration; } -static int do_token_out(USBDevice *dev, USBPacket *p) +static uint8_t usb_linux_get_alt_setting(USBHostDevice *s, + uint8_t configuration, uint8_t interface) { - USBHostDevice *s = (USBHostDevice *) dev; + uint8_t alt_setting; + struct usb_ctrltransfer ct; + int ret; - if (p->devep != 0) - return usb_host_handle_data(s, p); + if (usb_fs_type == USB_FS_SYS) { + char device_name[64], line[1024]; + int alt_setting; - switch(s->ctrl.state) { - case CTRL_STATE_ACK: - if (s->ctrl.req.bRequestType & USB_DIR_IN) { - s->ctrl.state = CTRL_STATE_IDLE; - /* transfer OK */ - } else { - /* ignore additional output */ - } - return 0; + sprintf(device_name, "%d-%s:%d.%d", s->bus_num, s->port, + (int)configuration, (int)interface); - case CTRL_STATE_DATA: - if (!(s->ctrl.req.bRequestType & USB_DIR_IN)) { - int len = s->ctrl.len - s->ctrl.offset; - if (len > p->len) - len = p->len; - memcpy(s->ctrl.buffer + s->ctrl.offset, p->data, len); - s->ctrl.offset += len; - if (s->ctrl.offset >= s->ctrl.len) - s->ctrl.state = CTRL_STATE_ACK; - return len; + if (!usb_host_read_file(line, sizeof(line), "bAlternateSetting", + device_name)) { + goto usbdevfs; } - - s->ctrl.state = CTRL_STATE_IDLE; - return USB_RET_STALL; - - default: - return USB_RET_STALL; + if (sscanf(line, "%d", &alt_setting) != 1) { + goto usbdevfs; + } + return alt_setting; } -} - -/* - * Packet handler. - * Called by the HC (host controller). - * - * Returns length of the transaction or one of the USB_RET_XXX codes. - */ -static int usb_host_handle_packet(USBDevice *s, USBPacket *p) -{ - switch(p->pid) { - case USB_MSG_ATTACH: - s->state = USB_STATE_ATTACHED; - return 0; - - case USB_MSG_DETACH: - s->state = USB_STATE_NOTATTACHED; - return 0; - case USB_MSG_RESET: - s->remote_wakeup = 0; - s->addr = 0; - s->state = USB_STATE_DEFAULT; - s->info->handle_reset(s); +usbdevfs: + 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 = &alt_setting; + ct.timeout = 50; + ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); + if (ret < 0) { + /* Assume alt 0 on error */ return 0; } - /* Rest of the PIDs must match our address */ - if (s->state < USB_STATE_DEFAULT || p->devaddr != s->addr) - return USB_RET_NODEV; - - switch (p->pid) { - case USB_TOKEN_SETUP: - return do_token_setup(s, p); - - case USB_TOKEN_IN: - return do_token_in(s, p); - - case USB_TOKEN_OUT: - return do_token_out(s, p); - - default: - return USB_RET_STALL; - } + return alt_setting; } /* returns 1 on problem encountered or 0 for success */ @@ -787,26 +1001,15 @@ static int usb_linux_update_endp_table(USBHostDevice *s) { uint8_t *descriptors; uint8_t devep, type, configuration, alt_interface; - struct usb_ctrltransfer ct; - int interface, ret, length, i; - - ct.bRequestType = USB_DIR_IN; - ct.bRequest = USB_REQ_GET_CONFIGURATION; - ct.wValue = 0; - ct.wIndex = 0; - ct.wLength = 1; - ct.data = &configuration; - ct.timeout = 50; + int interface, length, i; - ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); - if (ret < 0) { - perror("usb_linux_update_endp_table"); - return 1; - } + for (i = 0; i < MAX_ENDPOINTS; i++) + s->endp_table[i].type = INVALID_EP_TYPE; - /* in address state */ - if (configuration == 0) + i = usb_linux_get_configuration(s); + if (i < 0) return 1; + configuration = i; /* get the desired configuration, interface, and endpoint descriptors * from device description */ @@ -816,7 +1019,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) if (descriptors[i + 1] != USB_DT_CONFIG || descriptors[i + 5] != configuration) { - dprintf("invalid descriptor data - configuration\n"); + DPRINTF("invalid descriptor data - configuration\n"); return 1; } i += descriptors[i]; @@ -830,19 +1033,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) } 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 = &alt_interface; - ct.timeout = 50; - - ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); - if (ret < 0) { - alt_interface = interface; - } + alt_interface = usb_linux_get_alt_setting(s, configuration, interface); /* the current interface descriptor is the active interface * and has endpoints */ @@ -852,23 +1043,31 @@ static int usb_linux_update_endp_table(USBHostDevice *s) } /* advance to the endpoints */ - while (i < length && descriptors[i +1] != USB_DT_ENDPOINT) + 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) + if (descriptors[i + 1] != USB_DT_ENDPOINT) { break; + } devep = descriptors[i + 2]; + if ((devep & 0x0f) == 0) { + fprintf(stderr, "usb-linux: invalid ep descriptor, ep == 0\n"); + return 1; + } + switch (descriptors[i + 3] & 0x3) { case 0x00: type = USBDEVFS_URB_TYPE_CONTROL; break; case 0x01: type = USBDEVFS_URB_TYPE_ISO; + set_max_packet_size(s, (devep & 0xf), descriptors + i); break; case 0x02: type = USBDEVFS_URB_TYPE_BULK; @@ -877,7 +1076,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) type = USBDEVFS_URB_TYPE_INTERRUPT; break; default: - dprintf("usb_host: malformed endpoint type\n"); + DPRINTF("usb_host: malformed endpoint type\n"); type = USBDEVFS_URB_TYPE_BULK; } s->endp_table[(devep & 0xf) - 1].type = type; @@ -889,19 +1088,51 @@ static int usb_linux_update_endp_table(USBHostDevice *s) return 0; } -static int usb_host_initfn(USBDevice *dev) +/* + * Check if we can safely redirect a usb2 device to a usb1 virtual controller, + * this function assumes this is safe, if: + * 1) There are no isoc endpoints + * 2) There are no interrupt endpoints with a max_packet_size > 64 + * Note bulk endpoints with a max_packet_size > 64 in theory also are not + * usb1 compatible, but in practice this seems to work fine. + */ +static int usb_linux_full_speed_compat(USBHostDevice *dev) { - return 0; + int i, packet_size; + + /* + * usb_linux_update_endp_table only registers info about ep in the current + * interface altsettings, so we need to parse the descriptors again. + */ + for (i = 0; (i + 5) < dev->descr_len; i += dev->descr[i]) { + if (dev->descr[i + 1] == USB_DT_ENDPOINT) { + switch (dev->descr[i + 3] & 0x3) { + case 0x00: /* CONTROL */ + break; + case 0x01: /* ISO */ + return 0; + case 0x02: /* BULK */ + break; + case 0x03: /* INTERRUPT */ + packet_size = dev->descr[i + 4] + (dev->descr[i + 5] << 8); + if (packet_size > 64) + return 0; + break; + } + } + } + return 1; } -static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *prod_name) +static int usb_host_open(USBHostDevice *dev, int bus_num, + int addr, char *port, const char *prod_name, int speed) { int fd = -1, ret; - USBDevice *d = NULL; - USBHostDevice *dev; - struct usbdevfs_connectinfo ci; char buf[1024]; + if (dev->fd != -1) { + goto fail; + } printf("husb: open device %d.%d\n", bus_num, addr); if (!usb_host_device_path) { @@ -915,13 +1146,11 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p perror(buf); goto fail; } - dprintf("husb: opened %s\n", buf); - - d = usb_create(NULL /* FIXME */, "USB Host Device"); - dev = DO_UPCAST(USBHostDevice, dev, d); + DPRINTF("husb: opened %s\n", buf); dev->bus_num = bus_num; dev->addr = addr; + strcpy(dev->port, port); dev->fd = fd; /* read the device description */ @@ -935,73 +1164,146 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p { int x; printf("=== begin dumping device descriptor data ===\n"); - for (x = 0; x < dev->descr_len; x++) + for (x = 0; x < dev->descr_len; x++) { printf("%02x ", dev->descr[x]); + } printf("\n=== end dumping device descriptor data ===\n"); } #endif - /* - * Initial configuration is -1 which makes us claim first + /* + * Initial configuration is -1 which makes us claim first * available config. We used to start with 1, which does not - * always work. I've seen devices where first config starts + * always work. I've seen devices where first config starts * with 2. */ - if (!usb_host_claim_interfaces(dev, -1)) + if (!usb_host_claim_interfaces(dev, -1)) { goto fail; + } - ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci); - if (ret < 0) { - perror("usb_host_device_open: USBDEVFS_CONNECTINFO"); + ret = usb_linux_update_endp_table(dev); + if (ret) { goto fail; } - printf("husb: grabbed usb device %d.%d\n", bus_num, addr); + if (speed == -1) { + struct usbdevfs_connectinfo ci; - ret = usb_linux_update_endp_table(dev); - if (ret) - goto fail; + ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci); + if (ret < 0) { + perror("usb_host_device_open: USBDEVFS_CONNECTINFO"); + goto fail; + } - if (ci.slow) - dev->dev.speed = USB_SPEED_LOW; - else - dev->dev.speed = USB_SPEED_HIGH; + if (ci.slow) { + speed = USB_SPEED_LOW; + } else { + speed = USB_SPEED_HIGH; + } + } + dev->dev.speed = speed; + dev->dev.speedmask = (1 << speed); + if (dev->dev.speed == USB_SPEED_HIGH && usb_linux_full_speed_compat(dev)) { + dev->dev.speedmask |= USB_SPEED_MASK_FULL; + } - if (!prod_name || prod_name[0] == '\0') - snprintf(dev->dev.devname, sizeof(dev->dev.devname), + printf("husb: grabbed usb device %d.%d\n", bus_num, addr); + + if (!prod_name || prod_name[0] == '\0') { + snprintf(dev->dev.product_desc, sizeof(dev->dev.product_desc), "host:%d.%d", bus_num, addr); - else - pstrcpy(dev->dev.devname, sizeof(dev->dev.devname), + } else { + pstrcpy(dev->dev.product_desc, sizeof(dev->dev.product_desc), prod_name); + } + + ret = usb_device_attach(&dev->dev); + if (ret) { + goto fail; + } /* USB devio uses 'write' flag to check for async completions */ qemu_set_fd_handler(dev->fd, NULL, async_complete, dev); - hostdev_link(dev); - - qdev_init(&d->qdev); - return (USBDevice *) dev; + return 0; fail: - if (d) - qdev_free(&d->qdev); - if (fd != -1) - close(fd); - return NULL; + if (dev->fd != -1) { + close(dev->fd); + dev->fd = -1; + } + return -1; +} + +static int usb_host_close(USBHostDevice *dev) +{ + int i; + + if (dev->fd == -1 || !dev->dev.attached) { + return -1; + } + + qemu_set_fd_handler(dev->fd, NULL, NULL, NULL); + dev->closing = 1; + for (i = 1; i <= MAX_ENDPOINTS; i++) { + if (is_isoc(dev, i)) { + usb_host_stop_n_free_iso(dev, i); + } + } + async_complete(dev); + dev->closing = 0; + usb_device_detach(&dev->dev); + ioctl(dev->fd, USBDEVFS_RESET); + close(dev->fd); + dev->fd = -1; + return 0; +} + +static void usb_host_exit_notifier(struct Notifier* n) +{ + USBHostDevice *s = container_of(n, USBHostDevice, exit); + + if (s->fd != -1) { + ioctl(s->fd, USBDEVFS_RESET); + } +} + +static int usb_host_initfn(USBDevice *dev) +{ + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + + dev->auto_attach = 0; + s->fd = -1; + QTAILQ_INSERT_TAIL(&hostdevs, s, next); + s->exit.notify = usb_host_exit_notifier; + qemu_add_exit_notifier(&s->exit); + usb_host_auto_check(NULL); + return 0; } static struct USBDeviceInfo usb_host_dev_info = { - .qdev.name = "USB Host Device", + .product_desc = "USB Host Device", + .qdev.name = "usb-host", .qdev.size = sizeof(USBHostDevice), .init = usb_host_initfn, - .handle_packet = usb_host_handle_packet, - .handle_reset = usb_host_handle_reset, -#if 0 - .handle_control = usb_host_handle_control, + .handle_packet = usb_generic_handle_packet, + .cancel_packet = usb_host_async_cancel, .handle_data = usb_host_handle_data, -#endif + .handle_control = usb_host_handle_control, + .handle_reset = usb_host_handle_reset, .handle_destroy = usb_host_handle_destroy, + .usbdevice_name = "host", + .usbdevice_init = usb_host_device_open, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("hostbus", USBHostDevice, match.bus_num, 0), + DEFINE_PROP_UINT32("hostaddr", USBHostDevice, match.addr, 0), + DEFINE_PROP_STRING("hostport", USBHostDevice, match.port), + DEFINE_PROP_HEX32("vendorid", USBHostDevice, match.vendor_id, 0), + DEFINE_PROP_HEX32("productid", USBHostDevice, match.product_id, 0), + DEFINE_PROP_UINT32("isobufs", USBHostDevice, iso_urb_count, 4), + DEFINE_PROP_END_OF_LIST(), + }, }; static void usb_host_register_devices(void) @@ -1010,51 +1312,66 @@ static void usb_host_register_devices(void) } device_init(usb_host_register_devices) -static int usb_host_auto_add(const char *spec); -static int usb_host_auto_del(const char *spec); - USBDevice *usb_host_device_open(const char *devname) { - Monitor *mon = cur_mon; - int bus_num, addr; - char product_name[PRODUCT_NAME_SZ]; + struct USBAutoFilter filter; + USBDevice *dev; + char *p; + + dev = usb_create(NULL /* FIXME */, "usb-host"); if (strstr(devname, "auto:")) { - usb_host_auto_add(devname); - return NULL; + if (parse_filter(devname, &filter) < 0) { + goto fail; + } + } else { + if ((p = strchr(devname, '.'))) { + filter.bus_num = strtoul(devname, NULL, 0); + filter.addr = strtoul(p + 1, NULL, 0); + filter.vendor_id = 0; + filter.product_id = 0; + } else if ((p = strchr(devname, ':'))) { + filter.bus_num = 0; + filter.addr = 0; + filter.vendor_id = strtoul(devname, NULL, 16); + filter.product_id = strtoul(p + 1, NULL, 16); + } else { + goto fail; + } } - if (usb_host_find_device(&bus_num, &addr, product_name, sizeof(product_name), - devname) < 0) - return NULL; - - if (hostdev_find(bus_num, addr)) { - monitor_printf(mon, "husb: host usb device %d.%d is already open\n", - bus_num, addr); - return NULL; - } + qdev_prop_set_uint32(&dev->qdev, "hostbus", filter.bus_num); + qdev_prop_set_uint32(&dev->qdev, "hostaddr", filter.addr); + qdev_prop_set_uint32(&dev->qdev, "vendorid", filter.vendor_id); + qdev_prop_set_uint32(&dev->qdev, "productid", filter.product_id); + qdev_init_nofail(&dev->qdev); + return dev; - return usb_host_device_open_addr(bus_num, addr, product_name); +fail: + qdev_free(&dev->qdev); + return NULL; } int usb_host_device_close(const char *devname) { +#if 0 char product_name[PRODUCT_NAME_SZ]; int bus_num, addr; USBHostDevice *s; - if (strstr(devname, "auto:")) + if (strstr(devname, "auto:")) { return usb_host_auto_del(devname); - - if (usb_host_find_device(&bus_num, &addr, product_name, sizeof(product_name), - devname) < 0) + } + if (usb_host_find_device(&bus_num, &addr, product_name, + sizeof(product_name), devname) < 0) { return -1; - + } s = hostdev_find(bus_num, addr); if (s) { usb_device_delete_addr(s->bus_num, s->dev.addr); return 0; } +#endif return -1; } @@ -1066,15 +1383,18 @@ static int get_tag_value(char *buf, int buf_size, const char *p; char *q; p = strstr(str, tag); - if (!p) + if (!p) { return -1; + } p += strlen(tag); - while (qemu_isspace(*p)) + while (qemu_isspace(*p)) { p++; + } q = buf; while (*p != '\0' && !strchr(stopchars, *p)) { - if ((q - buf) < (buf_size - 1)) + if ((q - buf) < (buf_size - 1)) { *q++ = *p; + } p++; } *q = '\0'; @@ -1107,65 +1427,80 @@ static int usb_host_scan_dev(void *opaque, USBScanFunc *func) } device_count = 0; - bus_num = addr = speed = class_id = product_id = vendor_id = 0; + bus_num = addr = class_id = product_id = vendor_id = 0; + speed = -1; /* Can't get the speed from /[proc|dev]/bus/usb/devices */ for(;;) { - if (fgets(line, sizeof(line), f) == NULL) + if (fgets(line, sizeof(line), f) == NULL) { break; - if (strlen(line) > 0) + } + if (strlen(line) > 0) { line[strlen(line) - 1] = '\0'; + } if (line[0] == 'T' && line[1] == ':') { if (device_count && (vendor_id || product_id)) { /* New device. Add the previously discovered device. */ - ret = func(opaque, bus_num, addr, class_id, vendor_id, + ret = func(opaque, bus_num, addr, 0, class_id, vendor_id, product_id, product_name, speed); - if (ret) + if (ret) { goto the_end; + } } - if (get_tag_value(buf, sizeof(buf), line, "Bus=", " ") < 0) + if (get_tag_value(buf, sizeof(buf), line, "Bus=", " ") < 0) { goto fail; + } bus_num = atoi(buf); - if (get_tag_value(buf, sizeof(buf), line, "Dev#=", " ") < 0) + if (get_tag_value(buf, sizeof(buf), line, "Dev#=", " ") < 0) { goto fail; + } addr = atoi(buf); - if (get_tag_value(buf, sizeof(buf), line, "Spd=", " ") < 0) + if (get_tag_value(buf, sizeof(buf), line, "Spd=", " ") < 0) { goto fail; - if (!strcmp(buf, "480")) + } + if (!strcmp(buf, "5000")) { + speed = USB_SPEED_SUPER; + } else if (!strcmp(buf, "480")) { speed = USB_SPEED_HIGH; - else if (!strcmp(buf, "1.5")) + } else if (!strcmp(buf, "1.5")) { speed = USB_SPEED_LOW; - else + } else { speed = USB_SPEED_FULL; + } product_name[0] = '\0'; class_id = 0xff; device_count++; product_id = 0; vendor_id = 0; } else if (line[0] == 'P' && line[1] == ':') { - if (get_tag_value(buf, sizeof(buf), line, "Vendor=", " ") < 0) + if (get_tag_value(buf, sizeof(buf), line, "Vendor=", " ") < 0) { goto fail; + } vendor_id = strtoul(buf, NULL, 16); - if (get_tag_value(buf, sizeof(buf), line, "ProdID=", " ") < 0) + if (get_tag_value(buf, sizeof(buf), line, "ProdID=", " ") < 0) { goto fail; + } product_id = strtoul(buf, NULL, 16); } else if (line[0] == 'S' && line[1] == ':') { - if (get_tag_value(buf, sizeof(buf), line, "Product=", "") < 0) + if (get_tag_value(buf, sizeof(buf), line, "Product=", "") < 0) { goto fail; + } pstrcpy(product_name, sizeof(product_name), buf); } else if (line[0] == 'D' && line[1] == ':') { - if (get_tag_value(buf, sizeof(buf), line, "Cls=", " (") < 0) + if (get_tag_value(buf, sizeof(buf), line, "Cls=", " (") < 0) { goto fail; + } class_id = strtoul(buf, NULL, 16); } fail: ; } if (device_count && (vendor_id || product_id)) { /* Add the last device. */ - ret = func(opaque, bus_num, addr, class_id, vendor_id, + ret = func(opaque, bus_num, addr, 0, class_id, vendor_id, product_id, product_name, speed); } the_end: - if (f) + if (f) { fclose(f); + } return ret; } @@ -1179,9 +1514,9 @@ static int usb_host_scan_dev(void *opaque, USBScanFunc *func) * * @return 0 failed, 1 succeeded ('line' contains data) */ -static int usb_host_read_file(char *line, size_t line_size, const char *device_file, const char *device_name) +static int usb_host_read_file(char *line, size_t line_size, + const char *device_file, const char *device_name) { - Monitor *mon = cur_mon; FILE *f; int ret = 0; char filename[PATH_MAX]; @@ -1190,11 +1525,8 @@ static int usb_host_read_file(char *line, size_t line_size, const char *device_f device_file); f = fopen(filename, "r"); if (f) { - fgets(line, line_size, f); + ret = fgets(line, line_size, f) != NULL; fclose(f); - ret = 1; - } else { - monitor_printf(mon, "husb: could not open %s\n", filename); } return ret; @@ -1213,6 +1545,7 @@ static int usb_host_scan_sys(void *opaque, USBScanFunc *func) char line[1024]; int bus_num, addr, speed, class_id, product_id, vendor_id; int ret = 0; + char port[MAX_PORTLEN]; char product_name[512]; struct dirent *de; @@ -1224,60 +1557,72 @@ static int usb_host_scan_sys(void *opaque, USBScanFunc *func) while ((de = readdir(dir))) { if (de->d_name[0] != '.' && !strchr(de->d_name, ':')) { - char *tmpstr = de->d_name; - if (!strncmp(de->d_name, "usb", 3)) - tmpstr += 3; - bus_num = atoi(tmpstr); + if (sscanf(de->d_name, "%d-%7[0-9.]", &bus_num, port) < 2) { + continue; + } - if (!usb_host_read_file(line, sizeof(line), "devnum", de->d_name)) + if (!usb_host_read_file(line, sizeof(line), "devnum", de->d_name)) { goto the_end; - if (sscanf(line, "%d", &addr) != 1) + } + if (sscanf(line, "%d", &addr) != 1) { goto the_end; - + } if (!usb_host_read_file(line, sizeof(line), "bDeviceClass", - de->d_name)) + de->d_name)) { goto the_end; - if (sscanf(line, "%x", &class_id) != 1) + } + if (sscanf(line, "%x", &class_id) != 1) { goto the_end; + } - if (!usb_host_read_file(line, sizeof(line), "idVendor", de->d_name)) + if (!usb_host_read_file(line, sizeof(line), "idVendor", + de->d_name)) { goto the_end; - if (sscanf(line, "%x", &vendor_id) != 1) + } + if (sscanf(line, "%x", &vendor_id) != 1) { goto the_end; - + } if (!usb_host_read_file(line, sizeof(line), "idProduct", - de->d_name)) + de->d_name)) { goto the_end; - if (sscanf(line, "%x", &product_id) != 1) + } + if (sscanf(line, "%x", &product_id) != 1) { goto the_end; - + } if (!usb_host_read_file(line, sizeof(line), "product", de->d_name)) { *product_name = 0; } else { - if (strlen(line) > 0) + if (strlen(line) > 0) { line[strlen(line) - 1] = '\0'; + } pstrcpy(product_name, sizeof(product_name), line); } - if (!usb_host_read_file(line, sizeof(line), "speed", de->d_name)) + if (!usb_host_read_file(line, sizeof(line), "speed", de->d_name)) { goto the_end; - if (!strcmp(line, "480\n")) + } + if (!strcmp(line, "5000\n")) { + speed = USB_SPEED_SUPER; + } else if (!strcmp(line, "480\n")) { speed = USB_SPEED_HIGH; - else if (!strcmp(line, "1.5\n")) + } else if (!strcmp(line, "1.5\n")) { speed = USB_SPEED_LOW; - else + } else { speed = USB_SPEED_FULL; + } - ret = func(opaque, bus_num, addr, class_id, vendor_id, + ret = func(opaque, bus_num, addr, port, class_id, vendor_id, product_id, product_name, speed); - if (ret) + if (ret) { goto the_end; + } } } the_end: - if (dir) + if (dir) { closedir(dir); + } return ret; } @@ -1302,7 +1647,7 @@ static int usb_host_scan(void *opaque, USBScanFunc *func) strcpy(devpath, USBDEVBUS_PATH); usb_fs_type = USB_FS_SYS; closedir(dir); - dprintf(USBDBG_DEVOPENED, USBSYSBUS_PATH); + DPRINTF(USBDBG_DEVOPENED, USBSYSBUS_PATH); goto found_devices; } f = fopen(USBPROCBUS_PATH "/devices", "r"); @@ -1311,7 +1656,7 @@ static int usb_host_scan(void *opaque, USBScanFunc *func) strcpy(devpath, USBPROCBUS_PATH); usb_fs_type = USB_FS_PROC; fclose(f); - dprintf(USBDBG_DEVOPENED, USBPROCBUS_PATH); + DPRINTF(USBDBG_DEVOPENED, USBPROCBUS_PATH); goto found_devices; } /* try additional methods if an access method hasn't been found yet */ @@ -1321,20 +1666,24 @@ static int usb_host_scan(void *opaque, USBScanFunc *func) strcpy(devpath, USBDEVBUS_PATH); usb_fs_type = USB_FS_DEV; fclose(f); - dprintf(USBDBG_DEVOPENED, USBDEVBUS_PATH); + DPRINTF(USBDBG_DEVOPENED, USBDEVBUS_PATH); goto found_devices; } found_devices: if (!usb_fs_type) { - monitor_printf(mon, "husb: unable to access USB devices\n"); + if (mon) { + monitor_printf(mon, "husb: unable to access USB devices\n"); + } return -ENOENT; } /* the module setting (used later for opening devices) */ usb_host_device_path = qemu_mallocz(strlen(devpath)+1); strcpy(usb_host_device_path, devpath); - monitor_printf(mon, "husb: using %s file-system with %s\n", - fs_type[usb_fs_type], usb_host_device_path); + if (mon) { + monitor_printf(mon, "husb: using %s file-system with %s\n", + fs_type[usb_fs_type], usb_host_device_path); + } } switch (usb_fs_type) { @@ -1352,61 +1701,82 @@ static int usb_host_scan(void *opaque, USBScanFunc *func) return ret; } -struct USBAutoFilter { - struct USBAutoFilter *next; - int bus_num; - int addr; - int vendor_id; - int product_id; -}; - static QEMUTimer *usb_auto_timer; -static struct USBAutoFilter *usb_auto_filter; -static int usb_host_auto_scan(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_auto_scan(void *opaque, int bus_num, int addr, char *port, + int class_id, int vendor_id, int product_id, + const char *product_name, int speed) { struct USBAutoFilter *f; - struct USBDevice *dev; + struct USBHostDevice *s; /* Ignore hubs */ if (class_id == 9) return 0; - for (f = usb_auto_filter; f; f = f->next) { - if (f->bus_num >= 0 && f->bus_num != bus_num) - continue; + QTAILQ_FOREACH(s, &hostdevs, next) { + f = &s->match; - if (f->addr >= 0 && f->addr != addr) + if (f->bus_num > 0 && f->bus_num != bus_num) { continue; - - if (f->vendor_id >= 0 && f->vendor_id != vendor_id) + } + if (f->addr > 0 && f->addr != addr) { + continue; + } + if (f->port != NULL && (port == NULL || strcmp(f->port, port) != 0)) { continue; + } - if (f->product_id >= 0 && f->product_id != product_id) + if (f->vendor_id > 0 && f->vendor_id != vendor_id) { continue; + } + if (f->product_id > 0 && f->product_id != product_id) { + continue; + } /* We got a match */ - /* Allredy attached ? */ - if (hostdev_find(bus_num, addr)) + /* Already attached ? */ + if (s->fd != -1) { return 0; + } + DPRINTF("husb: auto open: bus_num %d addr %d\n", bus_num, addr); - dprintf("husb: auto open: bus_num %d addr %d\n", bus_num, addr); - - dev = usb_host_device_open_addr(bus_num, addr, product_name); - if (dev) - qdev_init(&dev->qdev); + usb_host_open(s, bus_num, addr, port, product_name, speed); + break; } return 0; } -static void usb_host_auto_timer(void *unused) +static void usb_host_auto_check(void *unused) { + struct USBHostDevice *s; + int unconnected = 0; + usb_host_scan(NULL, usb_host_auto_scan); - qemu_mod_timer(usb_auto_timer, qemu_get_clock(rt_clock) + 2000); + + QTAILQ_FOREACH(s, &hostdevs, next) { + if (s->fd == -1) { + unconnected++; + } + } + + if (unconnected == 0) { + /* nothing to watch */ + if (usb_auto_timer) { + qemu_del_timer(usb_auto_timer); + } + return; + } + + if (!usb_auto_timer) { + usb_auto_timer = qemu_new_timer_ms(rt_clock, usb_host_auto_check, NULL); + if (!usb_auto_timer) { + return; + } + } + qemu_mod_timer(usb_auto_timer, qemu_get_clock_ms(rt_clock) + 2000); } /* @@ -1428,19 +1798,21 @@ static int parse_filter(const char *spec, struct USBAutoFilter *f) const char *p = spec; int i; - f->bus_num = -1; - f->addr = -1; - f->vendor_id = -1; - f->product_id = -1; + f->bus_num = 0; + f->addr = 0; + f->vendor_id = 0; + f->product_id = 0; for (i = BUS; i < DONE; i++) { - p = strpbrk(p, ":."); - if (!p) break; + p = strpbrk(p, ":."); + if (!p) { + break; + } p++; - - if (*p == '*') - continue; + if (*p == '*') { + continue; + } switch(i) { case BUS: f->bus_num = strtol(p, NULL, 10); break; case DEV: f->addr = strtol(p, NULL, 10); break; @@ -1457,150 +1829,6 @@ static int parse_filter(const char *spec, struct USBAutoFilter *f) return 0; } -static int match_filter(const struct USBAutoFilter *f1, - const struct USBAutoFilter *f2) -{ - return f1->bus_num == f2->bus_num && - f1->addr == f2->addr && - f1->vendor_id == f2->vendor_id && - f1->product_id == f2->product_id; -} - -static int usb_host_auto_add(const char *spec) -{ - struct USBAutoFilter filter, *f; - - if (parse_filter(spec, &filter) < 0) - return -1; - - f = qemu_mallocz(sizeof(*f)); - - *f = filter; - - if (!usb_auto_filter) { - /* - * First entry. Init and start the monitor. - * Right now we're using timer to check for new devices. - * If this turns out to be too expensive we can move that into a - * separate thread. - */ - usb_auto_timer = qemu_new_timer(rt_clock, usb_host_auto_timer, NULL); - if (!usb_auto_timer) { - fprintf(stderr, "husb: failed to allocate auto scan timer\n"); - qemu_free(f); - return -1; - } - - /* Check for new devices every two seconds */ - qemu_mod_timer(usb_auto_timer, qemu_get_clock(rt_clock) + 2000); - } - - dprintf("husb: added auto filter: bus_num %d addr %d vid %d pid %d\n", - f->bus_num, f->addr, f->vendor_id, f->product_id); - - f->next = usb_auto_filter; - usb_auto_filter = f; - - return 0; -} - -static int usb_host_auto_del(const char *spec) -{ - struct USBAutoFilter *pf = usb_auto_filter; - struct USBAutoFilter **prev = &usb_auto_filter; - struct USBAutoFilter filter; - - if (parse_filter(spec, &filter) < 0) - return -1; - - while (pf) { - if (match_filter(pf, &filter)) { - dprintf("husb: removed auto filter: bus_num %d addr %d vid %d pid %d\n", - pf->bus_num, pf->addr, pf->vendor_id, pf->product_id); - - *prev = pf->next; - - if (!usb_auto_filter) { - /* No more filters. Stop scanning. */ - qemu_del_timer(usb_auto_timer); - qemu_free_timer(usb_auto_timer); - } - - return 0; - } - - prev = &pf->next; - pf = pf->next; - } - - return -1; -} - -typedef struct FindDeviceState { - int vendor_id; - int product_id; - int bus_num; - int addr; - char product_name[PRODUCT_NAME_SZ]; -} 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) || - (bus_num == s->bus_num && - addr == s->addr)) { - pstrcpy(s->product_name, PRODUCT_NAME_SZ, product_name); - 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, - char *product_name, int product_name_size, - 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); - fs.bus_num = *pbus_num; - fs.addr = *paddr; - ret = usb_host_scan(&fs, usb_host_find_device_scan); - if (ret) - pstrcpy(product_name, product_name_size, fs.product_name); - 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; - pstrcpy(product_name, product_name_size, fs.product_name); - return 0; - } - } - return -1; -} - /**********************/ /* USB host device info */ @@ -1630,14 +1858,15 @@ 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) + 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, +static void usb_info_device(Monitor *mon, int bus_num, int addr, char *port, + int class_id, int vendor_id, int product_id, const char *product_name, int speed) { @@ -1653,68 +1882,80 @@ static void usb_info_device(Monitor *mon, int bus_num, int addr, int class_id, case USB_SPEED_HIGH: speed_str = "480"; break; + case USB_SPEED_SUPER: + speed_str = "5000"; + break; default: speed_str = "?"; break; } - monitor_printf(mon, " Device %d.%d, speed %s Mb/s\n", - bus_num, addr, speed_str); + monitor_printf(mon, " Bus %d, Addr %d, Port %s, Speed %s Mb/s\n", + bus_num, addr, port, speed_str); class_str = usb_class_str(class_id); - if (class_str) + if (class_str) { monitor_printf(mon, " %s:", class_str); - else + } else { monitor_printf(mon, " Class %02x:", class_id); + } monitor_printf(mon, " USB device %04x:%04x", vendor_id, product_id); - if (product_name[0] != '\0') + 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, + char *path, 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, + usb_info_device(mon, bus_num, addr, path, class_id, vendor_id, product_id, product_name, speed); return 0; } static void dec2str(int val, char *str, size_t size) { - if (val == -1) + if (val == 0) { snprintf(str, size, "*"); - else - snprintf(str, size, "%d", val); + } else { + snprintf(str, size, "%d", val); + } } static void hex2str(int val, char *str, size_t size) { - if (val == -1) + if (val == 0) { snprintf(str, size, "*"); - else - snprintf(str, size, "%x", val); + } else { + snprintf(str, size, "%04x", val); + } } void usb_host_info(Monitor *mon) { struct USBAutoFilter *f; + struct USBHostDevice *s; usb_host_scan(mon, usb_host_info_device); - if (usb_auto_filter) - monitor_printf(mon, " Auto filters:\n"); - for (f = usb_auto_filter; f; f = f->next) { + if (QTAILQ_EMPTY(&hostdevs)) { + return; + } + + monitor_printf(mon, " Auto filters:\n"); + QTAILQ_FOREACH(s, &hostdevs, next) { char bus[10], addr[10], vid[10], pid[10]; + f = &s->match; dec2str(f->bus_num, bus, sizeof(bus)); dec2str(f->addr, addr, sizeof(addr)); hex2str(f->vendor_id, vid, sizeof(vid)); hex2str(f->product_id, pid, sizeof(pid)); - monitor_printf(mon, " Device %s.%s ID %s:%s\n", - bus, addr, vid, pid); + monitor_printf(mon, " Bus %s, Addr %s, Port %s, ID %s:%s\n", + bus, addr, f->port ? f->port : "*", vid, pid); } }