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