当前位置:   article > 正文

Rockchip RK3399 - linux通过libusb读取usb数据包

rk linux读取usb设备数据

----------------------------------------------------------------------------------------------------------------------------

开发板 :SOM-RK3399核心板+定制底板eMMC16GBLPDDR34GB
显示屏 :15.6英寸HDMI接口显示屏u-boot2017.09linux4.19
----------------------------------------------------------------------------------------------------------------------------

注意:本节介绍的内容基于Rockchip RK3399 - linux-headers制作》中移植的运行环境:内核版本4.19.193以及debian 11根文件系统。

在《Rockchip RK3399 - linux通过usbmon抓取usb数据包》中我们分析了我们所使用的USB触摸屏的HID报告描述符,并通过usbmon工具进行USB触摸屏数据包的抓取,最终结合HID报告描述符分析抓取到的数据包中数据的含义。

在《Rockchip RK3399 - linux-headers制作》中我们介绍了在SOM-RK3399核心板上,通过USB3.0 Type-C PHY1接口将开发板模拟成USB设备。

这一节我们将通过libusb来实现USB触摸屏数据包的捕获,并将捕获的数据通过模拟的USB触摸屏发送到PC

一、模拟USB设备回顾

我们首先回顾一下模拟USB设备是如何实现的,我们编写了一个hid_keyboard_mouse.sh脚本,由于USB3.0 Type-C PHY1接口被配置为了从设备,因此可以通过USB3.0 Type-C PHY1接口将开发板模拟成USB设备(同时模拟鼠标、键盘、触摸屏功能)。

1.1 hid_keyboard_mouse.sh

/etc/profile.d下新建hid_keyboard_mouse.sh脚本:

  1. #!/bin/bash
  2. gadget=g1
  3. do_start(){
  4. has_mount=$(mount -l | grep /sys/kernel/config)
  5. if [[ -z $has_mount ]];then
  6. mount -t configfs none /sys/kernel/config
  7. fi
  8. cd /sys/kernel/config/usb_gadget
  9. # 当我们创建完这个文件夹之后,系统自动的在这个文件夹中创建usb相关的内容 ,这些内容需要由创建者自己填写
  10. if [[ ! -d ${gadget} ]]; then
  11. mkdir ${gadget}
  12. else
  13. exit 0
  14. fi
  15. cd ${gadget}
  16. #设置USB协议版本USB2.0
  17. echo 0x0200 > bcdUSB
  18. #定义产品的VendorID和ProductID
  19. echo "0x0525" > idVendor
  20. echo "0xa4ac" > idProduct
  21. #实例化"英语"ID:
  22. mkdir strings/0x409
  23. #将开发商、产品和序列号字符串写入内核
  24. echo "76543210" > strings/0x409/serialnumber
  25. echo "mkelehk" > strings/0x409/manufacturer
  26. echo "keyboard_mouse" > strings/0x409/product
  27. #创建一个USB配置实例
  28. if [[ ! -d configs/c.1 ]]; then
  29. mkdir configs/c.1
  30. fi
  31. #定义配置描述符使用的字符串
  32. if [[ ! -d configs/c.1/strings/0x409 ]]; then
  33. mkdir configs/c.1/strings/0x409
  34. fi
  35. echo "hid" > configs/c.1/strings/0x409/configuration
  36. #创建功能实例,需要注意的是,一个功能如果有多个实例的话,扩展名必须用数字编号:
  37. mkdir functions/hid.0 #键盘
  38. mkdir functions/hid.1 #鼠标
  39. mkdir functions/hid.2 #触摸屏
  40. #配置hid描述符
  41. echo 1 > functions/hid.0/subclass #标识仅有一个接口描述符
  42. echo 1 > functions/hid.0/protocol #标识键盘设备
  43. echo 8 > functions/hid.0/report_length #标识该hid设备每次发送的报表长度为8字节
  44. echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.0/report_desc
  45. echo 1 > functions/hid.1/subclass
  46. echo 2 > functions/hid.1/protocol
  47. echo 4 > functions/hid.1/report_length
  48. echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x03\\x15\\x00\\x25\\x01\\x95\\x03\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x05\\x81\\x03\\x05\\x01\\x09\\x30\\x09\\x31\\x09\\x38\\x15\\x81\\x25\\x7f\\x75\\x08\\x95\\x03\\x81\\x06\\xc0\\xc0 > functions/hid.1/report_desc
  49. #配置hid描述符
  50. echo 0 > functions/hid.2/subclass
  51. echo 0 > functions/hid.2/protocol
  52. echo 5 > functions/hid.2/report_length #标识该hid设备每次发送的报表长度为5字节
  53. echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x05\\x15\\x00\\x25\\x01\\x95\\x05\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x03\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x15\\x00\\x26\\xff\\x7f\\x35\\x00\\x46\\xff\\x7f\\x75\\x10\\x95\\x02\\x81\\x02\\xc0\\xc0 > functions/hid.2/report_desc
  54. #捆绑功能实例到配置config.1
  55. ln -s functions/hid.0 configs/c.1
  56. ln -s functions/hid.1 configs/c.1
  57. ln -s functions/hid.2 configs/c.1
  58. #配置USB3.0/2.0 OTG0的工作模式为Device(设备):
  59. #echo peripheral > /sys/devices/platform/ff770000.syscon/ff770000.syscon:usb2-phy@e460/otg_mode
  60. echo "sleep 3s"
  61. sleep 3s
  62. #将gadget驱动注册到UDC上,插上USB线到电脑上,电脑就会枚举USB设备。
  63. echo fe900000.dwc3 > UDC
  64. }
  65. do_stop() {
  66. cd /sys/kernel/config/usb_gadget/${gadaget}
  67. echo "" > UDC
  68. }
  69. case $1 in
  70. start)
  71. echo "Start hid gadget "
  72. do_start
  73. ;;
  74. stop)
  75. echo "Stop hid gadget"
  76. do_stop
  77. ;;
  78. *)
  79. echo "Usage: $0 (stop | start)"
  80. ;;
  81. esac

我们将这个脚本配置为开机自动执行,重新开机,开机会自动执行/etc/profile.d的所有shell脚本。查看hid设备;

  1. root@SOM-RK3399v2:/home/pi# ls /dev/hidg* -nR
  2. crw------- 1 0 0 236, 0 Sep 26 14:25 /dev/hidg0 # 键盘
  3. crw------- 1 0 0 236, 1 Sep 26 14:25 /dev/hidg1 # 鼠标
  4. crw------- 1 0 0 236, 2 Sep 26 14:25 /dev/hidg2 # 触摸屏
  5. root@SOM-RK3399v2:/home/pi# ls /sys/kernel/config/usb_gadget/g1/
  6. UDC bDeviceProtocol bMaxPacketSize0 bcdUSB functions idVendor strings
  7. bDeviceClass bDeviceSubClass bcdDevice configs idProduct os_desc
1.2 HID报告描述符

既然我们想通过模拟的USB触摸屏向PC发送数据,自然而然的我们要搞懂模拟的USB触摸屏的HID报告描述符:

echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x05\\x15\\x00\\x25\\x01\\x95\\x05\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x03\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x15\\x00\\x26\\xff\\x7f\\x35\\x00\\x46\\xff\\x7f\\x75\\x10\\x95\\x02\\x81\\x02\\xc0\\xc0 > functions/hid.2/report_desc

根据《Rockchip RK3399 - linux通过usbmon抓取usb数据包》介绍的HID报告描述符的规则描述,我们得到;

  1. 0x05,0x01, // USAGE_PAGE (Generic Desktop)
  2. 0x09,0x02, // USAGE (Mouse)
  3. 0xa1,0x01, // COLLECTION (Application)
  4. 0x09,0x01, // USAGE (Pointer)
  5. 0xa1,0x00, // COLLECTION (Physical)
  6. /* 下面定义的是用途Button.1~Button.5,加上3个无效位,一共占用1个字节 */
  7. 0x05,0x09, // USAGE_PAGE (Button)
  8. 0x19,0x01, // USAGE_MINIMUM (Button 1)
  9. 0x29,0x05, // USAGE_MAXIMUM (Button 5)
  10. 0x15,0x00, // LOGICAL_MINIMUM (0)
  11. 0x25,0x01, // LOGICAL_MAXIMUM (1)
  12. 0x95,0x05, // REPORT_COUNT (5)
  13. 0x75,0x01, // REPORT_SIZE (1)
  14. 0x81,0x02, // INPUT (Data,Var,Abs)
  15. 0x95,0x01, // REPORT_COUNT (1)
  16. 0x75,0x03, // REPORT_SIZE (3)
  17. 0x81,0x01, // INPUT (Data,Var,Abs)
  18. /* 下面定义的是用途GenericDesktop.X、GenericDesktop.Y,各占用2个字节 */
  19. 0x05,0x01, // USAGE_PAGE (Generic Desktop)
  20. 0x09,0x30, // USAGE (X)
  21. 0x09,0x31, // USAGE (Y)
  22. 0x15,0x00, // LOGICAL_MINIMUM (0)
  23. 0x26,0xff,0x7f, // LOGICAL_MAXIMUM (0x7fff)
  24. 0x35,0x00, // PHYSICAL_MINIMUM (0)
  25. 0x46,0xff,0x7f, // PHYSICAL_MAXIMUM (0x7fff)
  26. 0x75,0x10, // REPORT_SIZE (16)
  27. 0x95,0x02, // REPORT_COUNT (2)
  28. 0x81,0x02, // INPUT (Data,Var,Abs)
  29. 0xc0, // End COLLECTION
  30. 0xc0 // End COLLECTION

我们需要关注一下HID报告描述符中X、Y坐标的逻辑最小值和最大值,这个后面应用程序中有使用到;

  1. Logical Minimum(0)
  2. Logical Maximum(32767)

由于HID报告描述符只有1个报告,因此在USB数据包中就不用指定报告ID(需要注意的是:如果HID报告描述符有多个报告,那么存在一个USB数据包中有包含多个报告的场景),通过分析,我们可以知道一个USB数据包包含5个字节:

字节描述
BYTE0bit [7~5]:未使用
bit[4] 1:表示EXTRA键按下 0:表示松开
bit[3] 1:表示SIDE键按下 0:表示松开
bit[2] 1:表示中键按下 0:表示松开
bit[1] 1:表示右键按下 0:表示松开
bit[0] 1:表示左键按下 0:表示松开
BYTE1X坐标低8位(绝对值)
BYTE2X坐标高8位(绝对值)
BYTE3Y坐标低8位(绝对值)
BYTE4Y坐标高8位(绝对值)

我们只需要按照上面表格的描述构建USB数据包,并将数据写入/dev/hidg2设备中,即可通过USB OTG线将数据发送到PC

二、USB触摸屏回顾

在《Rockchip RK3399 - linux通过usbmon抓取usb数据包》中我们分析了我们所使用的USB触摸屏(PID=e5e3,VID=1a86)的HID报告描述符;

  1. root@SOM-RK3399v2:/# cat /sys/kernel/debug/hid/0003\:1A86\:E5E3.0007/rdesc
  2. 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 15 00 25 7f 95 01 75 08 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0

同时对HID报告描述符进行了解析,得知第一个触摸点的描述数据位于捕获到的数据包的字节1~字节8中。

位7位6位5位4位3位2位1位0
字节0报告编号
字节1第一个触摸点状态
字节2第一个触摸点标识符
字节3X坐标低8位(绝对值)
字节4X坐标高8位(绝对值)
字节5Y坐标低8位(绝对值)
字节6Y坐标高8位(绝对值)
字节7触摸笔宽度低8位
字节8触摸笔宽度高8位

我们需要关注一下报告描述符中X、Y坐标的逻辑最小值和最大值;

  1. Logical Minimum(0)
  2. Logical Maximum(4096) // 0x1000

实际上这个就是移动触摸点时捕获到的USB数据包中X、Y坐标的区间范围;

  • 当触摸点移动到屏幕的最左侧,X->0;
  • 当触摸点移动到屏幕的最右侧,X->4096;
  • 当触摸点移动到屏幕的最上边,Y->0;
  • 当触摸点移动到屏幕的最下边,Y->4096;

三、应用程序

这里我们编写一个应用程序通过libusb来实现USB触摸屏数据包的捕获,并将捕获的数据通过模拟的USB触摸屏设备发送到PC。具体思路如下:

  • 通过libusb库读取USB触摸屏的数据包;
  • 结合USB触摸屏的HID报告描述符,获取到第一个触摸点的信息(按键状态、X、Y坐标);
  • 结合模拟的USB触摸屏的HID报告描述符,将按键状态、X、Y坐标转换成模拟的USB触摸屏的所需的报告格式;
  • 向模拟的USB触摸屏写入报告数据;
3.1 应用程序

/opt/libusb目录下编写测试应用程序usb_test.c

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <stdint.h>
  5. #include <errno.h>
  6. #include <fcntl.h>
  7. #include <unistd.h>
  8. #include <time.h>
  9. #include <getopt.h>
  10. #include <ctype.h>
  11. #include <signal.h>
  12. #include <linux/input.h>
  13. #include <linux/uinput.h>
  14. #include <stdbool.h>
  15. #include "libusb.h"
  16. /* Developers will wish to consult the API documentation: http://api.libusb.info */
  17. /* https://github.com/libusb/libusb */
  18. static int verbose = 1;
  19. static volatile sig_atomic_t rcv_exit;
  20. /* 触摸点ID */
  21. static int touch_id = 100;
  22. /* 模拟的usb触摸屏设备 x、y坐标逻辑最大值 */
  23. static int gadget_x_logical_maximum = 0x7fff;
  24. static int gadget_y_logical_maximum = 0x7fff;
  25. /* 存放hid报告描述符解析后的信息 */
  26. struct hid_descriptor_t
  27. {
  28. /* usb触摸屏描述信息 */
  29. uint16_t vid; /* 生产厂家ID */
  30. uint16_t pid; /* 产品ID */
  31. /* */
  32. uint8_t endpoint_address; /* 端点地址 */
  33. uint8_t report_length; /* 报告长度,标识该hid设备每次发送的报表长度 */
  34. /* 第一个触摸点描述信息 */
  35. uint16_t btn_offset; /* 按键状态偏移位,单位为位 */
  36. uint16_t index_x_low; /* x坐标低8位索引,单位为字节 */
  37. uint16_t index_x_high; /* x坐标高8位索引,单位为字节 */
  38. uint16_t index_y_low ; /* y坐标低8位索引,单位为字节 */
  39. uint16_t index_y_high; /* y坐标高8位索引,单位为字节 */
  40. int x_logical_minimum; /* x坐标逻辑最小值 */
  41. int x_logical_maximum; /* x坐标逻辑最大值 */
  42. int y_logical_minimum; /* y坐标逻辑最小值 */
  43. int y_logical_maximum; /* y坐标逻辑最大值 */
  44. };
  45. /* usb触摸屏报告 */
  46. struct touch_screen_report_t
  47. {
  48. uint8_t buttons; /* 按键按下状态 */
  49. uint16_t x; /* x坐标 */
  50. uint16_t y; /* y坐标 */
  51. };
  52. /* 存放不同usb触摸屏hid报告描述符解析后的信息 */
  53. static const struct hid_descriptor_t hids[] = {
  54. [0] = { 0x1a86, 0xe5e3, 0x82, 52, 8, 3, 4, 5, 6, 0, 0x1000, 0, 0x1000 },
  55. [1] = { 0x222a, 0x0001, 0x81, 52, 14, 2, 3, 4, 5, 0, 0x0780, 0, 0x0438 },
  56. };
  57. /**
  58. * 上报事件
  59. * @param fd:文件描述符
  60. * @param type:事件类型
  61. * @param code:事件code
  62. * @param value:事件值
  63. */
  64. static int report_key(int fd, int type, int code, int32_t value)
  65. {
  66. struct input_event event;
  67. event.type = type;
  68. event.code = code;
  69. event.value = value;
  70. gettimeofday(&event.time, 0);
  71. if (write(fd, &event, sizeof(struct input_event)) < 0)
  72. {
  73. perror("report key error!\n");
  74. return -1;
  75. }
  76. return 0;
  77. }
  78. /**
  79. * 上报slot,对于协议B,内核驱动应该把每一个识别出的触摸点和一个slot相关联
  80. * @param fd:文件描述符
  81. * @param slot: 当前发送的是哪个slot的坐标信息,也就是哪个触摸点
  82. * 比如10点触摸,最多可以同时支持10个触摸点,slot可以理解为是第几个触摸点
  83. */
  84. static void input_mt_slot(int fd, int slot)
  85. {
  86. report_key(fd, EV_ABS, ABS_MT_SLOT, slot);
  87. }
  88. /**
  89. * 使用当前slot来传播触摸状态的改变,通过修改关联slot的ABS_MT_TRACKING_ID来达到对触摸点的创建,替换和销毁。
  90. * 1. ABS_MT_TRACKING_ID用来跟踪触摸点属于哪一条线,如果触摸点的ID值与上一次事件中ID值相等,那么他们就属于同一条线,
  91. * ID值并不是随便赋值的,而是硬件上跟踪了触摸点的轨迹,比如按下一个点硬件会跟踪这个点的ID,只要不抬起上报的点都会和这个ID相关
  92. * 2. 上报ABS_MT_TRACKING_ID -1系统会清除对应的ID和slot
  93. * @param fd:文件描述符
  94. * @param active: 1连续触摸(表示触摸点一直按下),0抬起(表示触摸点无效了)
  95. */
  96. static void input_mt_report_slot_state(int fd, bool active)
  97. {
  98. // 上报ABS_MT_TRACKING_ID -1系统会清除对应的slot和ID,表示触摸点抬起
  99. if (!active) {
  100. report_key(fd, EV_ABS, ABS_MT_TRACKING_ID, -1);
  101. return;
  102. }
  103. // ABS_MT_TRACKING_ID大于0,表示触摸点按下 如果ABS_MT_TRACKING_ID本次并没有改变表示触摸点还在一个轨迹上
  104. report_key(fd, EV_ABS, ABS_MT_TRACKING_ID, touch_id);
  105. }
  106. /**
  107. * 上报触摸点坐标
  108. * @param fd:文件描述符
  109. * code: 要上报的是什么数据
  110. * value: 要上报的数据值
  111. * return: 无
  112. */
  113. static void input_report_abs(int fd, unsigned int code,int value)
  114. {
  115. report_key(fd, EV_ABS, code, value);
  116. }
  117. /**
  118. * 触摸点按下或者抬起
  119. * @param fd:文件描述符
  120. * @param x:x坐标
  121. * @param y:y坐标
  122. * @param down:true 按下状态,false抬起状态
  123. */
  124. static void touch_screen_pressed(int fd, int x, int y, bool down)
  125. {
  126. // 上报触摸点序号
  127. input_mt_slot(fd, 0);
  128. if(down){
  129. // 为触摸点分配ID
  130. input_mt_report_slot_state(fd, 1);
  131. // 上报触摸点X轴坐标信息
  132. input_report_abs(fd, ABS_MT_POSITION_X, x);
  133. // 上报触摸点Y轴坐标信息
  134. input_report_abs(fd, ABS_MT_POSITION_Y, y);
  135. }else{
  136. input_mt_report_slot_state(fd, 0);
  137. }
  138. // 同步事件
  139. report_key(fd, EV_SYN, SYN_REPORT, 0);
  140. }
  141. /**
  142. * 创建一个类似于触摸屏的虚拟设备,返回文件描述符
  143. */
  144. static int create_touch_screen()
  145. {
  146. int fd,err;
  147. struct uinput_user_dev dev;
  148. fd = open("/dev/uinput", O_WRONLY|O_NONBLOCK);
  149. if (fd < 0) {
  150. printf("Error could not open /dev/uinput device: %s\n", strerror(errno));
  151. return -1;
  152. }
  153. // configure touch device event properties
  154. memset(&dev, 0, sizeof(dev));
  155. // 设备的别名
  156. strncpy(dev.name, "TouchScreen", UINPUT_MAX_NAME_SIZE);
  157. dev.id.version = 1;
  158. dev.id.bustype = BUS_USB;
  159. // 支持10点触摸
  160. dev.absmin[ABS_MT_SLOT] = 0;
  161. dev.absmax[ABS_MT_SLOT] = 9;
  162. dev.absmin[ABS_MT_TRACKING_ID] = 0;
  163. dev.absmax[ABS_MT_TRACKING_ID] = 65535;
  164. dev.absmin[ABS_MT_TOUCH_MAJOR] = 0;
  165. dev.absmax[ABS_MT_TOUCH_MAJOR] = 0xff;
  166. // 屏幕最小/大的X尺寸
  167. dev.absmin[ABS_MT_POSITION_X] = 0;
  168. dev.absmax[ABS_MT_POSITION_X] = gadget_x_logical_maximum;
  169. // 屏幕最小/大的Y尺寸
  170. dev.absmin[ABS_MT_POSITION_Y] = 0;
  171. dev.absmax[ABS_MT_POSITION_Y] = gadget_y_logical_maximum;
  172. //屏幕按下的压力值
  173. dev.absmin[ABS_MT_PRESSURE] = 0;
  174. dev.absmax[ABS_MT_PRESSURE] = 0xff;
  175. // Setup the uinput device
  176. err = ioctl(fd, UI_SET_EVBIT, EV_KEY);
  177. if (err < 0){
  178. goto err;
  179. }
  180. err = ioctl(fd, UI_SET_EVBIT, EV_REL);
  181. if (err < 0){
  182. goto err;
  183. }
  184. err = ioctl (fd, UI_SET_EVBIT, EV_ABS);
  185. if (err < 0){
  186. goto err;
  187. }
  188. err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_SLOT);
  189. if (err < 0){
  190. goto err;
  191. }
  192. err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
  193. if (err < 0){
  194. goto err;
  195. }
  196. err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
  197. if (err < 0){
  198. goto err;
  199. }
  200. err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
  201. if (err < 0){
  202. goto err;
  203. }
  204. err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
  205. if (err < 0){
  206. goto err;
  207. }
  208. err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
  209. if (err < 0){
  210. goto err;
  211. }
  212. err = ioctl (fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
  213. if (err < 0){
  214. goto err;
  215. }
  216. err = ioctl (fd, UI_SET_KEYBIT, BTN_TOUCH);
  217. if (err < 0){
  218. goto err;
  219. }
  220. /* Create input device into input sub-system */
  221. err = write(fd, &dev, sizeof(dev));
  222. if (err < 0){
  223. goto err;
  224. }
  225. err = ioctl(fd, UI_DEV_CREATE);
  226. if (err < 0){
  227. goto err;
  228. }
  229. return fd;
  230. err:
  231. printf("(%s) Failed to initialise\n",__func__);
  232. close(fd);
  233. }
  234. /**
  235. * 输出usb端点额外信息
  236. * @param config:usb端点伴随描述符
  237. */
  238. static void print_endpoint_comp(const struct libusb_ss_endpoint_companion_descriptor *ep_comp)
  239. {
  240. printf(" USB 3.0 Endpoint Companion:\n");
  241. printf(" bMaxBurst: %u\n", ep_comp->bMaxBurst);
  242. printf(" bmAttributes: %02xh\n", ep_comp->bmAttributes);
  243. printf(" wBytesPerInterval: %u\n", ep_comp->wBytesPerInterval);
  244. }
  245. /**
  246. * 输出usb端点信息
  247. * @param config:usb端点描述符
  248. */
  249. static void print_endpoint(const struct libusb_endpoint_descriptor *endpoint)
  250. {
  251. int i, ret;
  252. printf(" Endpoint:\n");
  253. // 端点地址:位[3:0]表示端点号,第7位是方向:0位输出、1为输入
  254. printf(" bEndpointAddress: %02xh\n", endpoint->bEndpointAddress);
  255. // 端点属性:位[1:0]值00表示控制、01表示等时、10表示批量、11表示中断
  256. printf(" bmAttributes: %02xh\n", endpoint->bmAttributes);
  257. // 本端点接受或发送的最大信息包的大小
  258. printf(" wMaxPacketSize: %u\n", endpoint->wMaxPacketSize);
  259. // 轮询数据传输端点的时间间隔,用在中断传输上,比如间隔时间查询鼠标的数据;
  260. printf(" bInterval: %u\n", endpoint->bInterval);
  261. printf(" bRefresh: %u\n", endpoint->bRefresh);
  262. printf(" bSynchAddress: %u\n", endpoint->bSynchAddress);
  263. // 遍历usb端点额外信息
  264. for (i = 0; i < endpoint->extra_length;) {
  265. if (LIBUSB_DT_SS_ENDPOINT_COMPANION == endpoint->extra[i + 1]) {
  266. struct libusb_ss_endpoint_companion_descriptor *ep_comp;
  267. // 获取端点伴随描述符的详细信息
  268. ret = libusb_get_ss_endpoint_companion_descriptor(NULL, endpoint, &ep_comp);
  269. if (LIBUSB_SUCCESS != ret)
  270. continue;
  271. // 输出usb端点信息
  272. print_endpoint_comp(ep_comp);
  273. // 释放usb端点伴随描述符
  274. libusb_free_ss_endpoint_companion_descriptor(ep_comp);
  275. }
  276. i += endpoint->extra[i];
  277. }
  278. }
  279. /**
  280. * 输出usb接口信息
  281. * @param config:usb接口描述符
  282. */
  283. static void print_altsetting(const struct libusb_interface_descriptor *interface)
  284. {
  285. uint8_t i;
  286. printf(" Interface:\n");
  287. // 接口的编号
  288. printf(" bInterfaceNumber: %u\n", interface->bInterfaceNumber);
  289. // 接口的设置的编号
  290. printf(" bAlternateSetting: %u\n", interface->bAlternateSetting);
  291. // 使用的端点个数(不包括端点0), 表示有多少个端点描述符
  292. printf(" bNumEndpoints: %u\n", interface->bNumEndpoints);
  293. // 接口类型
  294. printf(" bInterfaceClass: %u\n", interface->bInterfaceClass);
  295. // 接口子类型
  296. printf(" bInterfaceSubClass: %u\n", interface->bInterfaceSubClass);
  297. // 接口所遵循的协议
  298. printf(" bInterfaceProtocol: %u\n", interface->bInterfaceProtocol);
  299. // 描述该接口的字符串索引值
  300. printf(" iInterface: %u\n", interface->iInterface);
  301. // 遍历usb端点描述符,输出usb端点信息
  302. for (i = 0; i < interface->bNumEndpoints; i++)
  303. print_endpoint(&interface->endpoint[i]);
  304. }
  305. static void print_2_0_ext_cap(struct libusb_usb_2_0_extension_descriptor *usb_2_0_ext_cap)
  306. {
  307. printf(" USB 2.0 Extension Capabilities:\n");
  308. printf(" bDevCapabilityType: %u\n", usb_2_0_ext_cap->bDevCapabilityType);
  309. printf(" bmAttributes: %08xh\n", usb_2_0_ext_cap->bmAttributes);
  310. }
  311. static void print_ss_usb_cap(struct libusb_ss_usb_device_capability_descriptor *ss_usb_cap)
  312. {
  313. printf(" USB 3.0 Capabilities:\n");
  314. printf(" bDevCapabilityType: %u\n", ss_usb_cap->bDevCapabilityType);
  315. printf(" bmAttributes: %02xh\n", ss_usb_cap->bmAttributes);
  316. printf(" wSpeedSupported: %u\n", ss_usb_cap->wSpeedSupported);
  317. printf(" bFunctionalitySupport: %u\n", ss_usb_cap->bFunctionalitySupport);
  318. printf(" bU1devExitLat: %u\n", ss_usb_cap->bU1DevExitLat);
  319. printf(" bU2devExitLat: %u\n", ss_usb_cap->bU2DevExitLat);
  320. }
  321. static void print_bos(libusb_device_handle *handle)
  322. {
  323. struct libusb_bos_descriptor *bos;
  324. uint8_t i;
  325. int ret;
  326. ret = libusb_get_bos_descriptor(handle, &bos);
  327. if (ret < 0)
  328. return;
  329. printf(" Binary Object Store (BOS):\n");
  330. printf(" wTotalLength: %u\n", bos->wTotalLength);
  331. printf(" bNumDeviceCaps: %u\n", bos->bNumDeviceCaps);
  332. for (i = 0; i < bos->bNumDeviceCaps; i++) {
  333. struct libusb_bos_dev_capability_descriptor *dev_cap = bos->dev_capability[i];
  334. if (dev_cap->bDevCapabilityType == LIBUSB_BT_USB_2_0_EXTENSION) {
  335. struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension;
  336. ret = libusb_get_usb_2_0_extension_descriptor(NULL, dev_cap, &usb_2_0_extension);
  337. if (ret < 0)
  338. return;
  339. print_2_0_ext_cap(usb_2_0_extension);
  340. libusb_free_usb_2_0_extension_descriptor(usb_2_0_extension);
  341. } else if (dev_cap->bDevCapabilityType == LIBUSB_BT_SS_USB_DEVICE_CAPABILITY) {
  342. struct libusb_ss_usb_device_capability_descriptor *ss_dev_cap;
  343. ret = libusb_get_ss_usb_device_capability_descriptor(NULL, dev_cap, &ss_dev_cap);
  344. if (ret < 0)
  345. return;
  346. print_ss_usb_cap(ss_dev_cap);
  347. libusb_free_ss_usb_device_capability_descriptor(ss_dev_cap);
  348. }
  349. }
  350. libusb_free_bos_descriptor(bos);
  351. }
  352. /**
  353. * 输出usb接口信息
  354. * @param config:usb接口
  355. */
  356. static void print_interface(const struct libusb_interface *interface)
  357. {
  358. int i;
  359. // 遍历接口所支持的可选设置
  360. for (i = 0; i < interface->num_altsetting; i++)
  361. print_altsetting(&interface->altsetting[i]);
  362. }
  363. /**
  364. * 输出usb配置信息
  365. * @param config:usb配置描述符
  366. */
  367. static void print_configuration(struct libusb_config_descriptor *config)
  368. {
  369. uint8_t i;
  370. printf(" Configuration:\n");
  371. // 配置描述符的总长度,以字节为单位
  372. printf(" wTotalLength: %u\n", config->wTotalLength);
  373. // 配置所支持的接口数量
  374. printf(" bNumInterfaces: %u\n", config->bNumInterfaces);
  375. // 配置值,用于在设置或选择配置时标识该配置
  376. printf(" bConfigurationValue: %u\n", config->bConfigurationValue);
  377. // 配置描述符字符串的索引。可以使用该索引在设备的字符串描述符中找到配置描述符的字符串表示
  378. printf(" iConfiguration: %u\n", config->iConfiguration);
  379. // 供电模式的选择
  380. printf(" bmAttributes: %02xh\n", config->bmAttributes);
  381. // 设备从总线提取的最大电流
  382. printf(" MaxPower: %u\n", config->MaxPower);
  383. // 遍历usb接口描述符,输出usb接口信息
  384. for (i = 0; i < config->bNumInterfaces; i++)
  385. print_interface(&config->interface[i]);
  386. }
  387. /**
  388. * 输出usb设备信息
  389. * @param dev:usb设备
  390. * @param handle:该变量是libusb库中的设备句柄,用于操作被封装的 USB 设备
  391. * @param vendor_id:生产厂商ID
  392. * @param product_id:产品ID
  393. */
  394. static void print_device(libusb_device *dev, libusb_device_handle *handle, uint16_t vid, uint16_t pid)
  395. {
  396. struct libusb_device_descriptor desc;
  397. unsigned char string[256];
  398. const char *speed;
  399. int ret;
  400. uint8_t i;
  401. // 获取usb设备的速度
  402. switch (libusb_get_device_speed(dev)) {
  403. case LIBUSB_SPEED_LOW: speed = "1.5M"; break;
  404. case LIBUSB_SPEED_FULL: speed = "12M"; break;
  405. case LIBUSB_SPEED_HIGH: speed = "480M"; break;
  406. case LIBUSB_SPEED_SUPER: speed = "5G"; break;
  407. case LIBUSB_SPEED_SUPER_PLUS: speed = "10G"; break;
  408. default: speed = "Unknown";
  409. }
  410. // 获取usb设备描述符
  411. ret = libusb_get_device_descriptor(dev, &desc);
  412. if (ret < 0) {
  413. fprintf(stderr, "failed to get device descriptor");
  414. return;
  415. }
  416. if (!handle)
  417. libusb_open(dev, &handle);
  418. // 生产厂商ID、产品ID和指定的匹配
  419. if((desc.idVendor == vid) && (desc.idProduct == pid)){
  420. // 输出usb总线编号、设备地址、生产厂商ID、产品ID、速度信息
  421. printf("Dev (bus %u, device %u): %04X - %04X speed: %s\n",
  422. libusb_get_bus_number(dev), libusb_get_device_address(dev),
  423. desc.idVendor, desc.idProduct, speed);
  424. if (handle){
  425. // 输出生产厂商信息
  426. if (desc.iManufacturer) {
  427. // 获取string描述符
  428. ret = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, string, sizeof(string));
  429. if (ret > 0)
  430. printf(" Manufacturer: %s\n", (char *)string);
  431. }
  432. // 输出产品信息
  433. if (desc.iProduct) {
  434. // 获取string描述符
  435. ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct, string, sizeof(string));
  436. if (ret > 0)
  437. printf(" Product: %s\n", (char *)string);
  438. }
  439. // 输出序列号信息
  440. if (desc.iSerialNumber && verbose) {
  441. ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, string, sizeof(string));
  442. if (ret > 0)
  443. printf(" Serial Number: %s\n", (char *)string);
  444. }
  445. }
  446. if (verbose) {
  447. // 遍历配置描述符
  448. for (i = 0; i < desc.bNumConfigurations; i++) {
  449. struct libusb_config_descriptor *config;
  450. // 获取配置描述符
  451. ret = libusb_get_config_descriptor(dev, i, &config);
  452. if (LIBUSB_SUCCESS != ret) {
  453. printf(" Couldn't retrieve descriptors\n");
  454. continue;
  455. }
  456. // 输出配置描述符信息
  457. print_configuration(config);
  458. // 释放通过libusb_get_config_descriptor函数获取到的配置描述符
  459. libusb_free_config_descriptor(config);
  460. }
  461. // USB2.0以上协议 USB 2.0 的版本号是 0x0200
  462. if (handle && desc.bcdUSB >= 0x0201)
  463. print_bos(handle);
  464. }
  465. }
  466. if(handle){
  467. libusb_close(handle);
  468. }
  469. }
  470. /**
  471. * 用于测试指定名称的usb设备,设备存在返回0,设备不存在返回1
  472. * @param device_name:usb设备名称
  473. * @param vendor_id:生产厂商ID
  474. * @param product_id:产品ID
  475. */
  476. static int test_wrapped_device(const char *device_name, uint16_t vendor_id, uint16_t product_id)
  477. {
  478. libusb_device_handle *handle;
  479. int r, fd;
  480. // 打开设备文件
  481. fd = open(device_name, O_RDWR);
  482. if (fd < 0) {
  483. printf("Error could not open %s: %s\n", device_name, strerror(errno));
  484. return 1;
  485. }
  486. // 使用libusb提供的函数对设备进行封装、第三个参数是用于保存设备句柄的指针handle
  487. r = libusb_wrap_sys_device(NULL, fd, &handle);
  488. if (r) {
  489. printf("Error wrapping device: %s: %s\n", device_name, libusb_strerror(r));
  490. close(fd);
  491. return 1;
  492. }
  493. // 输出设备信息
  494. print_device(libusb_get_device(handle), handle, vendor_id, product_id);
  495. close(fd);
  496. return 0;
  497. }
  498. /**
  499. * 信号处理函数
  500. * @param signum:信号值
  501. */
  502. static void sig_handler(int signum)
  503. {
  504. switch (signum) {
  505. case SIGTERM:
  506. rcv_exit = 1;
  507. break;
  508. case SIGINT:
  509. rcv_exit = 1;
  510. break;
  511. case SIGUSR1:
  512. break;
  513. }
  514. }
  515. static void usage(char *program)
  516. {
  517. printf("%s - test usb data transfers to/from usb device\n",program);
  518. printf("Usage:\n");
  519. printf(" %s [options]\n", program);
  520. printf("options are:\n");
  521. printf("Common:\n");
  522. printf(" --help (or -h)\n");
  523. printf(" -v vendor_id\n");
  524. printf(" -p product_id\n");
  525. printf(" -d usb device name\n");
  526. printf(" -o usb gadget device name\n");
  527. }
  528. /**
  529. * 向模拟的usb触摸屏设备发送数据,每次发送5字节报告
  530. * [0]的D0就是左键,D1就是右键,D2就是中键
  531. * [1]为X轴低字节
  532. * [2]为X轴高字节
  533. * [3]为Y轴低字节
  534. * [4]为Y轴高字节
  535. * @param fd:usb gadget device的文件描述符,文件所指向的hid设备的报告描述符需要按照发送的数据格式配置
  536. * @param touch_screen_report_t: 报告内容
  537. *
  538. * 写入成功返回0,否则返回-1
  539. */
  540. static int send_usb_packet(int fd, struct touch_screen_report_t *report)
  541. {
  542. char data[5] = {0,0,0,0,0};
  543. data[0] = report->buttons;
  544. data[1] = report->x & 0xFF;
  545. data[2] = (report->x >> 8) & 0xFF;
  546. data[3] = report->y & 0xFF;
  547. data[4] = (report->y >> 8) & 0xFF;
  548. // printf("send data:\n");
  549. // for(int i=0;i<5;i++){
  550. // printf("0x%x ",data[i]);
  551. // }
  552. // printf("\n");
  553. // 写入失败
  554. if (write(fd, data, 5) != 5) {
  555. printf("Error write usb gadget device\n");
  556. return -1;
  557. }
  558. /* 伪造双击效果,鼠标点击间隔小于系统设置的鼠标双击的间隔时间 */
  559. // data[0] = 0;
  560. // if (write(fd, data, 5) != 5) {
  561. // printf("Error write usb gadget device\n");
  562. // return -1;
  563. // }
  564. return 0;
  565. }
  566. /**
  567. * 解析读取到的usb数据包,获取按键状态、x、y坐标,并转换为模拟的usb触摸屏的数据格式;
  568. * @param hid:hid报告描述符信息,用于解析读取到的usb数据包
  569. * @param data:读取的usb数据包
  570. * @param report:写入解析后的按键状态、x、y坐标
  571. *
  572. * 解析成功返回0,否则返回-1
  573. */
  574. static int parse_usb_package(const struct hid_descriptor_t *hid,const char *data, struct touch_screen_report_t *report)
  575. {
  576. if(!hid){
  577. printf("Error parse usb package, invalid hid descriptor.\n");
  578. return -1;
  579. }
  580. // logical_minimum-logical_maximum 映射到 0~0x7fff
  581. double x_scale = 1.0 * (gadget_x_logical_maximum-0) / (hid->x_logical_maximum - hid->x_logical_minimum);
  582. double y_scale = 1.0 * (gadget_y_logical_maximum-0) / (hid->y_logical_maximum - hid->y_logical_minimum);
  583. report->buttons = (data[hid->btn_offset/8] & (1<< hid->btn_offset%8)) >> (hid->btn_offset%8);
  584. report->x = (data[hid->index_x_low] | data[hid->index_x_high] << 8) * x_scale ;
  585. report->y = (data[hid->index_y_low] | data[hid->index_y_high] << 8) * y_scale;
  586. return 0;
  587. }
  588. /**
  589. * 读取usb数据包
  590. * @param vendor_id:生产厂商ID
  591. * @param product_id:产品ID
  592. * @param gadget_device_name:模拟的usb触摸屏设备
  593. */
  594. static int interrupt_data_rw(uint16_t vendor_id, uint16_t product_id,const char * gadget_device_name)
  595. {
  596. int kernelDriverDetached = 0;
  597. unsigned char data[64]={0};
  598. int length = 0;
  599. int r,j,fd_gadget = 0,fd_event = 0;
  600. libusb_device_handle *handle;
  601. struct touch_screen_report_t report;
  602. const struct hid_descriptor_t *hid = NULL;
  603. // 查找匹配的hid报告描述符信息
  604. for(int i=0; i< sizeof(hids) / sizeof(hids[0]); i++){
  605. if((hids[i].vid == vendor_id) && (hids[i].pid == product_id)){
  606. hid = &hids[i];
  607. }
  608. }
  609. if(!hid){
  610. printf("Error please config hid descriptor info for usb device vid:0x%x pid:0x%x \n", vendor_id, product_id);
  611. return -1;;
  612. }
  613. // 打开指定Vendor ID和Product ID的USB设备,并返回设备句柄
  614. handle = libusb_open_device_with_vid_pid(NULL, vendor_id, product_id);
  615. if (handle == NULL) {
  616. printf("libusb_open() failed\n");
  617. return -1;;
  618. }
  619. /* 驱动必须解绑定,否则数据由驱动程序处理 */
  620. if(libusb_kernel_driver_active(handle, 0)){
  621. printf("Kernel Driver Active\n");
  622. r = libusb_detach_kernel_driver(handle, 0);
  623. if (r == 0) {
  624. printf("Detach Kernel Driver\n");
  625. kernelDriverDetached = 1;
  626. }else{
  627. fprintf(stderr, "Error detaching kernel driver.\n");
  628. return -1;;
  629. }
  630. }
  631. /* 指定当前接口 */
  632. r = libusb_claim_interface(handle, 0);
  633. if (r != 0){
  634. fprintf(stderr, "Error claiming interface.\n");
  635. goto exit;
  636. }
  637. // 打开模拟的usb触摸屏设备
  638. if(gadget_device_name){
  639. // 打开设备文件
  640. fd_gadget = open(gadget_device_name, O_RDWR, 0666);
  641. if (fd_gadget < 0) {
  642. printf("Error could not open usb gadget device %s: %s\n", gadget_device_name, strerror(errno));
  643. return 1;
  644. }
  645. }
  646. // 创建一个类似于触摸屏的虚拟设备
  647. fd_event = create_touch_screen();
  648. // 退出标志位
  649. while(!rcv_exit){
  650. memset(data, 0, sizeof(data));
  651. /* 中断方式读取断点数据,指定端点地址、报告长度,超时时间设置为0无线等待,直至有值 */
  652. r = libusb_interrupt_transfer(handle, hid->endpoint_address, data, hid->report_length, &length, 0);
  653. if ((r < 0) || (length == 0)){
  654. printf("bulk recive error,r:%s length:%d\n", libusb_strerror(r),length);
  655. usleep(500000); // 单位微秒
  656. }else{
  657. printf("receive data:\n");
  658. for(j=0; j<length; j++) {
  659. printf("0x%x ",data[j]);
  660. }
  661. printf("\n");
  662. // 解析usb device数据包,获取按键按下状态、以及x、y坐标
  663. r = parse_usb_package(hid,data,&report);
  664. // 向模拟的usb触摸屏设备发送数据
  665. if(fd_gadget > 0 && r == 0){
  666. send_usb_packet(fd_gadget,&report);
  667. }
  668. // 向触摸屏输入设备发送数据
  669. if(fd_event > 0 && r == 0){
  670. touch_screen_pressed(fd_event, report.x, report.y, report.buttons);
  671. }
  672. }
  673. }
  674. // 关闭usb gadget设备
  675. if(fd_gadget > 0){
  676. close(fd_gadget);
  677. }
  678. // 关闭触摸屏输入设备
  679. if(fd_event > 0){
  680. close(fd_event);
  681. }
  682. /* 释放指定的接口 */
  683. r = libusb_release_interface(handle, 0);
  684. if (0 != r){
  685. fprintf(stderr, "Error releasing interface.\n");
  686. }
  687. exit:
  688. if(kernelDriverDetached){
  689. //恢复驱动绑定,否则鼠标不可用
  690. libusb_attach_kernel_driver(handle, 0);
  691. }
  692. libusb_close(handle);
  693. return r;
  694. }
  695. int main(int argc, char *argv[])
  696. {
  697. char *program = argv[0];
  698. int option;
  699. const char *usb_device_name = NULL; // usb设备名称 /dev/bus/usb/001/005
  700. const char *gadget_device_name = NULL; // 模拟usb设备
  701. libusb_device **devs;
  702. ssize_t cnt;
  703. int r, i,fd = 0;
  704. uint16_t vid=0, pid=0;
  705. libusb_device_handle *handle = NULL;
  706. static const struct option options[] = {
  707. { "vendid", required_argument, NULL, 'v' },
  708. { "productid", required_argument, NULL, 'p' },
  709. { "usbdevicename", required_argument, NULL, 'd' },
  710. { "gadgetdevicename", required_argument, NULL, 'o' },
  711. { "help", no_argument, NULL, 'h' },
  712. };
  713. /* Parse command line options, if any */
  714. while ((option = getopt_long_only(argc, argv,"hv:p:d:o:",options, NULL))){
  715. if (option == -1)
  716. break;
  717. switch (option) {
  718. case 'v':
  719. vid = strtoul(optarg, NULL, 0);
  720. break;
  721. case 'p':
  722. pid = strtoul(optarg, NULL, 0);
  723. break;
  724. case 'd':
  725. usb_device_name = optarg;
  726. break;
  727. case 'o':
  728. gadget_device_name = optarg;
  729. break;
  730. case 'h':
  731. usage(program);
  732. exit(EXIT_SUCCESS);
  733. break;
  734. default:
  735. printf("ERROR: Invalid command line option\n");
  736. usage(program);
  737. exit(EXIT_FAILURE);
  738. }
  739. }
  740. printf("vid:0x%x pid:0x%x usbdevicename:%s gadgetdevicename:%s\n",vid,pid,usb_device_name,gadget_device_name);
  741. // 测试模拟的usb触摸屏设备
  742. if(gadget_device_name){
  743. // 打开设备文件
  744. fd = open(gadget_device_name, O_RDWR, 0666);
  745. if (fd < 0) {
  746. printf("Error could not open usb gadget device %s: %s\n", gadget_device_name, strerror(errno));
  747. return 1;
  748. }
  749. close(fd);
  750. }
  751. // 初始化libusb库
  752. r = libusb_init(NULL);
  753. if (r < 0)
  754. return r;
  755. if (usb_device_name) {
  756. printf("test_wrapped_device\n");
  757. r = test_wrapped_device(usb_device_name,vid,pid);
  758. } else {
  759. // 获取usb设备列表,保存到devs
  760. cnt = libusb_get_device_list(NULL, &devs);
  761. if (cnt < 0) {
  762. libusb_exit(NULL);
  763. return 1;
  764. }
  765. // 遍历每一个usb设备,如果和指定的vid,pid匹配,输出usb设备信息
  766. for (i = 0; devs[i]; i++)
  767. print_device(devs[i], handle, vid, pid);
  768. // 释放由libusb_get_device_list函数分配的usb设备列表资源
  769. libusb_free_device_list(devs, 1);
  770. }
  771. // 将程序中的SIGINT信号(通常由用户按下 Ctrl+C 产生)交给名为sig_handler的函数处理
  772. signal(SIGINT, sig_handler);
  773. // 将程序中的SIGTERM信号(通常由操作系统或其他进程发送给目标进程来请求其正常终止)交给名为sig_handler的函数处理
  774. signal(SIGTERM, sig_handler);
  775. // read usb data
  776. interrupt_data_rw(vid,pid, gadget_device_name);
  777. libusb_exit(NULL);
  778. return r;
  779. }

该应用程序目前仅支持两款USB触摸屏,其中有我们实验中一直使用的USB触摸屏(PID=e5e3,VID=1a86),这里我将USB触摸屏的HID报告描述符的描述信息保存到了hids数组中;由于我仅仅关注HID报告描述符中第一个触摸点的状态以及坐标等信息,因此上面记录了这些信息在介绍的USB数据包中的索引。

  1. /* 存放不同usb触摸屏hid报告描述符解析后的信息 */
  2. static const struct hid_descriptor_t hids[] = {
  3. [0] = { 0x1a86, 0xe5e3, 0x82, 52, 8, 3, 4, 5, 6, 0, 0x1000, 0, 0x1000 },
  4. [1] = { 0x222a, 0x0001, 0x81, 52, 14, 2, 3, 4, 5, 0, 0x0780, 0, 0x0438 },
  5. };

其中:

(1) 0x1a86USB设备的idVendor

(2) 0xe5e3USB设备的idProduct

(3) 0x82USB通信的端点地址;

获取USB通信端点地址有两种方法:

方法一:可以通过cat /sys/bus/usb/devices/1-1.2/1-1.2:1.0/查看;

  1. root@SOM-RK3399v2:/# cat /sys/bus/usb/devices/1-1.2/
  2. 1-1.2:1.0/ bDeviceProtocol bNumInterfaces descriptors driver/ manufacturer ep_00/ idProduct busnum devpath idVendor ......
  3. root@SOM-RK3399v2:/etc/profile.d# cat /sys/bus/usb/devices/1-1.2/1-1.2\:1.0/
  4. 0003:1A86:E5E3.0007/ bInterfaceNumber bInterfaceSubClass driver/ modalias
  5. authorized bInterfaceClass bInterfaceProtocol bNumEndpoints ep_82/
  6. ......

这里1-1.2表示的使用的USB触摸屏设备(开发板插入USB设备时,dmesg可以看到1-1.2),该目录下文件:

  • 1-1.2:1.0目录下为USB接口信息;
  • ep_00表示枚举设备时使用的通信端点地址;
  • descriptors存放的USB设备描述符。

如果USB设备下有多个USB接口,则需要挨个查看确定为触摸功能的那个接口;那如何确认哪个接口是触摸功能呢?可以通过查看接口的HID报告描述符信息来确定;

  1. root@SOM-RK3399v2:/# hexdump /sys/bus/usb/devices/1-1.2/1-1.2:1.0/0003:1A86:E5E3.0007/report_descriptor
  2. 0000000 0d05 0409 01a1 0185 2209 02a1 4209 0015
  3. 0000010 0125 0175 0195 0281 0795 0181 0875 5109
  4. 0000020 0195 0281 0105 0026 7510 5510 650e 0911
  5. 0000030 3530 4600 0879 0281 0026 4610 054c 3109
  6. 0000040 0281 0d05 4809 0281 09c0 a122 0902 1542
  7. 0000050 2500 7501 9501 8101 9502 8107 7501 0908
  8. 0000060 9551 8101 0502 2601 1000 1075 0e55 1165
  9. 0000070 3009 0035 7946 8108 2602 1000 4c46 0905
  10. 0000080 8131 0502 090d 8148 c002 2209 02a1 4209
  11. 0000090 0015 0125 0175 0195 0281 0795 0181 0875
  12. ....

通过分析HID报告描述符,来判断该描述符描述的是不是用于描述触摸屏;

  1. 0x05, 0x0d, // USAGE_PAGE (Digitizer)
  2. 0x09, 0x04, // USAGE (Touch Screen)
  3. 0xa1, 0x01, // COLLECTION (Application)

如果该接口是USB触摸屏功能,那么找到USB接口目录下的ep_xx目录,那么xx就是端口号,比如这里就是82,16进制。

  1. root@SOM-RK3399v2:/# ls /sys/bus/usb/devices/1-1.2/1-1.2:1.0/ | grep "ep"
  2. ep_82

方法二:usb_test应用程序启动时会输出接口所使用的的bEndpointAddress

需要注意的是如果USB通信端点地址错误,可能出现如下错误:

bulk recive error,r:Input/Output Error length:0

(4) 52:USB触摸屏发送的数据包的长度;通过usbmon抓取到的数据包中有USB数据包的长度;

  1. ffffffc0c88dab00 1925775309 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
  2. |__ 输入 |_______ 实际读取的长度

需要注意的是如果接收缓存区大小设置的小于实际读取的数据包的长度,可能出现如下错误:

bulk recive error,r:Overflow length:0

(5) 8:按键状态在接收到的USB数据包中的索引为1的字节的位[0];

(6) 3:x坐标低8位在接收到的USB数据包中的索引为3;

(7) 4:x坐标高8位在接收到的USB数据包中的索引为4;

(8) 5:y坐标低8位在接收到的USB数据包中的索引为5;

(9) 6:y坐标高8位在接收到的USB数据包中的索引为6;

(10) 0:x坐标逻辑最小值,即接收到的USB数据包中x坐标的最小值;

(11) 0x1000:x逻辑最大值,即接收到的USB数据包中x坐标的最大值;

(12) 0:y坐标逻辑最小值,即接收到的USB数据包中y坐标的最小值;

(13) 0x1000:y逻辑最大值,即接收到的USB数据包中y坐标的最大值;

如果需要支持其他的USB触摸屏,需要自行获取自己使用的USB触摸屏的HID报告描述符,并按照上面结构体填入相应信息。

编译应用程序:

  1. root@SOM-RK3399v2:/opt/libusb# gcc -o usb_test usb_test.c -I/usr/include/libusb-1.0 -lusb-1.0
  2. root@SOM-RK3399v2:/opt/libusb# ls -l usb_test
  3. -rwxr-xr-x 1 root root 25192 Oct 15 10:46 usb_test
3.2 测试
3.2.1 查看USB数据包

首先需要将USB触摸屏(PID=e5e3,VID=1a86)连接到开发板,输入如下命令:

  1. root@SOM-RK3399v2:/opt/libusb# ./usb_test -v 0x1a86 -p 0xe5e3
  2. vid:0x1a86 pid:0xe5e3 usbdevicename:(null) gadgetdevicename:(null)
  3. Dev (bus 1, device 4): 1A86 - E5E3 speed: 12M
  4. Manufacturer: wch.cn
  5. Product: USB2IIC_CTP_CONTROL
  6. Configuration:
  7. wTotalLength: 34
  8. bNumInterfaces: 1
  9. bConfigurationValue: 1
  10. iConfiguration: 0
  11. bmAttributes: 80h
  12. MaxPower: 32
  13. Interface: // USB触摸屏设备接口可能有多个,如果有多个,只关注bInterfaceSubClass=0/1、bInterfaceProtocol=0/2、bInterfaceClass=3的接口
  14. bInterfaceNumber: 0
  15. bAlternateSetting: 0
  16. bNumEndpoints: 1
  17. bInterfaceClass: 3 // hid
  18. bInterfaceSubClass: 0 // 子类为0
  19. bInterfaceProtocol: 0 // 协议为0
  20. iInterface: 0
  21. Endpoint:
  22. bEndpointAddress: 82h // 端点地址
  23. bmAttributes: 03h
  24. wMaxPacketSize: 64
  25. bInterval: 1
  26. bRefresh: 0
  27. bSynchAddress: 0
  28. Kernel Driver Active
  29. Detach Kernel Driver

其中:

  • v:后面是USB设备的idVendor
  • p:后面是USB设备的idProduct

此时如果单个手指点击触摸屏,将会输出接收到的数据包:

  1. receive data:
  2. 0x1 0x1 0x0 0x99 0x5 0xcc 0x6 0x30 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1 0x1 0x1
  3. receive data:
  4. 0x1 0x1 0x0 0x99 0x5 0xcc 0x6 0x30 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1 0x1 0x1
  5. receive data:
  6. 0x1 0x1 0x0 0x99 0x5 0xcc 0x6 0x30 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1 0x1 0x1
  7. receive data:
  8. 0x1 0x1 0x0 0x99 0x5 0xcc 0x6 0x30 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1 0x1 0x1

可以看到每次接收到的数据包含52个字节,在上一篇博客中我们已经说过USB触摸屏(PID=e5e3,VID=1a86)ID为1的报告实际读取的数据长度为52个字节:

字节索引描述
字节0报告ID(1)
字节1~8描述第一个触摸点
字节9~16描述第二个触摸点
字节17~24描述第三个触摸点
字节25~32描述第四个触摸点
字节33~40描述第五个触摸点
字节41~48描述第六个触摸点
字节49~50描述检测和更新触摸点状态的时间间隔
字节51描述触摸点数量

可以看到最后一个字节表示的是同时触摸点的数量,这里为1,因为我是单指按下的。

我们尝试同时6个手指点击触摸屏:

  1. receive data:
  2. 0x1 0x1 0x0 0xf 0xd 0x99 0x0 0x30 0x0 0x1 0x1 0xe1 0x8 0xe6 0x3 0x30 0x0 0x1 0x2 0x85 0x2 0x22 0x3 0x30 0x0 0x1 0x3 0x75 0x5 0x77 0x2 0x30 0x0 0x1 0x4 0xc7 0xb 0xb3 0xd 0x30 0x0 0x1 0x5 0x75 0x6 0x55 0x5 0x30 0x0 0x1 0x1 0x6
  3. receive data:
  4. 0x1 0x1 0x0 0x33 0xd 0xd5 0x0 0x30 0x0 0x1 0x1 0x0 0x9 0xee 0x3 0x30 0x0 0x1 0x2 0x8a 0x2 0x4c 0x3 0x30 0x0 0x1 0x3 0x61 0x5 0x22 0x3 0x30 0x0 0x0 0x4 0xc7 0xb 0xb3 0xd 0x30 0x0 0x1 0x5 0x70 0x6 0xee 0x5 0x30 0x0 0x1 0x1 0x6

可以看到最后一个字节为6,需要注意的是我使用的USB触摸屏最多支持同时有6个触摸点。

如果需要终止应用程序,输入CTRL + C,同时点击触摸屏即可。

3.2.2 模拟USB触摸

USB触摸屏(PID=e5e3,VID=1a86)连接到开发板,同时通过USB OTG线将USB3.0 Type-C PHY1接口连接到PC

此时PC识别到一个USB设备插入,在设备管理器查看该USB设备信息可以看到VIDPID就是我们在hid_keyboard_mouse.sh中设置的。

img

输入如下命令:

  1. root@SOM-RK3399v2:/opt/libusb# ./usb_test -v 0x1a86 -p 0xe5e3 -o /dev/hidg2
  2. vid:0x1a86 pid:0xe5e3 usbdevicename:(null) gadgetdevicename:/dev/hidg2
  3. Dev (bus 1, device 4): 1A86 - E5E3 speed: 12M
  4. Manufacturer: wch.cn
  5. Product: USB2IIC_CTP_CONTROL
  6. Configuration:
  7. wTotalLength: 34
  8. bNumInterfaces: 1
  9. bConfigurationValue: 1
  10. iConfiguration: 0
  11. bmAttributes: 80h
  12. MaxPower: 32
  13. Interface:
  14. bInterfaceNumber: 0
  15. bAlternateSetting: 0
  16. bNumEndpoints: 1
  17. bInterfaceClass: 3
  18. bInterfaceSubClass: 0
  19. bInterfaceProtocol: 0
  20. iInterface: 0
  21. Endpoint:
  22. bEndpointAddress: 82h
  23. bmAttributes: 03h
  24. wMaxPacketSize: 64
  25. bInterval: 1
  26. bRefresh: 0
  27. bSynchAddress: 0
  28. Kernel Driver Active
  29. Detach Kernel Driver

其中:

  • v:后面是USB设备的idVendor
  • p:后面是USB设备的idProduct
  • o:后面是模拟的USB触摸屏设备;

此时我们点击触摸屏,就会实现控制PC的效果。等价于将USB触摸屏(PID=e5e3,VID=1a86)连接到PC

补充:由于我们使用的触摸屏比较小,而PC的屏幕比较大,因此在触摸屏上移动一小段位置,PC屏幕上鼠标箭头就会移动很多,这里存在一个缩放比例的问题,因此想选中PC上某一图标也是比较麻烦的。为此可以考虑将触摸屏的HDMI接口连接到PC,这样缩放比例就为1,操作起来就会很方便。

测试发现一个问题,双击经常会失灵。因为应用程序程序读取USB触摸屏PID=e5e3,VID=1a86)的数据包,解析后再通过模拟的USB触摸屏设备发送到PC。由于每一个数据包从接收到发送处理的时间间隔较长,在windows系统中当接收到的两次连续点击的数据包时间间隔超过一定时就被当做点击处理了,目前有三种优化思路:

  • 减少应用程序的处理时长,比如屏蔽掉printf函数;
  • 记录上一个触摸点以及发生的时间,与当前触摸点发生时间进行比较,如果小于某个时间间隔,先模拟上一个触摸点的按下和松开(具体实现需要自己调试测试);
  • windows系统:控制面板 --> 硬件和声音 --> 设备和打印机 --> 鼠标 --> 双击速度(设置为最慢);

如果需要终止应用程序,输入CTRL + C,同时点击触摸屏即可。

参考文章

[1] https://github.com/libusb/libusb

[2] Developers will wish to consult the API documentation

[3] Linux模拟触摸滑动以及按下

[4] i.MX6ULL】驱动开发13——电容触摸驱动实践(下)

[5] Linux获取/dev/input目录下的event对应的设备【转】

[6] 利用uinput模拟touchscreen

[7] input subsystem (五) CTP多点触摸协议

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/640599
推荐阅读
相关标签
  

闽ICP备14008679号