* [Qemu-devel] Feature proposal: USB devices over TCP
@ 2007-04-07 1:11 Eduardo Felipe
2007-04-07 3:38 ` Anthony Liguori
0 siblings, 1 reply; 4+ messages in thread
From: Eduardo Felipe @ 2007-04-07 1:11 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1.1: Type: text/plain, Size: 1089 bytes --]
Hi everybody,
I think that a useful feature for QEMU would be to expose the USB interface
through TCP.
It would allow quick USB device development in high level languages without
recompiling QEMU. We could have an instance of QEMU running all the time
while we create our device and hot plug/unplug it whenever we want to.
This could also attract people interested in hardware emulation, but scared
of learning QEMU internals just to create a simple new device.
I think USB is quite suited for this, as it is designed for pluggable
external devices, but something similar could be made for serial and
parallel devices too.
The attached patch is a quick hack derived from the VNC server just to show
the idea, not intended for commiting. A dummy protocol is used for message
interchange between server and client.
It adds the new command line option:
-usbtcp port
It starts a socket listening on port for incoming connections. A sample USB
mouse in python is also provided that moves the cursor in circles.
Would such a feature be of any interest for QEMU?
Regards,
Eduardo Felipe
[-- Attachment #1.2: Type: text/html, Size: 1173 bytes --]
[-- Attachment #2: usb_over_tcp.diff --]
[-- Type: application/octet-stream, Size: 18575 bytes --]
*** hw/usb-tcp.c Wed Dec 31 14:00:00 1969
+++ hw/usb-tcp.c Fri Apr 6 23:36:01 2007
@@ -0,0 +1,590 @@
+/*
+ * QEMU USB over TCP protocol
+ *
+ * Copyright (c) 2007 Eduardo Felipe
+ *
+ * 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 "vl.h"
+#include "qemu_socket.h"
+
+//#define DEBUG_USBTCP
+
+#ifdef DEBUG_USBTCP
+#define updebug(fmt, arg...) printf("usb-tcp: " fmt, ##arg)
+#else
+#define updebug(fmt, arg...) ((void)0)
+#endif
+
+
+typedef struct USBTCPState USBTCPState;
+
+typedef struct Buffer
+{
+ size_t capacity;
+ size_t offset;
+ char *buffer;
+} Buffer;
+
+typedef int USBTCPReadEvent(USBTCPState *s, char *data, size_t len);
+
+struct USBTCPState {
+ USBDevice dev;
+ int lsock;
+ int csock;
+ int plugged;
+
+ Buffer output;
+ Buffer input;
+
+ USBTCPReadEvent *read_handler;
+ size_t read_handler_expect;
+
+};
+
+/* XXX remove global variable to allow multiple instances */
+USBTCPState *ps;
+
+extern int parse_host_port(struct sockaddr_in *saddr, const char *str);
+
+static void usb_tcp_write(USBTCPState *s, const void *data, size_t len);
+static void usb_tcp_write_s32(USBTCPState *s, int32_t value);
+static void usb_tcp_write_u32(USBTCPState *s, uint32_t value);
+static void usb_tcp_write_u16(USBTCPState *s, uint16_t value);
+static void usb_tcp_write_u8(USBTCPState *s, uint8_t value);
+static void usb_tcp_flush(USBTCPState *s);
+static int usb_tcp_client_io_error(USBTCPState *s, int ret, int last_errno);
+static void usb_tcp_client_error(USBTCPState *s);
+static void usb_tcp_client_write(void *opaque);
+static void usb_tcp_client_read(void *opaque);
+
+static uint8_t read_u8(uint8_t *data, size_t offset);
+static uint16_t read_u16(uint8_t *data, size_t offset);
+static int16_t read_s16(uint8_t *data, size_t offset);
+static int32_t read_s32(uint8_t *data, size_t offset);
+static uint32_t read_u32(uint8_t *data, size_t offset);
+
+static void usb_tcp_listen_read(void *opaque);
+static int usb_tcp_listen_poll(void *opaque);
+
+
+static void buffer_reserve(Buffer *buffer, size_t len)
+{
+ if ((buffer->capacity - buffer->offset) < len) {
+ buffer->capacity += (len + 1024);
+ buffer->buffer = realloc(buffer->buffer, buffer->capacity);
+ if (buffer->buffer == NULL) {
+ fprintf(stderr, "usb-tcp: out of memory\n");
+ exit(1);
+ }
+ }
+}
+
+static void buffer_consume(Buffer *buffer,size_t len)
+{
+ memmove(buffer->buffer, buffer->buffer + len, (buffer->offset - len));
+ buffer->offset -= len;
+}
+
+
+static int buffer_empty(Buffer *buffer)
+{
+ return buffer->offset == 0;
+}
+
+static char *buffer_end(Buffer *buffer)
+{
+ return buffer->buffer + buffer->offset;
+}
+
+static void buffer_reset(Buffer *buffer)
+{
+ buffer->offset = 0;
+}
+
+static void buffer_append(Buffer *buffer, const void *data, size_t len)
+{
+ memcpy(buffer->buffer + buffer->offset, data, len);
+ buffer->offset += len;
+}
+
+static int usb_tcp_client_io_error(USBTCPState *s, int ret, int last_errno)
+{
+ char delstr[6];
+
+ if (ret == 0 || ret == -1) {
+ if (ret == -1 && (last_errno == EINTR || last_errno == EAGAIN))
+ return 0;
+
+ // Detach device
+ if (s->plugged) {
+ s->plugged = 0;
+ sprintf(delstr,"%d.%d", 0, s->dev.addr);
+ do_usb_del(delstr);
+ }
+
+ // Close connection
+ if (s->csock!=-1) {
+ qemu_set_fd_handler2(s->csock, NULL, NULL, NULL, NULL);
+ closesocket(s->csock);
+ s->csock = -1;
+ buffer_reset(&s->input);
+ buffer_reset(&s->output);
+ }
+ return 0;
+ }
+ return ret;
+}
+
+static void usb_tcp_client_error(USBTCPState *s)
+{
+ usb_tcp_client_io_error(s, -1, EINVAL);
+}
+
+static void usb_tcp_client_write(void *opaque)
+{
+ long ret;
+ USBTCPState *s = opaque;
+
+ ret = send(s->csock, s->output.buffer, s->output.offset, 0);
+ ret = usb_tcp_client_io_error(s, ret, socket_error());
+ if (!ret)
+ return;
+
+ memmove(s->output.buffer, s->output.buffer + ret, (s->output.offset - ret));
+ s->output.offset -= ret;
+
+ if (s->output.offset == 0) {
+ qemu_set_fd_handler2(s->csock, NULL, usb_tcp_client_read, NULL, s);
+ }
+}
+
+
+static void usb_tcp_read_when(USBTCPState *s, USBTCPReadEvent *func, size_t expecting)
+{
+ s->read_handler = func;
+ s->read_handler_expect = expecting;
+}
+
+
+static int usb_tcp_read_all(void *opaque)
+{
+ USBTCPState *s = opaque;
+ int ret;
+
+ buffer_reserve(&s->input, 4096);
+
+ do {
+ ret = recv(s->csock, buffer_end(&s->input), 4096, 0);
+ } while (ret==-1 && socket_error()==EINTR);
+
+ ret = usb_tcp_client_io_error(s, ret, socket_error());
+
+ s->input.offset += ret;
+
+ return ret;
+}
+
+static void usb_tcp_client_read(void *opaque)
+{
+ USBTCPState *s = opaque;
+ int ret;
+
+ buffer_reserve(&s->input, 4096);
+
+ ret = recv(s->csock, buffer_end(&s->input), 4096, 0);
+
+ ret = usb_tcp_client_io_error(s, ret, socket_error());
+ if (!ret)
+ return;
+
+ s->input.offset += ret;
+
+ while (s->read_handler && s->input.offset >= s->read_handler_expect) {
+
+ int ret;
+ size_t len = s->read_handler_expect;
+
+ ret = s->read_handler(s, s->input.buffer, len);
+ if (s->csock == -1)
+ return;
+
+ if (!ret) {
+ buffer_consume(&s->input,len);
+ } else {
+ s->read_handler_expect = ret;
+ }
+ }
+}
+
+
+static void usb_tcp_write(USBTCPState *s, const void *data, size_t len)
+{
+ buffer_reserve(&s->output, len);
+
+ if (buffer_empty(&s->output)) {
+ qemu_set_fd_handler2(s->csock, NULL, usb_tcp_client_read, NULL, s);
+ }
+
+ buffer_append(&s->output, data, len);
+}
+
+static void usb_tcp_write_s32(USBTCPState *s, int32_t value)
+{
+ usb_tcp_write_u32(s, *(uint32_t *)&value);
+}
+
+static void usb_tcp_write_u32(USBTCPState *s, uint32_t value)
+{
+ uint8_t buf[4];
+
+ buf[0] = (value >> 24) & 0xFF;
+ buf[1] = (value >> 16) & 0xFF;
+ buf[2] = (value >> 8) & 0xFF;
+ buf[3] = value & 0xFF;
+
+ usb_tcp_write(s, buf, 4);
+}
+
+static void usb_tcp_write_u16(USBTCPState *s, uint16_t value)
+{
+ uint8_t buf[2];
+
+ buf[0] = (value >> 8) & 0xFF;
+ buf[1] = value & 0xFF;
+
+ usb_tcp_write(s, buf, 2);
+}
+
+static void usb_tcp_write_u8(USBTCPState *s, uint8_t value)
+{
+ usb_tcp_write(s, (char *)&value, 1);
+}
+
+static void usb_tcp_flush(USBTCPState *s)
+{
+ if (s->output.offset)
+ usb_tcp_client_write(s);
+}
+
+static uint8_t read_u8(uint8_t *data, size_t offset)
+{
+ uint8_t *ptr = (uint8_t *)data;
+ return ptr[offset];
+}
+
+static uint16_t read_u16(uint8_t *data, size_t offset)
+{
+ uint8_t *ptr = (uint8_t *)data;
+ return ((ptr[offset] & 0xFF) << 8) | (ptr[offset + 1] & 0xFF);
+}
+
+static int16_t read_s16(uint8_t *data, size_t offset)
+{
+ uint8_t *ptr = (uint8_t *)data;
+ return (int16_t)((ptr[offset] & 0xFF) << 8) | (ptr[offset + 1] & 0xFF);
+}
+
+static int32_t read_s32(uint8_t *data, size_t offset)
+{
+ uint8_t *ptr = (uint8_t *)data;
+ return (int32_t)((ptr[offset] << 24) | (ptr[offset + 1] << 16) |
+ (ptr[offset + 2] << 8) | ptr[offset + 3]);
+}
+
+static uint32_t read_u32(uint8_t *data, size_t offset)
+{
+ uint8_t *ptr = (uint8_t *)data;
+ return ((ptr[offset] << 24) | (ptr[offset + 1] << 16) |
+ (ptr[offset + 2] << 8) | ptr[offset + 3]);
+}
+
+
+static void usb_tcp_handle_reset(USBDevice *dev)
+{
+ USBTCPState *s = (USBTCPState *)dev;
+ int avlen;
+ int16_t datalen;
+
+ if (s->csock == -1)
+ return USB_RET_STALL;
+
+ updebug("[Reset]\n");
+ usb_tcp_write(s,"RS",2);
+ usb_tcp_flush(s);
+
+ avlen = usb_tcp_read_all(s);
+ datalen = read_s16(s->input.buffer,0);
+ if (datalen>=0)
+ buffer_consume(&s->input,datalen+2);
+}
+
+static int usb_tcp_handle_control(USBDevice *dev, int request, int value,
+ int index, int length, uint8_t *data)
+{
+ USBTCPState *s = (USBTCPState *)dev;
+ int ret = 0;
+ int avlen;
+ int16_t datalen;
+
+ updebug("[Control] request:0x%04x value:0x%04x\n",request,value);
+
+ if (s->csock == -1)
+ return USB_RET_STALL;
+
+ // QEMU side handling
+ switch(request) {
+ case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+ dev->addr = value;
+ break;
+ case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
+ if (value == USB_DEVICE_REMOTE_WAKEUP)
+ dev->remote_wakeup = 0;
+ break;
+ case DeviceOutRequest | USB_REQ_SET_FEATURE:
+ if (value == USB_DEVICE_REMOTE_WAKEUP)
+ dev->remote_wakeup = 1;
+ break;
+ // ...
+ }
+
+ usb_tcp_write(s,"CT",2);
+ usb_tcp_write_u16(s, request);
+ usb_tcp_write_u16(s, value);
+ usb_tcp_write_u16(s, index);
+ usb_tcp_write_u16(s, length);
+ usb_tcp_flush(s);
+
+ avlen = usb_tcp_read_all(s);
+ if (avlen>=2) {
+ datalen = read_s16(s->input.buffer,0);
+
+ if (datalen>0)
+ memcpy(data,s->input.buffer+2,datalen);
+
+ if (datalen>=0)
+ buffer_consume(&s->input,datalen+2);
+
+ return datalen;
+ }
+ else {
+ return USB_RET_STALL;
+ }
+}
+
+
+static int usb_tcp_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBTCPState *s = (USBTCPState *)dev;
+ int ret = 0;
+ int avlen;
+ int16_t datalen;
+
+ updebug("[Data] pid:0x%02x packet size:%d\n",p->pid,p->len);
+
+ if (s->csock == -1)
+ return USB_RET_STALL;
+
+ switch(p->pid) {
+ case USB_TOKEN_IN:
+ if (p->devep != 1)
+ goto fail;
+
+ usb_tcp_write(s,"DI",2);
+ usb_tcp_write_u16(s, p->len);
+ usb_tcp_flush(s);
+ avlen = usb_tcp_read_all(s);
+
+ if (avlen < 2)
+ goto fail;
+
+ datalen = read_s16(s->input.buffer,0);
+
+ if (datalen>0)
+ memcpy(p->data,s->input.buffer+2,datalen);
+
+ if (datalen>=0)
+ buffer_consume(&s->input,datalen+2);
+
+ ret = datalen;
+ break;
+ case USB_TOKEN_OUT:
+ /* XXX TODO */
+ default:
+ fail:
+ ret = USB_RET_STALL;
+ break;
+ }
+ return ret;
+}
+
+static void usb_tcp_handle_destroy(USBDevice *dev)
+{
+ USBTCPState *s = (USBTCPState *)dev;
+ updebug("[Destroy]\n");
+
+ usb_tcp_client_error(s);
+ memset(&s->dev, 0, sizeof(USBDevice));
+}
+
+static int protocol_version(USBTCPState *s, char *version, size_t len)
+{
+ char local[15];
+ int maj, min;
+
+ memcpy(local, version, 15);
+ local[15] = 0;
+
+ if (sscanf(local, "USBTCP %03d.%03d\n", &maj, &min) != 2) {
+ usb_tcp_client_error(s);
+ return 0;
+ }
+
+ updebug("new USB device plugged\n");
+ do_usb_add("TCPdevice");
+
+ s->plugged = 1;
+
+ qemu_set_fd_handler2(s->csock, NULL, NULL, NULL, NULL);
+}
+
+static void usb_tcp_listen_read(void *opaque)
+{
+ USBTCPState *s = opaque;
+ struct sockaddr_in addr;
+ socklen_t addrlen = sizeof(addr);
+
+ s->csock = accept(s->lsock, (struct sockaddr *)&addr, &addrlen);
+ if (s->csock != -1) {
+ qemu_set_fd_handler2(s->csock, NULL, usb_tcp_client_read, NULL, opaque);
+
+ usb_tcp_write(s,"USBTCP 000.001\n",15);
+ usb_tcp_flush(s);
+ usb_tcp_read_when(s, protocol_version, 15);
+ }
+}
+
+
+static int usb_tcp_listen_poll(void *opaque)
+{
+ USBTCPState *s = opaque;
+ if (s->csock == -1)
+ return 1;
+ return 0;
+}
+
+
+
+USBDevice *usb_tcp_device_init(void)
+{
+ USBDevice *d;
+
+ d = &ps->dev;
+
+ d->speed = USB_SPEED_FULL;
+ d->handle_packet = usb_generic_handle_packet;
+ d->handle_reset = usb_tcp_handle_reset;
+ d->handle_control = usb_tcp_handle_control;
+ d->handle_data = usb_tcp_handle_data;
+ d->handle_destroy = usb_tcp_handle_destroy;
+
+ pstrcpy(d->devname, sizeof(d->devname), "QEMU USB over TCP");
+
+ return (USBDevice *)ps;
+}
+
+
+
+void usb_tcp_init(const char *arg)
+{
+ struct sockaddr *addr;
+ struct sockaddr_in iaddr;
+#ifndef _WIN32
+ struct sockaddr_un uaddr;
+#endif
+ int reuse_addr, ret;
+ socklen_t addrlen;
+ const char *p;
+
+ ps = qemu_mallocz(sizeof(USBTCPState));
+ if (!ps)
+ return;
+
+ ps->lsock = -1;
+ ps->csock = -1;
+
+#ifndef _WIN32
+ if (strstart(arg, "unix:", &p)) {
+ addr = (struct sockaddr *)&uaddr;
+ addrlen = sizeof(uaddr);
+
+ ps->lsock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (ps->lsock == -1) {
+ fprintf(stderr, "usb-tcp: Could not create socket\n");
+ exit(1);
+ }
+
+ uaddr.sun_family = AF_UNIX;
+ memset(uaddr.sun_path, 0, 108);
+ snprintf(uaddr.sun_path, 108, "%s", p);
+
+ unlink(uaddr.sun_path);
+ } else
+#endif
+ {
+ addr = (struct sockaddr *)&iaddr;
+ addrlen = sizeof(iaddr);
+
+ ps->lsock = socket(PF_INET, SOCK_STREAM, 0);
+ if (ps->lsock == -1) {
+ fprintf(stderr, "usb-tcp: Could not create socket\n");
+ exit(1);
+ }
+
+ if (parse_host_port(&iaddr, arg) < 0) {
+ fprintf(stderr, "usb-tcp: Could not parse address\n");
+ exit(1);
+ }
+
+ reuse_addr = 1;
+ ret = setsockopt(ps->lsock, SOL_SOCKET, SO_REUSEADDR,
+ (const char *)&reuse_addr, sizeof(reuse_addr));
+ if (ret == -1) {
+ fprintf(stderr, "usb-tcp: setsockopt() failed\n");
+ exit(1);
+ }
+ }
+
+ if (bind(ps->lsock, addr, addrlen) == -1) {
+ fprintf(stderr, "usb-tcp: bind() failed\n");
+ exit(1);
+ }
+
+ if (listen(ps->lsock, 1) == -1) {
+ fprintf(stderr, "usb-tcp: listen() failed\n");
+ exit(1);
+ }
+
+ ret = qemu_set_fd_handler2(ps->lsock, usb_tcp_listen_poll, usb_tcp_listen_read, NULL, ps);
+ if (ret == -1) {
+ exit(1);
+ }
+}
+
+
*** Makefile.target 6 Apr 2007 16:49:48 -0000 1.158
--- Makefile.target 6 Apr 2007 22:16:31 -0000
@@ -397,7 +397,7 @@
VL_OBJS+= scsi-disk.o cdrom.o lsi53c895a.o
# USB layer
-VL_OBJS+= usb.o usb-hub.o usb-linux.o usb-hid.o usb-ohci.o usb-msd.o
+VL_OBJS+= usb.o usb-hub.o usb-linux.o usb-hid.o usb-ohci.o usb-msd.o usb-tcp.o
# EEPROM emulation
VL_OBJS += eeprom93xx.o
*** vl.c 6 Apr 2007 16:49:48 -0000 1.279
--- vl.c 6 Apr 2007 22:16:32 -0000
@@ -173,6 +173,7 @@
int win2k_install_hack = 0;
#endif
int usb_enabled = 0;
+const char *usbtcp_port;
static VLANState *first_vlan;
int smp_cpus = 1;
const char *vnc_display;
@@ -4293,6 +4294,8 @@
dev = usb_tablet_init();
} else if (strstart(devname, "disk:", &p)) {
dev = usb_msd_init(p);
+ } else if (!strcmp(devname, "TCPdevice")) {
+ dev = usb_tcp_device_init();
} else {
return -1;
}
@@ -6376,6 +6379,7 @@
#endif
"-usb enable the USB driver (will be the default soon)\n"
"-usbdevice name add the host or guest USB device 'name'\n"
+ "-usbtcp port start a USB over TCP server on port\n"
#if defined(TARGET_PPC) || defined(TARGET_SPARC)
"-g WxH[xDEPTH] Set the initial graphical resolution and depth\n"
#endif
@@ -6534,6 +6538,7 @@
QEMU_OPTION_win2k_hack,
QEMU_OPTION_usb,
QEMU_OPTION_usbdevice,
+ QEMU_OPTION_usbtcp,
QEMU_OPTION_smp,
QEMU_OPTION_vnc,
QEMU_OPTION_no_acpi,
@@ -6620,6 +6625,7 @@
{ "pidfile", HAS_ARG, QEMU_OPTION_pidfile },
{ "win2k-hack", 0, QEMU_OPTION_win2k_hack },
{ "usbdevice", HAS_ARG, QEMU_OPTION_usbdevice },
+ { "usbtcp", HAS_ARG, QEMU_OPTION_usbtcp },
{ "smp", HAS_ARG, QEMU_OPTION_smp },
{ "vnc", HAS_ARG, QEMU_OPTION_vnc },
@@ -7319,6 +7325,10 @@
optarg);
usb_devices_index++;
break;
+ case QEMU_OPTION_usbtcp:
+ usb_enabled = 1;
+ usbtcp_port = optarg;
+ break;
case QEMU_OPTION_smp:
smp_cpus = atoi(optarg);
if (smp_cpus < 1 || smp_cpus > MAX_CPUS) {
@@ -7637,6 +7647,8 @@
usb_devices[i]);
}
}
+ if (usbtcp_port != NULL)
+ usb_tcp_init(usbtcp_port);
}
gui_timer = qemu_new_timer(rt_clock, gui_update, NULL);
*** hw/usb.h 17 Mar 2007 16:59:30 -0000 1.12
--- hw/usb.h 5 Apr 2007 13:53:35 -0000
@@ -220,3 +220,7 @@
/* usb-msd.c */
USBDevice *usb_msd_init(const char *filename);
+
+/* usb-tcp.c */
+void usb_tcp_init(const char *arg);
+USBDevice *usb_tcp_device_init(void);
[-- Attachment #3: usb_mouse.py --]
[-- Type: text/x-python, Size: 7590 bytes --]
# Copyright (c) 2007 Eduardo Felipe
#
# 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.
import sys, socket, struct, math
USB_DIR_OUT = 0
USB_DIR_IN = 0x80
USB_TYPE_MASK = (0x03 << 5)
USB_TYPE_STANDARD = (0x00 << 5)
USB_TYPE_CLASS = (0x01 << 5)
USB_TYPE_VENDOR = (0x02 << 5)
USB_TYPE_RESERVED = (0x03 << 5)
USB_RECIP_MASK = 0x1f
USB_RECIP_DEVICE = 0x00
USB_RECIP_INTERFACE = 0x01
USB_RECIP_ENDPOINT = 0x02
USB_RECIP_OTHER = 0x03
DeviceRequest = ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8)
DeviceOutRequest = ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8)
InterfaceRequest =((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8)
InterfaceOutRequest = ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8)
EndpointRequest = ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8)
EndpointOutRequest = ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8)
USB_REQ_GET_STATUS = 0x00
USB_REQ_CLEAR_FEATURE = 0x01
USB_REQ_SET_FEATURE = 0x03
USB_REQ_SET_ADDRESS = 0x05
USB_REQ_GET_DESCRIPTOR = 0x06
USB_REQ_SET_DESCRIPTOR = 0x07
USB_REQ_GET_CONFIGURATION = 0x08
USB_REQ_SET_CONFIGURATION = 0x09
USB_REQ_GET_INTERFACE = 0x0A
USB_REQ_SET_INTERFACE = 0x0B
USB_REQ_SYNCH_FRAME = 0x0C
USB_DEVICE_SELF_POWERED = 0
USB_DEVICE_REMOTE_WAKEUP = 1
USB_DT_DEVICE = 0x01
USB_DT_CONFIG = 0x02
USB_DT_STRING = 0x03
USB_DT_INTERFACE = 0x04
USB_DT_ENDPOINT = 0x05
# HID interface requests
GET_REPORT = 0xa101
GET_IDLE = 0xa102
GET_PROTOCOL= 0xa103
SET_IDLE = 0x210a
SET_PROTOCOL= 0x210b
class USBdevice:
def __init__(self):
self.dev_descriptor = "\x12\x01\x00\x01\x00\x00\x00\x08\x27\x06\x01\x00\x00\x00\x03\x02\x01\x01"
self.conf_descriptor = '\x09\x02\x22\x00\x01\x01\x04\xA0\x32' \
'\x09\x04\x00\x00\x01\x03\x01\x02\x05' \
'\x09\x21\x01\x00\x00\x01\x22\x32\x00' \
'\x07\x05\x81\x03\x03\x00\x0A'
self.hid_report_descriptor = '\x05\x01\x09\x02\xA1\x01\x09\x01' \
'\xA1\x00\x05\x09\x19\x01\x29\x03' \
'\x15\x00\x25\x01\x95\x03\x75\x01' \
'\x81\x02\x95\x01\x75\x05\x81\x01' \
'\x05\x01\x09\x30\x09\x31\x15\x81' \
'\x25\x7F\x75\x08\x95\x02\x81\x06' \
'\xC0\xC0'
self.reset()
def usbstr(self,s):
return chr(len(s)*2+2)+'\x03' + ''.join(['%c\x00'%x for x in s])
def reset(self):
self.angle = 0.0
self.angleinc = 2.0 * math.pi / 360.0
self.radius = 150.0
self.px = int(self.radius)
self.py = 0
def get_string(self,stridx):
data = ''
if stridx == 0:
# language id
data = '\x04\x03\x09\x04' # English (US)
elif stridx == 1:
# serial number
data = self.usbstr('1')
elif stridx == 2:
# product description
data = self.usbstr('Python USB mouse')
elif stridx == 3:
# vendor description
data = self.usbstr('QEMU')
return data
def get_data(self,packetlen):
self.angle = ( self.angle + self.angleinc ) % (2.0 * math.pi)
nx = int(round(math.cos(self.angle) * self.radius))
ny = int(round(math.sin(self.angle) * self.radius))
dx = nx - self.px
dy = - (ny - self.py)
self.px = nx
self.py = ny
b = 0
data = '%c%c%c'%(b,dx&0xff,dy&0xff)
return data
def get_control(self, request, value, index, length):
data = 0
if request == DeviceRequest | USB_REQ_GET_DESCRIPTOR:
if value>>8 == USB_DT_DEVICE:
data = self.dev_descriptor
elif value>>8 == USB_DT_CONFIG:
data = self.conf_descriptor
elif value>>8 == USB_DT_STRING:
data = self.get_string(value&0xff)
elif request == DeviceOutRequest | USB_REQ_SET_ADDRESS:
data = 0
elif request == DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
if value == USB_DEVICE_REMOTE_WAKEUP:
data = 0
elif request == DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
data = 0
elif request == InterfaceRequest | USB_REQ_GET_DESCRIPTOR:
if value >> 8 == 0x22:
data = self.hid_report_descriptor
elif request == GET_REPORT:
data = self.get_data(length)
elif request == SET_IDLE:
data = 0
else:
print 'unhandled request: 0x%04x value: 0x%04x'%(req,value)
return data
def qemu_ret(data):
if isinstance(data,int):
return struct.pack('>H',data)
elif isinstance(data,basestring):
return struct.pack('>H',len(data)) + data
# -------------------------------------------------------------------------
if len(sys.argv)>1:
localhost,port = sys.argv[1].split(':')
else:
host='localhost'
port = 5555
outsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
outsocket.connect((host,port))
usb = USBdevice()
while 1:
sdata = outsocket.recv(16)
if not sdata:
print 'connection reset by peer'
break
if sdata == 'USBTCP 000.001\n':
# Handshake
outsocket.send('USBTCP 000.001\n')
else:
msg = sdata[:2]
param = sdata[2:]
if msg == 'CT':
request, value, index, length = struct.unpack('>HHHH',param)
outsocket.send( qemu_ret( usb.get_control(request, value, index, length) ) )
elif msg == 'RS':
usb.reset()
outsocket.send( qemu_ret( 0 ) )
elif msg == 'DI':
value = struct.unpack('>H',param)
outsocket.send( qemu_ret( usb.get_data(value) ) )
else:
print 'unknown message: %s\n%s'%(msg,param.encode('hex'))
break
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [Qemu-devel] Feature proposal: USB devices over TCP
2007-04-07 1:11 [Qemu-devel] Feature proposal: USB devices over TCP Eduardo Felipe
@ 2007-04-07 3:38 ` Anthony Liguori
2007-04-07 12:14 ` Eduardo Felipe
0 siblings, 1 reply; 4+ messages in thread
From: Anthony Liguori @ 2007-04-07 3:38 UTC (permalink / raw)
To: qemu-devel
Eduardo Felipe wrote:
> Hi everybody,
>
> I think that a useful feature for QEMU would be to expose the USB
> interface through TCP.
Hi Eduardo,
I don't know all that much about USB, but I think the most useful way to
do this would be to do something that's protocol compatible with USBIP.
We could then tunnel this traffic over VNC and allow for exposing local
USB devices to a remote VM.
Think of a virtual desktop being hosted on a server and exposed on a
thin client. If you could plug in your iPod and it would just work with
the VM, that would be an exceedingly cool feature.
Are you familiar with USBIP? If so, does this sound reasonable?
Regards,
Anthony Liguori
> It would allow quick USB device development in high level languages
> without recompiling QEMU. We could have an instance of QEMU running
> all the time while we create our device and hot plug/unplug it
> whenever we want to.
>
> This could also attract people interested in hardware emulation, but
> scared of learning QEMU internals just to create a simple new device.
>
> I think USB is quite suited for this, as it is designed for pluggable
> external devices, but something similar could be made for serial and
> parallel devices too.
>
> The attached patch is a quick hack derived from the VNC server just to
> show the idea, not intended for commiting. A dummy protocol is used
> for message interchange between server and client.
>
> It adds the new command line option:
> -usbtcp port
> It starts a socket listening on port for incoming connections. A
> sample USB mouse in python is also provided that moves the cursor in
> circles.
>
> Would such a feature be of any interest for QEMU?
>
> Regards,
> Eduardo Felipe
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [Qemu-devel] Feature proposal: USB devices over TCP
2007-04-07 3:38 ` Anthony Liguori
@ 2007-04-07 12:14 ` Eduardo Felipe
2007-04-07 12:49 ` Paul Brook
0 siblings, 1 reply; 4+ messages in thread
From: Eduardo Felipe @ 2007-04-07 12:14 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 1068 bytes --]
Hi Anthony,
I don't know all that much about USB, but I think the most useful way to
> do this would be to do something that's protocol compatible with USBIP.
Neither I do. I didn't know anything about USB/IP, it would be cool to be
protocol compatible with it. As we have to start from scratch, USB/IP can be
a very good reference.
We could then tunnel this traffic over VNC and allow for exposing local
> USB devices to a remote VM.
I don't think that tunneling traffic through VNC is possible. In QEMU the
VNC implementation is asynchronous, while the USB layer expects an immediate
response to any request.
Think of a virtual desktop being hosted on a server and exposed on a
> thin client. If you could plug in your iPod and it would just work with
> the VM, that would be an exceedingly cool feature.
>
> Are you familiar with USBIP? If so, does this sound reasonable?
It would be cool indeed. Maybe some wrapper around libusb could do the trick
for host OSes where no USB/IP server is available.
I'll have a look at USB/IP.
Regards,
Eduardo Felipe
[-- Attachment #2: Type: text/html, Size: 1646 bytes --]
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2007-04-07 12:53 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-04-07 1:11 [Qemu-devel] Feature proposal: USB devices over TCP Eduardo Felipe
2007-04-07 3:38 ` Anthony Liguori
2007-04-07 12:14 ` Eduardo Felipe
2007-04-07 12:49 ` Paul Brook
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).