----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板eMMC :16GBLPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏u-boot :2017.09linux :4.19
----------------------------------------------------------------------------------------------------------------------------
注意:本节介绍的内容基于《Rockchip RK3399 - linux-headers制作》中移植的运行环境:内核版本4.19.193以及debian 11根文件系统。
一、安装usbmon
usbmon即usb monitor,是linux内置的usb抓包工具。usbmon本质是一个内核模块,模块的位置:/lib/modules/4.19.193/kernel/drivers/usb/mon/usbmon.ko。
想要启用usbmon,必须挂载debugfs并加载usbmon模块。
1.1 挂载debugfs文件系统
在开发板debian执行如下命令,如果提示已经挂载,则下次抓包就无需运行该命令了,表示系统默认会挂载该文件系统。
root@SOM-RK3399v2:/# mount -t debugfs none /sys/kernel/debug
mount: /sys/kernel/debug: none_debugs already mounted or mount point busy.
如上所示, debian系统默认已经挂载了debugfs文件系统,无需再去手动挂载。
1.2 安装usbmon模块
需要注意的是:如果你已经将usbmon编译到内核中,就不需要安装了。
确认内核支持usbmon模块
root@SOM-RK3399v2:/# ls /sys/module/usbmon
ls: cannot access '/sys/module/usbmon': No such file or directory
如上所示,目前内核不支持usbmon模块,需要手动安装usbmon模块。
执行如下命令安装usbmon:
root@SOM-RK3399v2:/# modprobe usbmon
bash: modprobe: command not found
提示未找到命令则是因为/usr/sbin(或者/sbin)默认没有加到PATH;通过修改profile文件:
root@SOM-RK3399v2:/# vim /etc/profile
找到设置·PATH·的行,添加:
export PATH=/usr/sbin:$PATH
要想马上生效还要运行 source /etc/profile不然只能在下次重进此用户时生效。
重新运行命令:
root@SOM-RK3399v2:/# modprobe usbmon
root@SOM-RK3399v2:/# ls /sys/module/usbmon
coresize holders initsize initstate notes refcnt sections srcversion taint uevent
这里的原理是,usbmon是一个模块,使用modprobe安装该模块后,该模块内部调用debugfs相关的API,这样在 /sys/kernel/debug/usb目录下便形成了usbmon这个目录。
查看/sys/kernel/debug/usb/usbmon目录,
root@SOM-RK3399v2:/# ls /sys/kernel/debug/usb/usbmon
0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u 5s 5t 5u 6s 6t 6u
发现该目录下有以下内容:0s、0u、1s、1t、1u、2s、2t、2u等,其中1代表bus1,2代表bus2,0代表所有USB总线。
1.2.1 开机自动加载模块
这里有一个问题,就是内核4.19.193版本启动的时候为啥没有自动去加载/lib/modules/4.19.193/目录下的驱动模块,如果我们想让系统能自动加载该目录下的驱动模块,我们应该怎么做?
(1) 使用命令depmod -a
depmod命令用于分析可载入模块的相依性,-a参数的作用是探测所有的模块,建立模块的依赖关系,更新/lib/modules/4.19.193/modules.dep文件;
(2) 将模块设置为自动加载
在 /etc/modules文件里面添加我们要加载的驱动名(一个驱动占一行);
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
usbmon
1.2.2 重启验证
上述操作完成后,重启系统,使用 lsmod 或 cat /proc/modules 、ls /sys/module/usbmon命令查看驱动是否已经加载。
root@SOM-RK3399v2:/# lsmod
Module Size Used by
algif_hash 20480 1
algif_skcipher 16384 1
af_alg 24576 6 algif_hash,algif_skcipher
bnep 24576 2
hci_uart 61440 1
btbcm 16384 1 hci_uart
serdev 20480 1 hci_uart
hid_logitech_hidpp 36864 0
crct10dif_ce 16384 0
l2tp_ppp 24576 0
l2tp_netlink 24576 1 l2tp_ppp
l2tp_core 28672 2 l2tp_ppp,l2tp_netlink
ip6_udp_tunnel 16384 1 l2tp_core
udp_tunnel 16384 1 l2tp_core
pppox 16384 1 l2tp_ppp
joydev 28672 0
bcmdhd 1695744 0
cfg80211 638976 1 bcmdhd
hid_logitech_dj 20480 0
uio_pdrv_genirq 16384 0
uio 20480 1 uio_pdrv_genirq
binfmt_misc 20480 1
usbmon 36864 0 # 这里
ledtrig_netdev 16384 0
nfsd 344064 1
ip_tables 28672 0
root@SOM-RK3399v2:/# ls /sys/module/usbmon
coresize holders initsize initstate notes refcnt sections srcversion taint uevent
二、抓取USB数据
2.1 确usb设备挂在哪条总线
首先需要获取想要监测的设备所在的总线以及设备号。linux中查看USB设备列表以及USB设备详细信息的有多种方法:
2.1.1 内核日志
我们在开发板上随便找一个USB接口插入USB触摸屏,内核输出日志如下:
[ 1173.581227] logitech-hidpp-device 0003:046D:4052.0006: HID++ 4.5 device connected.
[ 1182.859503] usb 1-1.2: new full-speed USB device number 4 using ehci-platform
[ 1182.959039] usb 1-1.2: New USB device found, idVendor=1a86, idProduct=e5e3, bcdDevice= 0.00
[ 1182.959245] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 1182.959281] usb 1-1.2: Product: USB2IIC_CTP_CONTROL
[ 1182.959343] usb 1-1.2: Manufacturer: wch.cn
[ 1182.977982] input: wch.cn USB2IIC_CTP_CONTROL as /devices/platform/fe3c0000.usb/usb1/1-1/1-1.2/1-1.2:1.0/0003:1A86:E5E3.0007/input/input13
[ 1183.034737] hid-generic 0003:1A86:E5E3.0007: input,hidraw4: USB HID v1.00 Device [wch.cn USB2IIC_CTP_CONTROL] on usb-fe3c0000.usb-1.2/input0
[ 1183.142034] input: wch.cn USB2IIC_CTP_CONTROL as /devices/platform/fe3c0000.usb/usb1/1-1/1-1.2/1-1.2:1.0/0003:1A86:E5E3.0007/input/input14
[ 1183.144619] hid-multitouch 0003:1A86:E5E3.0007: input,hidraw4: USB HID v1.00 Device [wch.cn USB2IIC_CTP_CONTROL] on usb-fe3c0000.usb-1.2/input0
从上面的输出信息可以看到PID=e5e3,VID=1a86,USB总线编号为1,设备地址为4。
2.1.2 lsusb
我们可以通过lsusb命令查看USB设备信息:
root@SOM-RK3399v2:/# 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:0001 Linux Foundation 1.1 root hub
Bus 001 Device 004: ID 1a86:e5e3 QinHeng Electronics USB2IIC_CTP_CONTROL # 这里
Bus 001 Device 003: ID 1a2c:4d7e China Resource Semico Co., Ltd USB Keyboard
Bus 001 Device 002: ID 14cd:8601 Super Top 4-Port hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 006 Device 002: ID 046d:c52b Logitech, Inc. Unifying Receiver
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
USB总线1上,有1个root hub,1个4-Port hub、1个USB键盘、1个USB触摸屏。
2.1.3 查看设备文件
root@SOM-RK3399v2:/# cat /sys/kernel/debug/usb/devices
T: Bus=01 Lev=02 Prnt=02 Port=01 Cnt=02 Dev#= 4 Spd=12 MxCh= 0
D: Ver= 0.01 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
P: Vendor=1a86 ProdID=e5e3 Rev= 0.00
S: Manufacturer=wch.cn
S: Product=USB2IIC_CTP_CONTROL
C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr= 64mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=03(HID ) Sub=00 Prot=00 Driver=usbhid
E: Ad=82(I) Atr=03(Int.) MxPS= 64 Ivl=1ms
这里看到的信息实际上和内核日志输出的信息一样的。
2.2 监测USB总线上的数据
查看USB总线上的数据;
root@SOM-RK3399v2:/# cat /sys/kernel/debug/usb/usbmon/1u | grep "1:004"
ffffffc0c88dab00 1925763364 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925763683 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925775309 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925775578 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925787174 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925787301 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925800237 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925800380 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925811243 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925811395 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925821178 C Ii:1:004:2 0:1 52 = 01000075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925821328 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925822163 C Ii:1:004:2 0:1 52 = 01000075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925822309 S Ii:1:004:2 -115:1 52 <
其中的1:004中1表示USB总线编号,004时设备编号。
执行该命令的同时我们点击一下USB触摸屏,就可以查看USB总线上的数据传输
三、USB传输基础
USB数据抓取到了,但是放眼一看,密密麻麻的全是数字,它们代表什么含义呢?
在解读usbmon抓取的数据包的含义之前,我们需要了解一下与USB传输有关的基础知识,这样才能更好的理解数据包的各个字段所代表的含义。
USB总线上传输的数据是以包为基本单位的,但是不能随意的使用包来传输数据,必须按照一定的关系把这些不同的包组织成事务(transaction)进行传输。
3.1 USB信息包的种类
USB传输由一个或多个事务组成,每个事务又进一步含有多个USB包(packets)。
USB包的种类,总体上分为四类:令牌包、数据包、握手包、特殊包;
3.1.1 令牌包
令牌包用来发起一次USB传输,因为USB是主从结构的拓扑结构,所有的数据传输都是由主机发起的,设备只能被动的响应,这就需要主机发送一个令牌包来通知哪个设备进行响应,如何响应。
令牌包有 4 种,分别为输出(OUT)、输入(IN)、建立(SETUP)和帧起始(SOF)。
3.1.2 数据包
数据包就是用来传输数据的,可以从主机到设备,也可以从设备到主机,方向由令牌包来指定。
3.1.3 握手包
握手包的发送者一般为数据接收者,用来表示一个传输是否被对方确认。在传输正常的情况下,主机/设备会发送一个表示传输正确的 ACK握手包。
3.1.4 特殊包
特殊包用在一些特殊的场合,这里就不介绍了。
3.2 USB事务
介绍了USB信息包的分类,下面就要着重介绍USB的事务及传输类型。前面已经说了,我们不能随意的使用USB包来传输数据,必须按照一定的关系把这些不同的包组织成事务才能传输数据。
那么事务是什么呢? 事务通常由三个包组成:令牌包、数据包和握手包。

注意:usbmon只抓取事务中的数据包,不会抓取令牌包和握手包。
3.3 USB传输类型
USB协议规定了4种传输类型:批量传输、等时传输、中断传输和控制传输。其中;
- 批量传输、等时传输、中断传输每传输一次数据都是一个事务;
- 控制传输包括三个过程,建立过程和状态过程分别是一个事务,数据过程可能包含多个事务。
3.3.1 批量传输
批量传输用于传输大量数据。USB协议不保证这些数据传输可以在特定的时间内完成,但保证数据的准确性。如果总线上的带宽不足以发送整个批量包,则将数据拆分为多个包传输。批量传输数据可靠,但实时性较低。如USB硬盘、打印机等设备就采用的是批量传输方式;

3.3.2 等时传输
等时传输也可以传输大量数据,但数据的可靠性无法保证。采用等时传输的USB设备更加注重保持一个恒定的数据传输速度,对数据的可靠性要求不高。如USB摄像头就使用的是等时传输方式;

3.3.3 中断传输
当USB主机请求USB设备传输数据时,中断传输以一个固定的速率传送少量的数据。中断端点的数据传输方式为中断传输,数据传输可靠,实时性高,这里的中断并不是USB设备产生中断,而是USB主机每隔一个固定的时间主动查询USB设备是否有数据要传输,以轮询的方式提高实时性。如USB鼠标采用的是中断传输;

3.3.4 控制传输
控制传输用于配置设备、获取设备信息、发送命令到设备、获取设备的状态。每个USB设备都有端点0的控制端点,当USB设备插入到USB主机拓扑网络中时,USB主机就通过端点0与USB设备通信,对USB设备进行配置,便于后续的数据传输。USB协议保证控制传输有足够的带宽。控制传输可靠,时间有保证,但传输的数据量不大。如USB设备的枚举过程就采用的是控制传输;


四、USB数据包分析
下面是我点击USB触摸屏通过usbmon 抓取到的数据:
root@SOM-RK3399v2:/# cat /sys/kernel/debug/usb/usbmon/1u | grep "1:004"
ffffffc0c88dab00 1925763364 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925763683 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925775309 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925775578 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925787174 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925787301 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925800237 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925800380 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925811243 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925811395 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925821178 C Ii:1:004:2 0:1 52 = 01000075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925821328 S Ii:1:004:2 -115:1 52 <
ffffffc0c88dab00 1925822163 C Ii:1:004:2 0:1 52 = 01000075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
ffffffc0c88dab00 1925822309 S Ii:1:004:2 -115:1 52 <
下面从左到右分析这些字段代表的含义:URB标签 时间戳 事件类型 地址 URB状态 数据包的长度 数据标签 数据流。
4.1 URB Tag - URB标签
该字段表示驱动中定义的struct urb结构体变量在内核空间的地址,可以使用该字段区分不同的URB数据包。
URB标签字段的长度和系统的位数相同,以64为系统为例,该字段的长度为8个字节。
4.2 Timestamp - 时间戳
该字段表示的是时间戳,单位是微秒。1微秒 = 10^(-6)秒。
该时间是由下面的mon_get_timestamp函数获取的,使用ktime_get_ts64获取的时间位与上了0xFFF,因此usbmon显示的秒数范围为0 ~ 4096s;
// drivers/usb/mon/mon_text.c
static inline unsigned int mon_get_timestamp(void)
{
struct timespec64 now;
unsigned int stamp;
ktime_get_ts64(&now); // 获取当前时间
stamp = now.tv_sec & 0xFFF; /* 2^32 = 4294967296. Limit to 4096s. */
stamp = stamp * USEC_PER_SEC + now.tv_nsec / NSEC_PER_USEC; //USEC_PER_SEC=1000000 NSEC_PER_USEC=1000
return stamp;
}
4.3 Event Type - 事件类型
事件类型有三种:
S - submission,向USB Controller提交URB;C - callback,URB提交完成后的回调;E - submission error,向usb controller提交URB发生错误;
4.4 Address word - 地址
这个字段包含4部分,各个部分之间使用分号隔开,这4部分分别是URB类型及传输方向、USB总线号、USB设备地址、端点号。
Ii:1:004:2
| | | |
| | | |__________ 端口号
| | |_____________ USB设备地址
| |________________ USB总线号
|___________________ URB类型及传输方向
URB类型及传输方向:USB有四种传输方式,分别是控制传输、批量传输、等时传输和中断传输。USB数据的传输方向是以USB主机端为参考对象的,USB主机向USB设备发送数据那么传输方向就是Output,USB主机读取USB设备的数据那么传输方向就是Input。
Ci Co Control input and output
Zi Zo Isochronous input and output
Ii Io Interrupt input and output
Bi Bo Bulk input and output
USB总线号:该字段表示USB总线号,每个USB Controller都有一条对应的USB总线,使用USB总线号区分它们,USB设备可以挂接到某条总线上;
USB设备地址:该字段表示USB设备的地址,每一个USB设备经过枚举后在USB总线都有一个唯一的地址;
端点号:表明该次数据传输是Input/Output到设备的哪个端点。上图中该字段是2,就表示这次数据传输使用的是设备的端点2。
4.5 URB Status word - URB状态
这个字段有两种表示形式:
- s + 一串数字:
- 一串以分号间隔的数字(或单个数字)构成的,这串数字包含下面几个部分:
URB status、interval、start frame和error count。特别注意一点,该字段不同于地址字段,对于不同的传输方式这几部分是可选的,并非所有部分都是必须的。
下图是不同的传输方式包含的信息;

下面分析不同的传输方式所包含的信息:
(1) 批量传输:只包含URB status这个字段,它对应着struct urb结构体中的status成员变量,表示URB的状态。URB status仅仅对事件类型中的Callback有意义,对于Submission是无意义的,之所以这么做是为了统一格式,方便使用脚本分析usbmon的数数据;
(2) 中断传输:URB status和Interval,URB status见前面的分析,Interval表示该URB对端点轮询的间隔时间;
(3) 等时传输:URB status、Interval、start frame和error count。等时传输包含了所有部分,start frame和error count 是等时传输所特有的字段;
(4) 控制传输:控制传输在提交时(S:submission)这个字段是s,这里的s后面紧跟的数据是控制传输的建立过程主机发送的数据包(Setup packet),可以参考前面控制传输的示意图。控制传输在回调时(C:callback),这个字段代表的是URB status。

该字段从左到右的格式如下,括号中的数字表示该部分占用的字节大小:
bmRequestType(1) + bRequest(1) + wvalue(2) + wIndex(2) + wLength(2)
每个字段的含义可以在USB2.0规范中找到,这部分与USB的标准请求等相关。
4.6 Data Length - 数据包的长度
对于S(Submission),Data Length字段是主机请求发送/读取的数据长度,但是设备并不一定能够接收/发送主机请求的数据长度。实际接收/发送的数据长度在C(Callback)中的Data Length字段。
ffffffc0c88dab00 1925763683 S Ii:1:004:2 -115:1 52 <
|__ 输入 |____ 请求读取的长度
ffffffc0c88dab00 1925775309 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
|__ 输入 |_______ 实际读取的长度
4.7 Data tag - 数据标签
数据标签有三种:
-
=:后面紧跟数据流; -
>:表示这是一次Output数据传输; -
<:表示这是一次Input数据传输;
4.8 Data words follow - 数据流
这个字段就是一个事务中的数据包,我们平时分析usbmon抓取的数据包,也主要是看这个字段。注意一点,这个字段实际显示的数据 <= Data Length的值。
4.8.1 1个触摸点
1个触摸点时,我们读取到的数据:
ffffffc0c88dab00 1925775309 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
报文格式;
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| 0x01 | 0x01 | 0x00 | 0x75 | 0x09 | 0x08 | 0x09 | 0x30 |
| 设备ID | 第一个点触摸状态 | 手指ID | X坐标低8位 | X坐标高8位 | Y坐标低8位 | Y坐标高8位 | |
| 设备ID固定值 | 被按下 | 手指ID为0x00 | X坐标为0x0975 | X坐标为0x0975 | Y坐标为0x0908 | Y坐标为0x0908 |
4.8.2 2个触摸点
2个触摸点时,我们读取到的数据:
ffffffc0c88dab00 2543284410 C Ii:1:004:2 0:1 52 = 0101003d 02d50530 000101e1 035d0330 00000000 00000000 00000000 00000000
报文格式;
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| 0x01 | 0x01 | 0x00 | 0x3d | 0x02 | 0xd5 | 0x05 | 0x30 |
| 设备ID | 第一个点触摸状态 | 手指ID | X坐标低8位 | X坐标高8位 | Y坐标低8位 | Y坐标高8位 | |
| 设备ID固定值 | 被按下 | 手指ID为0x00 | X坐标为0x023d | X坐标为0x023d | Y坐标为0x05d5 | Y坐标为0x05d5 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|
| 0x00 | 0x01 | 0x01 | 0xe1 | 0x03 | 0x5d | 0x03 | 0x30 |
| 第二个点触摸状态 | 手指ID | X坐标低8位 | X坐标高8位 | Y坐标低8位 | Y坐标高8位 | ||
| 被按下 | 手指ID为0x01 | X坐标为0x03e1 | X坐标为0x03e1 | Y坐标为0x035d | Y坐标为0x035d |
参考文章
[1] Linux下USB抓包工具UsbMon的使用和包数据格式解析
[2] 使用usbmon抓取usb总线上的数据
[3] 详解usbmon抓取的log各字段的含义
[4] debian11 modprobe:未找到命令
[5] Ubuntu开机自动加载驱动模块
[6] Documentation/usb/usbmon.txt
[7] USB鼠标HID描述符以及数据格式



















