----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :2017.09
linux :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
的使用和包数据格式解析
[5] Ubuntu
开机自动加载驱动模块
[6] Documentation/usb/usbmon.txt