5.5 USB虚拟化


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传输

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值