USB 使用指南
1. 配置指南
ArtinChip 提供了 2 路 USB Host 端口 和 1 路 USB Device 端口,需要分别进行配置。
1.1. USB Host 配置
1.1.1. USB Host Controller 配置
首先需要配置好 USB Host Contoller ,ArtinChip 在 1 个 USB Host 端口中提供了 2 类 Host Contoller:
- 针对 USB 2.0 (High Speed) 的 EHCI 控制器
- 针对 USB 1.0/1.1 (Low/Full Speed) 的 OHCI 控制器
在软件上需要需要分开配置。
1.1.1.1. EHCI 配置
- Linux Kernel Kconfig 文件中使能相应 EHCI Driver:
> Device Drivers > USB support
<*> EHCI HCD (USB 2.0) support
[*] Root Hub Transaction Translators
[*] Improved Transaction Translator scheduling
<*> Support for Artinchip on-chip EHCI USB controller
注解
内核配置主要是通过 make menuconfig
命令进行kernel的功能选择,配置完成后的项目存储在 target/configs/xxx_defconfig
文件中。
- DTS 文件中配置相应 EHCI Device:
usbh0: usb@10210000 {
compatible = "artinchip,aic-usbh-v1.0";
reg = <0x0 0x10210000 0x0 0x100>;
interrupts-extended = <&plic0 35 IRQ_TYPE_LEVEL_HIGH>, <&plic0 4 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu CLK_USBH0>;
clock-names = "usbh";
resets = <&rst RESET_USBH0>;
reset-names = "usbh";
dr_mode = "host";
};
usbh1: usb@10220000 {
compatible = "artinchip,aic-usbh-v1.0";
reg = <0x0 0x10220000 0x0 0x100>;
interrupts-extended = <&plic0 37 IRQ_TYPE_LEVEL_HIGH>,
<&plic0 38 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu CLK_USBH1>;
clock-names = "usbh";
resets = <&rst RESET_USBH1>, <&rst RESET_USBPHY1>;
reset-names = "usbh", "usbh-phy";
dr_mode = "host";
};
注解
这些参数主要在文件 target/d211/common/d211.dtsi
中,模块系统参数随 IC 的设定而定,一般不能进行更改,除非更换了新的 IC,则需要在专业人士的指导下进行更改。
1.1.1.2. OHCI 配置
- Linux Kernel Kconfig 文件中使能相应 EHCI Driver:
> Device Drivers > USB support
<*> OHCI HCD (USB 1.1) support
<*> Support for Artinchip on-chip OHCI USB controller
- DTS 文件中配置相应 EHCI Device:
ohci0: usb@10210400 {
compatible = "artinchip,aic-ohci-v1.0";
reg = <0x10210400 0x100>;
interrupts = <&plic0 4 IRQ_TYPE_LEVEL_HIGH>;
num-ports = <1>;
};
ohci1: usb@10220400 {
compatible = "artinchip,aic-ohci-v1.0";
reg = <0x10220400 0x100>;
interrupts = <&plic0 6 IRQ_TYPE_LEVEL_HIGH>;
};
1.1.2. USB Interface 驱动配置
在配置好 USB Host Controller 以后,就能够正确识别插入 USB 总线的 Device 设备了。
但是 USB Device 有很多不同类型 (例如:U 盘、键盘鼠标、无线网卡 …) ,这些功能都是在 USB Device 中以 Interface 为单位提供的。所以要使用 USB Device 的具体功能,还需要配置不同类型 USB Interface 的驱动。
1.1.2.1. U 盘 配置
- U 盘是 USB 2.0 设备,所以首先得配置好上节中的 EHCI,再进行下面的配置。
- 在 Linux Kernel Kconfig 中使能对 USB Mass Storage 类型的 USB Interface 驱动的支持。
> Device Drivers > USB support
<*> USB Mass Storage support
- 还需要使能其他相关配置:
块设备:
> Device Drivers
[*] Block devices --->
SCSI 设备:
> Device Drivers > SCSI device support
<*> SCSI device support
[*] legacy /proc/scsi/ support
*** SCSI support type (disk, tape, CD-ROM) ***
<*> SCSI disk support
文件系统:
> File systems > DOS/FAT/EXFAT/NT Filesystems
<*> VFAT (Windows-95) fs support
- 插入 U 盘,通过
mount
命令将 U 盘挂载到合适的目录下就可以操作了:
[aic@] #
[ 1591.469696] usb 1-1: new high-speed USB device number 3 using aic-ehci
[ 1591.674435] usb-storage 1-1:1.0: USB Mass Storage device detected
[ 1591.682567] scsi host0: usb-storage 1-1:1.0
[ 1592.692021] scsi 0:0:0:0: Direct-Access SanDisk Cruzer Blade 1.00 PQ: 0 ANSI: 6
[ 1592.714329] sd 0:0:0:0: [sda] 30842880 512-byte logical blocks: (15.8 GB/14.7 GiB)
[ 1592.724171] sd 0:0:0:0: [sda] Write Protect is off
[ 1592.730166] sd 0:0:0:0: [sda] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
[ 1592.751720] sda: sda1
[ 1592.768330] sd 0:0:0:0: [sda] Attached SCSI removable disk
[aic@] # mount -t vfat /dev/sda1 /mnt/u
[aic@] # ls /mnt/u
System Volume Information u-boot-spl-dtb.bin
u-boot-dtb.bin vmlinux
u-boot-dtb.img zImage
u-boot-spl-dtb.aic
[aic@] #
1.1.2.2. USB 键盘/鼠标 配置
- U 盘是 USB 1.0/1.1 设备,所以首先得配置好上节中的 OHCI,再进行下面的配置。
- 在 Linux Kernel Kconfig 中使能对 USB HID 类型的 USB Interface 驱动的支持。
> Device Drivers > HID support > USB HID support
<*> USB HID transport layer
- 插入键盘鼠标,可以通过
/dev/input/event
文件读取到键盘鼠标上报的数据:
[aic@] #
[ 14.210983] usb 2-1: new low-speed USB device number 2 using aic-ohci
[ 14.478006] random: fast init done
[ 14.497013] input: PixArt Dell MS116 USB Optical Mouse as /devices/platform/soc/10220400.usb/usb2/2-1/2-1:1.0/0003:413C:301A.0001/input/input2
[ 14.510871] hid-generic 0003:413C:301A.0001: input: USB HID v1.11 Mouse [PixArt Dell MS116 USB Optical Mouse] on usb-10220400.usb-1/input0
[aic@] # hexdump /dev/input/event2
0000000 e138 5e0b 4c30 0004 0004 0004 0001 0009
0000010 e138 5e0b 4c30 0004 0001 0110 0001 0000
0000020 e138 5e0b 4c30 0004 0000 0000 0000 0000
0000030 e138 5e0b d657 0007 0004 0004 0001 0009
0000040 e138 5e0b d657 0007 0001 0110 0000 0000
0000050 e138 5e0b d657 0007 0000 0000 0000 0000
0000060 e139 5e0b 9085 0003 0004 0004 0001 0009
0000070 e139 5e0b 9085 0003 0001 0110 0001 0000
0000080 e139 5e0b 9085 0003 0000 0000 0000 0000
0000090 e139 5e0b a3bc 0005 0004 0004 0001 0009
00000a0 e139 5e0b a3bc 0005 0001 0110 0000 0000
00000b0 e139 5e0b a3bc 0005 0000 0000 0000 0000
1.2. USB Device 配置
首先要配置好 USB Device Controller。
1.2.1. USB Device Controller 配置
- Linux Kernel Kconfig 文件中使能相应 UDC Driver:
> Device Drivers > USB support > USB Gadget Support > USB Peripheral Controller
<*> ArtinChip USB2.0 Device Controller
- DTS 文件中配置相应 UDC Device:
aicudc: udc@10200000 {
compatible = "artinchip,aic-udc-v1.0";
reg = <0x0 0x10200000 0x0 0x1000>;
interrupts-extended = <&plic0 34 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu CLK_USBD>, <&cmu CLK_USB_PHY0>;
clock-names = "udc_clk";
resets = <&rst RESET_USBD>, <&rst RESET_USBPHY0>;
reset-names = "aicudc", "aicudc-ecc";
status = "okay";
};
1.2.2. USB Gadget 配置
为了方便 Linux 系统模拟成各种类型的 USB Device,Linux 设计了一个 Gadget Device
。为了方便用户使用 ,Linux 又将 ConfigFS
引入 USB Device 子系统,用来灵活配置 Gadget Device
。
所以在使用 USB Device 时,在 Linux Kernel 中把这两者都配置成使能。
1.2.2.1. Gadget 配置
> Device Drivers > USB support
<*> USB Gadget Support --->
1.2.2.2. ConfigFS 配置
> Device Drivers > USB support > USB Gadget Support
<*> USB Gadget functions configurable through configfs
1.2.3. USB Interface 配置
在 Gadget Device
基础之上,需要配置具体的 Interface / Function
才能提供具体的 USB Device 功能。
USB Gadget Device 可以模拟成各种功能的 USB 外设,例如:USB 串口、USB 网口、U 盘。。。
1.2.3.1. ACM 串口 配置
- Linux Kernel Kconfig 文件中使能
CDC ACM
类型的Gadget functions
:
> Device Drivers > USB support > USB Gadget Support
<*> USB Gadget functions configurable through configfs
[*] Abstract Control Model (CDC ACM)
> Device Drivers
[*] Block devices --->
- 通过用户态的 configfs 文件接口创建包含
ACM
串口功能的 USB Device:
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
mkdir g1
cd g1
echo "0x1d6b" > idVendor
echo "0x0104" > idProduct
mkdir strings/0x409
ls strings/0x409/
echo "0123456789" > strings/0x409/serialnumber
echo "AIC Inc." > strings/0x409/manufacturer
echo "Bar Gadget" > strings/0x409/product
mkdir functions/acm.GS0
mkdir configs/c.1
ls configs/c.1
mkdir configs/c.1/strings/0x409
ls configs/c.1/strings/0x409/
echo "ACM" > configs/c.1/strings/0x409/configuration
ln -s functions/acm.GS0 configs/c.1
echo `ls /sys/class/udc` > UDC
-
用户使用:
-
将单板的 USB Device 端口和 Windows PC 的 USB Host 端口连接,在 Windows PC 设备管理器会看到一个新的USB串口节点:
-
在 PC 端使用串口终端工具打开 COM12,波特率使用 115200。
-
在单板端执行:
echo abd > /dev/ttyGS0
,在 PC 端串口就会收到该字符串: -
在单板端执行
cat /dev/ttyGS0
,在 PC 端写一个字符串 “123412345” ,点回车后,在单板端也能收到该字符串。
-
1.2.3.2. U 盘 配置
- Linux Kernel Kconfig 文件中 :
使能 Mass storage
类型的 Gadget functions
::
> Device Drivers > USB support > USB Gadget Support
<*> USB Gadget functions configurable through configfs
[*] Mass storage
使能环回块设备:
> Device Drivers
<*> Loopback device support
- Busybox 中使能
losetup
命令:
> Linux System Utilities
[*] losetup (5.5 kb)
- 通过用户态的 configfs 文件接口创建包含
Mass storage
存储功能的 USB Device:
dd if=/dev/zero of=/tmp/mass.img bs=128K count=132
losetup /dev/loop0 /tmp/mass.img
mkdir /tmp/media
mkfs.vfat /dev/loop0
mount -t vfat /dev/loop0 /tmp/media/
cp /linuxrc /tmp/media
sync
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
mkdir g_mass
cd g_mass
echo "0x200" > bcdUSB
echo "0x100" > bcdDevice
echo "0x1234" > idVendor
echo "0x5678" > idProduct
mkdir configs/c1.1
mkdir functions/mass_storage.0
echo /dev/loop0 > functions/mass_storage.0/lun.0/file
mkdir strings/0x409
echo "0123456789ABCDEF" > strings/0x409/serialnumber
echo "river" > strings/0x409/manufacturer
echo "river_msc" > strings/0x409/product
mkdir configs/c1.1/strings/0x409
echo "abc" > configs/c1.1/strings/0x409/configuration
ln -s functions/mass_storage.0 configs/c1.1
echo `ls /sys/class/udc` > UDC
-
用户使用:
- 将单板的 USB Device 端口和 Windows PC 的 USB Host 端口连接,在 Windows PC 上会看到一个新增的 U 盘,可以正常读写。
1.2.3.3. NCM 网口 配置
- Linux Kernel Kconfig 文件中 :
使能 CDC NCM
类型的 Gadget functions
::
> Device Drivers > USB support > USB Gadget Support
<*> USB Gadget functions configurable through configfs
[*] Network Control Model (CDC NCM)
使能 TCP/IP 支持:
> Networking support > Networking options
[*] TCP/IP networking
- 通过用户态的 configfs 文件接口创建包含
CDC NCM
以太网功能的 USB Device:
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
mkdir g_ncm
cd g_ncm
echo "0xA55A" > idVendor
echo "0x0111" > idProduct
mkdir strings/0x409
echo "0123456789" > strings/0x409/serialnumber
echo "Xyz Inc." > strings/0x409/manufacturer
echo "NCM gadget" > strings/0x409/product
mkdir functions/ncm.usb0
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "NCM" > configs/c.1/strings/0x409/configuration
ln -s functions/ncm.usb0 configs/c.1
echo `ls /sys/class/udc` > UDC
ifconfig usb0 up
ifconfig usb0 173.11.1.1
-
用户使用:
-
将单板的 USB Device 端口和 Ubuntu PC 的 USB Host 端口连接,在 Ubuntu PC 会看到一个新的网络接口,名字随机,类似:
enx0afcc15d3417
。 -
配置 Ubuntu PC 端的网口为同一网段地址,
sudo ifconfig enx0afcc15d3417 173.11.1.2
。 -
两个网口相互可以 ping 通:
ubuntu@ubuntu $ ping 173.11.1.1
PING 173.11.1.1 (173.11.1.1) 56(84) bytes of data.
64 bytes from 173.11.1.1: icmp_seq=1 ttl=64 time=10.3 ms
64 bytes from 173.11.1.1: icmp_seq=2 ttl=64 time=5.02 ms -
1.2.3.4. ECM 网口 配置
- Linux Kernel Kconfig 文件中 :
使能 CDC ECM
类型的 Gadget functions
::
> Device Drivers > USB support > USB Gadget Support
<*> USB Gadget functions configurable through configfs
[*] Ethernet Control Model (CDC ECM)
使能 TCP/IP 支持:
> Networking support > Networking options
[*] TCP/IP networking
- 通过用户态的 configfs 文件接口创建包含
CDC ECM
以太网功能的 USB Device:
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
mkdir g_ecm
cd g_ecm
echo "0x1d6b" > idVendor
echo "0x0104" > idProduct
mkdir strings/0x409
echo "0123456789" > strings/0x409/serialnumber
echo "AIC Inc." > strings/0x409/manufacturer
echo "Bar Gadget" > strings/0x409/product
mkdir functions/ecm.usb0
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "ECM" > configs/c.1/strings/0x409/configuration
ln -s functions/ecm.usb0 configs/c.1
echo `ls /sys/class/udc` > UDC
ifconfig usb0 up
ifconfig usb0 173.11.1.1
- 用户使用:和上一节 NCM 网口 一样。
1.2.3.5. ADBD 配置
- Linux Kernel Kconfig 文件中 :
使能 FunctionFS
类型的 Gadget functions
::
> Device Drivers > USB support > USB Gadget Support
<*> USB Gadget functions configurable through configfs
[*] Function filesystem (FunctionFS)
使能 TCP/IP 支持:
> Networking support > Networking options
[*] TCP/IP networking
- 通过用户态的 configfs 文件接口创建
FunctionFS
中的 USB Device,挂载完 FunctionFS 文件系统以后,adbd
通过/dev/usb-ffs/adb
中映射成文件的 endpoint 直接和 USB Host 进行通讯:
mkdir /dev/pts
mount -t devpts none /dev/pts
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
mkdir g_adb
cd g_adb
echo "0x18d1" > idVendor
echo "0x4e26" > idProduct
mkdir configs/c.1
mkdir functions/ffs.adb
mkdir strings/0x409
mkdir configs/c.1/strings/0x409
echo "0123456789ABCDEF" > strings/0x409/serialnumber
echo "AIC Inc." > strings/0x409/manufacturer
echo "FunctionFS gadget (adb)" > strings/0x409/product
echo "Conf 1" > configs/c.1/strings/0x409/configuration
echo 120 > configs/c.1/MaxPower
ln -s functions/ffs.adb configs/c.1
mkdir -p /dev/usb-ffs/adb
mount -o uid=2000,gid=2000 -t functionfs adb /dev/usb-ffs/adb
ifconfig lo up
ifconfig
cd /root
adbd&
sleep 1
echo `ls /sys/class/udc/` > /sys/kernel/config/usb_gadget/g_adb/UDC
-
用户使用:
- 将单板的 USB Device 端口和 PC 的 USB Host 端口连接,在 PC 端运行
adb shell
命令即可进行 adb 操作。
- 将单板的 USB Device 端口和 PC 的 USB Host 端口连接,在 PC 端运行
1.3. USB OTG 配置
USB Host 0 和 USB Device 0 共享 1 路 phy。要么同时只能启用其中一种功能,要么启用 USB OTG 功能通过 id
管脚的值来动态切换对外功能。
- Linux Kernel Kconfig 文件中使能相应 OTG Driver:
> Device Drivers > USB support
[*] OTG support
[*] Support for Artinchip on-chip OTG Switch
- DTS 文件中配置相应 OTG Device:
otg: usb-otg {
compatible = "artinchip,aic-otg-v2.0";
};
&otg {
otg-mode = "auto"; // = auto/host/device
id-gpios = <&gpio_f 15 GPIO_ACTIVE_HIGH>;
vbus-en-gpios = <&gpio_a 7 GPIO_ACTIVE_HIGH>;
dp-sw-gpios = <&gpio_e 14 GPIO_ACTIVE_LOW>;
status = "okay";
};
1.3.1. OTG 模式配置
OTG 可以配置成 Auto 模式
或者 Force 模式
:
Auto 模式
。根据id
管脚的电平来决定当前 OTG 端口工作模式为Host
/Device
,通常情况下id = low
对应Host
模式,id = high
对应Device
模式。Force 模式
。手工配置工作模式,通过配置/sys/devices/platform/soc/soc\:usb-otg/otg_mode
文件节点的值来改变当前 OTG 端口的工作模式,host
对应Host
模式,device
对应Device
模式。另外auto
对应Auto
模式,需要使用id
管脚来进行判断。
两种模式对应 DTS 文件中的 otg
节点的不同配置:
Mode | DTS otg-mode 属性 | DTS xxx-gpios 属性 | 运行时 Host / Device 切换方法 |
---|---|---|---|
Auto | otg-mode = "auto"; (如果没有配置 otg-mode 属性, 默认也是 Auto 模式) | id-gpios 属性必须配置; vbus-en-gpios 和 dp-sw-gpios 属性根据硬件配置选配。 | OTG 驱动根据 id-gpios 管脚的电平变化 自动切换 USB 工作模式为 Host / Device 。 |
Force | otg-mode = "device"; 或者 otg-mode = "host"; | id-gpios 属性不需要配置; vbus-en-gpios 和 dp-sw-gpios 属性根据硬件配置选配。 | 需要配置文件节点来手工切换: echo devices > /sys/devices/platform/soc/soc\:usb-otg/otg_mode 或者 echo host > /sys/devices/platform/soc/soc\:usb-otg/otg_mode |
1.3.2. OTG 相关 GPIO
从上面配置可以看到和 OTG 功能相关的 GPIO 管脚有 3 个:
-
id-gpios
。用来检测当前插入的是不是 OTG 线,如果为 OTG 线则需要把本机切换到 USB Host 模式,否则本机切换到 USB Device 模式。该管脚在Auto 模式
模式下是必须配置的,如果缺少该管脚 OTG 只能工作在Force 模式
手工进行切换。 -
vbus-en-gpios
。该管脚是用来控制 VBUS 的 5V 输出的,通常情况下:切换到 USB Host 模式时需要使能本机端的 VBUS 5V 输出给对端 Device 供电,切换到 USB Device 模式时需要关闭本机端的 VBUS 5V 输出转而对端 Host 的供电。(实际使用上来说,不论本端是 Host/Device 模式,也可以在 VBUS 上一直供电 5V 两边 VBUS 无压差则无漏电,这种情况下vbus-en-gpios
无需配置。) -
dp-sw-gpios
。该管脚是在 OTG 外出两个独立 Host、Device 端口时,用来控制外部 Switch 的。非该模式时,dp-sw-gpios
无需配置。
3 个 GPIO 管脚的具体使用场景如上图所示,用户根据自己的使用场景来选择配置哪些 GPIO。每个 GPIO 的 输入输出正反电平有效,可以通过 DTS 中的 GPIO_ACTIVE_HIGH
和 GPIO_ACTIVE_LOW
来配置:
GPIO Name | Direction | GPIO_ACTIVE_HIGH | GPIO_ACTIVE_LOW |
---|---|---|---|
id-gpios | input | 输入低电平 = Host, 输入高电平 = Device | 输入低电平 = Device,输入高电平 = Host |
vbus-en-gpios | output | Host (VBUS on) = 输出高电平Device (VBUS off) = 输出低电平 | Host (VBUS on) = 输出低电平Device (VBUS off) = 输出高电平 |
dp-sw-gpios | output | Host = 输出高电平Device = 输出低电平 | Host = 低电平Device = 高电平 |
2. 调试指南
2.1. USB Host 调试
2.1.1. 查看 USB 设备
在单板的 USB Host 端口有设备插入或者拔出时,单板串口会有打印提示:
[aic@] #
[ 6792.678130] usb 1-1: new high-speed USB device number 2 using aic-ehci
[ 6792.884601] usb-storage 1-1:1.0: USB Mass Storage device detected
[ 6792.910596] scsi host0: usb-storage 1-1:1.0
[ 6793.970429] scsi 0:0:0:0: Direct-Access SanDisk Cruzer Blade 1.00 PQ: 0 ANSI: 6
[ 6793.995300] sd 0:0:0:0: [sda] 30842880 512-byte logical blocks: (15.8 GB/14.7 GiB)
[ 6794.018466] sd 0:0:0:0: [sda] Write Protect is off
[ 6794.025383] sd 0:0:0:0: [sda] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
[ 6794.055267] sda: sda1
[ 6794.074720] sd 0:0:0:0: [sda] Attached SCSI removable disk
[ 6806.436142] usb 1-1: USB disconnect, device number 2
也可以使用 lsusb
命令查看目前系统 USB 总线的情况:
[aic@] # lsusb
Bus 001 Device 001: ID 1d6b:0002
Bus 001 Device 003: ID 0781:5567
如果是 PC 上的 Linux 发行版, lsusb -v
命令可以查看 USB 设备的详细信息。但是单板上使用的 lsusb -v
被进行了简化。
2.1.2. Sysfs 节点
也可以使用 /sys/kernel/debug/usb/devices
文件节点查看 USB 设备的详细信息:
[aic@] # mount -t debugfs none /sys/kernel/debug
[aic@] # cat /sys/kernel/debug/usb/devices
T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=480 MxCh= 1
B: Alloc= 0/800 us ( 0%), #Int= 0, #Iso= 0
D: Ver= 2.00 Cls=09(hub ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
P: Vendor=1d6b ProdID=0002 Rev= 5.10
S: Manufacturer=Linux 5.10.44-00071-g935288d48127-dirty ehci_hcd
S: Product=EHCI Host Controller
S: SerialNumber=10220000.usb
C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr= 0mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
E: Ad=81(I) Atr=03(Int.) MxPS= 4 Ivl=256ms
2.1.3. USB 总线分析仪
在 USB 设备不能正常枚举或者 USB 通讯过程中出现问题时,我们一般使用 USB 总线分析仪,接入到 USB 总线上进行旁路抓包分析。
抓包数据的分析界面如下图所示:
USB 总线分析仪对抓取的数据包进行了解析,用起来非常方便。市面上这类仪器大同小异,非常容易上手。
2.1.4. usbmon
在没有外部 USB 分析仪的情况下,也可以使用 Linux 内核自带的 USB 抓包模块 usbmon 来抓取数据包进行分析。
- 首先得打开 Linux 内核中的 usbmon 配置:
> Device Drivers > USB support
<*> USB Monitor
- 重新编译内核后就可以进行抓包了:
[aic@] # mount -t debugfs none /sys/kernel/debug
[aic@] # ls /sys/kernel/debug/usb/usbmon/
0s 0u 1s 1t 1u 2s 2t 2u
[aic@] # cat /sys/kernel/debug/usb/usbmon/0u
c1b0e380 68846726 C Ii:1:001:1 0:2048 1 = 02
c1b0e380 68846851 S Ii:1:001:1 -115:2048 4 <
c7a15900 68847426 S Ci:1:001:0 s a3 00 0000 0001 0004 4 <
c7a15900 68847507 C Ci:1:001:0 0 4 = 01050100
c7a15900 68847595 S Co:1:001:0 s 23 01 0010 0001 0000 0
c7a15900 68847652 C Co:1:001:0 0 0
c7a15900 68847732 S Ci:1:001:0 s a3 00 0000 0001 0004 4 <
c7a15900 68847790 C Ci:1:001:0 0 4 = 01050000
c7a15900 68890082 S Ci:1:001:0 s a3 00 0000 0001 0004 4 <
c7a15900 68890154 C Ci:1:001:0 0 4 = 01050000
c7a15900 68940072 S Ci:1:001:0 s a3 00 0000 0001 0004 4 <
c7a15900 68940142 C Ci:1:001:0 0 4 = 01050000
c7a15900 68990067 S Ci:1:001:0 s a3 00 0000 0001 0004 4 <
c7a15900 68990129 C Ci:1:001:0 0 4 = 01050000
2.2. USB Device 调试
2.2.1. 查看 USB 设备
当单板充当 USB Device 连接到 PC 主机 USB Host 端口时,可以在主机上查看 USB 设备有没有成功被枚举:
-
Linux 主机,可以通过
lsusb
命令进行查看。 -
Windows 主机,可以通过
设备管理器
命令进行查看:
2.2.2. Sysfs 节点
在单板上也可以通过 /sys/kernel/debug/usb/xxxx.usb/
文件夹下的文件节点,对 UDC 驱动进行配置:
[aic@] # ls /sys/class/udc
10200000.usb
[aic@] # ls /sys/kernel/debug/usb/10200000.usb/
dr_mode ep1out ep3in ep4out params testmode
ep0 ep2in ep3out fifo regdump
ep1in ep2out ep4in hw_params state
[aic@] #
例如可以 dump 寄存器::
[aic@] # cat /sys/kernel/debug/usb/10200000.usb/regdump
GOTGCTL = 0x00000000
GOTGINT = 0x00000000
GAHBCFG = 0x00000000
GUSBCFG = 0x00000000
GRSTCTL = 0x00000000
GINTSTS = 0x00000000
GINTMSK = 0x00000000
GRXSTSR = 0x00000000
GRXFSIZ = 0x00000000
GNPTXFSIZ = 0x00000000
GNPTXSTS = 0x00000000
2.2.3. 抓包工具
和 USB Host 调试一样,抓包可以使用专门的 USB 总线分析仪或者在 Linux 主机上使用 usbmon 进行抓包。
3. 测试指南
3.1. 测试方案介绍
在测试 USB 时,普通的做法是找一些 U 盘、鼠标、键盘 等外设来做一些测试,但是这些测试还是偏上层偏功能的。相比较 HC (USB Host Controller) 和 UDC (USB Device Controller) 按照USB协议提供的完整功能来说,这种测试验证时不充分的。
在 Linux Kernel 中对 HC/UDC 有一套专有的测试方案,在底层对 control/bulk/int/iso 几种 endpoint 进行针对性的功能和压力测试。
上图的测试方案由几部分组成:
- 1、Device 侧的
gadget zero
测试设备,提供了测试通道。 - 2、Host 侧的
usbtest.ko
测试驱动,封装了 30 个 endpoint 层级的测试用例。 - 3、Host 侧的
testusb
用户程序,用来调用usbtest.ko
提供的测试用例。
3.2. Device 侧 gadget zero
提供测试需要的Device设备有很多种方式,例如可用使用专门的测试 Device 里面烧录专有的测试 Firmware。节约成本的方式还是使用 Linux gadget 功能来动态模拟 USB Device 设备。针对 USB 测试,Linux 专门提供了 gadget zero
设备。
3.2.1. Device 创建
gadget zero
的核心是创建一个 Composite Device
,其包含了两个 Configuration
,其中一个 Configuration 0
包含 SourceSink Function/Interface
,另一个 Configuration 1
包含 Loopback Function/Interface
。某一时刻只能选择使用一个 Configuration
,通常情况下使用 Configuration 0
即 SourceSink
的功能。
gadget zero
Device 由两种方式创建:
- 1、通过
zero_driver
创建,只要把对应驱动文件drivers\usb\gadget\legacy\zero.c
编译进内核即可。 - 2、通过
functionfs
动态创建,这种方式更灵活,实例命令如下:
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
mkdir g2
cd g2
echo "0x04e8" > idVendor
echo "0x2d01" > idProduct
mkdir configs/c.1
mkdir configs/c.2
mkdir functions/Loopback.0
mkdir functions/SourceSink.0
mkdir strings/0x409
mkdir configs/c.1/strings/0x409
mkdir configs/c.2/strings/0x409
echo "0x0525" > idVendor
echo "0xa4a0" > idProduct
echo "0123456789" > strings/0x409/serialnumber
echo "Samsung Inc." > strings/0x409/manufacturer
echo "Bar Gadget" > strings/0x409/product
echo "Conf 1" > configs/c.1/strings/0x409/configuration
echo "Conf 2" > configs/c.2/strings/0x409/configuration
echo 120 > configs/c.1/MaxPower
// SourceSink:驱动 set configuration 会选取 第一个 configuration
ln -s functions/Loopback.0 configs/c.2
ln -s functions/SourceSink.0 configs/c.1
echo 4100000.udc-controller > UDC
整个过程就是创建了一个 Vendor ID = 0x0525
、 Product ID = 0xa4a0
的 Composite Device
,在 Host 侧可以查看这个设备:
$ lsusb -s 1:3
Bus 001 Device 003: ID 0525:a4a0 Netchip Technology, Inc. Linux-USB "Gadget Zero"
$ lsusb -v -s 1:3
Bus 001 Device 003: ID 0525:a4a0 Netchip Technology, Inc. Linux-USB "Gadget Zero"
Couldn't open device, some information will be missing
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x0525 Netchip Technology, Inc.
idProduct 0xa4a0 Linux-USB "Gadget Zero"
bcdDevice 5.10
iManufacturer 1
iProduct 2
iSerial 3
bNumConfigurations 2
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0045
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 4
bmAttributes 0x80
(Bus Powered)
MaxPower 120mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 1
bNumEndpoints 4
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 1
Transfer Type Isochronous
Synch Type None
Usage Type Data
wMaxPacketSize 0x0400 1x 1024 bytes
bInterval 4
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 1
Transfer Type Isochronous
Synch Type None
Usage Type Data
wMaxPacketSize 0x0400 1x 1024 bytes
bInterval 4
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0020
bNumInterfaces 1
bConfigurationValue 2
iConfiguration 5
bmAttributes 0x80
(Bus Powered)
MaxPower 2mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 6
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
3.2.2. SourceSink Function
SourceSink Function
的主要功能是提供了一组 USB 测试 endpoint,其中:
Sink
。sinks bulk packets OUT to the peripheral。意思是把数据从 Host 引流到 Device,即OUT
方向。Source
。sources them IN to the host。意思是把从 Device 发送数据到 Device,即IN
方向。
具体提供了 4 组 测试 endpoint:
Endpoint | Type | Direction | Descript| |
---|---|---|---|
in_ep | bulk | IN | Source 发送数据到 Host, 注意这数据是 Device 主动生成的 |
out_ep | bulk | OUT | Sink 接收 Host 的数据 |
iso_in_ep | iso | IN | Source 发送数据到 Host |
iso_out_ep | iso | OUT | Sink 接收 Host 的数据 |
主要流程如下:
drivers\usb\gadget\function\f_sourcesink.c:
sourcesink_bind():
static int
sourcesink_bind(struct usb_configuration *c, struct usb_function *f)
{
/* (1) 从 gadget 中分配 2 个 bulk endpoint */
/* allocate bulk endpoints */
ss->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc);
ss->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_sink_desc);
/* (2) 如果支持ISO,再从 gadget 中分配 2 个 iso endpoint */
/* allocate iso endpoints */
ss->iso_in_ep = usb_ep_autoconfig(cdev->gadget, &fs_iso_source_desc);
if (!ss->iso_in_ep)
goto no_iso;
ss->iso_out_ep = usb_ep_autoconfig(cdev->gadget, &fs_iso_sink_desc);
if (!ss->iso_out_ep) {
}
sourcesink_set_alt() → enable_source_sink() → usb_ep_enable()/source_sink_start_ep():
// 启动上述 endpoint
→ source_sink_complete():
// urb 的 complete() 函数,urb 发送/接收完成后,重新挂载 urb
还支持一些参数调整:
# ls functions/SourceSink.0/
bulk_buflen iso_qlen isoc_maxburst isoc_mult
bulk_qlen isoc_interval isoc_maxpacket pattern
3.2.3. Loopback Function
Loopback Function
提供的功能更为简单,它分配了两个 bulk endpoint,所做的 就是把 out_ep
接收到的数据 转发到 in_ep
。
主要流程如下:
drivers\usb\gadget\function\f_loopback.c:
loopback_bind():
static int loopback_bind(struct usb_configuration *c, struct usb_function *f)
{
/* (1) 从 gadget 中分配 2 个 bulk endpoint */
/* allocate endpoints */
loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);
loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);
}
loopback_set_alt() → enable_loopback() → alloc_requests():
static int alloc_requests(struct usb_composite_dev *cdev,
struct f_loopback *loop)
{
for (i = 0; i < loop->qlen && result == 0; i++) {
result = -ENOMEM;
in_req = usb_ep_alloc_request(loop->in_ep, GFP_ATOMIC);
if (!in_req)
goto fail;
out_req = lb_alloc_ep_req(loop->out_ep, loop->buflen);
if (!out_req)
goto fail_in;
in_req->complete = loopback_complete;
out_req->complete = loopback_complete;
in_req->buf = out_req->buf;
/* length will be set in complete routine */
in_req->context = out_req;
out_req->context = in_req;
/* (2) 先启动 OUT endpoint */
result = usb_ep_queue(loop->out_ep, out_req, GFP_ATOMIC);
if (result) {
ERROR(cdev, "%s queue req --> %d\n",
loop->out_ep->name, result);
goto fail_out;
}
}
}
static void loopback_complete(struct usb_ep *ep, struct usb_request *req)
{
struct f_loopback *loop = ep->driver_data;
struct usb_composite_dev *cdev = loop->function.config->cdev;
int status = req->status;
switch (status) {
case 0: /* normal completion? */
if (ep == loop->out_ep) {
/*
* We received some data from the host so let's
* queue it so host can read the from our in ep
*/
struct usb_request *in_req = req->context;
in_req->zero = (req->actual < req->length);
in_req->length = req->actual;
ep = loop->in_ep;
req = in_req;
} else {
/*
* We have just looped back a bunch of data
* to host. Now let's wait for some more data.
*/
req = req->context;
ep = loop->out_ep;
}
/* (3) 环回的关键:
OUT endpoint 接收到的数据 转发到 IN endpoint
IN endpoint 数据发送完成后 req 重新挂载到 OUT endpoint
*/
/* queue the buffer back to host or for next bunch of data */
status = usb_ep_queue(ep, req, GFP_ATOMIC);
}
也支持一些参数调整:
# ls functions/Loopback.0/
bulk_buflen qlen
3.3. Host 侧 usbtest.ko
在 Host 侧的 usbtest.ko
它就是一个标准的 usb interface driver
。它根据 Vendor ID = 0x0525
、 Product ID = 0xa4a0
适配上一节 Composite Device
中的 SourceSink Interface
或者 Loopback Interface
。
static const struct usb_device_id id_table[] = {
/* "Gadget Zero" firmware runs under Linux */
{ USB_DEVICE(0x0525, 0xa4a0),
.driver_info = (unsigned long) &gz_info,
},
}
MODULE_DEVICE_TABLE(usb, id_table);
static struct usb_driver usbtest_driver = {
.name = "usbtest",
.id_table = id_table,
.probe = usbtest_probe,
.unlocked_ioctl = usbtest_ioctl,
.disconnect = usbtest_disconnect,
.suspend = usbtest_suspend,
.resume = usbtest_resume,
};
3.3.1. TestCase
其在 SourceSink Interface
提供的 4 个测试 endpoint、或者 Loopback Interface
提供的 2 个测试 endpoint + Composite Device
本身的 ep0 control endpoint 基础之上,提供了 30 个 testcase:
drivers\usb\misc\usbtest.c:
usbtest_do_ioctl()
index | type | iterations | vary | sglen | unaligned | testcase | descript |
---|---|---|---|---|---|---|---|
0 | nop | “TEST 0: NOPn” | |||||
1 | bulk | Y | “TEST 1: write %d bytes %u timesn”, param->length, param->iterations | Simple non-queued bulk I/O tests | |||
2 | bulk | Y | “TEST 2: read %d bytes %u timesn”, param->length, param->iterations | ||||
3 | bulk | Y | Y | “TEST 3: write/%d 0..%d bytes %u timesn”, param->vary, param->length, param->iterations | |||
4 | bulk | Y | Y | “TEST 4: read/%d 0..%d bytes %u timesn”, param->vary, param->length, param->iterations | |||
5 | bulk | Y | Y | “TEST 5: write %d sglists %d entries of %d bytesn”, param->iterations,param->sglen, param->length | Queued bulk I/O tests | ||
6 | bulk | Y | Y | “TEST 6: read %d sglists %d entries of %d bytesn”, param->iterations,param->sglen, param->length | |||
7 | bulk | Y | Y | Y | “TEST 7: write/%d %d sglists %d entries 0..%d bytesn”, param->vary, param->iterations,param->sglen, param->length | ||
8 | bulk | Y | Y | Y | “TEST 8: read/%d %d sglists %d entries 0..%d bytesn”, param->vary, param->iterations,param->sglen, param->length | ||
9 | control | Y | “TEST 9: ch9 (subset) control tests, %d timesn”, param->iterations | non-queued sanity tests for control (chapter 9 subset) | |||
10 | control | Y | Y | “TEST 10: queue %d control calls, %d timesn”, param->sglen, param->iterations) | queued control messaging | ||
11 | bulk | Y | “TEST 11: unlink %d reads of %dn”, param->iterations, param->length | simple non-queued unlinks (ring with one urb) | |||
12 | bulk | Y | “TEST 12: unlink %d writes of %dn”, param->iterations, param->length | ||||
13 | control | Y | “TEST 13: set/clear %d haltsn” param->iterations | ep halt tests | |||
14 | control | Y | Y | “TEST 14: %d ep0out, %d..%d vary %dn”, param->iterations,realworld ? 1 : 0, param->length,param->vary | control write tests | ||
15 | iso | Y | Y | “TEST 15: write %d iso, %d entries of %d bytesn”, param->iterations, param->sglen, param->length | iso write tests | ||
16 | iso | Y | Y | “TEST 16: read %d iso, %d entries of %d bytesn”, param->iterations, param->sglen, param->length | iso read tests | ||
17 | bulk | Y | Y | “TEST 17: write odd addr %d bytes %u times core mapn” param->length, param->iterations | Tests for bulk I/O using DMA mapping by core and odd address | ||
18 | bulk | Y | Y | “TEST 18: read odd addr %d bytes %u times core mapn”, param->length, param->iterations | |||
19 | bulk | Y | Y | “TEST 19: write odd addr %d bytes %u times premappedn”, param->length, param->iterations | Tests for bulk I/O using premapped coherent buffer and odd address | ||
20 | bulk | Y | Y | “TEST 20: read odd addr %d bytes %u times premappedn”, param->length, param->iterations | |||
21 | control | Y | Y | Y | “TEST 21: %d ep0out odd addr, %d..%d vary %dn”, param->iterations,realworld ? 1 : 0, param->length, param->vary | control write tests with unaligned buffer | |
22 | iso | Y | Y | Y | “TEST 22: write %d iso odd, %d entries of %d bytesn”, param->iterations, param->sglen, param->length | unaligned iso tests | |
23 | iso | Y | Y | Y | “TEST 23: read %d iso odd, %d entries of %d bytesn”, param->iterations, param->sglen, param->length | ||
24 | bulk | Y | Y | “TEST 24: unlink from %d queues of %d %d-byte writesn”, param->iterations, param->sglen, param->length | unlink URBs from a bulk-OUT queue | ||
25 | int | Y | “TEST 25: write %d bytes %u timesn”, param->length, param->iterations | Simple non-queued interrupt I/O tests | |||
26 | int | Y | “TEST 26: read %d bytes %u timesn”, param->length, param->iterations | ||||
27 | bulk | Y | Y | “TEST 27: bulk write %dMbytesn”, (param->iterations * param->sglen * param->length) / (1024 * 1024)) | Performance test | ||
28 | bulk | Y | Y | “TEST 28: bulk read %dMbytesn”, (param->iterations * param->sglen * param->length) / (1024 * 1024)) | |||
29 | bulk | Y | “TEST 29: Clear toggle between bulk writes %d timesn”, param->iterations | Test data Toggle/seq_nr clear between bulk out transfers |
3.3.2. Ioctl
usbtest.ko 以 ioctl 的形式向用户态提供对 testcase 的调用:
usbdev_file_operations → usbdev_ioctl() → usbdev_do_ioctl() → proc_ioctl_default() → proc_ioctl():
static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl)
{
/* (1) 找到对应的 usb interface device */
else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno)))
retval = -EINVAL;
/* talk directly to the interface's driver */
default:
if (intf->dev.driver)
/* (2) 找到 usb interface device 对应的 driver */
driver = to_usb_driver(intf->dev.driver);
if (driver == NULL || driver->unlocked_ioctl == NULL) {
retval = -ENOTTY;
} else {
/* (3) 调用 driver 的 ioctl 函数 */
retval = driver->unlocked_ioctl(intf, ctl->ioctl_code, buf);
if (retval == -ENOIOCTLCMD)
retval = -ENOTTY;
}
}
↓
usbtest_ioctl() → usbtest_do_ioctl()
3.4. Host 侧 testusb
testusb 源码包含在 linux 内核当中, 路径为 linux-5.10\tools\usb\testusb.c
。可以通过 luban 编译,或者简单编译:
gcc -Wall -g -lpthread -o testusb testusb.c
就可以启动测试了:
$ sudo ./testusb -a
unknown speed /dev/bus/usb/001/002
/dev/bus/usb/001/002 test 0, 0.000011 secs
/dev/bus/usb/001/002 test 1, 1.625031 secs
/dev/bus/usb/001/002 test 2 --> 110 (Connection timed out)
/dev/bus/usb/001/002 test 3, 1.639717 secs
/dev/bus/usb/001/002 test 4 --> 110 (Connection timed out)
/dev/bus/usb/001/002 test 5, 1.915198 secs
/dev/bus/usb/001/002 test 6 --> 110 (Connection timed out)
/dev/bus/usb/001/002 test 7, 1.928419 secs
/dev/bus/usb/001/002 test 8 --> 110 (Connection timed out)
/dev/bus/usb/001/002 test 9, 13.835084 secs
sudo ./testusb -a
sudo ./testusb -a -t1 -c1 -s512 -g32 -v32
sudo ./testusb -a -t29 -c1 -s512 -g32 -v32
// test 10 需要特别注意,容易挂死 host
sudo ./testusb -a -t10 -c1 -s512 -g5 -v32
// test 28 需要特别注意,容易挂死 host
sudo ./testusb -a -t28 -c1 -s512 -g32 -v32
4. 设计说明
整个 USB 系统的软件栈如上图所示,本文仅描述其中的 HCD (Host Controller Driver) 和 DCD (Device Controller Driver)。
4.1. USB Host Controller Driver
下面以 EHCI 为例,说明 HCD 软件设计思想。
4.1.1. 源码说明
相关模块 | 源码路径 |
---|---|
EHCI | source\linux-5.10\drivers\usb\host\ehci-aic.c source\linux-5.10\drivers\usb\host\ehci-hcd.c source\linux-5.10\drivers\usb\host\ehci-mem.c source\linux-5.10\drivers\usb\host\ehci-q.c source\linux-5.10\drivers\usb\host\ehci-timer.c source\linux-5.10\drivers\usb\host\ehci-hub.c |
4.1.2. 模块架构
从 HCD (Host Controller Driver) 的框架图中可以看到,HCD 主要提供了两大功能:
- 普通 URB 数据收发功能。
将 USB Class Driver 下发的 URB,按照硬件控制器要求的格式,按分类发送到硬件 List 当中。
- RootHub URB 的处理功能。
对于 RootHub Driver 下发的 ep0 控制命令 URB,系统不会发送到硬件控制器之上,而是转发给 HCD 使用软件来模拟执行。
对于 RootHub Driver 下发的端口状态查询 URB,通过响应中断进行上报。
4.1.3. 关键流程
4.1.3.1. 初始化流程
HCD 驱动的入口是 platform 驱动,初始化流程先获取 irq、reg、clk、reset 等资源并进行初始化,最后调用 usb_add_hcd() 向系统中注册。
大致的流程如下:
|-->ehci_platform_init()
|-->ehci_init_driver()
|-->platform_driver_register()
|-->aic_ehci_platform_probe()
|-->hcd = usb_create_hcd()
|-->irq = platform_get_irq(dev, 0);
|-->priv->clks[i] = of_clk_get(dev->dev.of_node, i);
|-->priv->rst[i] = devm_reset_control_get_shared_by_index(&dev->dev, i);
|-->hcd->regs = devm_ioremap_resource(&dev->dev, res_mem);
|-->aic_ehci_platform_power_on()
|-->reset_control_deassert(priv->rst[i]);
|-->clk_prepare_enable(priv->clks[i]);
|-->usb_add_hcd(hcd, irq, IRQF_SHARED);
4.1.3.2. 普通 URB 处理流程
如上图所示,一个普通 urb 的处理分为两步:
- urb enqueue。首先调用 hcd 的
.urb_enqueue()
函数,将需要传输的数据插入到硬件控制器的链表当中。 - urb complete。在链表中的一帧数据传输完成后硬件会产生
complete
中断,在中断服务程序中对相应 urb 发送complete
信号,让usb_start_wait_urb()
的流程继续执行。
4.1.3.3. Roothub URB 处理流程
如上图所示,roothub urb 的处理分为两种类型:
- ep0 control urb。对于 roothub control urb,HCD 需要使用软件来模拟,实际上 urb 没有发送到硬件控制器中,因为是软件模拟所以无需等待
complete
可以立即释放。 - 获取端口状态 urb。这类 urb 会阻塞等待端口状态改变,一旦端口状态改变会触发硬件中断,在中断处理中唤醒对应 urb 的
complete
信号,让usb_start_wait_urb()
的流程继续执行。
4.1.4. 数据结构
4.1.4.1. ehci_hc_driver
HCD 核心的数据结构为 hc_driver,EHCI 实现了以下的核心函数:
static const struct hc_driver ehci_hc_driver = {
.description = hcd_name,
.product_desc = "EHCI Host Controller",
.hcd_priv_size = sizeof(struct ehci_hcd),
/*
* generic hardware linkage
*/
.irq = ehci_irq,
.flags = HCD_MEMORY | HCD_DMA | HCD_USB2 | HCD_BH,
/*
* basic lifecycle operations
*/
.reset = ehci_setup,
.start = ehci_run,
.stop = ehci_stop,
.shutdown = ehci_shutdown,
/*
* managing i/o requests and associated device resources
*/
.urb_enqueue = ehci_urb_enqueue,
.urb_dequeue = ehci_urb_dequeue,
.endpoint_disable = ehci_endpoint_disable,
.endpoint_reset = ehci_endpoint_reset,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
/*
* scheduling support
*/
.get_frame_number = ehci_get_frame,
/*
* root hub support
*/
.hub_status_data = ehci_hub_status_data,
.hub_control = ehci_hub_control,
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
.relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over,
.get_resuming_ports = ehci_get_resuming_ports,
/*
* device support
*/
.free_dev = ehci_remove_device,
};
4.1.5. 接口设计
4.1.5.1. ehci_urb_enqueue
函数原型 | int ehci_urb_enqueue (struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) |
---|---|
功能说明 | 接收上层传入的 urb,并将其压入 EHCI 的硬件队列。 |
参数定义 | hcd:当前 hcd 控制结构 urb:当前 urb 控制结构 mem_flags:分配内存时使用的标志 |
返回值 | 0,成功; < 0,失败 |
注意事项 |
4.1.5.2. ehci_hub_control
函数原型 | int ehci_hub_control (struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength) |
---|---|
功能说明 | 处理 roothub 相关的 control 命令。 |
参数定义 | hcd:当前 hcd 控制结构 typeReq:setup token 中的对应字段 wValue:setup token 中的对应字段 wIndex:setup token 中的对应字段 buf:setup data 需要的数据 wLength:setup token 中的对应字段 |
返回值 | 0,成功; < 0,失败 |
注意事项 |
4.1.5.3. ehci_hub_status_data
函数原型 | int ehci_hub_status_data (struct usb_hcd *hcd, char *buf) |
---|---|
功能说明 | 查询 hub 端口状态。 |
参数定义 | hcd:当前 hcd 控制结构 buf:返回获取的 hub 端口状态 |
返回值 | >0,成功获取端口状态的长度; = 0,失败 |
注意事项 |
4.2. USB Device Controller Driver
Linux 利用 Device Controller Driver 把整个单板模拟成一个 USB Device 设备。
4.2.1. 源码说明
相关模块 | 源码路径 |
---|---|
AIC UDC | source\linux-5.10\drivers\usb\gadget\udc\aic_udc.c source\linux-5.10\drivers\usb\gadget\udc\aic_udc.h |
4.2.2. 模块架构
从上述 DCD (Device Controller Driver) 的框架图中可以看到,DCD 主要提供了两大功能:
- 普通 ep 的 usb request 处理。
DCD 提供了一个 endpoint 资源池,Gadget Function Driver 可以在这个资源池中分配需要的 endpoint ,这部分的 endpoint 就称为 普通 ep。
- ep0 的 usb request 处理。
因为 DCD 对外呈现为一个 USB Device,USB Device 的 ep0 是管理通道是需要特殊处理的。对 ep0 传达过来的 control 数据需要在 DCD 层开始解析。
4.2.3. 关键流程
4.2.3.1. 初始化流程
DCD 驱动的入口是 platform 驱动,初始化流程先获取 irq、reg、clk、reset 等资源并进行初始化,最后调用 usb_add_gadget_udc() 向系统中注册。
大致的流程如下:
|-->aic_udc_probe()
|-->gg->regs = devm_ioremap_resource(&dev->dev, res);
|-->gg->reset = devm_reset_control_get_optional(gg->dev, "aicudc");
|-->gg->reset_ecc = devm_reset_control_get_optional_shared(gg->dev,"aicudc-ecc");
|-->gg->clks[i] = of_clk_get(gg->dev->of_node, i);
|-->aic_gadget_init(gg);
|-->aic_low_hw_enable()
|-->clk_prepare_enable(gg->clks[i]);
|-->reset_control_deassert(gg->reset);
|-->reset_control_deassert(gg->reset_ecc);
|-->res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
|-->gg->irq = res->start;
|-->usb_add_gadget_udc(gg->dev, &gg->gadget);