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: usb_over_tcp.diff
Description: Binary data

# 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

Reply via email to