Qemu针对USB设备的虚拟化有方式有两种:

(1) 直接调用VMM主机的USB设备方式(仅限于Linux OS)

   例: -usb -usbdevice host:xxxx:yyyy (xxxx:yyyy为vendorid:deviceid)

(2) 全虚拟化, 目前支持mouse, keyboard, bulk-only usb mass storage(该方式支持的设别有限).

   例: -usb -usbdevice tablet //虚拟usb鼠标

      -usbdisk:[filepath]  //用文件来虚拟usb mass storage

(3) USBRedir

  usbredir是通过网络连接将USB设备的数据包从主机端通过网络协议(现在一般是TCP/IP)发给客户机(虚拟机),它包括一个USB协议的解析库,主机库和其他一些工具。Usbredir是spice社区为了支持USB设备的重定向而开发的,下面网址是关于它的一个协议介绍:http://cgit.freedesktop.org/~jwrdegoede/usbredir/tree/usb-redirection-protocol.txt

例:-deviceusb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=ehci.0,debug=3 -chardevspicevmc,name=usbredir,id=usbredirchardev2

需要配合一定的客户端使用,如spice

该方式的上层实现与前两种一致,只是底层实现方式不同。 本文分析前两种方式。

 

5.5.1 USB Host 虚拟化

usb host controller有ohci(usb1.0), uhci(usb1.0), ehci(usb2.0), xhci(3.0).Qemu PC虚拟机默认采用UHCI控制器, 要采用其他控制器可使用如下方式启动虚拟机:

-device usb-ehci,id=usb1,bus=pci.0,addr=0x5 使用ehci控制器的例子。

本文这里仅分析uhci方式。

 

type_init(uhci_register_types)(hw\hcd_uhci.c)
static TypeInfo piix3_uhci_info = {
    .name          = "piix3-usb-uhci",
    .parent        = TYPE_PCI_DEVICE,
   .instance_size = sizeof(UHCIState),
   .class_init    =piix3_uhci_class_init,
};
piix3_uhci_class_init ==>  usb_uhci_common_initfn ==> 
a. usb_bus_new(&s->bus, &uhci_bus_ops,&s->dev.qdev); 注册uhci总线的操作
b. usb_register_port(&s->bus,&s->ports[i].port, s, i, &uhci_port_ops,
              USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);

usb device是插在host 的port上的,这里注册uhci port的注册函数指针。

usb_bus_new(hw\usb\bus.c)
        ==> qbus_create_inplace(&bus->qbus,TYPE_USB_BUS, host, NULL);

这样会生成usbclass object

type_init(usb_register_types(hw\usb\bus.c)
static const TypeInfo usb_bus_info = {
    .name =TYPE_USB_BUS,
    .parent =TYPE_BUS,
   .instance_size = sizeof(USBBus),
    .class_init= usb_bus_class_init,
}; 
 
static USBPortOps uhci_port_ops = {
    .attach =uhci_attach,
    .detach =uhci_detach,
   .child_detach = uhci_child_detach,
    .wakeup =uhci_wakeup,
    .complete =uhci_async_complete,
};

将由usb core层来回调

 

host controller主要根据Guest OS的寄存器操作,推导出:

(1) port上设备的检测

(2) 获取要方向设别的usb命令

这里我们分析2个情景

(1) usb device插入

(2) usb mass storage的一次bulk 传输

 

(1) uhci_reset ==>   if(port->port.dev && port->port.dev->attached) {

usb_port_reset(&port->port); ==> port->ops->attach(port)==> uhci_attach
(port 上的device绑定在下一节分析)

(2) uhci用qh, qtd来封装数据与命令, 因此虚拟驱动首先需要取出qh和qtd

 

uhci_frame_timer==> uhci_process_frame==> uhci_handle_td
 uhci timer定期从uhci 的frame list中取出qh
 
static int uhci_handle_td(UHCIState *s, uint32_t addr,UHCI_TD *td,
                          uint32_t *int_mask,bool queuing) {
........
    dev =uhci_find_device(s, (td->token >> 8) & 0x7f); //得到与该port绑定的usb device 
    ep =usb_ep_get(dev, pid, (td->token >> 15) & 0xf);//得到port 的ep
   usb_packet_setup(&async->packet, pid, ep, addr);//初始化USBPacket
   qemu_sglist_add(&async->sgl, td->buffer, max_len);
   usb_packet_map(&async->packet, &async->sgl);
   .......
usb_handle_packet(dev, &async->packet); //调用core层向device层转发packet
}

这里最重要的就是USBPacket, 它是qemu host层到device层命令与数据的封装。

下面时bulk的调用流程

usb_handle_packet ==> usb_device_handle_data ==>klass->handle_control

handle_control由device 层实现, 下一节分析

 

本节的最后一个问题是, uhci host何时被虚拟机建立; 这个很简单在

pc_init1 ==>  pci_create_simple(pci_bus, piix3_devfn + 2,"piix3-usb-uhci"); 在虚拟机上创建了usb host controller。

5.5.2 Linux Host 直接方式

由上节的分析知,usb device层要解决2个问题:

(1) usb Device如何初始化并与host 的port关联

(2) 如何处理 Host 层发下来的USBPacket

 

先看usb Device如何初始化并与host 的port关联,本节以linux host方式为例。

type_init(usb_host_register_types) (host_linux.c)
static void usb_host_register_types(void)
{
   type_register_static(&usb_host_dev_info);
   usb_legacy_register("usb-host", "host",usb_host_device_open);
}
 
static void usb_host_class_initfn(ObjectClass *klass,void *data)
{
    DeviceClass*dc = DEVICE_CLASS(klass);
   USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
 
   uc->init           =usb_host_initfn;
   uc->product_desc   = "USBHost Device";
   uc->cancel_packet  =usb_host_async_cancel;
   uc->handle_data    =usb_host_handle_data;
   uc->handle_control = usb_host_handle_control;
   uc->handle_reset   =usb_host_handle_reset;
   uc->handle_destroy = usb_host_handle_destroy;
    dc->vmsd= &vmstate_usb_host;
    dc->props= usb_host_dev_properties;
}

这里注册了USBDeviceClass的回调,改回调正式host layer到device layer的接口

另外usb_legacy_register向qemu中注册了usb device的初始化回调和名称。

void usb_legacy_register(const char *typename, constchar *usbdevice_name,
                         USBDevice*(*usbdevice_init)(USBBus *bus,
                                                     const char *params))
{
    if(usbdevice_name) {
       LegacyUSBFactory *f = g_malloc0(sizeof(*f));
       f->name = typename;
       f->usbdevice_name = usbdevice_name;
       f->usbdevice_init = usbdevice_init;
       legacy_usb_factory = g_slist_append(legacy_usb_factory, f);
    }
}
usb device 的添加由usb_device_add (vl.c)来实现
static int usb_device_add(const char *devname)
{
    const char*p;
    USBDevice*dev = NULL;
    dev =usbdevice_create(devname);
    if (dev)
        gotodone;
 
    /* the otherones */
#ifndef CONFIG_LINUX
    /* only thelinux version is qdev-ified, usb-bsd still needs this */
    if(strstart(devname, "host:", &p)) {
        dev =usb_host_device_open(usb_bus_find(-1), p);
    } else
#endif
              。。。。。。
 
done:
    return 0;
}

devname为命令-usb 后面的字符串,如 "-usbdevice tablet", " -usbdevicehost:xxxx:yyyy , " disk:[filepath]"等。

首先用usbdevice_create来建立usbdevice

USBDevice *usbdevice_create(const char *cmdline) {
       a. 根据名字查找legacy_usb_factory中已注册的init方式
    b. 调用f->usbdevice_init, 这里为usb_host_device_open
}

usb_bus_find 返回当前虚拟机的一个usb bus(usb bus 由上节的usb_bus_new创建)

 

usb_host_device_open (host-bsd.c) 流程如下:

a. 在vmm usb host上查找该设备,并返回usb address

b. 打开usb 设备文件,并读取usb info(USB_GET_DEVICEINFO)

c. usb_create(guest_bus, "usb-host"); 建立usb device, 此时usb_host_initfn会被调用

由此完成了usb device与host 的绑定。

usb_host_initfn ==》usb_host_auto_check==> usb_host_scan 对vmm host做扫描读取设备信息。 底层实现函数为
static int usb_host_open(USBHostDevice *dev, intbus_num,
                         int addr, const char*port,
                         const char *prod_name,int speed)  {
 usb_ep_init(&dev->dev);
ret = usb_linux_update_endp_table(dev); 来决定usb device支持的数据传输类型。
usb_device_attach(&dev->dev); //由此会调用到uhci 的attach
qemu_set_fd_handler(dev->fd, NULL, async_complete,dev);//对设备文件注册回调,当usb device disconnect或数据传输完成时,async_complete会被执行
}

 

解决了第一个问题,第二个问题也就较简单了,数据传输的回调为:

static int usb_host_handle_data(USBDevice *dev,USBPacket *p)

这里仅将USBPacket翻译成urb结构,并调用ioctl(s->fd, USBDEVFS_SUBMITURB, urb);向真实物理设备发起请求。

 

当数据传输完成时async_complete 会

if(aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL) {
               usb_generic_async_ctrl_complete(&s->dev, p);
            }else if (!aurb->more) {
               usb_packet_complete(&s->dev, p);
            }
 
usb_packet_complete  ==> __usb_packet_complete ==> dev->port->ops->complete==> uhci_async_complete

 

5.5.3本机虚拟化方式

对于上层接口形式完全和上节一样,不同的是上节需要真正物理设备的支持,而本节方式实现了完全虚拟化。本节以usb storage为例来分析(dev-storage.c)。

首先usb storage需要关联一个block device(关联方法与5.4节类似,这里不再分析)

下面先看devie的初始化:

static int usb_msd_initfn(USBDevice *dev)
{
       a. 取出BlockDriverState *bs = s->conf.bs;
    b. 初始化usb descritor
    c.  用bs建立一个虚拟scsi disk(因为usb bulk设备建立在scsi协议上)
scsi_bus_new(&s->bus, &s->dev.qdev,&usb_msd_scsi_info);
   s->scsi_dev = scsi_bus_legacy_add_drive(&s->bus, bs, 0,!!s->removable,
                                           s->conf.bootindex);
 
}
 
static const struct SCSIBusInfo usb_msd_scsi_info = {
    .tcq =false,
    .max_target= 0,
    .max_lun =0,
 
   .transfer_data = usb_msd_transfer_data,
    .complete =usb_msd_command_complete,
    .cancel =usb_msd_request_cancelled,
   .load_request = usb_msd_load_request,
};

时scsi层的回调。

 

下面分析bulk数据的处理

static int usb_msd_handle_data(USBDevice *dev,USBPacket *p)

usb bulk-only数据传输分三个阶段CBW(发送命令), DATA(读写数据),CSW(返回命令状态)。并向scsi device发送请求scsi_req_enqueue(s->req);

CBW阶段建立SCSI命令: s->req = scsi_req_new(s->scsi_dev,tag, 0, cbw.cmd, NULL);

DATA阶段进行数据拷贝usb_msd_copy_data

CSW阶段返回结果:usb_msd_send_status,如果scsi层还未完成传输则返回USB_RET_ASYNC

 

当scsi 层完成传输时,usb_msd_command_complete会被执行,并调用usb_msd_packet_complete==》usb_packet_complete来真正完成usb layer传输