介绍
如今,设备的数量越来越多,现代操作系统必须尝试在每次集成、每次发布时都支持所有类型和其中的几种。 维护大量设备是困难的、昂贵的并且也难以测试,特别是对于即插即用设备,如 USB 设备。
因此,有必要建立一种机制,方便新旧USB设备的维护和测试。 这就是 USB 设备仿真的用武之地。这样,一个包含大量仿真和验证的 USB 设备的完整框架将允许更轻松的集成和发布。 应用领域将非常广泛:即使在开发期间更早的错误搜索/检测、自动测试、持续集成等。
如何模拟 USB 设备
USB/IP 项目 允许共享连接到本地计算机的 USB 设备,以便它们可以由通过 TCP/IP 连接连接到网络的另一台计算机管理。
那么USB/IP项目由两部分组成:
- 本地设备支持(主机)以允许远程访问每个必要的控制事件和数据
- 远程控制,可捕获每个必要的控制事件和数据,以像普通驱动程序一样进行处理
该过程适用于 Linux 和 Windows,这里我将只关注 Linux。
模拟背后的想法是用行为相同的应用程序替换远程设备支持。 通过这种方式,我们可以使用遵循注释 USB/IP 协议规范的软件应用程序来模拟设备。
在以下几点中,我将描述如何配置和运行远程支持以及如何连接到我们的 USB 仿真设备。
远程支持
远程支持分为两部分:
- 内核空间来控制远程设备,因为它是本地的,即由普通驱动程序探测。
- 用户空间应用程序来配置对远程设备的访问。
在这一点上,重要的是要注意设备仿真器在用户空间应用程序配置后,将直接与内核空间通信。
本地支持具有非常相似的结构,但本文的重点是设备仿真。
让我们分析远程支持的每个部分。
内核空间
首先,为了获得我们需要使用以下选项编译 Linux 内核的功能:
CONFIG_USBIP_CORE=m
CONFIG_USBIP_VHCI_HCD=m
这些选项启用在远程机器上运行的 USB/IP 虚拟主机控制器驱动程序。
还需要包含普通 USB 驱动程序,因为它们将以相同的方式从虚拟主机控制器驱动程序进行探测和配置。
此外还有其他重要的配置选项:
CONFIG_USBIP_VHCI_HC_PORTS=8
CONFIG_USBIP_VHCI_NR_HCS=1
这些选项定义每个 USB/IP 虚拟主机控制器的端口数和 USB/IP 虚拟主机控制器的数量,就像添加物理主机控制器一样。 如果启用了 CONFIG_USBIP_VHCI_HCD,这些是默认值,如有必要,请增加。
注释的选项和内核模块已经包含在一些 Linux 发行版中,例如 Fedora Linux。
让我们看一个 example 我们稍后将使用的可用虚拟 USB 总线和端口。
默认资源和真实资源 example 设备:
$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 002: ID 0627:0001 Adomax Technology Co., Ltd
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
$ lsusb -t
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/15p, 5000M
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/15p, 480M
|__ Port 1: Dev 2, If 0, Class=Human Interface Device, Driver=usbhid, 480M
$
现在,我们将模块 vhci-hcd 加载到系统中(CONFIG_USBIP_VHCI_HC_PORTS 和 CONFIG_USBIP_VHCI_NR_HCS 的默认配置):
$ sudo modprobe vhci-hcd
$ lsusb
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 002: ID 0627:0001 Adomax Technology Co., Ltd
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
$ lsusb -t
/: Bus 04.Port 1: Dev 1, Class=root_hub, Driver=vhci_hcd/8p, 5000M
/: Bus 03.Port 1: Dev 1, Class=root_hub, Driver=vhci_hcd/8p, 480M
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/15p, 5000M
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/15p, 480M
|__ Port 1: Dev 2, If 0, Class=Human Interface Device, Driver=usbhid, 480M
$
远程 USB/IP 虚拟主机控制器驱动程序将仅使用配置的虚拟化资源。 当然,模拟设备将以相同的方式工作。
用户空间
USB/IP项目中另一个必要的部分是用户空间工具usbip,需要用来配置两边引用的内核空间,虽然,同样的,我们只关注远程端,因为本地端将由模拟器表示。
即usbip工具会在内核空间配置USB/IP虚拟控制器(tcp客户端)连接到设备仿真器(tcp服务器),以便在它们之间建立直接连接,进行USB配置、事件、数据等的交换.
该工具独立于设备类型,并且能够提供有关可用和保留资源的信息(请参阅下面示例中的更多信息)。
本地 USB/IP 虚拟主机控制器需要指定将用于远程访问的总线端口对,对于模拟设备将是相同的,但在这种情况下,这对可以是任何东西,因为没有真实的设备和资源不需要预订。
这个工具可以在 Linux Kernel 存储库中找到,以便与它完全同步。
该工具在 Linux 内核存储库中的位置:./tools/usb/usbip
在某些发行版中,例如 Fedora 在 Linux 下,可以通过存储库中的 usbip 包安装 usbip 实用程序。 如果找不到 usbip 实用程序或相关软件包,请按照可用 README 文件中的说明进行编译和安装。 合适的 rpm 包也可以从 usbip模拟器 存储库:
$ git clone https://github.com/jtornosm/USBIP-Virtual-USB-Device.git $ cd USBIP-Virtual-USB-Device/usbip $ make rpm ... $
如何模拟 USB 设备
模拟器是用 Python 和 C 生成的。我已经开始使用 C 开发(我将专注于这部分),但同样可以在 Python 中完成。
对于 C 开发,从 usbip模拟器 存储库:
$ git clone https://github.com/jtornosm/USBIP-Virtual-USB-Device.git $ cd USBIP-Virtual-USB-Device/c $ make ... $
将生成此时模拟的所有支持的设备:
- 隐藏键盘
- 隐藏鼠标
- cdc-adm
- hso
- cdc-醚
- BT
rpm包(usbip-emulator)也可以通过以下方式生成:
$ make rpm ... $
例如,供应商和产品 ID 在代码中是硬编码的。
以下三个示例展示了仿真的工作原理。 我们对仿真器和远程 USB/IP 使用相同的设备,但它们可以在不同的设备上运行。 此外,我们保留了不同的资源,以便可以同时模拟所有设备。
示例 1:hso
从一个终端,让我们模拟 hso 设备:
(“1-1”是本地机器上 USB 设备的一对总线端口,正如我们所模拟的,它可以是任何东西。这很重要,因为 usbip 工具必须使用相同的名称来请求模拟设备)
$ hso -p 3241 -b 1-1 hso started.... server usbip tcp port: 3241 Bus-Port: 3-0:1.0 ...
从另一个终端连接到模拟器:
(本地主机,因为模拟器在同一设备中运行,并且与模拟器的对总线端口具有相同的名称)
$ sudo modprobe vhci-hcd
$ sudo usbip --tcp-port 3241 attach -r 127.0.0.1 -b 1-1
usbip: info: using port 3241 ("3241")
$
现在我们可以检查新设备是否存在:
(正如我们之前看到的,为此 example 机,总线 3 被虚拟化)
$ ip addr show dev hso 0 3: hso0: <POINTOPOINT,MULTICAST,NOARP> mtu 1486 qdisc noop state DOWN group default qlen 10 link/none $ rfkill list 0: hso-0: Wireless WAN Soft blocked: no Hard blocked: no ... $ lsusb ... Bus 003 Device 002: ID 0af0:6711 Option GlobeTrotter Express 7.2 v2 ... $ lsusb -t ... /: Bus 03.Port 1: Dev 1, Class=root_hub, Driver=vhci_hcd/8p, 480M |__ Port 1: Dev 2, If 0, Class=Vendor Specific Class, Driver=hso, 12M ... $
为了释放资源:
$ sudo usbip port
Imported USB devices
====================
Port 00: <Port in Use> at Full Speed(12Mbps)
Option : GlobeTrotter Express 7.2 v2 (0af0:6711)
3-1 -> usbip://127.0.0.1:3241/1-1
-> remote bus/dev 001/002
$ sudo usbip detach -p 00
usbip: info: Port 0 is now detached!
$
我们可以检查设备是否已发布:
$ ip addr show dev hso0 Device "hso0" does not exist. $ rfkill list ... $ lsusb ... $
之后,我们可以再次模拟或从第一个终端停止模拟设备(即使用 Ctrl-C)。
示例 2:cdc-ether
从一个终端,让我们模拟 cdc-ether 设备(需要 root 权限,因为原始套接字需要绑定到数据平面的指定接口):
(“1-1”是本地机器上 USB 设备的一对总线端口,正如我们所模拟的,它可以是任何东西。这很重要,因为 usbip 工具必须使用相同的名称来请求模拟设备)
$ sudo cdc-ether -e 88:00:66:99:5b:aa -i enp1s0 -p 3242 -b 1-1 cdc-ether started.... server usbip tcp port: 3242 Bus-Port: 1-1 Ethernet address: 88:00:66:99:5b:aa Manufacturer: Temium Network interface to bind: enp1s0 ...
从另一个终端连接到模拟器:
(本地主机,因为模拟器在同一设备中运行,并且与模拟器的对总线端口具有相同的名称)
$ sudo modprobe vhci-hcd
$ sudo usbip --tcp-port 3242 attach -r 127.0.0.1 -b 1-1
usbip: info: using port 3242 ("3242")
$
现在我们可以检查新设备是否存在:
(正如我们之前看到的,为此 example 机,总线 3 被虚拟化)
$ ip addr show dev eth0 4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000 link/ether 88:00:66:99:5b:aa brd ff:ff:ff:ff:ff:ff $ sudo ethtool eth0 ... Link detected: yes $ lsusb ... Bus 003 Device 003: ID 0fe6:9900 ICS Advent ... $ lsusb -t ... /: Bus 03.Port 1: Dev 1, Class=root_hub, Driver=vhci_hcd/8p, 480M |__ Port 2: Dev 3, If 0, Class=Communications, Driver=cdc_ether, 480M |__ Port 2: Dev 3, If 1, Class=CDC Data, Driver=cdc_ether, 480M ... $
为了这 example,我们也可以测试数据平面。
(两边都禁用IP转发)
首先,我们可以在仿真设备中配置IP地址:
$ sudo ip addr add 10.0.0.1/24 dev eth0
$ ip addr show dev eth0
4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
link/ether 88:00:66:99:5b:aa brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/24 scope global eth0
valid_lft forever preferred_lft forever
$
二、对于 example,从其他直接连接以太网的机器(真实或虚拟)我们可以在同一子网中配置一个 macvlan 接口来发送/接收流量(ping、iperf 等):
$ sudo ip link add macvlan0 link enp1s0 type macvlan mode bridge $ sudo ip addr add 10.0.0.2/24 dev macvlan0 $ sudo ip link set macvlan0 up $ ip addr show dev macvlan0 3: macvlan0@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether d6:f1:cd:f1:cc:02 brd ff:ff:ff:ff:ff:ff inet 10.0.0.2/24 scope global macvlan0 valid_lft forever preferred_lft forever inet6 fe80::d4f1:cdff:fef1:cc02/64 scope link valid_lft forever preferred_lft forever $ ping 10.0.0.1 PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data. 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=55.6 ms 64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=2.19 ms 64 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=1.74 ms 64 bytes from 10.0.0.1: icmp_seq=4 ttl=64 time=1.76 ms 64 bytes from 10.0.0.1: icmp_seq=5 ttl=64 time=1.93 ms 64 bytes from 10.0.0.1: icmp_seq=6 ttl=64 time=1.65 ms ...
为了释放资源:
$ sudo usbip port Imported USB devices ==================== ... Port 01: <Port in Use> at High Speed(480Mbps) ICS Advent : unknown product (0fe6:9900) 3-2 -> usbip://127.0.0.1:3245/1-1 -> remote bus/dev 001/003 $ sudo usbip detach -p 01 usbip: info: Port 1 is now detached! $
我们可以检查设备是否已发布:
$ ip addr show dev eth0
Device "eth0" does not exist.
$ lsusb
...
$
当然,来自另一台机器的流量无法正常工作:
From 10.0.0.2 icmp_seq=167 Destination Host Unreachable From 10.0.0.2 icmp_seq=168 Destination Host Unreachable From 10.0.0.2 icmp_seq=169 Destination Host Unreachable From 10.0.0.2 icmp_seq=170 Destination Host Unreachable ...
之后,我们可以再次模拟或从第一个终端停止模拟设备(即使用 Ctrl-C)。
示例 3:bt
从一个终端,让我们模拟蓝牙设备:
(“1-1”是本地机器上 USB 设备的一对总线端口,正如我们所模拟的,它可以是任何东西。这很重要,因为 usbip 工具必须使用相同的名称来请求模拟设备)
$ bt -a aa:bb:cc:dd:ee:11 -p 3243 -b 1-1 bt started.... server usbip tcp port: 3243 Bus-Port: 1-1 BD address: aa:bb:cc:dd:ee:11 Manufacturer: Trust ...
从另一个终端连接到模拟器:
(本地主机,因为模拟器在同一设备中运行,并且与模拟器的对总线端口具有相同的名称)
$ sudo modprobe vhci-hcd
$ sudo usbip --tcp-port 3243 attach -r 127.0.0.1 -b 1-1
usbip: info: using port 3243 ("3243")
$
现在我们可以检查新设备是否存在:
(正如我们之前看到的,为此 example 机,总线 3 被虚拟化)
$ hciconfig -a hci0: Type: Primary Bus: USB BD Address: AA:BB:CC:DD:EE:11 ACL MTU: 310:10 SCO MTU: 64:8 UP RUNNING PSCAN ISCAN INQUIRY RX bytes:1451 acl:0 sco:0 events:80 errors:0 TX bytes:1115 acl:0 sco:0 commands:73 errors:0 Features: 0xff 0xff 0x8f 0xfe 0xdb 0xff 0x5b 0x87 Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3 Link policy: RSWITCH HOLD SNIFF PARK Link mode: SLAVE ACCEPT Name: 'BT USB TEST - CSR8510 A10' Class: 0x000000 Service Classes: Unspecified Device Class: Miscellaneous, HCI Version: 4.0 (0x6) Revision: 0x22bb LMP Version: 3.0 (0x5) Subversion: 0x22bb Manufacturer: Cambridge Silicon Radio (10) $ rfkill list ... 1: hci0: Bluetooth Soft blocked: no Hard blocked: no $ lsusb ... Bus 003 Device 004: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode) ... $ lsusb -t ... /: Bus 03.Port 1: Dev 1, Class=root_hub, Driver=vhci_hcd/8p, 480M |__ Port 3: Dev 4, If 0, Class=Wireless, Driver=btusb, 12M |__ Port 3: Dev 4, If 1, Class=Wireless, Driver=btusb, 12M ... $
我们可以关闭和打开模拟的蓝牙设备,检测几个假蓝牙设备:
(此时,假蓝牙设备没有被模拟/模拟,所以我们无法设置)
为了释放资源:
$ sudo usbip port Imported USB devices ==================== ... Port 02: <Port in Use> at Full Speed(12Mbps) Cambridge Silicon Radio, Ltd : Bluetooth Dongle (HCI mode) (0a12:0001) 3-3 -> usbip://127.0.0.1:3243/1-1 -> remote bus/dev 001/002 $ sudo usbip detach -p 02 usbip: info: Port 2 is now detached! $
我们可以检查设备是否已发布:
$ hciconfig $ rfkill list ... $ lsusb ... $
当然,未检测到设备(如仿真之前):

之后,我们可以再次模拟或从第一个终端停止模拟设备(即使用 Ctrl-C)。
模拟与真实 USB 设备
当不使用真实硬件和/或最终设备进行测试时,我们总是会对结果感到不安全,这是我们必须克服的最大障碍,以通过仿真检查设备的正确操作。
所以,为了有信心,仿真必须是 close 尽可能的真实硬件,为了获得最真实的仿真,必须覆盖设备的每个方面(或者至少是必要的,如果它们与其他方面无关)。 事实上,为了正确的测试,我们不能修改驱动,也就是只模拟物理层,驱动无法知道设备是真实的还是模拟的。
开始使用真实的硬件设备进行测试是一个非常好的主意,以获得构建具有相同功能的模拟器的参考。 对于 USB 设备的情况,设备仿真器的构建更容易,因为现有的程序可以获得符合上述所有特征的远程控制。
结论
USB 设备仿真是以高效、自动和简单的方式集成和测试相关功能的最佳方式。 但是,为了对仿真过程充满信心,设备仿真器需要事先经过验证,以确认它们的工作方式与真实硬件相同。
当然,USB设备模拟器和真正的硬件设备是不一样的,但是注释的方法,感谢测试程序可以远程控制设备,非常 close 到真实场景,可以帮助我们改进发布和测试流程。
最后,我想评论一下,使用软件模拟器的最大优势之一是我们将能够以简单的方式导致特定的行为,这将很难用真实的硬件重现,这有助于找到问题并变得更加健壮。