如何在Linux内核模块中发送HID指令并读取HID数据?
内核态读写HID设备的实现方案
一、先解决ioctl编译报错问题
你遇到的implicit declaration of function 'ioctl'错误,是因为内核态使用ioctl需要包含正确的头文件,并且遵循内核态函数原型:
- 必须包含头文件
<linux/ioctl.h>和对应的设备头文件(比如HIDRAW需要<linux/hidraw.h>) - 内核态ioctl的原型为:
long ioctl(struct file *file, unsigned int cmd, unsigned long arg);
不过更推荐直接使用内核HID子系统提供的封装接口,而非手动调用ioctl,下面分两种方案说明。
二、方案1:基于内核HID子系统实现(推荐)
这是内核态操作HID设备的标准方式,通过注册HID驱动匹配指定VID/PID,利用子系统提供的API读写报告,无需手动处理设备节点。
核心步骤:
- 定义匹配目标设备的VID/PID列表
- 实现HID驱动的probe/remove函数,完成设备初始化与清理
- 注册输入报告回调函数处理接收的数据
- 使用
hid_output_report发送输出报告
代码示例:
#include <linux/hid.h> #include <linux/module.h> // 匹配你的目标HID设备VID/PID static const struct hid_device_id target_hid_ids[] = { { HID_USB_DEVICE(0x2752, 0x0012) }, { } // 终止符 }; MODULE_DEVICE_TABLE(hid, target_hid_ids); // 处理接收到的输入报告 static int hid_report_handler(struct hid_device *hdev, struct hid_report *report) { // 打印接收到的报告数据(可替换为RPC发送逻辑) dev_info(&hdev->dev, "Received input report: %*ph\n", report->size, report->value); return 0; } static int hid_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret; // 解析HID设备描述符 ret = hid_parse(hdev); if (ret) { dev_err(&hdev->dev, "Failed to parse HID device\n"); return ret; } // 启动HID硬件 ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { dev_err(&hdev->dev, "Failed to start HID hardware\n"); return ret; } // 注册输入报告回调 hdev->report_fetcher = hid_report_handler; // 发送你需要的输出报告(对应用户态的send-output指令) u8 output_report[] = {0x03, 0x53, 0x02, 0x58}; ret = hid_output_report(hdev, output_report, sizeof(output_report), HID_OUTPUT_REPORT); if (ret < 0) { dev_err(&hdev->dev, "Failed to send output report\n"); } return 0; } static void hid_remove(struct hid_device *hdev) { hid_hw_stop(hdev); } // 定义HID驱动结构体 static struct hid_driver target_hid_driver = { .name = "target-hid-driver", .id_table = target_hid_ids, .probe = hid_probe, .remove = hid_remove, }; // 注册HID驱动 module_hid_driver(target_hid_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Kernel driver for specific HID device");
三、方案2:内核态直接操作HIDRAW设备(不推荐)
如果必须模拟用户态HIDRAW的操作逻辑,可以通过内核态文件操作接口打开HIDRAW设备节点,使用vfs_read/vfs_write/vfs_ioctl完成读写。
注意事项:
- 需要提前确定HIDRAW设备节点路径(如
/dev/hidraw0),但节点编号可能随设备插拔变化,需额外处理 - 内核态操作文件需注意上下文与权限,避免死锁或安全问题
代码片段:
#include <linux/fs.h> #include <linux/ioctl.h> #include <linux/hidraw.h> #include <linux/module.h> static struct file *hidraw_fp; static int __init hidraw_init(void) { loff_t pos = 0; ssize_t ret; // 打开HIDRAW设备节点 hidraw_fp = filp_open("/dev/hidraw0", O_RDWR | O_NONBLOCK, 0); if (IS_ERR(hidraw_fp)) { pr_err("Failed to open hidraw device\n"); return PTR_ERR(hidraw_fp); } // 发送输出报告 u8 output_buf[] = {0x03, 0x53, 0x02, 0x58}; ret = vfs_write(hidraw_fp, output_buf, sizeof(output_buf), &pos); if (ret < 0) { pr_err("Failed to send output report\n"); filp_close(hidraw_fp, NULL); return ret; } // 读取输入报告(需处理非阻塞逻辑) u8 input_buf[8]; ret = vfs_read(hidraw_fp, input_buf, sizeof(input_buf), &pos); if (ret > 0) { pr_info("Received input report: %*ph\n", (int)ret, input_buf); // 此处添加RPC发送逻辑 } return 0; } static void __exit hidraw_exit(void) { if (!IS_ERR(hidraw_fp)) { filp_close(hidraw_fp, NULL); } } module_init(hidraw_init); module_exit(hidraw_exit); MODULE_LICENSE("GPL");
四、关键提醒
- 优先使用方案1,它是内核HID子系统的标准用法,自动处理设备热插拔、匹配,稳定性更高
- 内核态发送RPC到虚拟机管理程序时,需注意使用对应的hypervisor提供的内核接口(如KVM的virtio或自定义RPC通道),避免用户态调用
内容的提问来源于stack exchange,提问作者Aaron Moreno
相关产品推荐
相关产品推荐

