USB控制器类型 简单地讲,OHCI、UHCI都是USB1.1的接口标准,而EHCI是对应USB2.0的接口标准 ,最新的xHCI是USB3.0的接口标准 。
OHCI(Open Host Controller Interface)是支持USB1.1的标准,但它不仅仅是针对USB,还支持其他的一些接口,比如它还支持Apple的火线(Firewire,IEEE 1394)接口。与UHCI相比,OHCI的硬件复杂,硬件做的事情更多,所以实现对应的软件驱动的任务,就相对较简单。主要用于非x86的USB,如扩展卡、嵌入式开发板的USB主控。
UHCI(Universal Host Controller Interface),是Intel主导的对USB1.0、1.1的接口标准,与OHCI不兼容。UHCI的软件驱动的任务重,需要做得比较复杂,但可以使用较便宜、较简单的硬件的USB控制器。Intel和VIA使用UHCI,而其余的硬件提供商使用OHCI。
EHCI(Enhanced Host Controller Interface),是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCI或OHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。
xHCI(eXtensible Host Controller Interface),是最新最火的USB3.0的接口标准,它在速度、节能、虚拟化等方面都比前面3中有了较大的提高。xHCI支持所有种类速度的USB设备(USB 3.0 SuperSpeed, USB 2.0 Low-, Full-, and High-speed, USB 1.1 Low- and Full-speed)。xHCI的目的是为了替换前面3中(UHCI/OHCI/EHCI)。
usb 层级 用lsusb -t还可以看到USB设备的层级关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [root@localhost xqk]# lsusb -t /: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=ehci-pci/2p, 480M |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/8p, 480M |__ Port 1: Dev 3, If 0, Class=Mass Storage, Driver=usb-storage, 480M Bus 02.Port 1 表示第二个USB主控制器,Port号为1 Dev 1, Class=root_hub 分配的设备号为1,类型是root_hub Driver=ehci-pci/2p root_hub的类型是ehci(usb 2.0),总共有两个port |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/8p, 480M root_hub的其中一个port有个Hub设备,port的id是1,此Hub有8个port。 |__ Port 1: Dev 3, If 0, Class=Mass Storage, Driver=usb-storage, 480M Hub的其中一个port有大容量USB设备,port的id为1.3(树状结构,依次以.作为低一级设备成员)
USB设备直通 虚拟机使用USB设备可以通过两种方式:
这里重点说下非PCI方式的usb 设备直通 qemu支持hostaddr,hostport以及vendorid,productid等信息的组合, 相对于libvirt,多了hostport的支持,hostport即是主机usb的port。
1 2 3 4 5 6 7 8 9 10 (1) vendorid+productid -- match for a specific device, pass it to the guest when it shows up somewhere in the host. (2) hostbus+hostport -- match for a specific physical port in the host, any device which is plugged in there gets passed to the guest. (3) hostbus+hostaddr -- most useful for ad-hoc pass through as the hostaddr isn't stable, the next time you plug in the device it gets a new one ...
注意点
devnum会变化 在host上同一个USB口上插拔U盘,会导致devnum的变化(增加),而port对应于USB的物理口,不会随着插拔U盘而变化,但是libvirt不支持传入port,只能自己适配开发。
是否需要开启vt-d iommu 通常情况下,直通物理设备(PCI)时需要开启vt-d,USB设备有些不同,USB设备是由controller控制,而各种类型的控制器都是qemu模拟的,只不过最终直接打开物理机上的USB设备而已,数据还是由qemu控制。因此USB设备直通不需要开启vt-d iommu。
No free USB ports 模拟的控制器对应的port数量有限,如果要直通多个USB设备,就会造成port数目不够。piix3-uhci有两个port,ehci与nec-xhci有6个port(一般够用)。当数目不够时,可以添加hub设备来增加额外的port。
hub设备包含8个port。
不要直通主机hub设备 主机上的root_hub以及hub直通给虚拟机没有任何意义,一方面虚拟机中的root_hub是模拟控制器自带的,而hub是需要单独添加hub设备,另一方面直通hub设备,虚拟机占用主机hub,会导致主机上hub下的USB设备不可用。
qemu usb 非PCI 直通解读
qemu默认不支持 usb-host, 需要在编译时打开 --enable-libusb
编译, 依赖libusb 库.
按上一章的参数介绍, 简单看下代码结构 注意
USB设备是由controller控制,而各种类型的控制器都是qemu模拟的,只不过最终直接打开物理机上的USB设备而已,数据还是由qemu控制
qemu 命令行方式添加usb 直通设备
1 2 3 -device usb-host,hostbus=BUS,hostaddr=ADDR,id=[hostdev0] -device usb-host,vendorid=VID,productid=PRID,id=[hostdev0] # 对应lsusb的Bus xxx, Device xxx
注意上述命令需要以 sudo 运行 qemu 命令, 否则libusb 库无法打开给usb设备.
添加这个设备后, 最终会走到 TYPE_USB_HOST_DEVICE 设备的具现化函数中 TYPE_DEVICE -> TYPE_USB_DEVICE -> TYPE_USB_HOST_DEVICE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 -+ usb_host_realize(USBDevice *udev, Error **errp) \ - ldev = usb_host_find_ref(s->match.bus_num, s->match.addr) ; "libusb 库函数查找 hostbus, hostaddr 匹配的usb设备" | -+ usb_host_open(s, ldev, 0 ) ; \ - bus_num = libusb_get_bus_number(dev) ; addr = libusb_get_device_address(dev) ; s->dev = dev; s->bus_num = bus_num; s->addr = addr; \ -+ usb_host_detach_kernel(s) ; \ - libusb_detach_kernel_driver(s->dh, i) ; "从主机中解绑" | - USBDevice *udev = USB_DEVICE(s) ; | - usb_ep_init(udev) ; | -+ usb_host_ep_update(s) ; "将设备绑定到 qemu 模拟的usb controller 上" \ - libusb_get_active_config_descriptor(s->dev, &conf) ; | -+ for i in conf->bNumInterfaces "遍历接口描述符" \ - intf = &conf->interface[i].altsetting[0 ]; \ -+ for e in intf->bNumEndpoints "遍历端点描述符" \ - endp = &intf->endpoint[e]; | - devep = endp->bEndpointAddress; | - pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT; "是in 端点还是 out端点" | - ep = devep & 0 xf; | - | -+ usb_device_attach(udev, &local_err); \ - USBPort *port = udev->port; | -+ usb_attach(port); \ - USBDevice *dev = port->dev; | - port->ops->attach(port); "usb controller 注册的port, 如usb2.0 ehci" | - dev->state = USB_STATE_ATTACHED; | -+ usb_device_handle_attach(dev); \ - klass->handle_attach(dev); "按设备类型进行, 不一定实现了这个方法"
这里重点说下 port->ops->attach(port)
, 这个是由 qemu 模拟的usb controller 注册的port, 这里以usb2.0 的ehci 看下注册过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void usb_ehci_realize (EHCIState *s, DeviceState *dev, Error **errp) { for (i = 0 ; i < s->portnr; i++) { usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops, USB_SPEED_MASK_HIGH); s->ports[i].dev = 0 ; } } static USBPortOps ehci_port_ops = { .attach = ehci_attach, .detach = ehci_detach, .child_detach = ehci_child_detach, .wakeup = ehci_wakeup, .complete = ehci_async_complete_packet, };
所以 port->ops->attach(port)
指向了 ehci_port_ops.ehci_attach
, 重点看下这个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static void ehci_attach (USBPort *port) { EHCIState *s = port->opaque; uint32_t *portsc = &s->portsc[port->index]; const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci" ; ... *portsc |= PORTSC_CONNECT; *portsc |= PORTSC_CSC; -+ ehci_raise_irq(s, USBSTS_PCD); \ ---+ qemu_set_irq(s->irq, level); } static void usb_ehci_sysbus_realize (DeviceState *dev, Error **errp) { SysBusDevice *d = SYS_BUS_DEVICE(dev); EHCISysBusState *i = SYS_BUS_EHCI(dev); EHCIState *s = &i->ehci; usb_ehci_realize(s, dev, errp); sysbus_init_irq(d, &s->irq); "中断的注册在这里" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s, "operational" , s->portscbase); s->async_bh = qemu_bh_new(ehci_work_bh, s); static void ehci_opreg_write (void *ptr, hwaddr addr, uint64_t val, unsigned size) { ... qemu_bh_schedule(s->async_bh); } static void ehci_work_bh (void *opaque) { -+ ehci_advance_async_state(ehci); \ -+ ehci_advance_state(EHCIState *ehci, int async) \ -+ ehci_state_execute(q); \ -+ ehci_execute \ -+ usb_handle_packet(p->queue ->dev, &p->packet); \ -+ usb_process_one(p); \ -+ usb_device_handle_data(dev, p); \ -+ klass->handle_data(dev, p); \ -+ usb_host_handle_data(dev, p) \ - libusb_fill_bulk_transfer() | - libusb_submit_transfer(r->xfer); }
从上述过程中可以大概了解到
传入的usb设备需要先借助libusb 库与host 进行解绑 detach, 此时该usb设备与host就没啥关系了
与host解绑后, 需要绑定到qemu模拟的usb 控制器上, 由qemu 模拟的usb控制器控制usb设备的行为, usb控制器有MemoryRegion等的设定, 以让 guest 进行io的模拟
中断相关的仍然是使用的qemu的虚拟的中断控制器进行中断的触发
数据流中最终起作用的是 usb 设备的ep 端点, 最终数据流通过 libusb_*_transfer
等数据相关的接口以ep参数控制对应的端点进行发送接收数据.