--- /dev/null
+/*
+ * Emulator USB Pass-through
+ *
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact:
+ * Chulho Song <ch81.song@samsung.com>
+ * HakHyun Kim <haken.kim@samsung.com>
+ * Jinhyung Choi <jinhyung2.choi@samsung.com>
+ * Byeongki Shin <bk0121.shin@samsung.com>
+ * Sangho Park <sangho1206.park@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+
+#ifdef CONFIG_LINUX
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <stdint.h>
+#include <linux/version.h>
+#include <linux/usbdevice_fs.h>
+#endif
+
+#include "qemu-common.h"
+#include "qemu/timer.h"
+#include "monitor/monitor.h"
+#include "sysemu/sysemu.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "hw/usb/host.h"
+#include "libtizenusb.h"
+#include "tizen/src/debug_ch.h"
+#include "trace.h"
+
+#define ISOC 0
+
+#ifdef CONFIG_LINUX
+#undef RT_FAILURE
+#define RT_FAILURE(rc) ((rc) != 0)
+#endif
+
+#define VBOX_PROXY_DEVNAME "usb-host"
+#define MAX_PORTLEN 16
+#define VBOX_MAX_PATH 4096
+#define MAX_EXPIRE_TIME 1000
+
+MULTI_DEBUG_CHANNEL(usb, vboxusb);
+
+enum USBHostDeviceOptions {
+ USB_HOST_OPT_PIPELINE,
+};
+
+#if ISOC
+typedef struct USBHostRequest USBHostRequest;
+
+struct endp_data {
+ uint8_t type;
+ uint8_t halted;
+ uint8_t iso_started;
+ USBHostRequest *iso_urb;
+ int iso_urb_idx;
+ int iso_buffer_used;
+ int max_packet_size;
+ int inflight;
+};
+#endif
+
+typedef struct USBHostDevice {
+ USBDevice dev;
+ int fd;
+ int speed;
+ int speedmask;
+ struct USBAutoFilter match;
+ int32_t bootindex;
+ uint32_t iso_urb_count;
+ uint32_t iso_urb_frames;
+ uint32_t options;
+ QEMUBH *bh_nodev; //for nodev error
+ Notifier exit;
+ int seen, errcount;
+ int bus_num;
+ int addr;
+ USBPROXYDEV vboxdev;
+#ifdef CONFIG_DARWIN
+ QEMUTimer *callback_timer;
+ unsigned int expire_time;
+#endif
+#if ISOC
+ struct endp_data ep_in[USB_MAX_ENDPOINTS];
+ struct endp_data ep_out[USB_MAX_ENDPOINTS];
+#endif
+ QTAILQ_ENTRY(USBHostDevice) next;
+ QTAILQ_HEAD(, USBHostRequest) requests;
+} USBHostDevice;
+
+typedef struct USBHostRequest{
+ VUSBURB vbox_urb;
+ USBHostDevice *host;
+ USBPacket *p;
+ bool in;
+ unsigned char *cbuf;
+ unsigned int clen;
+#if ISOC
+ int iso_frame_idx; /* -1 means in flight */
+#endif
+#ifdef CONFIG_WIN32
+ HANDLE event_handle;
+#endif
+ QTAILQ_ENTRY(USBHostRequest) next;
+}USBHostRequest;
+
+static QTAILQ_HEAD(, USBHostDevice) hostdevs = QTAILQ_HEAD_INITIALIZER(hostdevs);
+
+
+static void usb_host_auto_check(void *unused);
+static int usb_host_open_device(USBHostDevice* s, int bus, int addr, int product_id, int vendor_id,
+ const char* port, const char* prod_name, int speed);
+static USBHostRequest* usb_host_req_find(USBHostDevice *s, USBPacket* p);
+static void usb_host_req_free(USBHostRequest* req);
+static USBHostRequest* usb_host_req_alloc(USBHostDevice* s, USBPacket* p, bool in);
+static void usb_fill_data_urb(USBHostRequest* req, USBPacket *p, uint8_t type, size_t size);
+static void usb_host_nodev(USBHostDevice *s);
+static void async_complete(void *opaque);
+
+struct desc_counts
+{
+ size_t num_ed, num_id, num_if;
+ /** bitmap (128 bits) */
+ uint32_t idmap[4];
+};
+
+#if ISOC
+static struct endp_data *get_endp(USBHostDevice *s, int pid, int ep)
+{
+ struct endp_data *eps = pid == USB_TOKEN_IN ? s->ep_in : s->ep_out;
+ assert(pid == USB_TOKEN_IN || pid == USB_TOKEN_OUT);
+ assert(ep > 0 && ep <= USB_MAX_ENDPOINTS);
+ return eps + ep - 1;
+}
+
+static int is_iso_started(USBHostDevice *s, int pid, int ep)
+{
+ return get_endp(s, pid, ep)->iso_started;
+}
+
+static void clear_iso_started(USBHostDevice *s, int pid, int ep)
+{
+ get_endp(s, pid, ep)->iso_started = 0;
+}
+
+static void set_iso_started(USBHostDevice *s, int pid, int ep)
+{
+ struct endp_data *e = get_endp(s, pid, ep);
+ if (!e->iso_started) {
+ e->iso_started = 1;
+ e->inflight = 0;
+ }
+}
+
+static int change_iso_inflight(USBHostDevice *s, int pid, int ep, int value)
+{
+ struct endp_data *e = get_endp(s, pid, ep);
+
+ e->inflight += value;
+ return e->inflight;
+}
+
+static void set_iso_urb(USBHostDevice *s, int pid, int ep, USBHostRequest *iso_urb)
+{
+ get_endp(s, pid, ep)->iso_urb = iso_urb;
+}
+
+static USBHostRequest *get_iso_urb(USBHostDevice *s, int pid, int ep)
+{
+ return get_endp(s, pid, ep)->iso_urb;
+}
+
+static void set_iso_urb_idx(USBHostDevice *s, int pid, int ep, int i)
+{
+ get_endp(s, pid, ep)->iso_urb_idx = i;
+}
+
+static int get_iso_urb_idx(USBHostDevice *s, int pid, int ep)
+{
+ return get_endp(s, pid, ep)->iso_urb_idx;
+}
+
+static void set_iso_buffer_used(USBHostDevice *s, int pid, int ep, int i)
+{
+ get_endp(s, pid, ep)->iso_buffer_used = i;
+}
+
+static int get_iso_buffer_used(USBHostDevice *s, int pid, int ep)
+{
+ return get_endp(s, pid, ep)->iso_buffer_used;
+}
+#endif
+
+static void usb_host_reset_callback_time(USBHostDevice *s)
+{
+#ifdef CONFIG_DARWIN
+ s->expire_time = 1;
+ timer_mod(s->callback_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + s->expire_time);
+#endif
+}
+
+static void usb_host_increase_callback_time(USBHostDevice *s)
+{
+#ifdef CONFIG_DARWIN
+ if (s->expire_time < MAX_EXPIRE_TIME)
+ s->expire_time++;
+ timer_mod(s->callback_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + s->expire_time);
+#endif
+}
+
+#ifdef CONFIG_WIN32
+/*
+ * After queuing a urb to proxy,
+ * the event handle is inserted to global main wait object
+ * to process it on single thread.
+ * This function should called after pfnUrbQueue()
+ *
+ * XXX : Data of usb library is safe?
+ */
+static int usb_host_add_event(USBHostRequest *req)
+{
+ USBHostDevice *s = req->host;
+ USBPROXYDEV *pThis = &s->vboxdev;
+ PVUSBURB vurb = &req->vbox_urb;
+ HANDLE hevent = (HANDLE)vurb->handle;
+
+ /* checking handle validation
+ *
+ * if usb backend failed to something or already reaped,
+ * the handle is already freed.
+ */
+ if (hevent == INVALID_HANDLE_VALUE) {
+ ERR("(%s) handle is no more valid\n", __func__);
+ return -1;
+ }
+
+ if (qemu_add_wait_object (hevent, async_complete, pThis)) {
+ ERR("(%s) failed add wait object\n", __func__);
+ return -1;
+ }
+
+ req->event_handle = hevent;
+
+ TRACE("(%s) req->vbox_urb=%p, req->event_handle=%d\n",
+ __func__, &req->vbox_urb, (int)req->event_handle);
+ return 0;
+}
+
+static void usb_host_del_event(USBHostRequest *req)
+{
+ USBHostDevice *s = req->host;
+ USBPROXYDEV *pThis = &s->vboxdev;
+ //PVUSBURB vurb = &req->vbox_urb;
+ HANDLE hevent = req->event_handle;
+
+ TRACE("(%s) req->vbox_urb=%p, req->event_handle=%d\n",
+ __func__, &req->vbox_urb, (int)req->event_handle);
+
+ qemu_del_wait_object(hevent, async_complete, pThis);
+}
+#endif
+
+
+static bool usb_host_use_combining(USBEndpoint *ep)
+{
+ int type;
+
+ if (!ep->pipeline) {
+ return false;
+ }
+ if (ep->pid != USB_TOKEN_IN) {
+ return false;
+ }
+ type = usb_ep_get_type(ep->dev, ep->pid, ep->nr);
+ if (type != USB_ENDPOINT_XFER_BULK) {
+ return false;
+ }
+ return true;
+}
+
+static const void *collect_stray_bits(uint8_t *this_desc, uint8_t *end, uint16_t *cbExtra)
+{
+ uint8_t *tmp, *buf;
+ uint8_t type;
+
+ Assert(*(this_desc + 1) == VUSB_DT_INTERFACE || *(this_desc + 1) == VUSB_DT_ENDPOINT);
+ buf = this_desc;
+
+ /* Skip the current interface/endpoint descriptor. */
+ buf += *(uint8_t *)buf;
+
+ /* Loop until we find another descriptor we understand. */
+ for (tmp = buf; ((tmp + 1) < end) && *tmp; tmp += *tmp)
+ {
+ type = *(tmp + 1);
+ if (type == VUSB_DT_INTERFACE || type == VUSB_DT_ENDPOINT)
+ break;
+ }
+ *cbExtra = tmp - buf;
+ if (*cbExtra)
+ return buf;
+ else
+ return NULL;
+}
+
+
+static int copy_interface(PVUSBINTERFACE pIf, uint8_t ifnum,
+ PVUSBDESCINTERFACEEX *id, PVUSBDESCENDPOINTEX *ed,
+ uint8_t *buf, size_t len)
+{
+ PVUSBDESCINTERFACEEX cur_if = NULL;
+ uint32_t altmap[4] = {0,};
+ uint8_t *tmp, *end = buf + len;
+ uint8_t alt;
+ int state;
+ size_t num_ep = 0;
+
+ buf += *(uint8_t *)buf;
+
+ pIf->cSettings = 0;
+ pIf->paSettings = NULL;
+
+ for (tmp = buf, state = 0; ((tmp + 1) < end) && *tmp; tmp += *tmp)
+ {
+ uint8_t type;
+ PVUSBDESCINTERFACE ifd;
+ PVUSBDESCENDPOINT epd;
+ PVUSBDESCENDPOINTEX cur_ep;
+
+ type = tmp[1];
+
+ switch ( type ) {
+ case VUSB_DT_INTERFACE:
+ state = 0;
+ ifd = (PVUSBDESCINTERFACE)tmp;
+
+ /* Ignoring this interface */
+ if ( ifd->bInterfaceNumber != ifnum )
+ break;
+
+ /* Check we didn't see this alternate setting already
+ * because that will break stuff
+ */
+ alt = ifd->bAlternateSetting;
+ if ( altmap[alt >> 6] & (1 << (alt & 0x1f)) )
+ return 0;
+ altmap[alt >> 6] |= (1 << (alt & 0x1f));
+
+ cur_if = *id;
+ (*id)++;
+ if ( pIf->cSettings == 0 )
+ pIf->paSettings = cur_if;
+
+ memcpy(cur_if, ifd, sizeof(cur_if->Core));
+
+ /* Point to additional interface descriptor bytes, if any. */
+ if (cur_if->Core.bLength - VUSB_DT_INTERFACE_MIN_LEN > 0)
+ cur_if->pvMore = tmp + VUSB_DT_INTERFACE_MIN_LEN;
+ else
+ cur_if->pvMore = NULL;
+
+ cur_if->pvClass = collect_stray_bits(tmp, end, &cur_if->cbClass);
+
+ pIf->cSettings++;
+
+ state = 1;
+ num_ep = 0;
+ break;
+ case VUSB_DT_ENDPOINT:
+ if ( state == 0 )
+ break;
+
+ epd = (PVUSBDESCENDPOINT)tmp;
+
+ cur_ep = *ed;
+ (*ed)++;
+
+ if ( num_ep == 0 )
+ cur_if->paEndpoints = cur_ep;
+
+ if ( num_ep > cur_if->Core.bNumEndpoints )
+ return 0;
+
+ memcpy(cur_ep, epd, sizeof(cur_ep->Core));
+
+ /* Point to additional endpoint descriptor bytes, if any. */
+ if (cur_ep->Core.bLength - VUSB_DT_ENDPOINT_MIN_LEN > 0)
+ cur_ep->pvMore = tmp + VUSB_DT_ENDPOINT_MIN_LEN;
+ else
+ cur_ep->pvMore = NULL;
+
+ cur_ep->pvClass = collect_stray_bits(tmp, end, &cur_ep->cbClass);
+
+ cur_ep->Core.wMaxPacketSize = RT_LE2H_U16(cur_ep->Core.wMaxPacketSize);
+
+ num_ep++;
+ break;
+ default:
+ /* Skip unknown descriptors. */
+ WARN("unknown type:%d\n", type);
+ break;
+ }
+ }
+
+ return 1;
+}
+
+
+static int count_descriptors(struct desc_counts *cnt, uint8_t *buf, size_t len)
+{
+ PVUSBDESCCONFIG cfg;
+ uint8_t *tmp, *end;
+ uint32_t i, x;
+
+ memset(cnt, 0, sizeof(*cnt));
+
+ end = buf + len;
+
+ cfg = (PVUSBDESCCONFIG)buf;
+ if ( cfg->bLength < VUSB_DT_CONFIG_MIN_LEN )
+ return 0;
+ if ( cfg->bLength > len )
+ return 0;
+
+ for (tmp = buf + cfg->bLength; ((tmp + 1) < end) && *tmp; tmp += *tmp)
+ {
+ uint8_t type;
+ uint32_t ifnum;
+ PVUSBDESCINTERFACE id;
+ PVUSBDESCENDPOINT ed;
+
+ type = *(tmp + 1);
+
+ switch ( type ) {
+ case VUSB_DT_INTERFACE:
+ id = (PVUSBDESCINTERFACE)tmp;
+ if ( id->bLength < VUSB_DT_INTERFACE_MIN_LEN )
+ return 0;
+ cnt->num_id++;
+ ifnum = id->bInterfaceNumber;
+ cnt->idmap[ifnum >> 6] |= (1 << (ifnum & 0x1f));
+ break;
+ case VUSB_DT_ENDPOINT:
+ ed = (PVUSBDESCENDPOINT)tmp;
+ if ( ed->bLength < VUSB_DT_ENDPOINT_MIN_LEN )
+ return 0;
+ cnt->num_ed++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* count interfaces */
+ for(i=0; i < RT_ELEMENTS(cnt->idmap); i++)
+ for(x=1; x; x<<=1)
+ if ( cnt->idmap[i] & x )
+ cnt->num_if++;
+
+ return 1;
+}
+
+
+
+
+/* Synchronously obtain a standard USB descriptor for a device, used in order
+ * to grab configuration descriptors when we first add the device
+ */
+static void *GetStdDescSync(PUSBPROXYDEV pProxyDev, uint8_t iDescType, uint8_t iIdx, uint16_t LangId, uint16_t cbHint)
+{
+ for (;;)
+ {
+ /*
+ * Setup a MSG URB, queue and reap it.
+ */
+ VUSBURB Urb;
+ Urb.u32Magic = VUSBURB_MAGIC;
+ Urb.enmState = VUSBURBSTATE_IN_FLIGHT;
+ Urb.pszDesc = (char*)"URB sync";
+ memset(&Urb.VUsb, 0, sizeof(Urb.VUsb));
+ memset(&Urb.Hci, 0, sizeof(Urb.Hci));
+ Urb.pDev = (struct VUSBDEV*) pProxyDev;
+ Urb.Dev.pvPrivate = NULL;
+ Urb.Dev.pNext = NULL;
+#ifndef RT_OS_LINUX
+ Urb.pUsbIns = (PPDMUSBINS)pProxyDev;
+#endif
+ Urb.DstAddress = 0;
+ Urb.EndPt = 0;
+ Urb.enmType = VUSBXFERTYPE_MSG;
+ Urb.enmDir = VUSBDIRECTION_IN;
+ Urb.fShortNotOk = false;
+ Urb.enmStatus = VUSBSTATUS_INVALID;
+ cbHint = RT_MIN(cbHint, sizeof(Urb.abData) - sizeof(VUSBSETUP));
+ Urb.cbData = cbHint + sizeof(VUSBSETUP);
+
+ PVUSBSETUP pSetup = (PVUSBSETUP)Urb.abData;
+ pSetup->bmRequestType = VUSB_DIR_TO_HOST | VUSB_REQ_STANDARD | VUSB_TO_DEVICE;
+ pSetup->bRequest = VUSB_REQ_GET_DESCRIPTOR;
+ pSetup->wValue = (iDescType << 8) | iIdx;
+ pSetup->wIndex = LangId;
+ pSetup->wLength = cbHint;
+
+ if (RT_FAILURE(pProxyDev->pOps->pfnUrbQueue(&Urb)))
+ {
+ ERR("urb queue failed\n");
+ break;
+ }
+
+
+ /* Don't wait forever, it's just a simple request that should
+ return immediately. Since we're executing in the EMT thread
+ it's important not to get stuck here. (Some of the builtin
+ iMac devices may not refuse respond for instance.) */
+ PVUSBURB pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, 10000 /* ms */);
+ if (!pUrbReaped)
+ {
+ pProxyDev->pOps->pfnUrbCancel(&Urb);
+ pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, RT_INDEFINITE_WAIT);
+ }
+ if (pUrbReaped != &Urb)
+ {
+ ERR("GetStdDescSync: pfnUrbReap failed, pUrbReaped=%p\n", pUrbReaped);
+ break;
+ }
+
+ if (Urb.enmStatus != VUSBSTATUS_OK)
+ {
+ ERR("GetStdDescSync: Urb.enmStatus=%d\n", Urb.enmStatus);
+ break;
+ }
+
+ /*
+ * Check the length, config descriptors have total_length field
+ */
+ uint8_t *pbDesc = (uint8_t *)(pSetup + 1);
+ uint32_t cbDesc;
+ if (iDescType == VUSB_DT_CONFIG)
+ {
+ if (Urb.cbData < sizeof(VUSBSETUP) + 4)
+ {
+ TRACE("GetStdDescSync: Urb.cbData=%#x (min 4)\n", Urb.cbData);
+ break;
+ }
+ cbDesc = RT_LE2H_U16(((uint16_t *)pbDesc)[1]);
+ }
+ else
+ {
+ if (Urb.cbData < sizeof(VUSBSETUP) + 1)
+ {
+ TRACE("GetStdDescSync: Urb.cbData=%#x (min 1)\n", Urb.cbData);
+ break;
+ }
+ cbDesc = ((uint8_t *)pbDesc)[0];
+ }
+
+ TRACE("GetStdDescSync: got Urb.cbData=%u, cbDesc=%u cbHint=%u\n", Urb.cbData, cbDesc, cbHint);
+
+ if ( Urb.cbData == cbHint + sizeof(VUSBSETUP)
+ && cbDesc > Urb.cbData - sizeof(VUSBSETUP))
+ {
+ cbHint = cbDesc;
+ if (cbHint > sizeof(Urb.abData))
+ {
+ AssertMsgFailed(("cbHint=%u\n", cbHint));
+ break;
+ }
+ continue;
+ }
+ Assert(cbDesc <= Urb.cbData - sizeof(VUSBSETUP));
+#if 0
+ vusbUrbTrace(&Urb, "GetStdDescSync", true);
+#endif
+
+ /*
+ * Fine, we got everything return a heap duplicate of the descriptor.
+ */
+
+ char* ret = (char*) malloc(cbDesc);
+ if( ret )
+ memcpy((void*)ret, (void*)pbDesc, cbDesc);
+ return ret;
+
+ }
+ return NULL;
+}
+static bool copy_config(PUSBPROXYDEV pProxyDev, uint8_t idx, PVUSBDESCCONFIGEX out)
+{
+ PVUSBDESCCONFIG cfg;
+ PVUSBINTERFACE pIf;
+ PVUSBDESCINTERFACEEX ifd;
+ PVUSBDESCENDPOINTEX epd;
+ struct desc_counts cnt;
+ void *descs;
+ size_t tot_len;
+ size_t cbIface;
+ uint32_t i, x;
+
+ descs = GetStdDescSync(pProxyDev, VUSB_DT_CONFIG, idx, 0, VUSB_DT_CONFIG_MIN_LEN);
+ if ( descs == NULL ) {
+ ERR("copy_config: GetStdDescSync failed\n");
+ return false;
+ }
+
+ cfg = (PVUSBDESCCONFIG)descs;
+ tot_len = RT_LE2H_U16(cfg->wTotalLength);
+
+ if ( !count_descriptors(&cnt, (uint8_t *)descs, tot_len) ) {
+ ERR("copy_config: count_descriptors failed\n");
+ goto err;
+ }
+
+ if ( cfg->bNumInterfaces != cnt.num_if )
+ ERR("usb-proxy: config%u: bNumInterfaces %u != %u\n",
+ idx, cfg->bNumInterfaces, cnt.num_if);
+
+ TRACE("usb-proxy: config%u: %u bytes id=%u ed=%u if=%u\n",
+ idx, tot_len, cnt.num_id, cnt.num_ed, cnt.num_if);
+
+ cbIface = cnt.num_if * sizeof(VUSBINTERFACE)
+ + cnt.num_id * sizeof(VUSBDESCINTERFACEEX)
+ + cnt.num_ed * sizeof(VUSBDESCENDPOINTEX);
+ out->paIfs = (PCVUSBINTERFACE)malloc(cbIface);
+ if ( out->paIfs == NULL ) {
+ free(descs);
+ return false;
+ }
+
+ /* Stash a pointer to the raw config descriptor; we may need bits of it later. */
+ out->pvOriginal = descs;
+
+ pIf = (PVUSBINTERFACE)out->paIfs;
+ ifd = (PVUSBDESCINTERFACEEX)&pIf[cnt.num_if];
+ epd = (PVUSBDESCENDPOINTEX)&ifd[cnt.num_id];
+
+ out->Core.bLength = cfg->bLength;
+ out->Core.bDescriptorType = cfg->bDescriptorType;
+ out->Core.wTotalLength = 0; /* Auto Calculated */
+ out->Core.bNumInterfaces = (uint8_t)cnt.num_if;
+ out->Core.bConfigurationValue = cfg->bConfigurationValue;
+ out->Core.iConfiguration = cfg->iConfiguration;
+ out->Core.bmAttributes = cfg->bmAttributes;
+ out->Core.MaxPower = cfg->MaxPower;
+
+ for(i=0; i < 4; i++)
+ for(x=0; x < 32; x++)
+ if ( cnt.idmap[i] & (1 << x) )
+ if ( !copy_interface(pIf++, (i << 6) | x, &ifd, &epd, (uint8_t *)out->pvOriginal, tot_len) ) {
+ ERR("copy_interface(%d,,) failed\n", pIf - 1);
+ goto err;
+ }
+
+ return true;
+err:
+ ERR("usb-proxy: config%u: Corrupted configuration descriptor\n", idx);
+ free(descs);
+ return false;
+}
+
+
+static bool usbProxyGetDeviceDesc(PUSBPROXYDEV pProxyDev, PVUSBDESCDEVICE pOut)
+{
+ /*
+ * Get the descriptor from the device.
+ */
+ PVUSBDESCDEVICE pIn = (PVUSBDESCDEVICE)GetStdDescSync(pProxyDev, VUSB_DT_DEVICE, 0, 0, VUSB_DT_DEVICE_MIN_LEN);
+ if (!pIn)
+ {
+ ERR("usbProxyGetDeviceDesc: pProxyDev=: GetStdDescSync failed\n");
+ return false;
+ }
+ if (pIn->bLength < VUSB_DT_DEVICE_MIN_LEN)
+ return false;
+
+ /*
+ * Convert it.
+ */
+ pOut->bLength = VUSB_DT_DEVICE_MIN_LEN;
+ pOut->bDescriptorType = VUSB_DT_DEVICE;
+ pOut->bcdUSB = RT_LE2H_U16(pIn->bcdUSB);
+ pOut->bDeviceClass = pIn->bDeviceClass;
+ pOut->bDeviceSubClass = pIn->bDeviceSubClass;
+ pOut->bDeviceProtocol = pIn->bDeviceProtocol;
+ pOut->bMaxPacketSize0 = pIn->bMaxPacketSize0;
+ pOut->idVendor = RT_LE2H_U16(pIn->idVendor);
+ pOut->idProduct = RT_LE2H_U16(pIn->idProduct);
+ pOut->bcdDevice = RT_LE2H_U16(pIn->bcdDevice);
+ pOut->iManufacturer = pIn->iManufacturer;
+ pOut->iProduct = pIn->iProduct;
+ pOut->iSerialNumber = pIn->iSerialNumber;
+ pOut->bNumConfigurations = pIn->bNumConfigurations;
+
+ free(pIn);
+ return true;
+}
+
+
+
+
+
+
+typedef int USBScanFunc(void *opaque, int bus_num, int addr, const char *port,
+ int class_id, int vendor_id, int product_id,
+ const char *product_name, int speed);
+
+
+static QEMUTimer *usb_auto_timer;
+static VMChangeStateEntry *usb_vmstate;
+
+
+
+static void async_complete(void *opaque)
+{
+#ifdef CONFIG_LINUX
+ USBHostDevice *s = opaque;
+ USBPROXYDEV* pThis=&s->vboxdev;
+#else
+ USBPROXYDEV* pThis= (PUSBPROXYDEV)opaque;
+ USBHostDevice *s = container_of(pThis, USBHostDevice, vboxdev);
+#endif
+ PVUSBURB vUrb = NULL;
+ TRACE("callback called async_complete Hostdevice: 0x%p, PROXYDEV:0x%p\n", s, pThis);
+
+ if (QTAILQ_EMPTY(&s->requests)) {
+ /* TODO : need to handle unplugged event
+ *
+ * Qemu can't know unplugged event directly so that should poll host device.
+ * But, the usb lib doesn't supply that interface.
+ * Besides, it is difficult to use existing interface,
+ * becasue of difference from implemetation for each host.
+ * (Usb lib sets 'fDetached' only when it fails to io operations which is
+ * invoked by exported interface, such like open, queue, reap, and so on.)
+ * Therefore, we have other idea that is to support by command of host.
+ */
+ return;
+ }
+
+ while (1) {
+ USBHostRequest* r;
+ USBPacket* p;
+ vUrb = pThis->pOps->pfnUrbReap(pThis, 0);
+ if (!vUrb) {
+ TRACE("(%s) urb reap failed(errno=%d)\n", __func__, errno);
+ usb_host_increase_callback_time(s);
+ return;
+ }
+ r = (USBHostRequest*)vUrb;
+ TRACE("complete req:0x%p\n", r);
+#ifdef CONFIG_WIN32
+ usb_host_del_event(r);
+#endif
+
+#if ISOC
+ if (r->iso_frame_idx == -1) {
+ int inflight;
+ int pid = (r->vbox_urb.EndPt & USB_DIR_IN) ?
+ USB_TOKEN_IN : USB_TOKEN_OUT;
+ int ep = r->vbox_urb.EndPt & 0xf;
+ if (vUrb->enmStatus == VUSBSTATUS_STALL) {
+ INFO("async_complete set status halt\n");
+ return;
+ }
+ r->iso_frame_idx = 0;
+ inflight = change_iso_inflight(s, pid, ep, -1);
+ if (inflight == 0 && is_iso_started(s, pid, ep)) {
+ ERR("husb: out of buffers for iso stream\n");
+ }
+ continue;
+ }
+#endif
+
+ p = r->p;
+ //fill packet structure
+ if (p) {
+ switch (vUrb->enmStatus){
+ /** Transer was ok. */
+ case VUSBSTATUS_OK:
+ {
+ p->actual_length = vUrb->cbData;
+ p->status = USB_RET_SUCCESS;
+ }
+ break;
+ /** Transfer stalled, endpoint halted. */
+ case VUSBSTATUS_STALL:
+ //set_halt(s, p->pid, p->ep->nr);
+ p->status = USB_RET_STALL;
+ ERR("VUSBSTATUS_STALL\n");
+ break;
+ /** Device not responding. */
+ case VUSBSTATUS_DNR:
+ ERR("VUSBSTATUS_DNR\n");
+ /** CRC error. */
+ case VUSBSTATUS_CRC:
+ ERR("VUSBSTATUS_CRC\n");
+ /** Data overrun error. */
+ case VUSBSTATUS_DATA_UNDERRUN:
+ ERR("VUSBSTATUS_DATA_UNDERRUN\n");
+ /** The isochronous buffer hasn't been touched. */
+ case VUSBSTATUS_NOT_ACCESSED:
+ ERR("VUSBSTATUS_NOT_ACCESSED\n");
+ /** Canceled/undone URB (VUSB internal). */
+ case VUSBSTATUS_UNDO:
+ ERR("VUSBSTATUS_UNDO\n");
+ /** Invalid status. */
+ p->status = USB_RET_IOERROR;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ /* status OK */
+ if ( p->state == USB_PACKET_CANCELED )
+ {
+ usb_host_req_free(r);
+ continue;
+ }
+
+ if ( vUrb->enmType == VUSBXFERTYPE_MSG )
+ {
+ trace_usb_host_req_complete(s->bus_num, s->addr, p,
+ p->status, vUrb->cbData);
+ if( r->in && p->actual_length )
+ memcpy(r->cbuf, (void*)(((unsigned char*)vUrb->abData) + 8), p->actual_length);
+ usb_generic_async_ctrl_complete(&s->dev, p);
+ } else {
+
+ if (r->in && vUrb->cbData) {
+ p->actual_length = 0;
+ usb_packet_copy(r->p, vUrb->abData, vUrb->cbData);
+ p->actual_length = vUrb->cbData;
+ }
+ usb_packet_complete(&s->dev, p);
+ }
+ usb_host_req_free(r);
+ }
+ }
+ usb_host_reset_callback_time(s);
+}
+
+static int usb_host_auto_scan(void *opaque, int bus_num,
+ int addr, const char *port,
+ int class_id, int vendor_id, int product_id,
+ const char *product_name, int speed)
+{
+ struct USBAutoFilter *f;
+ struct USBHostDevice *s;
+
+ /* Ignore hubs */
+ if (class_id == 9)
+ return 0;
+
+ QTAILQ_FOREACH(s, &hostdevs, next) {
+ f = &s->match;
+
+ if (f->bus_num > 0 && f->bus_num != bus_num) {
+ continue;
+ }
+ if (f->addr > 0 && f->addr != addr) {
+ continue;
+ }
+ if (f->port != NULL && strcmp(f->port, port) != 0) {
+ continue;
+ }
+
+ 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 */
+ s->seen++;
+
+ /* Already attached ? */
+ if (s->dev.attached) {
+ return 0;
+ }
+ //DPRINTF("husb: auto open: bus_num %d addr %d\n", bus_num, addr);
+
+ if (usb_host_open_device(s, bus_num, addr, product_id, vendor_id, port, product_name, speed) < 0) {
+ s->errcount++;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+
+
+/*
+ * modify to use libvboxusb to enumerate
+ */
+static int usb_host_scan(void *opaque, USBScanFunc *func)
+{
+ libvbox_usb *iter, *intern = NULL;
+ int ret = 0;
+ int rc, i;
+
+ rc = VBOXUSB_enum_devices(&intern);
+ if(!rc)
+ goto end;
+
+
+ iter = intern;
+ for( i = 0; i < rc; i++)
+ {
+ ret = func(opaque, iter->bus_num, iter->addr, iter->port, iter->class_id, iter->vendor_id, iter->product_id, iter->product_name, iter->speed);
+ if( ret )
+ goto end;
+ iter ++;
+ }
+
+end:
+ if(intern != NULL)
+ VBOXUSB_free(intern);
+ return ret;
+}
+
+/**********************/
+/* 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_host_ep_update(USBHostDevice *s)
+{
+ int i, j, k;
+ uint8_t devep, type;
+ int pid, ep;
+ USBPROXYDEV* pThis=&s->vboxdev;
+ USBDevice *udev = USB_DEVICE(s);
+
+#if ISOC
+ int m;
+
+ usb_ep_reset(udev);
+ TRACE("usb_host_ep_update conf_num %d\n", pThis->DevDesc.bNumConfigurations);
+
+ for (i = 0; i < pThis->DevDesc.bNumConfigurations; i++){
+ PVUSBDESCCONFIGEX ifconfig = (PVUSBDESCCONFIGEX)&pThis->paCfgDescs[i] ;
+ const VUSBINTERFACE* paIfs = ifconfig->paIfs;
+ TRACE("usb_host_ep_update interface %d\n", ifconfig->Core.bNumInterfaces);
+ for( m = 0; m < ifconfig->Core.bNumInterfaces; m++ )
+ {
+ TRACE("usb_host_ep_update cSettings %d\n", paIfs[m].cSettings);
+ for( j = 0; j < paIfs[m].cSettings; j++ )
+ {
+ if (udev->altsetting[m] != j) {
+ continue;
+ }
+ const PVUSBDESCINTERFACEEX endp = (PVUSBDESCINTERFACEEX)&paIfs[m].paSettings[j];
+ TRACE("usb_host_ep_update endpoint %d\n", endp->Core.bNumEndpoints);
+ for( k = 0; k < endp->Core.bNumEndpoints; k++ )
+ {
+ const PVUSBDESCENDPOINTEX end = (PVUSBDESCENDPOINTEX)&endp->paEndpoints[k];
+ devep = end->Core.bEndpointAddress;
+ pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT;
+ ep = devep & 0xf;
+ TRACE("usb_host_ep_update bmAttributes: %d, max: %d\n", end->Core.bmAttributes, end->Core.wMaxPacketSize);
+ type = end->Core.bmAttributes & 0x3;
+ TRACE("usb_host_ep_update ep: %d\n", ep);
+ if (ep == 0) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "invalid endpoint address");
+ ERR("INVALID endpoint address\n");
+ return;
+ }
+ TRACE("usb_host_ep_update get_type: %d:%d\n", usb_ep_get_type(udev, pid, ep), USB_ENDPOINT_XFER_INVALID);
+ if (usb_ep_get_type(udev, pid, ep) != USB_ENDPOINT_XFER_INVALID) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "duplicate endpoint address");
+ ERR("duplicate endpoint address\n");
+ continue;
+ }
+ INFO("usb_host_ep_update pid: %d, ep: %d, type : %d, interface: %d, max: %d\n", pid, ep, type, m, end->Core.wMaxPacketSize);
+ usb_ep_set_max_packet_size(udev, pid, ep,
+ end->Core.wMaxPacketSize);
+ usb_ep_set_type(udev, pid, ep, type);
+ usb_ep_set_ifnum(udev, pid, ep, m);
+
+ if (type == USB_ENDPOINT_XFER_ISOC) {
+ usb_ep_set_pipeline(udev, pid, ep, true);
+ usb_ep_set_max_streams(udev, pid, ep, end->Core.wMaxPacketSize);
+ }
+
+ usb_ep_set_halted(udev, pid, ep, 0);
+ }
+ }
+ }
+ }
+#else
+ for (i = 0; i < pThis->DevDesc.bNumConfigurations; i++){
+ PVUSBDESCCONFIGEX ifconfig = (PVUSBDESCCONFIGEX)&pThis->paCfgDescs[i] ;
+ const VUSBINTERFACE* paIfs = ifconfig->paIfs;
+ for( j = 0; j < paIfs->cSettings; j++ )
+ {
+ const PVUSBDESCINTERFACEEX endp = (PVUSBDESCINTERFACEEX)&paIfs->paSettings[j];
+ for( k = 0; k < endp->Core.bNumEndpoints; k++ )
+ {
+ const PVUSBDESCENDPOINTEX end = (PVUSBDESCENDPOINTEX)&endp->paEndpoints[k];
+ devep = end->Core.bEndpointAddress;
+ pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT;
+ ep = devep & 0xf;
+ type = end->Core.bmAttributes & 0x3;
+ if (ep == 0) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "invalid endpoint address");
+ return;
+ }
+ if (usb_ep_get_type(udev, pid, ep) != USB_ENDPOINT_XFER_INVALID) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "duplicate endpoint address");
+ return;
+ }
+ usb_ep_set_max_packet_size(udev, pid, ep,
+ end->Core.wMaxPacketSize);
+ usb_ep_set_type(udev, pid, ep, type);
+ usb_ep_set_ifnum(udev, pid, ep, i);
+ usb_ep_set_halted(udev, pid, ep, 0);
+ }
+ }
+ }
+#endif
+}
+/*
+ * 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_host_full_speed_compat(USBHostDevice *s)
+{
+ int i, j, k;
+ USBPROXYDEV* pThis=&s->vboxdev;
+
+ for (i = 0; i < pThis->DevDesc.bNumConfigurations; i++){
+ PVUSBDESCCONFIGEX ifconfig = (PVUSBDESCCONFIGEX)&pThis->paCfgDescs[i] ;
+ const VUSBINTERFACE* paIfs = ifconfig->paIfs;
+ for( j = 0; j < paIfs->cSettings; j++ )
+ {
+ const PVUSBDESCINTERFACEEX endp = (PVUSBDESCINTERFACEEX)&paIfs->paSettings[j];
+ for( k = 0; k < endp->Core.bNumEndpoints; k++ )
+ {
+ const PVUSBDESCENDPOINTEX end = (PVUSBDESCENDPOINTEX)&endp->paEndpoints[k];
+ switch ( end->Core.bmAttributes & 0x3 ) {
+ case 0x00: /* CONTROL */
+ break;
+ case 0x01: /* ISO */
+ return 0;
+ case 0x02: /* BULK */
+ break;
+ case 0x03: /* INTERRUPT */
+ if (end->Core.wMaxPacketSize > 64)
+ return 0;
+ break;
+ }
+ }
+ }
+ }
+ return 1;
+}
+static int usb_host_open_device(USBHostDevice* s, int nBus, int nAddr, int nPid, int nVid,
+ const char* port, const char* prod_name, int speed)
+{
+ char filename[VBOX_MAX_PATH] = {0,};
+ int rc;
+ USBDevice *udev = USB_DEVICE(s);
+ USBPROXYDEV* pThis=&s->vboxdev;
+ void* Backend = NULL;
+ unsigned i;
+ Error* open_error = NULL;
+
+ rc = VBOXUSB_preparedev(nBus, nAddr, nPid, nVid);
+ if( rc )
+ {
+ ERR("VBOXUSB_preparedev failed:%d\n", rc);
+ goto fail;
+ }
+ TRACE("prepare device :%d\n", rc);
+ rc = VBOXUSB_getdevname(filename, nBus, nAddr, nPid, nVid);
+ if( rc )
+ {
+ ERR("VBOXUSB_getdevname failed:%d\n", rc);
+ goto fail;
+ //todo _cleanup
+ }
+ TRACE("sizeof(path):%d, nBuf:%d, nAddr:%d\n", sizeof(filename), nBus, nAddr);
+ TRACE("open:%s \n", filename);
+ rc = pThis->pOps->pfnOpen(pThis, filename, Backend);
+ if( RT_FAILURE(rc))
+ {
+ ERR("usbProxyOpen failed with %d\n", rc);
+ goto fail;
+ }
+ s->bus_num = nBus;
+ s->addr = nAddr;
+
+ TRACE("open device:%d\n", rc);
+
+ /* 4. get(clear) all interface */
+ if (!usbProxyGetDeviceDesc(pThis, &pThis->DevDesc))
+ {
+ ERR("usbProxyConstruct: usbProxyGetDeviceDesc failed\n");
+ rc = VERR_READ_ERROR;
+ goto desc_fail;
+ }
+
+ size_t cbConfigs = pThis->DevDesc.bNumConfigurations * sizeof(pThis->paCfgDescs[0]);
+ pThis->paCfgDescs = (PVUSBDESCCONFIGEX)malloc(cbConfigs);
+
+ for (i = 0; i < pThis->DevDesc.bNumConfigurations; i++){
+ if (!copy_config(pThis, i, (PVUSBDESCCONFIGEX)&pThis->paCfgDescs[i]))
+ break;
+ }
+
+ TRACE("%d/%d\n", i, pThis->DevDesc.bNumConfigurations);
+ if (i < pThis->DevDesc.bNumConfigurations)
+ {
+ ERR("usbProxyConstruct: copy_config failed, i=%d\n", i);
+ rc = VERR_READ_ERROR;
+ goto desc_fail;
+ }
+
+ if( pThis->pOps->pfnInit){
+ rc = pThis->pOps->pfnInit(pThis);
+ if (RT_FAILURE(rc)){
+ ERR("init failed:%d\n", rc);
+ goto init_fail;
+ }
+ pThis->fInited = true;
+ }
+
+/*
+ * Register callbacks of its own OS
+ *
+ * NOTE : Windows makes changes of event handlers on runtime
+ * Therefore, for windows, registered to qemu main polling loop
+ * for each urb request.
+ */
+#if defined(CONFIG_DARWIN)
+ s->callback_timer = timer_new_ms(QEMU_CLOCK_REALTIME, async_complete, pThis);
+ usb_host_reset_callback_time(s);
+#elif defined(CONFIG_LINUX)
+ s->fd = (int)*((int*)pThis->Backend.pv);
+ qemu_set_fd_handler(s->fd, NULL, async_complete, s);
+#endif
+
+ if (!prod_name || prod_name[0] == '\0') {
+ snprintf(udev->product_desc, sizeof(udev->product_desc),
+ "host:%d.%d", nBus, nAddr);
+ } else {
+ pstrcpy(udev->product_desc, sizeof(udev->product_desc),
+ prod_name);
+ }
+
+ udev->speed = speed;
+ udev->speedmask = (1 << udev->speed);
+ if (udev->speed == USB_SPEED_HIGH && usb_host_full_speed_compat(s)) {
+ udev->speedmask |= USB_SPEED_MASK_FULL;
+ }
+
+ usb_ep_init(udev);
+ usb_host_ep_update(s);
+
+ usb_device_attach(udev, &open_error);
+ if (open_error) {
+ error_report("%s", error_get_pretty(open_error));
+ error_free(open_error);
+ rc = VERR_UNRESOLVED_ERROR;
+ goto fail;
+
+ }
+ INFO("usb device attached\n");
+
+ return 0;
+init_fail:
+desc_fail:
+ pThis->pOps->pfnClose(&s->vboxdev);
+fail:
+ return rc;
+}
+
+static void usb_host_cancel_packet(USBDevice *dev, USBPacket *p)
+{
+ USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev);
+ USBPROXYDEV* pThis=&s->vboxdev;
+ USBHostRequest *r;
+
+ if (p->combined) {
+ WARN("combined:%d\n", __LINE__);
+ //usb_combined_packet_cancel(udev, p);
+ return;
+ }
+ trace_usb_host_req_canceled(s->bus_num, s->addr, p);
+ r = usb_host_req_find(s, p);
+ if (r && r->p) {
+ pThis->pOps->pfnUrbCancel(&r->vbox_urb);
+ r->p = NULL; /* mark as dead */
+ }
+}
+static void usb_host_handle_reset(USBDevice *dev)
+{
+ int rc;
+ USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev);
+ USBPROXYDEV* pThis = &s->vboxdev;
+#ifndef CONFIG_LINUX
+ if (dev->configuration == 0) {
+ return;
+ }
+#endif
+ rc = pThis->pOps->pfnReset(pThis, 1);
+ if (RT_FAILURE(rc)){
+ ERR("proxy reset failed :%d\n", rc);
+ }
+}
+#if 0
+static void usb_host_abort_pendings(USBHostDevice* s)
+{
+ USBHostRequest *r;
+ USBPROXYDEV* pThis=&s->vboxdev;
+ QTAILQ_FOREACH(r, &s->requests, next) {
+ if(r && r->p){
+ r->p->status = USB_RET_NODEV;
+ pThis->pOps->pfnUrbCancel(&r->vbox_urb);
+ r->p = NULL;
+ if (r->p->ep->nr == 0) {
+ usb_generic_async_ctrl_complete(USB_DEVICE(s), r->p);
+ } else {
+ usb_packet_complete(USB_DEVICE(s), r->p);
+ }
+ usb_host_req_free(r);
+ }
+ }
+
+
+}
+#endif
+static int usb_host_close(USBHostDevice *s)
+{
+ int rc = 0;
+ USBDevice *udev = USB_DEVICE(s);
+ USBPROXYDEV* pThis=&s->vboxdev;
+
+ TRACE("host_close\n");
+ if (!s->vboxdev.Backend.pv)
+ {
+ INFO("USB device object does not exist\n");
+ return -1;
+ }
+#if 0
+ usb_host_iso_free_all(s);
+#endif
+ if (udev->attached) {
+ usb_device_detach(udev);
+ }
+
+ rc = pThis->pOps->pfnReset(pThis, 1);
+ if (RT_FAILURE(rc))
+ {
+ ERR("proxy dev reset failed in host_close:%d\n", rc);
+ }
+
+ if( pThis->pOps->pfnReleaseInterface )
+ {
+ int i, j;
+ for( j = 0; j < pThis->DevDesc.bNumConfigurations; j++){
+ PCVUSBDESCCONFIGEX pOldCfgDesc = (PCVUSBDESCCONFIGEX)&pThis->paCfgDescs[j];
+ for (i = 0; i < pOldCfgDesc->Core.bNumInterfaces; i++)
+ pThis->pOps->pfnReleaseInterface(pThis, i);
+ free((void*)pThis->paCfgDescs[j].paIfs);
+ }
+ }
+ free(pThis->paCfgDescs);
+ pThis->pOps->pfnClose(&s->vboxdev);
+
+#if defined(CONFIG_LINUX)
+ qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
+#elif defined(CONFIG_DARWIN)
+ timer_del(s->callback_timer);
+ timer_free(s->callback_timer);
+#endif
+ //TODO WIndows
+
+ s->fd = -1;
+ usb_host_auto_check(NULL);
+ return 0;
+}
+
+static void usb_host_nodev_bh(void *opaque)
+{
+ TRACE("(%s)\n", __func__);
+ USBHostDevice *s = opaque;
+ usb_host_close(s);
+}
+
+static void usb_host_nodev(USBHostDevice *s)
+{
+ if (!s->bh_nodev) {
+ s->bh_nodev = qemu_bh_new(usb_host_nodev_bh, s);
+ }
+ TRACE("(%s) invoking bh_nodev\n", __func__);
+ qemu_bh_schedule(s->bh_nodev);
+}
+
+static void usb_host_handle_destroy(USBDevice *dev)
+{
+ TRACE("(%s)\n", __func__);
+ USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev);
+ qemu_remove_exit_notifier(&s->exit);
+ QTAILQ_REMOVE(&hostdevs, s, next);
+ usb_host_close(s);
+}
+
+#if ISOC
+#define ISO_FRAME_DESC_PER_URB 8
+static USBHostRequest *usb_host_alloc_iso(USBHostDevice *s, USBPacket *p, int pid, uint8_t ep)
+{
+ USBHostRequest *req;
+ int i, j, len = p->ep->max_packet_size;
+
+ req = g_malloc0(s->iso_urb_count * sizeof(USBHostRequest));
+ for (i = 0; i < s->iso_urb_count; i++) {
+ req[i].host = s;
+ req[i].p = p;
+ req[i].in = (p->pid == USB_TOKEN_IN);
+ req[i].vbox_urb.pDev = (PVUSBDEV)&req->host->vboxdev;
+ req[i].vbox_urb.EndPt = p->ep->nr;
+ req[i].vbox_urb.enmDir = (p->pid == USB_TOKEN_IN) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT;
+ req[i].vbox_urb.fShortNotOk = false;
+ req[i].vbox_urb.enmType = VUSBXFERTYPE_ISOC;
+ req[i].vbox_urb.cbData = ISO_FRAME_DESC_PER_URB * len;
+ req[i].vbox_urb.cIsocPkts = ISO_FRAME_DESC_PER_URB;
+ for (j = 0 ; j < ISO_FRAME_DESC_PER_URB; j++)
+ req[i].vbox_urb.aIsocPkts[j].cb = len;
+ if (pid == USB_TOKEN_IN) {
+ req[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB;
+ }
+ }
+ set_iso_urb(s, pid, ep, req);
+
+ return req;
+}
+
+static void urb_status_to_usb_ret(USBPacket *p, int status)
+{
+ switch (status) {
+ case VUSBSTATUS_STALL:
+ p->status = USB_RET_STALL;
+ break;
+ default:
+ p->status = USB_RET_NAK;
+ }
+}
+
+static void usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in)
+{
+ int i, j, rc, max_packet_size, offset, len = 0;
+ uint8_t *buf;
+ USBHostRequest *req;
+ USBPROXYDEV* pThis=&s->vboxdev;
+
+ max_packet_size = p->ep->max_packet_size;
+ if (max_packet_size == 0)
+ return;
+
+ req = get_iso_urb(s, p->pid, p->ep->nr);
+ if (!req) {
+ req = usb_host_alloc_iso(s, p, p->pid, p->ep->nr);
+ }
+
+ i = get_iso_urb_idx(s, p->pid, p->ep->nr);
+ j = req[i].iso_frame_idx;
+
+ INFO("usb_host_handle_iso_data urb idx: %d, iso_frame_index: %d\n", i, j);
+
+ if (j >= 0 && j < ISO_FRAME_DESC_PER_URB) {
+ if (in) {
+ if (req[i].vbox_urb.enmStatus) {
+ urb_status_to_usb_ret(p, req[i].vbox_urb.enmStatus);
+ /* Move to the next urb */
+ req[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB - 1;
+ /* Check frame status */
+ } else if (req[i].vbox_urb.aIsocPkts[j].enmStatus) {
+ urb_status_to_usb_ret(p, req[i].vbox_urb.aIsocPkts[j].enmStatus);
+ /* Check the frame fits */
+ } else if (req[i].vbox_urb.aIsocPkts[j].cb
+ > p->iov.size) {
+ ERR("received iso data is larger then packet\n");
+ p->status = USB_RET_NAK;
+ /* All good copy data over */
+ } else {
+ len = req[i].vbox_urb.aIsocPkts[j].cb;
+ buf = req[i].vbox_urb.abData +
+ j * req[i].vbox_urb.aIsocPkts[0].cb;
+ usb_packet_copy(p, buf, len);
+ }
+ } else {
+ len = p->iov.size;
+ offset = (j == 0) ? 0 : get_iso_buffer_used(s, p->pid, p->ep->nr);
+ INFO("packet size: %d, offset : %d\n", len, offset);
+
+ /* Check the frame fits */
+ if (len > max_packet_size) {
+ ERR("husb: send iso data is larger then max packet size\n");
+ return;
+ }
+
+ /* All good copy data over */
+ usb_packet_copy(p, req[i].vbox_urb.abData + offset, len);
+ req[i].vbox_urb.aIsocPkts[j].cb = len;
+ req[i].vbox_urb.aIsocPkts[j].off = offset;
+ offset += len;
+ set_iso_buffer_used(s, p->pid, p->ep->nr, offset);
+
+ /* Start the stream once we have buffered enough data */
+ if (!is_iso_started(s, p->pid, p->ep->nr) && i == (s->iso_urb_count / 2)
+ && j == (ISO_FRAME_DESC_PER_URB - 1)) {
+ set_iso_started(s, p->pid, p->ep->nr);
+ }
+ }
+ req[i].iso_frame_idx++;
+ if (req[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) {
+ i = (i + 1) % s->iso_urb_count;
+ set_iso_urb_idx(s, p->pid, p->ep->nr, i);
+ }
+ } else {
+ if (in) {
+ set_iso_started(s, p->pid, p->ep->nr);
+ } else {
+ ERR("hubs: iso out error no free buffer, dropping packet\n");
+ }
+ }
+
+ if (is_iso_started(s, p->pid, p->ep->nr)) {
+ for (i = 0; i < s->iso_urb_count; i++) {
+ if (req[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) {
+ TRACE("Queue urb address: %p\n", &req[i].vbox_urb);
+ rc = pThis->pOps->pfnUrbQueue(&req[i].vbox_urb);
+ if (rc != 0) {
+ ERR("usb_host_handle_iso_data pfnUrbQueue RC: %d\n", rc);
+#ifdef CONFIG_LINUX
+ if( rc == ETIMEDOUT )
+ p->status = USB_RET_NAK;
+ else
+#endif
+ p->status = USB_RET_STALL;
+ return;
+ }
+
+ req[i].iso_frame_idx = -1;
+ change_iso_inflight(s, p->pid, p->ep->nr, 1);
+ }
+ }
+ }
+}
+#endif
+
+static void usb_host_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev);
+ USBPROXYDEV* pThis=&s->vboxdev;
+ USBHostRequest* req;
+ int rc;
+ size_t size;
+ uint8_t type;
+
+ if (usb_host_use_combining(p->ep) && p->state == USB_PACKET_SETUP) {
+ p->status = USB_RET_ADD_TO_QUEUE;
+ return;
+ }
+
+ trace_usb_host_req_data(s->bus_num, s->addr, p,
+ p->pid == USB_TOKEN_IN,
+ p->ep->nr, p->iov.size);
+
+ if (p->ep->halted) {
+ p->status = USB_RET_STALL;
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+ }
+
+ type = usb_ep_get_type(dev, p->pid, p->ep->nr);
+ switch (type){
+ case USB_ENDPOINT_XFER_INT:
+ size = p->iov.size;
+ req = usb_host_req_alloc(s, p, p->pid == USB_TOKEN_IN);
+ assert(req);
+ break;
+ case USB_ENDPOINT_XFER_BULK:
+ size = usb_packet_size(p);
+ req = usb_host_req_alloc(s, p, p->pid == USB_TOKEN_IN);
+ assert(req);
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+#if ISOC
+ usb_host_handle_iso_data(s, p, p->pid == USB_TOKEN_IN);
+ return;
+#endif
+ default:
+ WARN("not implemented\n");
+ p->status = USB_RET_STALL;
+ return;
+ }
+ usb_fill_data_urb(req, p, type, size);
+ rc = pThis->pOps->pfnUrbQueue(&req->vbox_urb);
+ if (RT_FAILURE(rc)) {
+ p->status = USB_RET_NODEV;
+ ERR("(%s) urb queueing failed(err=%d, detached=%d)\n",
+ __func__, rc, pThis->fDetached? 1:0);
+ if (pThis->fDetached)
+ usb_host_nodev(s);
+ return;
+ }
+
+#ifdef CONFIG_WIN32
+ usb_host_add_event(req);
+#endif
+ usb_host_reset_callback_time(s);
+ p->status = USB_RET_ASYNC;
+}
+static void usb_host_set_address(USBHostDevice *s, int addr)
+{
+ USBDevice *udev = USB_DEVICE(s);
+ trace_usb_host_set_address(s->bus_num, s->addr, addr);
+ udev->addr = addr;
+}
+
+static void usb_host_set_config(USBHostDevice *s, int config, USBPacket *p)
+{
+#ifndef CONFIG_LINUX
+ USBPROXYDEV* pThis=&s->vboxdev;
+ int ret = 0;
+
+ if( pThis->pOps->pfnReleaseInterface )
+ {
+ int i;
+ PCVUSBDESCCONFIGEX pOldCfgDesc = (PCVUSBDESCCONFIGEX)&pThis->paCfgDescs[config-1];
+ for (i = 0; i < pOldCfgDesc->Core.bNumInterfaces; i++)
+ pThis->pOps->pfnReleaseInterface(pThis, i);
+ }
+
+ if(pThis->pOps->pfnSetConfig)
+ {
+ ret = pThis->pOps->pfnSetConfig(pThis, config);
+ if( RT_FAILURE(ret) ){
+ ERR("set config failed then what???:%d\n", ret);
+ return ;
+ }
+ }
+ p->status = USB_RET_SUCCESS;
+ usb_host_ep_update(s);
+ TRACE("set config success\n");
+ ret = pThis->pOps->pfnClaimInterface(pThis, config) ;
+
+ if( RT_FAILURE(ret))
+ ERR("claim falied: %d\n", ret);
+#endif
+}
+
+#if ISOC
+static void usb_host_stop_n_free_iso(USBHostDevice *s, USBPacket *p)
+{
+ USBHostRequest *req;
+ USBPROXYDEV* pThis=&s->vboxdev;
+ int i, killed = 0;
+
+ req = get_iso_urb(s, p->pid, p->ep->nr);
+ if (!req) {
+ return;
+ }
+
+ for (i = 0; i < s->iso_urb_count; i++) {
+ /* in flight? */
+ if (req[i].iso_frame_idx == -1) {
+ pThis->pOps->pfnUrbCancel(&req[i].vbox_urb);
+ killed++;
+ }
+ }
+
+ if (killed)
+ async_complete(s);
+
+ g_free(req);
+
+ set_iso_urb(s, p->pid, p->ep->nr, NULL);
+ set_iso_urb_idx(s, p->pid, p->ep->nr, 0);
+ clear_iso_started(s, p->pid, p->ep->nr);
+}
+#endif
+
+static void usb_host_set_interface(USBHostDevice *s, int iface, int alt,
+ USBPacket *p)
+{
+#if ISOC
+ INFO("usb_host_set_interface %d, %d\n", iface, alt);
+ USBDevice *udev = USB_DEVICE(s);
+ USBPROXYDEV* pThis=&s->vboxdev;
+ int rc;
+ int type;
+
+ trace_usb_host_set_interface(s->bus_num, s->addr, iface, alt);
+
+ type = usb_ep_get_type(udev, p->pid, p->ep->nr);
+ if (type == USB_ENDPOINT_XFER_ISOC) {
+ usb_host_stop_n_free_iso(s, p);
+ }
+
+ if (iface >= USB_MAX_INTERFACES) {
+ p->status = USB_RET_STALL;
+ return;
+ }
+
+ rc = pThis->pOps->pfnSetInterface(pThis, iface, alt);
+ if (RT_FAILURE(rc))
+ {
+ p->status = USB_RET_STALL;
+ ERR("proxy dev reset failed in host_set_interface:%d\n", rc);
+ return;
+ }
+
+ udev->altsetting[iface] = alt;
+ usb_host_ep_update(s);
+#else
+ assert(0);
+#endif
+}
+
+static USBHostRequest* usb_host_req_alloc(USBHostDevice* s, USBPacket* p, bool in)
+{
+ USBHostRequest *r = g_new0(USBHostRequest, 1);
+ r->host = s;
+ r->p = p;
+ r->in = in;
+ QTAILQ_INSERT_TAIL(&s->requests, r, next);
+ return r;
+
+}
+static void usb_host_req_free(USBHostRequest* req)
+{
+ if( req->host )
+ {
+ QTAILQ_REMOVE(&req->host->requests, req, next);
+ }
+ g_free(req);
+}
+static USBHostRequest* usb_host_req_find(USBHostDevice *s, USBPacket* p)
+{
+ USBHostRequest *r;
+ QTAILQ_FOREACH(r, &s->requests, next) {
+ if (r->p == p) {
+ return r;
+ }
+ }
+ return NULL;
+}
+static void printVUrb(PVUSBURB vurb)
+{
+ /*
+ INFO("===print VUrb====\n");
+ INFO("pdev:%p, endpt:0x%x, enmDir:0x%x, cbdata:%d, enmType:%d\n",
+ vurb->pDev, vurb->EndPt, vurb->enmDir, vurb->cbData, vurb->enmType);
+ INFO("####end print VUrb ######\n");
+ */
+}
+static void usb_fill_data_urb(USBHostRequest* req, USBPacket *p, uint8_t type, size_t size)
+{
+ PVUSBURB vurb = &req->vbox_urb;
+
+#ifndef RT_OS_LINUX
+ USBHostDevice *s = req->host;
+ USBPROXYDEV* pThis=&s->vboxdev;
+ vurb->pUsbIns = (PPDMUSBINS)pThis ;
+#endif
+ vurb->pDev = (PVUSBDEV)&req->host->vboxdev;
+ vurb->EndPt = p->ep->nr;
+ vurb->enmDir = (req->in) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT;
+ vurb->fShortNotOk = false;
+
+ if(!req->in)
+ {
+ usb_packet_copy(p, (void*)vurb->abData, size);
+ }
+ vurb->cbData = size;
+ switch(type) {
+ case USB_ENDPOINT_XFER_INT:
+ vurb->enmType = VUSBXFERTYPE_INTR;
+ break;
+ case USB_ENDPOINT_XFER_BULK:
+ vurb->enmType = VUSBXFERTYPE_BULK;
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ vurb->enmType = VUSBXFERTYPE_ISOC;
+ break;
+ default:
+ ERR("Unknown data urb type \n");
+ assert(0);
+ }
+
+ TRACE("QUEUE: data: req:0x%p\n", req);
+ printVUrb(vurb);
+}
+
+static void usb_fill_msg_urb(USBDevice *dev, USBHostRequest* req, USBPacket *p, int length, uint8_t *data)
+{
+ PVUSBURB vurb = &req->vbox_urb;
+#ifndef RT_OS_LINUX
+ USBHostDevice *s = req->host;
+ USBPROXYDEV* pThis=&s->vboxdev;
+ vurb->pUsbIns = (PPDMUSBINS)pThis ;
+#endif
+ vurb->pDev = (PVUSBDEV)&req->host->vboxdev;
+ vurb->EndPt = p->ep->nr;
+ vurb->enmType = VUSBXFERTYPE_MSG;
+ vurb->enmDir = (req->in) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT;
+ memcpy((void*)vurb->abData, (void*)dev->setup_buf, 8);
+ if( !req->in)
+ memcpy((void*)((unsigned char*)vurb->abData + 8), data, length);
+ vurb->cbData = length + 8;
+ vurb->fShortNotOk = false;
+ TRACE("QUEUE: msg: req:0x%p\n", req);
+ printVUrb(vurb);
+}
+/**
+ @brief whether device is running or not
+ @param state : device state
+ @param running : running state
+ @return 0 on success, and a different value on error
+ @remark input parameter running is not null
+ */
+
+static void 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);
+ /* Note request is (bRequestType << 8) | bRequest */
+ int rc = 0;
+ USBPROXYDEV* pThis=&s->vboxdev;
+ USBHostRequest* req;
+
+
+ switch (request) {
+ case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+ usb_host_set_address(s, value);
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+
+ case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+ usb_host_set_config(s, value & 0xff, p);
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+
+ case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
+ usb_host_set_interface(s, index, value, p);
+ trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
+ return;
+ case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+ if (value == 0) { /* clear halt */
+ int pid = (index & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT;
+ rc = pThis->pOps->pfnClearHaltedEndpoint(&s->vboxdev, index);
+ if( RT_FAILURE(rc))
+ { //TODO don't care about errors?
+ ERR("failed on clear halted ep:%d\n", rc);
+ }
+ usb_ep_set_halted(dev, pid, index & 0x0f, 0);
+ return;
+ }
+ }
+
+ /* The rest are asynchronous */
+ if (length > sizeof(dev->data_buf)) {
+ fprintf(stderr, "husb: ctrl buffer too small (%d > %zu)\n",
+ length, sizeof(dev->data_buf));
+ p->status = USB_RET_STALL;
+ return;
+ }
+
+ req = usb_host_req_alloc(s, p, (request >> 8) & USB_DIR_IN);
+ assert(req);
+ req->cbuf = data;
+ req->clen = length;
+ usb_fill_msg_urb(dev, req, p, length, data);
+ rc = pThis->pOps->pfnUrbQueue(&req->vbox_urb);
+ if( RT_FAILURE(rc) )
+ {
+ ERR("fail on handle_control due to queuing failed:%d\n", rc);
+ //TODO fix error code
+ usb_host_req_free(req);
+#ifdef CONFIG_LINUX
+ if( rc == ETIMEDOUT )
+ p->status = USB_RET_NAK;
+ else
+#endif
+ p->status = USB_RET_STALL;
+
+ return;
+ }
+#ifdef CONFIG_WIN32
+ usb_host_add_event(req);
+#endif
+ usb_host_reset_callback_time(s);
+ p->status = USB_RET_ASYNC;
+}
+
+static int usb_host_post_load(void *opaque, int version_id)
+{
+ USBHostDevice *dev = opaque;
+ NOREF(dev);
+ return 0;
+}
+
+static const VMStateDescription vmstate_usb_host = {
+ .name = VBOX_PROXY_DEVNAME,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = usb_host_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(dev, USBHostDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+static Property usb_host_dev_properties[] = {
+ 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_UINT32("vendorid", USBHostDevice, match.vendor_id, 0),
+ DEFINE_PROP_UINT32("productid", USBHostDevice, match.product_id, 0),
+ DEFINE_PROP_UINT32("isobufs", USBHostDevice, iso_urb_count, 4),
+ DEFINE_PROP_INT32("bootindex", USBHostDevice, bootindex, -1),
+ DEFINE_PROP_BIT("pipeline", USBHostDevice, options,
+ USB_HOST_OPT_PIPELINE, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_host_exit_notifier(struct Notifier *n, void *data)
+{
+ USBHostDevice *s = container_of(n, USBHostDevice, exit);
+ QTAILQ_REMOVE(&hostdevs, s, next);
+ usb_host_close(s);
+}
+
+
+static void usb_host_realize(USBDevice *dev, Error **errp)
+{
+ USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev);
+
+ if (s->match.vendor_id > 0xffff) {
+ error_setg(errp, "vendorid out of range");
+ return;
+ }
+ if (s->match.product_id > 0xffff) {
+ error_setg(errp, "productid out of range");
+ return;
+ }
+ if (s->match.addr > 127) {
+ error_setg(errp, "hostaddr out of range");
+ return;
+ }
+
+ //init host device members
+ dev->auto_attach = 0;
+ dev->flags |= (1 << USB_DEV_FLAG_IS_HOST);
+
+ s->fd = -1;
+
+ s->exit.notify = usb_host_exit_notifier;
+ s->vboxdev.pOps = VBOXUSB_getbackend();
+ qemu_add_exit_notifier(&s->exit);
+
+ QTAILQ_INIT(&s->requests);
+ QTAILQ_INSERT_TAIL(&hostdevs, s, next);
+ usb_host_auto_check(NULL);
+}
+
+static void usb_host_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+ uc->realize = usb_host_realize;
+ uc->product_desc = "VBOX USB Proxy Device";
+ uc->cancel_packet = usb_host_cancel_packet;
+ uc->handle_data = usb_host_handle_data;
+ uc->handle_control = usb_host_handle_control;
+ uc->handle_reset = usb_host_handle_reset;
+ uc->handle_destroy = usb_host_handle_destroy;
+ dc->vmsd = &vmstate_usb_host;
+ dc->props = usb_host_dev_properties;
+}
+
+static const TypeInfo usb_host_dev_info = {
+ .name = VBOX_PROXY_DEVNAME,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBHostDevice),
+ .class_init = usb_host_class_initfn,
+};
+
+static void usb_host_register_types(void)
+{
+ type_register_static(&usb_host_dev_info);
+}
+type_init(usb_host_register_types)
+
+static void usb_host_vm_state(void *unused, int running, RunState state)
+{
+ if (running) {
+ usb_host_auto_check(unused);
+ }
+}
+static void usb_host_auto_check(void *unused)
+{
+ struct USBHostDevice *s;
+ int unconnected = 0;
+
+ if (runstate_is_running()) {
+ usb_host_scan(NULL, usb_host_auto_scan);
+
+ QTAILQ_FOREACH(s, &hostdevs, next) {
+ if (s->fd == -1) {
+ unconnected++;
+ }
+ if (s->seen == 0) {
+ s->errcount = 0;
+ }
+ s->seen = 0;
+ }
+#if 0
+ if (unconnected == 0) {
+ /* nothing to watch */
+ if (usb_auto_timer) {
+ qemu_del_timer(usb_auto_timer);
+ trace_usb_host_auto_scan_disabled();
+ }
+ return;
+ }
+#endif
+ }
+
+ if (!usb_vmstate) {
+ usb_vmstate = qemu_add_vm_change_state_handler(usb_host_vm_state, NULL);
+ }
+ if (!usb_auto_timer) {
+ usb_auto_timer = timer_new_ms(QEMU_CLOCK_REALTIME, usb_host_auto_check, NULL);
+ if (!usb_auto_timer) {
+ return;
+ }
+ trace_usb_host_auto_scan_enabled();
+ }
+ timer_mod(usb_auto_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 2000);
+}
+
+static void usb_info_device(Monitor *mon, int bus_num,
+ int addr, const char *port,
+ 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;
+ case USB_SPEED_SUPER:
+ speed_str = "5000";
+ break;
+ default:
+ speed_str = "?";
+ break;
+ }
+
+ 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) {
+ 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,
+ const 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, path, class_id, vendor_id, product_id,
+ product_name, speed);
+ return 0;
+}
+
+static void dec2str(int val, char *str, size_t size)
+{
+ if (val == 0) {
+ snprintf(str, size, "*");
+ } else {
+ snprintf(str, size, "%d", val);
+ }
+}
+
+static void hex2str(int val, char *str, size_t size)
+{
+ if (val == 0) {
+ snprintf(str, size, "*");
+ } else {
+ snprintf(str, size, "%04x", val);
+ }
+}
+
+void usb_host_info(Monitor *mon, const QDict *qdict)
+{
+ struct USBAutoFilter *f;
+ struct USBHostDevice *s;
+
+ usb_host_scan(mon, usb_host_info_device);
+
+ 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, " Bus %s, Addr %s, Port %s, ID %s:%s\n",
+ bus, addr, f->port ? f->port : "*", vid, pid);
+ }
+}
+
+