You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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读写报告,无需手动处理设备节点。

核心步骤:

  1. 定义匹配目标设备的VID/PID列表
  2. 实现HID驱动的probe/remove函数,完成设备初始化与清理
  3. 注册输入报告回调函数处理接收的数据
  4. 使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 16:02:07