RaspberryZero当作键鼠设备

项目运行环境

编译所需模块

  1. 安装内核编译所需环境
1
sudo apt install bc bison flex libssl-dev git raspberrypi-kernel-headers
  1. 下载官方内核里g_hid实现的源码和依赖的头文件。请根据你的系统选择内核分支,我这里是rpi-4.19.y分支
1
2
3
# 官方内核源码地址:https://github.com/raspberrypi/linux
wget https://raw.githubusercontent.com/raspberrypi/linux/rpi-4.19.y/drivers/usb/gadget/legacy/hid.c
wget https://raw.githubusercontent.com/raspberrypi/linux/rpi-4.19.y/drivers/usb/gadget/function/u_hid.h
  1. 将下面内容复制当前目录下的补丁文件hid.patch中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
diff -uprN a/drivers/usb/gadget/legacy/hid.c b/drivers/usb/gadget/legacy/hid.c
--- a/drivers/usb/gadget/legacy/hid.c
+++ b/drivers/usb/gadget/legacy/hid.c
@@ -15,15 +15,15 @@
#include <linux/usb/composite.h>
#include <linux/usb/g_hid.h>

-#define DRIVER_DESC "HID Gadget"
+#define DRIVER_DESC "Logitech USB Receiver" // PRODUCT_IDX
#define DRIVER_VERSION "2010/03/16"

#include "u_hid.h"

/*-------------------------------------------------------------------------*/

-#define HIDG_VENDOR_NUM 0x0525 /* XXX NetChip */
-#define HIDG_PRODUCT_NUM 0xa4ac /* Linux-USB HID gadget */
+#define HIDG_VENDOR_NUM 0x046d /* idVendor */
+#define HIDG_PRODUCT_NUM 0xc53f /* idProduct */

/*-------------------------------------------------------------------------*/

@@ -67,7 +67,7 @@

/* string IDs are assigned dynamically */
static struct usb_string strings_dev[] = {
- [USB_GADGET_MANUFACTURER_IDX].s = "",
+ [USB_GADGET_MANUFACTURER_IDX].s = "Logitech",
[USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC,
[USB_GADGET_SERIAL_IDX].s = "",
{ } /* end of list */
@@ -254,6 +254,107 @@

/****************************** Some noise ******************************/

+// 防止移除模块时报找不到release函数的错误
+static void release_hid_dev(struct device *dev) {}
+
+// 添加键盘报告描述符
+static struct hidg_func_descriptor keyboard_report_data = {
+ .subclass = 0, /* No subclass */
+ .protocol = 1, /* 1 Keyboard */ /* 2 Mouse*/
+ .report_length = 8,
+ .report_desc_length = 63,
+ .report_desc = {
+ 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+ 0x09, 0x06, // USAGE (Keyboard)
+ 0xa1, 0x01, // COLLECTION (Application)
+ 0x05, 0x07, // USAGE_PAGE (Keyboard)
+ 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
+ 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
+ 0x15, 0x00, // LOGICAL_MINIMUM (0)
+ 0x25, 0x01, // LOGICAL_MAXIMUM (1)
+ 0x75, 0x01, // REPORT_SIZE (1)
+ 0x95, 0x08, // REPORT_COUNT (8)
+ 0x81, 0x02, // INPUT (Data,Var,Abs)
+ 0x95, 0x01, // REPORT_COUNT (1)
+ 0x75, 0x08, // REPORT_SIZE (8)
+ 0x81, 0x03, // INPUT (Cnst,Var,Abs)
+ 0x95, 0x05, // REPORT_COUNT (5)
+ 0x75, 0x01, // REPORT_SIZE (1)
+ 0x05, 0x08, // USAGE_PAGE (LEDs)
+ 0x19, 0x01, // USAGE_MINIMUM (Num Lock)
+ 0x29, 0x05, // USAGE_MAXIMUM (Kana)
+ 0x91, 0x02, // OUTPUT (Data,Var,Abs)
+ 0x95, 0x01, // REPORT_COUNT (1)
+ 0x75, 0x03, // REPORT_SIZE (3)
+ 0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
+ 0x95, 0x06, // REPORT_COUNT (6)
+ 0x75, 0x08, // REPORT_SIZE (8)
+ 0x15, 0x00, // LOGICAL_MINIMUM (0)
+ 0x25, 0x65, // LOGICAL_MAXIMUM (101)
+ 0x05, 0x07, // USAGE_PAGE (Keyboard)
+ 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
+ 0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
+ 0x81, 0x00, // INPUT (Data,Ary,Abs)
+ 0xc0 // END_COLLECTION
+ }
+};
+
+// 定义键盘设备
+static struct platform_device keyboard_device = {
+ .name = "hidg",
+ .id = 0,
+ .num_resources = 0,
+ .resource = 0,
+ .dev.platform_data = &keyboard_report_data,
+ .dev.release = &release_hid_dev,
+};
+
+// 添加鼠标报告描述符
+static struct hidg_func_descriptor mouse_report_data = {
+ .subclass = 1, /* No subclass */
+ .protocol = 2, /* 1 Keyboard */ /* 2 Mouse*/
+ .report_length = 4,
+ .report_desc_length = 52,
+ .report_desc = {
+ 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+ 0x09, 0x02, // USAGE (Mouse)
+ 0xa1, 0x01, // COLLECTION (Application)
+ 0x09, 0x01, // USAGE (Pointer)
+ 0xa1, 0x00, // COLLECTION (Physical)
+ 0x05, 0x09, // USAGE_PAGE (Button)
+ 0x19, 0x01, // USAGE_MINIMUM (Button 1)
+ 0x29, 0x03, // USAGE_MAXIMUM (Button 3)
+ 0x15, 0x00, // LOGICAL_MINIMUM (0)
+ 0x25, 0x01, // LOGICAL_MAXIMUM (1)
+ 0x95, 0x03, // REPORT_COUNT (3)
+ 0x75, 0x01, // REPORT_SIZE (1)
+ 0x81, 0x02, // INPUT (Data,Var,Abs)
+ 0x95, 0x01, // REPORT_COUNT (1)
+ 0x75, 0x05, // REPORT_SIZE (5)
+ 0x81, 0x03, // INPUT (Cnst,Var,Abs)
+ 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
+ 0x09, 0x30, // USAGE (X)
+ 0x09, 0x31, // USAGE (Y)
+ 0x09, 0x38, // USAGE (Wheel)
+ 0x15, 0x81, // LOGICAL_MINIMUM (-127)
+ 0x25, 0x7f, // LOGICAL_MAXIMUM (127)
+ 0x75, 0x08, // REPORT_SIZE (8)
+ 0x95, 0x03, // REPORT_COUNT (3)
+ 0x81, 0x06, // INPUT (Data,Var,Rel)
+ 0xc0, // END_COLLECTION
+ 0xc0 // END_COLLECTION
+ }
+};
+
+// 定义鼠标设备
+static struct platform_device mouse_device = {
+ .name = "hidg",
+ .id = 1,
+ .num_resources = 0,
+ .resource = 0,
+ .dev.platform_data = &mouse_report_data,
+ .dev.release = &release_hid_dev,
+};

static struct usb_composite_driver hidg_driver = {
.name = "g_hid",
@@ -280,6 +376,22 @@
{
int status;

+ // 申请注册键盘设备
+ status = platform_device_register(&keyboard_device);
+ if (status < 0) {
+ printk("Failed: Gadget HID Keyboard Device Register Failed!\n");
+ platform_device_unregister(&keyboard_device);
+ return status;
+ }
+
+ // 申请注册鼠标设备
+ status = platform_device_register(&mouse_device);
+ if (status < 0) {
+ printk("Failed: Gadget HID Mouse Device Register Failed!\n");
+ platform_device_unregister(&mouse_device);
+ return status;
+ }
+
status = platform_driver_probe(&hidg_plat_driver,
hidg_plat_driver_probe);
if (status < 0)
@@ -297,5 +409,7 @@
{
usb_composite_unregister(&hidg_driver);
platform_driver_unregister(&hidg_plat_driver);
+ platform_device_unregister(&keyboard_device); // 销毁释放键鼠设备
+ platform_device_unregister(&mouse_device);
}
module_exit(hidg_cleanup);
  1. 将下面内容复制当前目录下的Makefile中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OBJECT_HEADER := input
OBJECT_SOURCE := $(shell pwd)
KERNEL_HEADER := $(shell uname -r)
KERNEL_SOURCE := /usr/src/linux-headers-$(KERNEL_HEADER)/

obj-m += $(OBJECT_HEADER).o

all:
make -C $(KERNEL_SOURCE) M=$(OBJECT_SOURCE) modules
clean:
make -C $(KERNEL_SOURCE) M=$(OBJECT_SOURCE) clean
install:
-rmmod $(OBJECT_HEADER)
-insmod $(OBJECT_HEADER).ko
uninstall:
-rmmod $(OBJECT_HEADER)
  1. 打补丁后编译该模块
1
2
3
4
5
6
7
8
# 给下载后的源码打补丁使生成的模块加载后注册一个键盘和鼠标
patch hid.c < hid.patch

# 可更改上面Makefile中的OBJECT_HEADER为你想要生成的模块名,这里为input
mv hid.c input.c

# 编译完成后会在当前目录下生成名为input.ko的模块
make
  1. 如果想用生成的模块来替换自带的g_hid模块,那么请如下操作
1
2
3
4
5
6
7
# 先把Makefile中OBJECT_HEADER := input的input为g_hid
# 再把input.c重命名为g_hid.c后再编译,编译完成后执行下面替换自带模块并加载

cd /lib/modules/`uname -r`/kernel/drivers/usb/gadget/legacy/
sudo mv g_hid.ko g_hid.ko.bak
sudo cp /home/leux/hid/g_hid.ko ./g_hid.ko
sudo modprobe g_hid

加载内核模块

  1. 加载模块
1
2
3
4
5
6
7
8
9
10
11
# 加载input模块后被插入的主机上可发现多了键鼠设备
sudo modprobe libcomposite # 先加载依赖的模块
sudo insmod input.ko # 加载生成的.ko文件的路径

# 可选是否安装到/lib/modules/`uname -r`/kernel/drivers/usb/gadget/legacy/下
sudo make install

# 查看新增的hidg0键盘设备和hidg1鼠标设备,通过对它们写入指定格式的字符可实现键盘输入
ls -al /dev/hid*
# crw------- 1 root root 242, 0 Aug 23 00:30 /dev/hidg0
# crw------- 1 root root 242, 1 Aug 23 00:30 /dev/hidg1
  1. 卸载模块
1
2
sudo rmmod input.ko	# 使用insmod加载的不在默认目录的模块必须用rmmod卸载
sudo make uninstall # 或者在项目文件夹下用此命令
  1. 加载模块成功后在ZERO的内核日志会看到类似如下的信息
1
2
3
4
5
6
7
# dmesg
[ 2029.721497] g_hid gadget: Zero, version: 2019/08/20
[ 2029.721512] g_hid gadget: g_hid ready
[ 2029.721527] dwc2 20980000.usb: bound driver g_hid
[ 2029.899565] dwc2 20980000.usb: new device is high-speed
[ 2029.935530] dwc2 20980000.usb: new address 17
[ 2029.954008] g_hid gadget: high-speed config #1: HID Gadget

模块开机自启

  1. 假设你已经用生成的模块替换自带的g_hid模块,那么可如下使其开机自动加载

  2. 先在/boot/config.txt文末添加 dtoverlay=dwc2 开启dwc2,注意使用g_hid模块时不能同时使用g_ether模块

  3. 模块g_hid依赖libcomposite模块,加载模块 modprobe g_hid,移除模块 modprobe -r g_hid

  4. 可使用 modinfo g_hid.ko | grep depend 来查看g_hid.ko模块的依赖

  5. 开机加载g_hid模块,在/etc/modules文末添加如下两行:

1
2
libcomposite
g_hid

键鼠实机测试

  1. 开机加载模块后将Zero靠近HDMI接口的MicroUSB通过数据线连接到Windows主机的USB口上

  2. 可以看到我们的树莓派zero已被成功识别为:USB HID v1.01 Zero [Raspberry],“idVendor=1d6b, idProduct=0104” 的键鼠设备

  3. 在ZERO中使用root用户输入以下指令可以在被插入的电脑里接收到按键动作

1
2
3
4
5
6
7
8
# 按下 按键a 并松开 按键 (输出一个 a)
# echo -ne "\0\0\x4\0\0\0\0\0\0\0\0\0\0\0\0\0" > /dev/hidg0

# 按下 按键a (连续输出 a)
# echo -ne "\0\0\x4\0\0\0\0\0" > /dev/hidg0

# 松开 按键 (停止连续输出 a)
# echo -ne "\0\0\x0\0\0\0\0\0" > /dev/hidg0
  1. 运行测试程序来模拟其他功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 下载测试程序源码
wget https://raw.githubusercontent.com/pelya/android-keyboard-gadget/master/hid-gadget-test/jni/hid-gadget-test.c

# 编译测试程序源码
gcc hid-gadget-test.c -o hid-gadget-test

# 使用如下命令进入键盘模拟环境
./hid-gadget-test /dev/hidg0 keyboard
a # 输出小写 a
enter # 输出回车
left-shift a # 输出大写 A

# 使用如下命令进入鼠标模拟环境
./hid-gadget-test /dev/hidg1 mouse
--hold # 保持按下
--b1 # 点击鼠标,b1左键,b2右键,b3中键
80 30 # 向右移动80后往下移动30。负往左和上,正往右和下
--b1 -80 -30 # 向左移动80后往上移动30后点击右键

# 更多使用方法请查看帮助

出现的问题点

  1. 由于无法同时使用两种模式,请不要使用g_ether模块通过USB连接到ZERO,且必须先添加dtoverlay=dwc2到/boot/config.txt后启动时加载才有效,在终端里加载dwc2模块是没有用的

  2. dmesg出现警告:g_hid: loading out-of-tree module taints kernel,这是内核3.7后增加的为了Kernel安全的签名机制,模块能正常加载不会有影响的

  3. insmod时出现错误: could not insert module input.ko: Unknown symbol in module,请执行modprobe libcomposite加载依赖模块

  4. gadget的驱动依赖于udc模块和composite模块,而composite模块又依赖于udc模块,udc是硬件接口模块,直接操作USB寄存器等。composite为上层驱动提供操作UDC

  5. 必须通过root用户才能向/dev/hidg设备写入字符,使用sudo我这里无效