一、Linux USB驱动总体结构
USB主机、设备与Gadget驱动之Linux USB驱动层次
1.主机侧与设备侧USB驱动
在Linux系统中,提供主机侧和设备侧视角的USB驱动框架,从主机侧看到的USB主机控制器和设备驱动,以及从设备侧看到的设备控制器和Gadget驱动。
- 主机侧角度
- 主机侧USB驱动程序包括主机控制器驱动(Host Controller)和设备驱动两类,USB主机控制器驱动程序控制插入其中的USB设备,USB设备驱动程序控制该设备如何作为从设备与主机通信。
- 驱动分层
- USB设备驱动层(插入主机上的U盘、鼠标、USB转串口等设备驱动)
- USB核心层:负责USB驱动管理和协议处理
- 通过定义一些数据结构、宏和功能函数,向上为设备驱动提供编程接口,向下为USB主机控制器驱动提供编程接口;
- 维护整个系统的USB设备信息;
- 完成设备热插拔控制、总线数据传输控制等。
- USB主机控制器驱动
- USB主机控制器硬件
- 设备侧角度
- 设备侧USB驱动程序包含设备控制器(Device Controller)驱动和Gadget Function驱动两类。
- 驱动分层
- USB设备控制器硬件
- USB设备控制器驱动程序
- 直接访问硬件,控制USB设备和主机间的底层通信,向上层提供与硬件相关操作的回调函数。
- Gadget Function API
- UDC驱动程序回调函数的简单包装。
- 把下层的UDC驱动程序和上层的Gadget Function驱动程序隔离开,使得在Linux系统中编写USB设备侧驱动程序时能够把功能的实现和底层通信分离。
- Gadget(小巧机械装置) Function驱动程序。
- 控制USB设备功能的实现,使设备表现出“网络连接”、“打印机”或“USBMass Storage”等特性。
2.USB设备组织结构
在USB设备组织结构中,从上到下分为设备(device)、配置(config)、接口(interface)和端点(endpoint)四个层次
-
设备
代表一个可以实现插入操作的USB设备
-
设备描述符
- USB设备用描述符报告他们的属性,一个描述符是一个已定义格式的数据结构体。每个描述符以一个表示描述符长度的字节和一个表示描述符类型的字节开始。
- USB描述符信息存储在USB设备中,在枚举过程中,USB主机会向USB设备发送GetDescriptor请求,USB设备在收到这个请求之后,会将USB描述符信息返回给USB主机,USB主机分析返回来的数据,判断出该设备是哪一种USB设备,建立相应的数据链接通道。
-
标准USB设备类
Base Class Descriptor Usage Description 00h Device Use class information in the Interface Descriptors种类信息定义在接口描述符中 01h Interface Audio音频设备 02h Both Communications&CDC通信设备 03h Interface HID(Human Interface Device)人机接口设备 05h Interface Physical物理设备 06h Interface Image图像设备 07h Interface Printer打印机 08h Interface Mass Storage 大容量存储 09h Device Hub集线器 0Ah Interface CDC-Data通信设备 0Bh Interface Smart Card智能卡 0Dh Interface Content Security内容安全设备 0Eh Interface Video视频设备 0Fh Interface Personal Healthcare个人健康设备 10h Interface Audio/Video Devices声音/视频设备 11h Device Billboard Device Class广播牌设备 12h Interface USB Type-C Bridge Class DCh Both Diagnostic Device E0h Interface Wireless Controller EFh Both Miscellaneous FEh Interface Application Specific
-
配置
- 在Linux系统中,一个USB设备可以使用多个配置,并且可在各个配置之间进行转换,以改变设备的不同状态。
-
接口
- 接口由多个端点组成,一个USB接口代表一个基本的功能,每个USB驱动控制一个接口,一个功能复杂的USB设备可以具有多个接口,设备接口是端点的汇集。
- 一个配置中的所有接口可以同时有效,并可被不同的驱动程序连接。
-
端点
- USB通信的最基本形式是通过端点来实现,一个USB端点只能向一个方向传输数据(从主机到设备或者从设备到主机),可以将端点看作是一个单向的管道。
- 主机只能通过端点与设备进行通信,以使用设备的功能。在USB系统中每一个端点都有唯一的地址,这是由设备地址和端点号给出的。
- 在Linux系统中,一个USB端点有4种不同的类型,分别对应着4种不同的数据传送方式。
- 控制CONTROL: 控制端点被用来控制对USB设备的不同部分进行访问。通常用作配置设备、获取设备信息、发送命令到设备或获取设备状态报告,这些端点通常较小。每个USB设备都有一个控制端点称为“端点0”,被USB核心用来在插入时配置设备。USB协议保证总有足够的带宽留给控制端点传送数据到设备。
- 中断INTERRUPT:每当USB主机向设备请求数据时,中断端点以固定的速率传送少量的数据。这是为USB键盘和鼠标的主要的数据传送方法,并且还用以传送数据到USB设备的方式来控制设备,此时不用来传送大量数据。USB协议保证总有足够的带宽留给中断端点传送数据到设备。
- 批量BULK:表示批量端点用以传送大量数据,这些端点常比中断端点大得多。USB协议不保证传输在特定时间范围内完成,如果总线上没有足够的空间来发送整个BULK包,则被分为多个包进行传输。这些端点普遍用于打印机、USB Mass Storage和USB网络设备上。
- 等时ISOCHRONOUS: 此类型端点也能批量传送大量数据,但是这个数据不被保证能 送达。这些端点用在可以处理数据丢失的设备中,并且更多依赖于保持持续的数据流。如音频和视频设备等。
3.USB设备枚举
在Linux系统中,USB设备枚举的基本步骤如下
-
step 1 将设备插入主机,主机检测到设备,复位设备。
- 当主机向USB设备控制器发送一个包时,USB设备控制器就会产生相应的中断处理
step 2 主机向设备控制端点发送Get_Descriptor,以了解设备默认管道的大小。
step 3 主机指定一个地址,发送Set_Address标准请求设置设备的地址。
step 4 主机使用新的地址,再次发送Get_Descriptor以获得各种描述符。
step 5 主机加载一个USB设备驱动。
step 6 USB设备驱动再发送Set_Confuration标准设备请求配置设备。
二、Linux-USB Gadget 简介
在USB_Gadget驱动系统中,Gadget功能驱动层位于整个驱动软件结构的最上层,功能是实现USB设备的功能。此层通常与Linux内核的其他层有密切的联系。
Gadget 框架提出了一套标准 API, 在底层USB 设备控制器驱动实现这一套 API, 不同的 UDC需要不同的驱动。
1.Linux USB Gadget 支持的设备驱动
可以在Linux内核menuconfig中的设备驱动程序→USB支持→USB Gadget支持菜单下找到(在kernel/drivers/usb/gadget路径下)
- g_zero(CONFIG_USB_ZERO)- 类似于 dummy hcd, 该驱动用于测试 udc 驱动。它会帮助您通过 USB-IF 测试。
- g_audio(CONFIG_USB_AUDIO)
- g_ether(CONFIG_USB_ETH)-实现“通信设备类”(CDC)以太网设备,以创建“ USB到以太网”网络连接。
- g_ncm(CONFIG_USB_G_NCM)-实现USB CDC NCM子类标准。NCM是用于以太网封装的高级协议,它允许将多个以太网帧分组为一个USB传输。
- g_mass_storage(CONFIG_USB_MASS_STORAGE)-充当USB Mass Storage磁盘驱动程序。其存储库可以使用指定为模块参数或sysfs选项的常规文件或块设备。
- g_serial(CONFIG_USB_G_SERIAL)-充当ACM串行设备以创建“ USB到串行”连接,可用于与MS-Windows主机或Linux-USB“ cdc-acm”驱动程序互操作。
- g_midi(CONFIG_USB_MIDI_GADGET)-充当具有一个MIDI输入和一个MIDI输出的USB音频设备。
- g_printer(CONFIG_USB_G_PRINTER)-在USB主机和驱动打印引擎的用户空间程序之间传递数据。
- g_cdc(CONFIG_USB_CDC_COMPOSITE)-在一种配置中提供两种功能:CDC以太网(ECM)链接和CDC ACM(串行端口)链接
- g_acm_ms(CONFIG_USB_G_ACM_MS)-在一种配置中提供两种功能:USB大容量存储设备和CDC ACM(串行端口)链接
- g_multi(CONFIG_USB_G_MULTI)-多功能复合小工具,可以提供以太网(RNDIS和/或CDC),大容量存储和ACM串行链接接口
- g_hid(CONFIG_USB_G_HID)-人机接口设备(HID)小工具,可为诸如键盘,鼠标,触摸屏之类的事物提供通用接口
- g_webcam-网络摄像头设备
三、RK3399 TypeC配置USB以太网
基于Firefly-AIO-RK3399C开发板,将设备TypeC接口作为设备模式,然后模拟成USB-CDC-ECM设备(虚拟网卡)。主机通过 USB 连接设备并通过设备访问互联网。
1.修改内核配置
进入内核配置菜单后依次选择:Device Drivers
-> USB Support
-> USB Gadget Support
将 USB Gadget Driver
设置成编译进内核,然后可以在下边找到 Ethernet Gadget (with CDC Ethernet support)
选项,同样选择编译进内核,同时要选择上 RNDIS support
。
<*> USB Gadget Drivers
<*> USB functions configurable through configfs
<*> Ethernet Gadget (with CDC Ethernet support)
[*] RNDIS support (NEW)
2.修改DTS文件
firefly_rk3399开发板type-c口默认使用的OTG控制器,OTG线与常用的type-c数据线并不相同,普通的type-c充电线CC管脚通过56K电阻上拉到vbus,而OTG数据线通常是通过5.1K电阻下拉到地,因此主板电路,在插入OTG线时,将CC管脚的电平通过MOS管和三极管到ID管脚检测为0V,而插入普通充电线时,ID管脚检测大于1.8V即可,因此,若想用普通充电线做USB-adb使用,可采取将type-c控制器转为普通usb2.0 OTG方式。
$ git diff arch/arm64/boot/dts/rockchip/rk3399-firefly-aioc.dts
--- a/arch/arm64/boot/dts/rockchip/rk3399-firefly-aioc.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3399-firefly-aioc.dts
@@ -160,6 +160,38 @@
regulator-max-microvolt = <900000>;
};
+&u2phy0 {
+ status = "okay";
+ /delete-property/ extcon;
+ vbus-supply = <&gpio3 RK_PC6 GPIO_ACTIVE_HIGH>; /* Vbus GPIO配置 */
+};
+
+&usbdrd3_0 {
+ status = "okay";
+ // extcon = <&fusb0>;
+ extcon = <&u2phy0>;
+};
+
+&usbdrd_dwc3_0 {
+ status = "okay";
+ dr_mode = "peripheral";
+ maximum-speed = "high-speed";
+ phys = <&u2phy0_otg>; /* phys 属性只引用USB2 PHY节点 */
+ phy-names = "usb2-phy";
+};
+
+&cdn_dp {
+ status = "disabled";
+};
+
+&tcphy0 {
+ status = "disabled";
+};
+
+&fusb0 {
+ status = "disabled";
+};
+
3.测试USB虚拟网卡
1)设备侧
重新编译并烧录固件后,会在usb0网卡设备
$ ls /sys/class/net/
dummy0 eth0 lo usb0 wlan0
$ udevadm info -p /sys/class/net/usb0
P: /devices/platform/usb0/fe800000.dwc3/gadget/net/usb0
E: DEVPATH=/devices/platform/usb0/fe800000.dwc3/gadget/net/usb0
E: DEVTYPE=gadget
E: ID_MM_CANDIDATE=1
E: ID_NET_DRIVER=g_ether
E: ID_NET_LINK_FILE=/lib/systemd/network/99-default.link
E: ID_PATH=platform-fe800000.dwc3
E: ID_PATH_TAG=platform-fe800000_dwc3
E: IFINDEX=4
E: INTERFACE=usb0
E: NM_UNMANAGED=1
E: SUBSYSTEM=net
E: SYSTEMD_ALIAS=/sys/subsystem/net/devices/usb0
E: TAGS=:systemd:
E: USEC_INITIALIZED=4860606
# usb 插入主机
$ dmesg | tail -n 10
[ 331.356762] phy phy-ff770000.syscon:usb2-phy@e450.1: charger = USB_SDP_CHARGER
[ 331.357431] rockchip-dwc3 usb0: USB peripheral connected
[ 331.746362] g_ether gadget: high-speed config #2: RNDIS
配置usb0 IP 地址
$ sudo ifconfig usb0 192.168.3.10
$ ifconfig usb0
usb0 Link encap:Ethernet HWaddr 7e:71:82:2b:92:97
inet addr:192.168.3.10 Bcast:192.168.3.255 Mask:255.255.255.0
inet6 addr: fe80::7c71:82ff:fe2b:9297/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:27 errors:27 dropped:0 overruns:0 frame:27
TX packets:31 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:5413 (5.4 KB) TX bytes:5532 (5.5 KB)
2)主机侧
# usb 插入主机,主机出现Linux-USB Ethernet/RNDIS Gadget设备
$ lsusb
...
Bus 001 Device 095: ID 0525:a4a2 Netchip Technology, Inc. Linux-USB Ethernet/RNDIS Gadget
...
$ dmesg |tail -n 10
[7170774.297349] usb 1-12: new high-speed USB device number 95 using xhci_hcd
[7170774.447158] usb 1-12: New USB device found, idVendor=0525, idProduct=a4a2
[7170774.447161] usb 1-12: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[7170774.447163] usb 1-12: Product: RNDIS/Ethernet Gadget
[7170774.447165] usb 1-12: Manufacturer: Linux 4.4.179-gc26d2e0-dirty with dwc3-gadget
[7170774.448216] cdc_subset: probe of 1-12:2.0 failed with error -22
[7170774.450133] rndis_host 1-12:2.0 usb0: register 'rndis_host' at usb-0000:00:14.0-12, RNDIS device, e6:ee:a1:6e:54:bb
[7170774.472140] rndis_host 1-12:2.0 enp0s20f0u12c2: renamed from usb0
[7170774.494267] IPv6: ADDRCONF(NETDEV_UP): enp0s20f0u12c2: link is not ready
$ ls /sys/class/net/
br-887ae21410ef br-b31ab6e197a8 docker0 eno1 enp0s20f0u12c2 lo vboxnet0 veth6432db0
# 出现usb网卡设备usb0,被系统udev重命名为enp0s20f0u12c2
# 配置usb网卡ip,需要与设备侧同一网段,此时二者可以ping通
$ sudo ifconfig enp0s20f0u12c2 192.168.3.11
4.设备端安装DHCP服务
如果设备端没有安装dhcp服务,主机插入usb后,生成的虚拟网卡无法动态获取IP,这会导致主机DHCP请求失败而定期清除手动配置的IP地址,影响通信。
# 安装isc-dhcp-server
$ sudo apt update
$ sudo apt install isc-dhcp-server
# 指定网卡
$ sudo vim /etc/default/isc-dhcp-server
INTERFACES="usb0"
# 修改配置文件
$ sudo vim /etc/dhcp/dhcpd.conf
ddns-update-style interim;
ignore client-updates;
#authoritative;
allow booting;
allow bootp;
allow unknown-clients;
subnet 192.168.3.0 netmask 255.255.255.0 {
range 192.168.3.11 192.168.3.250; # dhcp客户端可以获取的ip范围(非绑定的)
default-lease-time 6000; #为客户机获取网络参数的默认租约时间
max-lease-time 72000; #为客户机获取网络参数之后的最大租约时
option subnet-mask 255.255.255.0; #子网掩码
option routers 192.168.3.1; #路由
option domain-name-servers 114.114.114.114; #域名服务器
}
# 测试配置文件
$ sudo dhcpd -d
Server starting service.
DHCPDISCOVER from ba:0a:89:46:0d:82 via usb0
DHCPOFFER on 192.168.3.36 to ba:0a:89:46:0d:82 (jiangxing) via usb0
DHCPREQUEST for 192.168.3.36 (192.168.3.10) from ba:0a:89:46:0d:82 (jiangxing) via usb0
DHCPACK on 192.168.3.36 to ba:0a:89:46:0d:82 (jiangxing) via usb0
# 自动给主机端分配ip
# 启动 isc-dhcp-server.service服务
sudo systemctl restart isc-dhcp-server.service
# 开机自启动
sudo systemctl enable isc-dhcp-server.service
四、参考
Class definitions for Communication Devices 1.2
CDC Subclass Specification for Ethernet Emulation Model Devices 1.0