qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH] USB over network
@ 2008-10-06 11:43 Gal Hammer
  2008-10-06 12:09 ` Paul Brook
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Gal Hammer @ 2008-10-06 11:43 UTC (permalink / raw)
  To: qemu-devel


[-- Attachment #1.1: Type: text/plain, Size: 231 bytes --]

Hi,

 

Attached is a preliminary  patch which add QEmu the ability to use local
USB devices over network. It should work with DOK devices and might work
with web cameras.

 

Thanks,

 

                Gal.

 


[-- Attachment #1.2: Type: text/html, Size: 2107 bytes --]

[-- Attachment #2: usb-over-ip.patch --]
[-- Type: application/octet-stream, Size: 58722 bytes --]

diff --git a/Makefile b/Makefile
index 35061a4..ff8611f 100644
--- a/Makefile
+++ b/Makefile
@@ -78,7 +78,7 @@ OBJS+=ssd0303.o ssd0323.o ads7846.o stellaris_input.o twl92230.o
 OBJS+=tmp105.o lm832x.o
 OBJS+=scsi-disk.o cdrom.o
 OBJS+=scsi-generic.o
-OBJS+=usb.o usb-hub.o usb-linux.o usb-hid.o usb-msd.o usb-wacom.o
+OBJS+=usb.o usb-hub.o usb-linux.o usb-hid.o usb-msd.o usb-wacom.o usb-remote.o
 OBJS+=usb-serial.o usb-net.o
 OBJS+=sd.o ssi-sd.o
 OBJS+=bt.o bt-host.o bt-vhci.o bt-l2cap.o bt-sdp.o bt-hci.o bt-hid.o usb-bt.o
diff --git a/hw/usb.h b/hw/usb.h
index 4204808..8e2c520 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -242,6 +242,7 @@ USBDevice *usb_hub_init(int nb_ports);
 USBDevice *usb_host_device_open(const char *devname);
 int usb_host_device_close(const char *devname);
 void usb_host_info(void);
+int usb_remote_start(const char *host_str);
 
 /* usb-hid.c */
 USBDevice *usb_mouse_init(void);
diff --git a/qemu-usbd.c b/qemu-usbd.c
new file mode 100644
index 0000000..55bc334
--- /dev/null
+++ b/qemu-usbd.c
@@ -0,0 +1,1059 @@
+/*
+* Linux remote USB redirector
+*
+* Copyright (c) 2003-2008 Fabrice Bellard
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <linux/usbdevice_fs.h>
+#include <linux/usb_ch9.h>
+
+typedef struct NICInfo NICInfo;
+typedef struct HCIInfo HCIInfo;
+typedef struct musb_s musb_s;
+typedef struct IRQState *qemu_irq;
+
+#include "hw/usb.h"
+#include "usb-remote.h"
+
+#define USBDEVFS_PATH "/proc/bus/usb"
+#define PRODUCT_NAME_SZ 32
+
+//#define DEBUG
+
+#ifdef DEBUG
+#define dprintf printf
+#else
+#define dprintf(...)
+#endif
+
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+#define socket_error() errno
+#define closesocket(s) close(s)
+
+typedef struct USBDeviceX {
+    int     fd;
+    int     sock;
+
+    char    devname[PRODUCT_NAME_SZ];
+    uint8_t descr[1024];
+    int     descr_len;
+    int     closing;
+
+    /* Host side address */
+    int     bus_num;
+    int     addr;
+
+    /* Network buffer */
+    char    *tail_ptr;
+    char    buffer[0x1000];
+
+    struct USBDeviceX *next;
+
+} USBDeviceX;
+
+void *qemu_mallocz(size_t size)
+{
+    void *ptr;
+    ptr = malloc(size);
+    if (!ptr)
+        return NULL;
+    memset(ptr, 0, size);
+    return ptr;
+}
+
+void qemu_free(void *ptr)
+{
+    free(ptr);
+}
+
+void pstrcpy(char *buf, int buf_size, const char *str)
+{
+    int c;
+    char *q = buf;
+
+    if (buf_size <= 0)
+        return;
+
+    for(;;) {
+        c = *str++;
+        if (c == 0 || q >= buf + buf_size - 1)
+            break;
+        *q++ = c;
+    }
+    *q = '\0';
+}
+
+static socket_set_nonblock(int s)
+{
+    int f;
+    f = fcntl(s, F_GETFL);
+    fcntl(s, F_SETFL, f | O_NONBLOCK);
+}
+
+typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id,
+                        int vendor_id, int product_id,
+                        const char *product_name, int speed);
+
+static int usb_host_find_device(int *pbus_num, int *paddr,
+                                char *product_name, int product_name_size,
+                                const char *devname);
+
+static USBDeviceX *hostdev_list;
+
+static void hostdev_link(USBDeviceX *dev)
+{
+    dev->next = hostdev_list;
+    hostdev_list = dev;
+}
+
+static void hostdev_unlink(USBDeviceX *dev)
+{
+    USBDeviceX *pdev = hostdev_list;
+    USBDeviceX **prev = &hostdev_list;
+
+    while (pdev) {
+        if (pdev == dev) {
+            *prev = dev->next;
+            return;
+        }
+
+        prev = &pdev->next;
+        pdev = pdev->next;
+    }
+}
+
+static USBDeviceX *hostdev_find(int bus_num, int addr)
+{
+    USBDeviceX *s = hostdev_list;
+    while (s) {
+        if (s->bus_num == bus_num && s->addr == addr)
+            return s;
+        s = s->next;
+    }
+    return NULL;
+}
+
+/* 
+* Async URB state.
+* We always allocate one isoc descriptor even for bulk transfers
+* to simplify allocation and casts. 
+*/
+typedef struct AsyncURB
+{
+    struct usbdevfs_urb urb;
+    struct usbdevfs_iso_packet_desc isocpd;
+
+    USBPacket   *packet;
+    USBDeviceX  *dev;
+} AsyncURB;
+
+static AsyncURB *async_alloc(void)
+{
+    return (AsyncURB *) qemu_mallocz(sizeof(AsyncURB));
+}
+
+static void async_free(AsyncURB *aurb)
+{
+    qemu_free(aurb);
+}
+
+static void async_complete(USBDeviceX *s)
+{
+    UNRB_SUBMIT_URB nurb;
+    AsyncURB *aurb;
+    int ret;
+
+    while (1) {
+
+        ret = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &aurb);
+        if (ret < 0) {
+            if (errno == EAGAIN)
+                return;
+
+            if (errno == ENODEV && !s->closing) {
+                printf("husb: device %d.%d disconnected\n", s->bus_num, s->addr);
+                return;
+            }
+
+            dprintf("husb: async. reap urb failed errno %d\n", errno);
+            return;
+        }
+
+        dprintf("husb: async completed. aurb %p status %d alen %d\n", 
+            aurb, aurb->urb.status, aurb->urb.actual_length);
+
+        nurb.header.magic = USB_REMOTE_MAGIC;
+        nurb.header.opcode = UNRB_OPCODE_SUBMIT_URB;
+        nurb.header.size = sizeof(nurb) + aurb->urb.buffer_length;
+
+        nurb.urb.opaque = (uint64_t)(aurb->packet);
+
+        nurb.urb.type = aurb->urb.type;
+        nurb.urb.status = aurb->urb.status;
+        nurb.urb.length = aurb->urb.actual_length;
+
+        ret = send(s->sock, &nurb, sizeof(nurb), 0);
+        if (ret > 0) {
+            ret = send(s->sock, aurb->urb.buffer, aurb->urb.buffer_length, 0);
+        }
+
+        qemu_free(aurb->urb.buffer);
+        async_free(aurb);
+    }
+}
+
+static void async_cancel(USBPacket *unused, void *opaque)
+{
+    AsyncURB *aurb = opaque;
+    USBDeviceX *s = aurb->dev;
+
+    dprintf("husb: async cancel. aurb %p\n", aurb);
+
+    /* 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 void usb_host_handle_destroy(USBDeviceX *s)
+{
+    s->closing = 1;
+
+    hostdev_unlink(s);
+
+    async_complete(s);
+
+    if (s->fd >= 0)
+        close(s->fd);
+
+    qemu_free(s);
+}
+
+static int ctrl_error(void)
+{
+    if (errno == ETIMEDOUT)
+        return USB_RET_NAK;
+    else 
+        return USB_RET_STALL;
+}
+
+static int usb_host_set_config(USBDeviceX *s, int config)
+{
+    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)
+        return ctrl_error();
+
+    return 0;
+}
+
+static int usb_host_set_interface(USBDeviceX *s, int iface, int alt)
+{
+    struct usbdevfs_setinterface si;
+    int ret;
+
+    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();
+
+    return 0;
+}
+
+static USBDeviceX *usbd_host_device_open_addr(int bus_num, int addr, const char *prod_name)
+{
+    USBDeviceX *dev = NULL;
+    int fd = -1, sock = -1;
+    unsigned long opt = 1;
+    char buf[1024];
+
+    dev = qemu_mallocz(sizeof(USBDeviceX));
+    if (!dev)
+        goto fail;
+
+    dev->bus_num = bus_num;
+    dev->addr = addr;
+
+    printf("husb: open device %d.%d\n", bus_num, addr);
+
+    snprintf(buf, sizeof(buf), USBDEVFS_PATH "/%03d/%03d",
+        bus_num, addr);
+    fd = open(buf, O_RDWR | O_NONBLOCK);
+    if (fd < 0) {
+        perror(buf);
+        goto fail;
+    }
+
+    sock = socket(PF_INET, SOCK_STREAM, 0);
+    if (sock < 0) {
+        perror("socket");
+        goto fail;
+    }
+
+    setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&opt, sizeof(opt));
+
+    /* read the device description */
+    dev->descr_len = read(fd, dev->descr, sizeof(dev->descr));
+    if (dev->descr_len <= 0) {
+        perror("husb: reading device data failed");
+        goto fail;
+    }
+
+#ifdef DEBUG
+    {
+        int x;
+        printf("=== begin dumping device descriptor data ===\n");
+        for (x = 0; x < dev->descr_len; x++)
+            printf("%02x ", dev->descr[x]);
+        printf("\n=== end dumping device descriptor data ===\n");
+    }
+#endif
+
+    dev->fd = fd;
+    dev->sock = sock;
+    dev->tail_ptr = dev->buffer;
+
+    if (!prod_name || prod_name[0] == '\0')
+        snprintf(dev->devname, sizeof(dev->devname), "host:%d.%d", bus_num, addr);
+    else
+        pstrcpy(dev->devname, sizeof(dev->devname), prod_name);
+
+    hostdev_link(dev);
+
+    return dev;
+
+fail:
+    if (dev) {
+        qemu_free(dev);
+    }
+    close(fd);
+    close(sock);
+    return NULL;
+}
+
+USBDeviceX *usbd_host_device_open(const char *devname)
+{
+    int bus_num, addr;
+    char product_name[PRODUCT_NAME_SZ];
+
+    if (usb_host_find_device(&bus_num, &addr, product_name, sizeof(product_name),
+        devname) < 0)
+        return NULL;
+
+    if (hostdev_find(bus_num, addr)) {
+        printf("husb: host usb device %d.%d is already open\n", bus_num, addr);
+        return NULL;
+    }
+
+    return usbd_host_device_open_addr(bus_num, addr, product_name);
+}
+
+int usbd_host_device_close(const char *devname)
+{
+    char product_name[PRODUCT_NAME_SZ];
+    int bus_num, addr;
+    USBDeviceX *s;
+
+    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) {
+        return 0;
+    }
+
+    return -1;
+}
+
+static int get_tag_value(char *buf, int buf_size,
+                         const char *str, const char *tag,
+                         const char *stopchars)
+{
+    const char *p;
+    char *q;
+    p = strstr(str, tag);
+    if (!p)
+        return -1;
+    p += strlen(tag);
+    while (isspace(*p))
+        p++;
+    q = buf;
+    while (*p != '\0' && !strchr(stopchars, *p)) {
+        if ((q - buf) < (buf_size - 1))
+            *q++ = *p;
+        p++;
+    }
+    *q = '\0';
+    return q - buf;
+}
+
+static int usb_host_scan(void *opaque, USBScanFunc *func)
+{
+    FILE *f;
+    char line[1024];
+    char buf[1024];
+    int bus_num, addr, speed, device_count, class_id, product_id, vendor_id;
+    int ret;
+    char product_name[512];
+
+    f = fopen(USBDEVFS_PATH "/devices", "r");
+    if (!f) {
+        printf("husb: could not open %s\n", USBDEVFS_PATH "/devices");
+        return 0;
+    }
+    device_count = 0;
+    bus_num = addr = speed = class_id = product_id = vendor_id = 0;
+    ret = 0;
+    for(;;) {
+        if (fgets(line, sizeof(line), f) == NULL)
+            break;
+        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,
+                    product_id, product_name, speed);
+                if (ret)
+                    goto the_end;
+            }
+            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)
+                goto fail;
+            addr = atoi(buf);
+            if (get_tag_value(buf, sizeof(buf), line, "Spd=", " ") < 0)
+                goto fail;
+            if (!strcmp(buf, "480"))
+                speed = USB_SPEED_HIGH;
+            else if (!strcmp(buf, "1.5"))
+                speed = USB_SPEED_LOW;
+            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)
+                goto fail;
+            vendor_id = strtoul(buf, NULL, 16);
+            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)
+                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)
+                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,
+            product_id, product_name, speed);
+    }
+the_end:
+    fclose(f);
+    return ret;
+}
+
+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 */
+
+struct usb_class_info {
+    int class;
+    const char *class_name;
+};
+
+static const struct usb_class_info usb_class_info[] = {
+    { USB_CLASS_AUDIO, "Audio"},
+    { USB_CLASS_COMM, "Communication"},
+    { USB_CLASS_HID, "HID"},
+    { USB_CLASS_HUB, "Hub" },
+    { USB_CLASS_PHYSICAL, "Physical" },
+    { USB_CLASS_PRINTER, "Printer" },
+    { USB_CLASS_MASS_STORAGE, "Storage" },
+    { USB_CLASS_CDC_DATA, "Data" },
+    { USB_CLASS_APP_SPEC, "Application Specific" },
+    { USB_CLASS_VENDOR_SPEC, "Vendor Specific" },
+    { USB_CLASS_STILL_IMAGE, "Still Image" },
+    { USB_CLASS_CSCID, "Smart Card" },
+    { USB_CLASS_CONTENT_SEC, "Content Security" },
+    { -1, NULL }
+};
+
+static const char *usb_class_str(uint8_t class)
+{
+    const struct usb_class_info *p;
+    for(p = usb_class_info; p->class != -1; p++) {
+        if (p->class == class)
+            break;
+    }
+    return p->class_name;
+}
+
+static void usb_info_device(int bus_num, int addr, int class_id,
+                            int vendor_id, int product_id,
+                            const char *product_name,
+                            int speed)
+{
+    const char *class_str, *speed_str;
+
+    switch(speed) {
+case USB_SPEED_LOW:
+    speed_str = "1.5";
+    break;
+case USB_SPEED_FULL:
+    speed_str = "12";
+    break;
+case USB_SPEED_HIGH:
+    speed_str = "480";
+    break;
+default:
+    speed_str = "?";
+    break;
+    }
+
+    printf("  Device %d.%d, speed %s Mb/s\n",
+        bus_num, addr, speed_str);
+    class_str = usb_class_str(class_id);
+    if (class_str)
+        printf("    %s:", class_str);
+    else
+        printf("    Class %02x:", class_id);
+    printf(" USB device %04x:%04x", vendor_id, product_id);
+    if (product_name[0] != '\0')
+        printf(", %s", product_name);
+    printf("\n");
+}
+
+static int usb_host_info_device(void *opaque, int bus_num, int addr,
+                                int class_id,
+                                int vendor_id, int product_id,
+                                const char *product_name,
+                                int speed)
+{
+    usb_info_device(bus_num, addr, class_id, vendor_id, product_id,
+        product_name, speed);
+    return 0;
+}
+
+void usb_host_info(void)
+{
+    usb_host_scan(NULL, usb_host_info_device);
+}
+
+int handle_submit_urb(USBDeviceX *s, UNRB_HEADER *hdr)
+{
+    UNRB_SUBMIT_URB *nurb = (UNRB_SUBMIT_URB *)hdr;
+    struct usbdevfs_urb *urb;
+    AsyncURB *aurb;
+    int ret;
+
+    aurb = async_alloc();
+    if (!aurb) {
+        dprintf("husb: async malloc failed\n");
+        return USB_RET_NAK;
+    }
+
+    aurb->packet = (USBPacket*)(nurb->urb.opaque);
+
+    urb = &aurb->urb;
+
+    urb->type = nurb->urb.type;
+    urb->endpoint = nurb->urb.endpoint;
+    urb->status = nurb->urb.status;
+    urb->flags = nurb->urb.flags;
+    urb->number_of_packets = nurb->urb.number_of_packets;
+
+    if (urb->number_of_packets) {
+        urb->iso_frame_desc[0].length = nurb->urb.length;
+    }
+
+    urb->buffer_length = nurb->urb.length;
+    urb->buffer = qemu_mallocz(urb->buffer_length);
+
+    memcpy(urb->buffer, nurb->urb.buffer, nurb->urb.length);
+
+#ifdef DEBUG
+    {
+        int x;
+        printf("=== begin dumping urb buffer data ===\n");
+        for (x = 0; x < urb->buffer_length; ++x)
+            printf("%02x ", ((unsigned char*)urb->buffer)[x]);
+        printf("\n=== end dumping urb buffer data ===\n");
+    }
+#endif
+
+    ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
+
+    dprintf("husb: data submit. ep 0x%x len %u aurb %p\n", urb->endpoint, nurb->urb.length, 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;
+        }
+    }
+}
+
+int handle_set_config(USBDeviceX *s, UNRB_HEADER *hdr)
+{
+    UNRB_SET_CONFIG *nurb = (UNRB_SET_CONFIG *)hdr;
+
+    usb_host_set_config(s, nurb->config);
+}
+
+int handle_set_interface(USBDeviceX *s, UNRB_HEADER *hdr)
+{
+    UNRB_SET_INTERFACE *nurb = (UNRB_SET_INTERFACE *)hdr;
+
+    usb_host_set_interface(s, nurb->iface, nurb->altsetting);
+}
+
+int handle_release_interace(USBDeviceX *s, UNRB_HEADER *hdr)
+{
+    UNRB_RELEASE_INTERFACE *nurb = (UNRB_RELEASE_INTERFACE *)hdr;
+    int ret;
+
+    dprintf("husb: releasing interfaces\n");
+
+    ret = ioctl(s->fd, USBDEVFS_RELEASEINTERFACE, &nurb->iface);
+    if (ret < 0) {
+        perror("husb: failed to release interface");
+        return 0;
+    }
+
+    return 1;
+}
+
+static void handle_device_reset(USBDeviceX *dev)
+{
+    dprintf("husb: reset device %u.%u\n", dev->bus_num, dev->addr);
+
+    ioctl(dev->fd, USBDEVFS_RESET);
+}
+
+int handle_claim_interface(USBDeviceX *s, UNRB_HEADER *hdr)
+{
+    UNRB_CLAIM_INTERFACE *nurb = (UNRB_CLAIM_INTERFACE *)hdr;
+    int ret;
+
+    dprintf("husb: claiming interface. interace %d\n", nurb->iface);
+
+    ret = ioctl(s->fd, USBDEVFS_CLAIMINTERFACE, &nurb->iface);
+    if (ret < 0) {
+        if (errno == EBUSY) {
+            printf("husb: update iface. device already grabbed\n");
+        } else {
+            perror("husb: failed to claim interface");
+        }
+        return 0;
+    }
+
+    return 1;
+}
+
+int handle_connection_info(USBDeviceX *s, UNRB_HEADER *hdr)
+{
+    UNRB_CONNECTION_INFO *nurb = (UNRB_CONNECTION_INFO *)hdr;
+
+    struct usbdevfs_connectinfo ci;
+    int ret;
+
+    ret = ioctl(s->fd, USBDEVFS_CONNECTINFO, &ci);
+    if (ret < 0) {
+        perror("usb_host_device_open: USBDEVFS_CONNECTINFO");
+        return 0;
+    }
+
+    nurb->devnum = ci.devnum;
+    nurb->slow = ci.slow;
+
+    printf("husb: connect info devnum=%d slow=%d\n", ci.devnum, ci.slow);
+
+    ret = send(s->sock, nurb, sizeof(*nurb), 0);
+
+    return ret;
+}
+
+int handle_io_control(USBDeviceX *s, UNRB_HEADER *hdr)
+{
+    UNRB_IO_CTRL *nurb = (UNRB_IO_CTRL *)hdr;
+
+    struct usbdevfs_ioctl ctrl;
+    int ret;
+
+    ctrl.ioctl_code = nurb->ioctl_code;
+    ctrl.ifno = nurb->ifno;
+
+    ret = ioctl(s->fd, USBDEVFS_IOCTL, &ctrl);
+    if (ret < 0 && errno != ENODATA) {
+        perror("USBDEVFS_IOCTL");
+        return 0;
+    }
+
+    return 1;
+}
+
+int handle_usb_control(USBDeviceX *s, UNRB_HEADER *hdr)
+{
+    UNRB_USB_CONTROL *nurb = (UNRB_USB_CONTROL *)hdr;
+
+    uint8_t configuration;
+    struct usbdevfs_ctrltransfer ct;
+    int ret;
+
+    ct.bRequestType = nurb->type;
+    ct.bRequest = nurb->request;
+    ct.wValue = nurb->value;
+    ct.wIndex = nurb->index;
+    ct.wLength = nurb->length;
+    ct.data = &configuration;
+    ct.timeout = 50;
+
+    ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
+    if (ret < 0) {
+        perror("usb_linux_update_endp_table");
+        return 1;
+    }
+
+    dprintf("USBDEVFS_CONTROL: configuration = %d\n", configuration);
+
+    ret = send(s->sock, &configuration, sizeof(configuration), 0);
+
+    return ret;
+}
+
+void handle_nurb(USBDeviceX *dev, UNRB_HEADER *hdr)
+{
+    switch (hdr->opcode)
+    {
+    case UNRB_OPCODE_NEW_DEVICE:
+        printf("UNRB_OPCODE_NEW_DEVICE\n"); exit(1);
+        break;
+    case UNRB_OPCODE_CLAIM_INTERFACE:
+        handle_claim_interface(dev, hdr);
+        break;
+    case UNRB_OPCODE_CLEAR_HALT:
+        printf("UNRB_OPCODE_CLEAR_HALT\n"); exit(1);
+        break;
+    case UNRB_OPCODE_CONNECTION_INFO:
+        handle_connection_info(dev, hdr);
+        break;
+    case UNRB_OPCODE_DISCARD_URB:
+        printf("UNRB_OPCODE_DISCARD_URB\n"); exit(1);
+        break;
+    case UNRB_OPCODE_IO_CTRL:
+        handle_io_control(dev, hdr);
+        break;
+    case UNRB_OPCODE_GET_INTERFACE:
+        printf("UNRB_OPCODE_GET_INTERFACE\n"); exit(1);
+        break;
+    case UNRB_OPCODE_USB_CONTROL:
+        handle_usb_control(dev, hdr);
+        break;
+    case UNRB_OPCODE_RELEASE_INTERFACE:
+        handle_release_interace(dev, hdr);
+        break;
+    case UNRB_OPCODE_RESET_DEVICE:
+        handle_device_reset(dev);
+        break;
+    case UNRB_OPCODE_SUBMIT_URB:
+        handle_submit_urb(dev, hdr);
+        break;
+    case UNRB_OPCODE_SET_CONFIG:
+        handle_set_config(dev, hdr);
+        break;
+    case UNRB_OPCODE_SET_INTERFACE:
+        handle_set_interface(dev, hdr);
+        break;
+    default:
+        printf("unknown opcode %d!\n", hdr->opcode); exit(1);
+    }
+}
+
+void read_from_network(USBDeviceX *s)
+{
+    UNRB_HEADER *hdr;
+    char *head_ptr = s->buffer;
+    int nread = s->tail_ptr - s->buffer;
+    int size;
+
+    size = recv(s->sock, s->tail_ptr, sizeof(s->buffer) - nread, 0);
+    if ((size == 0) || ((size < 0) && (socket_error() != EWOULDBLOCK))) {
+        dprintf("host disconnect %d\n", socket_error());
+        exit(1);
+    }
+
+    nread += size;
+
+    while (nread > 0) {
+
+        if (nread < sizeof(UNRB_HEADER)) {
+            s->tail_ptr += size;
+            break;
+        }
+
+        hdr = (UNRB_HEADER *)(head_ptr);
+
+        if ((hdr->magic != USB_REMOTE_MAGIC) || (hdr->size == 0)) {
+            printf("bad nurb header\n");
+            exit(1);
+        }
+
+        if (nread < hdr->size) {
+            s->tail_ptr += size;
+            /* need to wait for more data from network. */
+            break;
+        }
+
+        handle_nurb(s, hdr);
+
+        nread -= hdr->size;
+        head_ptr += hdr->size;
+    }
+
+    if ((nread > 0) && (head_ptr != s->buffer)) {
+        memcpy(s->buffer, head_ptr, nread);
+    }
+
+    s->tail_ptr = s->buffer + nread;
+}
+
+static int connect_to_host(int sock, const char *host, uint16_t port)
+{
+    struct sockaddr_in sin;
+    int ret;
+
+    if (!inet_aton(host, &sin.sin_addr)) {
+        printf("error %s!\n", host);
+        return -1;
+    }
+
+    sin.sin_port = htons(port);
+    sin.sin_family = AF_INET;
+
+    ret = connect(sock, (struct sockaddr *)&sin, sizeof(sin));
+    if (ret != 0)
+    {
+        printf("failed to connect %d\n", socket_error());
+    }
+
+    return ret;
+}
+
+static int register_new_device(USBDeviceX *s)
+{
+    UNRB_NEW_DEVICE nurb;
+    int ret;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_NEW_DEVICE;
+    nurb.header.size = sizeof(nurb) + s->descr_len;
+
+    nurb.bus_num = s->bus_num;
+    nurb.addr = s->addr;
+    pstrcpy(nurb.dev_name, sizeof(nurb.dev_name), s->devname);
+    nurb.descr_len = s->descr_len;
+
+    ret = send(s->sock, &nurb, sizeof(nurb), 0);
+    if (ret > 0) {
+        ret = send(s->sock, s->descr, s->descr_len, 0);
+    }
+
+    return ret;
+}
+
+static void main_loop()
+{
+    USBDeviceX *dev = hostdev_list;
+    fd_set rfds, wfds;
+    int ret, nfds;
+
+    nfds = -1;
+    FD_ZERO(&rfds);
+    FD_ZERO(&wfds);
+
+    while (dev) {
+        FD_SET(dev->sock, &rfds);
+        FD_SET(dev->fd, &wfds);
+        nfds = MAX(nfds, MAX(dev->fd, dev->sock));
+        dev = dev->next;
+    }
+
+    ret = select(nfds + 1, &rfds, &wfds, NULL, NULL);
+    if (ret > 0) {
+        dev = hostdev_list;
+        while (dev) {
+            if (FD_ISSET(dev->sock, &rfds)) {
+                read_from_network(dev);
+            }
+            if (FD_ISSET(dev->fd, &wfds)) {
+                async_complete(dev);
+            }
+            dev = dev->next;
+        }
+    }
+}
+
+int main(int argc, char **argv)
+{
+    const char *host;
+    uint16_t port;
+    const char *device;
+
+    if (argc < 4) {
+        printf("%s: host port (bus.addr | vendor:product)\n\n", argv[0]);
+        printf("\tbus.addr - (decimal)\n");
+        printf("\tvendor:product - (hexadecimal)\n");
+        return EXIT_SUCCESS;
+    }
+
+    host = argv[1];
+    port = atoi(argv[2]);
+    device = argv[3];
+
+    USBDeviceX *dev = usbd_host_device_open(device);
+    if (!dev) {
+        return;
+    }
+
+    if (connect_to_host(dev->sock, host, port) != 0) {
+        return;
+    }
+
+    socket_set_nonblock(dev->sock);
+
+    if (register_new_device(dev) < 0) {
+        printf("failed to register device %s %d\n", dev->devname, socket_error());
+        return;
+    }
+
+    while (hostdev_list) {
+        main_loop();
+    }
+
+    shutdown(dev->sock, SHUT_RDWR);
+    closesocket(dev->sock);
+
+    return EXIT_SUCCESS;
+}
diff --git a/usb-linux.c b/usb-linux.c
index c5da5b5..24a7cb8 100644
--- a/usb-linux.c
+++ b/usb-linux.c
@@ -28,6 +28,7 @@
 
 #include "qemu-common.h"
 #include "qemu-timer.h"
+#include "qemu_socket.h"
 #include "console.h"
 
 #if defined(__linux__)
@@ -38,6 +39,7 @@
 #include <linux/usbdevice_fs.h>
 #include <linux/version.h>
 #include "hw/usb.h"
+#include "usb-remote.h"
 
 /* We redefine it to avoid version problems */
 struct usb_ctrltransfer {
@@ -58,12 +60,19 @@ struct usb_ctrlrequest {
     uint16_t wLength;
 };
 
+typedef int __ioctl(int fd, unsigned long int request, ...);
+
 typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id,
                         int vendor_id, int product_id,
                         const char *product_name, int speed);
 static int usb_host_find_device(int *pbus_num, int *paddr,
                                 char *product_name, int product_name_size,
                                 const char *devname);
+
+extern int usb_remote_ioctl(int fd, unsigned long int request, ...);
+
+extern int parse_host_port(struct sockaddr_in *saddr, const char *str);
+
 //#define DEBUG
 
 #ifdef DEBUG
@@ -119,9 +128,20 @@ typedef struct USBHostDevice {
     int bus_num;
     int addr;
 
+    __ioctl *ioctl;
+
     struct USBHostDevice *next;
 } USBHostDevice;
 
+typedef struct USBRemoteDevice {
+    USBHostDevice hdev;
+
+    /* Network buffer */
+    char *read_ptr;
+    char buffer[4096];
+
+} USBRemoteDevice;
+
 static int is_isoc(USBHostDevice *s, int ep)
 {
     return s->endp_table[ep - 1].type == USBDEVFS_URB_TYPE_ISO;
@@ -229,7 +249,7 @@ static void async_complete(void *opaque)
     while (1) {
     	USBPacket *p;
 
-	int r = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &aurb);
+        int r = s->ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &aurb);
         if (r < 0) {
             if (errno == EAGAIN)
                 return;
@@ -282,7 +302,7 @@ static void async_cancel(USBPacket *unused, void *opaque)
     /* Mark it as dead (see async_complete above) */
     aurb->packet = NULL;
 
-    int r = ioctl(s->fd, USBDEVFS_DISCARDURB, aurb);
+    int r = s->ioctl(s->fd, USBDEVFS_DISCARDURB, aurb);
     if (r < 0) {
         dprintf("husb: async. discard urb failed errno %d\n", errno);
     }
@@ -339,7 +359,7 @@ 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;
-            ret = ioctl(dev->fd, USBDEVFS_IOCTL, &ctrl);
+            ret = dev->ioctl(dev->fd, USBDEVFS_IOCTL, &ctrl);
             if (ret < 0 && errno != ENODATA) {
                 perror("USBDEVFS_DISCONNECT");
                 goto fail;
@@ -350,7 +370,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++) {
-        ret = ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &interface);
+        ret = dev->ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &interface);
         if (ret < 0) {
             if (errno == EBUSY) {
                 printf("husb: update iface. device already grabbed\n");
@@ -377,7 +397,7 @@ static int usb_host_release_interfaces(USBHostDevice *s)
     dprintf("husb: releasing interfaces\n");
 
     for (i = 0; i < s->ninterfaces; i++) {
-        ret = ioctl(s->fd, USBDEVFS_RELEASEINTERFACE, &i);
+        ret = s->ioctl(s->fd, USBDEVFS_RELEASEINTERFACE, &i);
         if (ret < 0) {
             perror("husb: failed to release interface");
             return 0;
@@ -393,7 +413,7 @@ static void usb_host_handle_reset(USBDevice *dev)
 
     dprintf("husb: reset device %u.%u\n", s->bus_num, s->addr);
 
-    ioctl(s->fd, USBDEVFS_RESET);
+    s->ioctl(s->fd, USBDEVFS_RESET);
 
     usb_host_claim_interfaces(s, s->configuration);
 }
@@ -440,7 +460,7 @@ static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
     	urb->endpoint = p->devep;
 
     if (is_halted(s, p->devep)) {
-	ret = ioctl(s->fd, USBDEVFS_CLEAR_HALT, &urb->endpoint);
+        ret = s->ioctl(s->fd, USBDEVFS_CLEAR_HALT, &urb->endpoint);
         if (ret < 0) {
             dprintf("husb: failed to clear halt. ep 0x%x errno %d\n", 
                    urb->endpoint, errno);
@@ -465,7 +485,7 @@ static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
 
     urb->usercontext = s;
 
-    ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
+    ret = s->ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
 
     dprintf("husb: data submit. ep 0x%x len %u aurb %p\n", urb->endpoint, p->len, aurb);
 
@@ -505,7 +525,7 @@ static int usb_host_set_config(USBHostDevice *s, int config)
 {
     usb_host_release_interfaces(s);
 
-    int ret = ioctl(s->fd, USBDEVFS_SETCONFIGURATION, &config);
+    int ret = s->ioctl(s->fd, USBDEVFS_SETCONFIGURATION, &config);
  
     dprintf("husb: ctrl set config %d ret %d errno %d\n", config, ret, errno);
     
@@ -523,7 +543,7 @@ static int usb_host_set_interface(USBHostDevice *s, int iface, int alt)
 
     si.interface  = iface;
     si.altsetting = alt;
-    ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si);
+    ret = s->ioctl(s->fd, USBDEVFS_SETINTERFACE, &si);
     
     dprintf("husb: ctrl set iface %d altset %d ret %d errno %d\n", 
     	iface, alt, ret, errno);
@@ -592,7 +612,7 @@ static int usb_host_handle_control(USBHostDevice *s, USBPacket *p)
 
     urb->usercontext = s;
 
-    ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
+    ret = s->ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
 
     dprintf("husb: submit ctrl. len %u aurb %p\n", urb->buffer_length, aurb);
 
@@ -782,7 +802,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s)
     ct.data = &configuration;
     ct.timeout = 50;
 
-    ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
+    ret = s->ioctl(s->fd, USBDEVFS_CONTROL, &ct);
     if (ret < 0) {
         perror("usb_linux_update_endp_table");
         return 1;
@@ -823,7 +843,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s)
         ct.data = &alt_interface;
         ct.timeout = 50;
 
-        ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
+        ret = s->ioctl(s->fd, USBDEVFS_CONTROL, &ct);
         if (ret < 0) {
             perror("usb_linux_update_endp_table");
             return 1;
@@ -916,6 +936,7 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p
 #endif
 
     dev->fd = fd;
+    dev->ioctl = ioctl;
 
     /* 
      * Initial configuration is -1 which makes us claim first 
@@ -926,7 +947,7 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p
     if (!usb_host_claim_interfaces(dev, -1))
         goto fail;
 
-    ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci);
+    ret = dev->ioctl(fd, USBDEVFS_CONNECTINFO, &ci);
     if (ret < 0) {
         perror("usb_host_device_open: USBDEVFS_CONNECTINFO");
         goto fail;
@@ -1483,6 +1504,279 @@ void usb_host_info(void)
     }
 }
 
+static int usb_remote_recv_nurb(USBRemoteDevice *dev, int op)
+{
+    UNRB_HEADER *hdr;
+    int nread = dev->read_ptr - dev->buffer;
+    int size;
+
+    size = recv(dev->hdev.fd, dev->read_ptr, sizeof(dev->buffer) - nread, 0);
+    if ((size == 0) || ((size < 0) && (socket_error() != EWOULDBLOCK))) {
+        return -1;
+    }
+
+    dev->read_ptr += size;
+
+    nread = dev->read_ptr - dev->buffer;
+    if (nread < sizeof(UNRB_HEADER)) {
+        return 0;
+    }
+
+    hdr = (UNRB_HEADER *)(dev->buffer);
+
+    if ((hdr->magic != USB_REMOTE_MAGIC) || (hdr->size == 0) || (hdr->opcode != op)) {
+        return -1;
+    }
+
+    if (nread < hdr->size) {
+        /* need to wait for more data from network. */
+        return 0;
+    }
+
+    /* rewind buffer head after all data was received. */
+    dev->read_ptr = dev->buffer;
+
+    return 1;
+}
+
+static void remote_async_complete(void *opaque)
+{
+    USBRemoteDevice *s = opaque;
+    UNRB_SUBMIT_URB *nurb;
+    AsyncURB *aurb;
+    USBPacket *p;
+    int ret, len;
+
+    ret = usb_remote_recv_nurb(s, UNRB_OPCODE_SUBMIT_URB);
+    if (ret == 0) {
+        return;
+    } else if (ret < 0) {
+        printf("rusb: device %d.%d disconnected\n", s->hdev.bus_num, s->hdev.dev.addr);
+        usb_device_del_addr(0, s->hdev.dev.addr);
+        return;
+    }
+
+    nurb = (UNRB_SUBMIT_URB *)(s->buffer);
+
+    aurb = (AsyncURB *)(nurb->urb.opaque);
+
+    aurb->urb.type = nurb->urb.type;
+    aurb->urb.status = nurb->urb.status;
+    aurb->urb.actual_length = nurb->urb.length;
+   
+    memcpy(aurb->urb.buffer, nurb->urb.buffer, aurb->urb.buffer_length);
+
+    p = aurb->packet;
+
+    dprintf("husb: async completed. aurb %p status %d alen %d\n", 
+        aurb, aurb->urb.status, aurb->urb.actual_length);
+
+    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->hdev, p);
+            break;
+
+        case -EPIPE:
+            set_halt(&s->hdev, p->devep);
+            /* fall through */
+        default:
+            p->len = USB_RET_NAK;
+            break;
+        }
+
+        usb_packet_complete(p);
+    }
+
+    async_free(aurb);
+}
+
+static void usb_remote_device_open(USBRemoteDevice *s, UNRB_NEW_DEVICE *nurb)
+{
+    USBHostDevice *dev = &s->hdev;
+    struct usbdevfs_connectinfo ci;
+    int ret;
+
+    dev->bus_num = nurb->bus_num;
+    dev->addr = nurb->addr;
+
+    printf("rusb: open device %d.%d\n", nurb->bus_num, nurb->addr);
+
+    /* "read" the device description */
+    dev->descr_len = nurb->descr_len;
+    memcpy(dev->descr, nurb->descr, nurb->descr_len);
+
+#ifdef DEBUG
+    {
+        int x;
+        printf("=== begin dumping device descriptor data ===\n");
+        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 
+    * available config. We used to start with 1, which does not
+    * always work. I've seen devices where first config starts 
+    * with 2.
+    */
+    if (!usb_host_claim_interfaces(dev, -1))
+        goto fail;
+
+    ret = dev->ioctl(dev->fd, USBDEVFS_CONNECTINFO, &ci);
+    if (ret < 0) {
+        perror("usb_host_device_open: USBDEVFS_CONNECTINFO");
+        goto fail;
+    }
+
+    printf("rusb: grabbed usb device %d.%d\n", nurb->bus_num, nurb->addr);
+
+    ret = usb_linux_update_endp_table(dev);
+    if (ret)
+        goto fail;
+
+    if (ci.slow)
+        dev->dev.speed = USB_SPEED_LOW;
+    else
+        dev->dev.speed = USB_SPEED_HIGH;
+
+    dev->dev.handle_packet  = usb_host_handle_packet;
+    dev->dev.handle_reset   = usb_host_handle_reset;
+    dev->dev.handle_destroy = usb_host_handle_destroy;
+
+    if (!nurb->dev_name || nurb->dev_name[0] == '\0')
+        snprintf(dev->dev.devname, sizeof(dev->dev.devname),
+            "host:%d.%d", nurb->bus_num, nurb->addr);
+    else
+        pstrcpy(dev->dev.devname, sizeof(dev->dev.devname),
+            nurb->dev_name);
+
+    qemu_set_fd_handler(dev->fd, remote_async_complete, NULL, s);
+
+    hostdev_link(dev);
+
+    // Now that we know which device was arrived we can add it to QEmu.
+    usb_device_add_dev(&dev->dev);
+
+    return;
+fail:
+    close(dev->fd);
+    qemu_free(dev);
+}
+
+static void usb_remote_device_init(void *opaque)
+{
+    USBRemoteDevice *s = opaque;
+    UNRB_NEW_DEVICE *nurb;
+    int ret;
+
+    ret = usb_remote_recv_nurb(s, UNRB_OPCODE_NEW_DEVICE);
+    if (ret == 1) {
+        nurb = (UNRB_NEW_DEVICE *)(s->buffer);
+        usb_remote_device_open(s, nurb);
+    } else if (ret == -1) {
+        qemu_set_fd_handler(s->hdev.fd, NULL, NULL, NULL);
+        close(s->hdev.fd);
+        qemu_free(s);
+    }
+}
+
+static USBRemoteDevice* usb_remote_device_new(int sock)
+{
+    USBRemoteDevice *dev;
+    const int nodelay = 1;
+
+    dev = qemu_mallocz(sizeof(USBRemoteDevice));
+    if (!dev) {
+        return NULL;
+    }
+
+    socket_set_nonblock(sock);
+
+    setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+        (const char*)&nodelay, sizeof(nodelay));
+
+    dev->hdev.fd = sock;
+    dev->hdev.ioctl = usb_remote_ioctl;
+
+    dev->read_ptr = dev->buffer;
+
+    qemu_set_fd_handler(dev->hdev.fd, usb_remote_device_init, NULL, dev);
+
+    return dev;
+}
+
+static void usb_remote_accept(void *opaque)
+{
+    int sock = opaque;
+    USBRemoteDevice *dev;
+    struct sockaddr_in saddr;
+    socklen_t addrlen;
+    int csock;
+
+    for (;;) {
+        addrlen = sizeof(saddr);
+        csock = accept(sock, (struct sockaddr *)&saddr, &addrlen);
+        if ((csock < 0) && (errno != EINTR)) {
+            return;
+        } else if (csock >= 0) {
+            break;
+        }
+    }
+
+    dev = usb_remote_device_new(csock);
+    if (!dev) {
+        closesocket(csock);
+    } else {
+        dprintf("rusb: connection from %s:%d\n",
+            inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
+    }
+}
+
+int usb_remote_start(const char *host_str)
+{
+    const int reuse = 1;
+    int sock;
+    struct sockaddr_in saddr;
+
+    if (parse_host_port(&saddr, host_str) < 0) {
+        return 0;
+    }
+
+    sock = socket(PF_INET, SOCK_STREAM, 0);
+    if (sock == -1) {
+        fprintf(stderr, "rusb: socket() failed (%d)\n", socket_error());
+        return 0;
+    }
+
+    socket_set_nonblock(sock);
+    
+    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+        (const char *)&reuse, sizeof(reuse));
+
+    if (bind(sock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) {
+        fprintf(stderr, "rusb: bind() failed (%d)\n", socket_error());
+        goto fail;
+    }
+
+    if (listen(sock, 1) == -1) {
+        fprintf(stderr, "rusb: listen() failed (%d)\n", socket_error());
+        goto fail;
+    }
+
+    qemu_set_fd_handler(sock, usb_remote_accept, NULL, sock);
+
+    return 1;
+
+fail:
+    closesocket(sock);
+    return 0;
+}
+
 #else
 
 #include "hw/usb.h"
@@ -1503,4 +1797,10 @@ int usb_host_device_close(const char *devname)
     return 0;
 }
 
+int usb_remote_start(const char *host_str)
+{
+    term_printf("USB remote devices not supported\n");
+    return 0;
+}
+
 #endif
diff --git a/usb-remote.c b/usb-remote.c
new file mode 100644
index 0000000..75c345b
--- /dev/null
+++ b/usb-remote.c
@@ -0,0 +1,324 @@
+/*
+* Linux remote USB redirector
+*
+* Copyright (c) 2005 Fabrice Bellard
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+#include "qemu-common.h"
+#include "qemu_socket.h"
+#include "usb-remote.h"
+
+#if defined(__linux__)
+
+#include <sys/ioctl.h>
+#include <linux/usbdevice_fs.h>
+
+#define DEBUG
+
+#ifdef DEBUG
+#define dprintf printf
+#else
+#define dprintf(...)
+#endif
+
+static void fill_nurb(UNRB_URB *nurb, struct usbdevfs_urb *urb)
+{
+    nurb->opaque = (uint64_t)urb;
+    nurb->type = urb->type;
+    nurb->endpoint = urb->endpoint;
+    nurb->status = urb->status;
+    nurb->flags = urb->flags;
+    nurb->length = urb->buffer_length;
+    nurb->number_of_packets = urb->number_of_packets;
+}
+
+static int usb_remote_claim_interface(int fd, int interface)
+{
+    UNRB_CLAIM_INTERFACE nurb;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_CLAIM_INTERFACE;
+    nurb.header.size = sizeof(nurb);
+
+    nurb.iface = interface;
+
+    return send(fd, &nurb, sizeof(nurb), 0);
+}
+
+static int usb_remote_clear_halt(int fd, char endpoint)
+{
+    UNRB_CLEAR_HALT nurb;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_CLEAR_HALT;
+    nurb.header.size = sizeof(nurb);
+
+    nurb.endpoint = endpoint;
+
+    return send(fd, &nurb, sizeof(nurb), 0);
+}
+
+static int usb_remote_connect_info(int fd, struct usbdevfs_connectinfo *ci)
+{
+    UNRB_CONNECTION_INFO nurb;
+    int ret;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_CONNECTION_INFO;
+    nurb.header.size = sizeof(nurb);
+
+    ret = send(fd, &nurb, sizeof(nurb), 0);
+
+    if (ret > 0) {
+        fd_set rfds;
+        struct timeval tv;
+        int ret;
+
+        FD_ZERO(&rfds);
+        FD_SET(fd, &rfds);
+
+        tv.tv_sec = 15;
+        tv.tv_usec = 0;
+
+        ret = select(fd + 1, &rfds, NULL, NULL, &tv);
+        if ((ret > 0) && FD_ISSET(fd, &rfds)) {
+            ret = recv(fd, &nurb, sizeof(nurb), MSG_WAITALL);
+        }
+    }
+
+    if (ret > 0) {
+        ci->devnum = nurb.devnum;
+        ci->slow = nurb.slow;
+    }
+
+    return ret;
+}
+
+static int usb_remote_control(int fd, struct usbdevfs_ctrltransfer *ct)
+{
+    UNRB_USB_CONTROL nurb;
+    int ret;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_USB_CONTROL;
+    nurb.header.size = sizeof(nurb);
+
+    nurb.type = ct->bRequestType;
+    nurb.request = ct->bRequest;
+    nurb.value = ct->wValue;
+    nurb.index = ct->wIndex;
+    nurb.length = ct->wLength;
+    nurb.timeout = ct->timeout;
+
+    ret = send(fd, &nurb, sizeof(nurb), 0);
+
+    if (ret > 0) {
+        fd_set rfds;
+        struct timeval tv;
+        int ret;
+
+        FD_ZERO(&rfds);
+        FD_SET(fd, &rfds);
+
+        tv.tv_sec = 15;
+        tv.tv_usec = 0;
+
+        ret = select(fd + 1, &rfds, NULL, NULL, &tv);
+        if ((ret > 0) && FD_ISSET(fd, &rfds)) {
+            ret = recv(fd, ct->data, ct->wLength, MSG_WAITALL);
+        }
+    }
+
+    return ret;
+}
+
+static int usb_remote_discard_urb(int fd, struct usbdevfs_urb *urb)
+{
+    UNRB_DISCARD_URB nurb;
+    int ret;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_DISCARD_URB;
+    nurb.header.size = sizeof(nurb) + urb->buffer_length;
+
+    fill_nurb(&nurb.urb, urb);
+
+    ret = send(fd, &nurb, sizeof(nurb), 0);
+    if (ret > 0) {
+        ret = send(fd, urb->buffer, urb->buffer_length, 0);
+    }
+
+    return ret;
+}
+
+static int usb_remote_usb_ioctl(int fd, struct usbdevfs_ioctl *ctrl)
+{
+    UNRB_IO_CTRL nurb;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_IO_CTRL;
+    nurb.header.size = sizeof(nurb);
+
+    nurb.ifno = ctrl->ifno;
+    nurb.ioctl_code = ctrl->ioctl_code;
+    nurb.data = (uint64_t)(ctrl->data);
+
+    return send(fd, &nurb, sizeof(nurb), 0);
+}
+
+static int usb_remote_read_urb(int fd, struct usbdevfs_urb *urb)
+{
+    /* Could be nice to implement but the network buffer is out of reach when
+    we have only the file descriptor identifier. */
+    return -1;
+}
+
+static int usb_remote_release_interface(int fd, int interface)
+{
+    UNRB_RELEASE_INTERFACE nurb;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_RELEASE_INTERFACE;
+    nurb.header.size = sizeof(nurb);
+
+    nurb.iface = interface;
+
+    return send(fd, &nurb, sizeof(nurb), 0);
+}
+
+static int usb_remote_reset(int fd)
+{
+    UNRB_RESET_DEVICE nurb;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_RESET_DEVICE;
+    nurb.header.size = sizeof(nurb);
+
+    return send(fd, &nurb, sizeof(nurb), 0);
+}
+
+static int usb_remote_set_config(int fd, int config)
+{
+    UNRB_SET_CONFIG nurb;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_SET_CONFIG;
+    nurb.header.size = sizeof(nurb);
+
+    nurb.config = config;
+
+    return send(fd, &nurb, sizeof(nurb), 0);
+}
+
+static int usb_remote_set_interface(int fd, struct usbdevfs_setinterface *si)
+{
+    UNRB_SET_INTERFACE nurb;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_SET_INTERFACE;
+    nurb.header.size = sizeof(nurb);
+
+    nurb.iface = si->interface;
+    nurb.altsetting = si->altsetting;
+
+    return send(fd, &nurb, sizeof(nurb), 0);
+}
+
+static int usb_remote_submit_urb(int fd, struct usbdevfs_urb *urb)
+{
+    UNRB_SUBMIT_URB nurb;
+    int ret;
+
+    nurb.header.magic = USB_REMOTE_MAGIC;
+    nurb.header.opcode = UNRB_OPCODE_SUBMIT_URB;
+    nurb.header.size = sizeof(nurb) + urb->buffer_length;
+
+    fill_nurb(&nurb.urb, urb);
+
+    ret = send(fd, &nurb, sizeof(nurb), 0);
+    if (ret > 0) {
+        ret = send(fd, urb->buffer, urb->buffer_length, 0);
+    }
+
+    return ret;
+}
+
+int usb_remote_ioctl(int fd, unsigned long int request, ...)
+{
+    va_list args;
+    va_start(args, request);
+    int ret;
+
+    switch (request) {
+    case USBDEVFS_CLAIMINTERFACE:
+        ret = usb_remote_claim_interface(fd, *va_arg(args, int*));
+        break;
+    case USBDEVFS_CLEAR_HALT:
+        ret = usb_remote_clear_halt(fd, *va_arg(args, char*));
+        break;
+    case USBDEVFS_CONNECTINFO:
+        ret = usb_remote_connect_info(fd, va_arg(args, struct usbdevfs_connectinfo *));
+        break;
+    case USBDEVFS_CONTROL:
+        ret = usb_remote_control(fd, va_arg(args, struct usbdevfs_ctrltransfer *));
+        break;
+    case USBDEVFS_DISCARDURB:
+        ret = usb_remote_discard_urb(fd, va_arg(args, struct usbdevfs_urb *));
+        break;
+    case USBDEVFS_IOCTL:
+        ret = usb_remote_usb_ioctl(fd, va_arg(args, struct usbdevfs_ioctl *));
+        break;
+    case USBDEVFS_REAPURBNDELAY:
+        ret = usb_remote_read_urb(fd, va_arg(args, struct usbdevfs_urb *));
+        break;
+    case USBDEVFS_RELEASEINTERFACE:
+        ret = usb_remote_release_interface(fd, *va_arg(args, int*));
+        break;
+    case USBDEVFS_RESET:
+        ret = usb_remote_reset(fd);
+        break;
+    case USBDEVFS_SETCONFIGURATION:
+        ret = usb_remote_set_config(fd, *va_arg(args, int*));
+        break;
+    case USBDEVFS_SETINTERFACE:
+        ret = usb_remote_set_interface(fd, va_arg(args, struct usbdevfs_setinterface *));
+        break;
+    case USBDEVFS_SUBMITURB:
+        ret = usb_remote_submit_urb(fd, va_arg(args, struct usbdevfs_urb *));
+        break;
+    default:
+        fprintf(stderr, "husb: unknown ioctl() request %ld\n", request);
+        ret = -1;
+    }
+
+    va_end(args);
+
+    return ret;
+}
+
+#else
+
+int usb_remote_ioctl(int fd, unsigned long int request, ...)
+{
+    return -1;
+}
+
+#endif // defined(__linux__)
diff --git a/usb-remote.h b/usb-remote.h
new file mode 100644
index 0000000..52694f6
--- /dev/null
+++ b/usb-remote.h
@@ -0,0 +1,160 @@
+/*
+ * Linux remote USB redirector
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef _USB_REMOTE_H
+#define _USB_REMOTE_H
+
+#define USB_REMOTE_MAGIC (*(uint32_t*)"RUSB")
+
+#define PRODUCT_NAME_SZ 32
+
+enum {
+    UNRB_OPCODE_NEW_DEVICE,
+    UNRB_OPCODE_CLAIM_INTERFACE,
+    UNRB_OPCODE_CLEAR_HALT,
+    UNRB_OPCODE_CONNECTION_INFO,
+    UNRB_OPCODE_DISCARD_URB,
+    UNRB_OPCODE_IO_CTRL,
+    UNRB_OPCODE_GET_INTERFACE,
+    UNRB_OPCODE_USB_CONTROL,
+    UNRB_OPCODE_RELEASE_INTERFACE,
+    UNRB_OPCODE_RESET_DEVICE,
+    UNRB_OPCODE_SUBMIT_URB,
+    UNRB_OPCODE_SET_CONFIG,
+    UNRB_OPCODE_SET_INTERFACE,
+};
+
+typedef struct UNRB_HEADER {
+    uint32_t    magic;
+    uint32_t    size;
+    uint32_t    opcode;
+
+} __attribute__((packed)) UNRB_HEADER, *PUNRB_HEADER;
+
+typedef struct UNRB_URB {
+    uint64_t    opaque;
+    uint8_t     type;
+    uint8_t     endpoint;
+    int32_t     status;
+    int32_t     flags;
+    int32_t     number_of_packets;
+    int32_t     length;
+    uint8_t     buffer[];
+
+} __attribute__((packed)) UNRB_URB, *PUNRB_URB;
+
+typedef struct UNRB_NEW_DEVICE {
+    UNRB_HEADER header;
+    int32_t     bus_num;
+    int32_t     addr;
+    uint8_t     dev_name[PRODUCT_NAME_SZ];
+    int32_t     descr_len;
+    uint8_t     descr[];
+
+} __attribute__((packed)) UNRB_NEW_DEVICE, *PUNRB_NEW_DEVICE;
+
+typedef struct UNRB_CLAIM_INTERFACE {
+    UNRB_HEADER header;
+    uint32_t    iface;
+
+} __attribute__((packed)) UNRB_CLAIM_INTERFACE, *PUNRB_CLAIM_INTERFACE;
+
+typedef struct UNRB_CLEAR_HALT {
+    UNRB_HEADER header;
+    uint8_t     endpoint;
+
+} __attribute__((packed)) UNRB_CLEAR_HALT, *PUNRB_CLEAR_HALT;
+
+typedef struct UNRB_CONNECTION_INFO {
+    UNRB_HEADER header;
+    uint32_t    devnum;
+    uint8_t     slow;
+
+} __attribute__((packed)) UNRB_CONNECTION_INFO, *PUNRB_CONNECTION_INFO;
+
+typedef struct UNRB_DISCARD_URB {
+    UNRB_HEADER header;
+    UNRB_URB    urb;
+
+} __attribute__((packed)) UNRB_DISCARD_URB, *PUNRB_DISCARD_URB;
+
+typedef struct UNRB_IO_CTRL {
+    UNRB_HEADER header;
+    uint32_t    ifno;
+    uint32_t    ioctl_code;
+    uint64_t    data;
+
+} __attribute__((packed)) UNRB_IO_CTRL, *PUNRB_IO_CTRL;
+
+typedef struct UNRB_GET_INTERFACE {
+    UNRB_HEADER header;
+    uint8_t     configuration;
+    uint32_t    number_of_ifaces;
+    uint8_t     alt_iface[];
+
+} __attribute__((packed)) UNRB_GET_INTERFACE, *PUNRB_GET_INTERFACE;
+
+typedef struct UNRB_USB_CONTROL {
+    UNRB_HEADER header;
+    uint8_t     type;
+    uint8_t     request;
+    uint16_t    value;
+    uint16_t    index;
+    uint16_t    length;
+    uint32_t    timeout;  /* in milliseconds */
+    uint8_t     data[];
+
+} __attribute__((packed)) UNRB_USB_CONTROL, *PUNRB_USB_CONTROL;
+
+typedef struct UNRB_SUBMIT_URB {
+    UNRB_HEADER header;
+    UNRB_URB    urb;
+
+} __attribute__((packed)) UNRB_SUBMIT_URB, *PUNRB_SUBMIT_URB;
+
+typedef struct UNRB_RELEASE_INTERFACE {
+    UNRB_HEADER header;
+    uint32_t    iface;
+
+} __attribute__((packed)) UNRB_RELEASE_INTERFACE, *PUNRB_RELEASE_INTERFACE;
+
+typedef struct UNRB_RESET_DEVICE {
+    UNRB_HEADER header;
+
+} __attribute__((packed)) UNRB_RESET_DEVICE, *PUNRB_RESET_DEVICE;
+
+typedef struct UNRB_SET_CONFIG {
+    UNRB_HEADER header;
+    uint32_t    config;
+
+} __attribute__((packed)) UNRB_SET_CONFIG, *PUNRB_SET_CONFIG;
+
+typedef struct UNRB_SET_INTERFACE {
+    UNRB_HEADER header;
+    uint32_t    iface;
+    uint32_t    altsetting;
+
+} __attribute__((packed)) UNRB_SET_INTERFACE, *PUNRB_SET_INTERFACE;
+
+#endif // _USB_REMOTE_H
diff --git a/vl.c b/vl.c
index c94fdc0..b140bd3 100644
--- a/vl.c
+++ b/vl.c
@@ -7902,6 +7902,8 @@ static void help(int exitcode)
 #endif
            "-usb            enable the USB driver (will be the default soon)\n"
            "-usbdevice name add the host or guest USB device 'name'\n"
+           "-usbremote hostname:port\n"
+           "                enable a remote USB server (USB over network)\n"
 #if defined(TARGET_PPC) || defined(TARGET_SPARC)
            "-g WxH[xDEPTH]  Set the initial graphical resolution and depth\n"
 #endif
@@ -8080,6 +8082,7 @@ enum {
     QEMU_OPTION_win2k_hack,
     QEMU_OPTION_usb,
     QEMU_OPTION_usbdevice,
+    QEMU_OPTION_usbremote,
     QEMU_OPTION_smp,
     QEMU_OPTION_vnc,
     QEMU_OPTION_no_acpi,
@@ -8181,6 +8184,7 @@ static const QEMUOption qemu_options[] = {
     { "pidfile", HAS_ARG, QEMU_OPTION_pidfile },
     { "win2k-hack", 0, QEMU_OPTION_win2k_hack },
     { "usbdevice", HAS_ARG, QEMU_OPTION_usbdevice },
+    { "usbremote", HAS_ARG, QEMU_OPTION_usbremote },
     { "smp", HAS_ARG, QEMU_OPTION_smp },
     { "vnc", HAS_ARG, QEMU_OPTION_vnc },
 #ifdef CONFIG_CURSES
@@ -8497,6 +8501,7 @@ int main(int argc, char **argv)
     const char *cpu_model;
     const char *usb_devices[MAX_USB_CMDLINE];
     int usb_devices_index;
+    const char *usb_hostname = NULL;
     int fds[2];
     int tb_size;
     const char *pid_file = NULL;
@@ -8994,6 +8999,10 @@ int main(int argc, char **argv)
                 usb_devices[usb_devices_index] = optarg;
                 usb_devices_index++;
                 break;
+            case QEMU_OPTION_usbremote:
+                usb_enabled = 1;
+                usb_hostname = optarg;
+                break;
             case QEMU_OPTION_smp:
                 smp_cpus = atoi(optarg);
                 if (smp_cpus < 1 || smp_cpus > MAX_CPUS) {
@@ -9422,6 +9431,10 @@ int main(int argc, char **argv)
                         usb_devices[i]);
             }
         }
+
+        if (usb_hostname) {
+            usb_remote_start(usb_hostname);
+        }
     }
 
     if (display_state.dpy_refresh) {

^ permalink raw reply related	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2008-10-07 18:08 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-10-06 11:43 [Qemu-devel] [PATCH] USB over network Gal Hammer
2008-10-06 12:09 ` Paul Brook
2008-10-06 12:22   ` Paul Brook
2008-10-06 12:42     ` Dor Laor
2008-10-06 13:15   ` Gal Hammer
2008-10-06 13:32     ` Paul Brook
2008-10-06 13:41     ` Anthony Liguori
2008-10-06 14:20       ` Gal Hammer
2008-10-06 15:24         ` Alexander Graf
2008-10-06 12:32 ` Daniel P. Berrange
2008-10-06 15:05 ` Blue Swirl
2008-10-07  8:34   ` Gal Hammer
2008-10-07 18:08     ` Blue Swirl

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).