WinWing舵踏板LED无法通过HID访问,内核驱动无效求解决方案
问题分析与解决
你的核心问题并非需要修改硬件的报告描述符,而是当前内核驱动存在几处关键错误,导致LED控制逻辑无法正常工作。以下是具体问题和修复方案:
驱动中的关键错误
1. LED结构体数组内存分配不足
你在winwing_probe中分配winwing_drv_data时,计算的大小是struct_size(data, leds, 3),但你的led_info定义了4个LED(索引0-3),这会导致第4个LED的结构体越界,初始化时出现未知错误。
2. 互斥锁未初始化
winwing_drv_data中的lock互斥锁没有调用mutex_init进行初始化,使用时会触发内核错误。
3. 错误的日志级别
winwing_input_configured中使用hid_err打印"init led done",这属于正常信息,应该改用hid_info,避免混淆错误日志。
4. 结构体指针获取方式不规范
直接强制转换struct winwing_led *led = (struct winwing_led *) cdev;不符合内核编码规范,容易引发指针错误。
修复后的驱动代码
// SPDX-License-Identifier: GPL-2.0 /* * HID driver for WinWing Metall rudder * * Copyright (c) 2023 Ivan Gorinov, modified for LED control */ #include <linux/device.h> #include <linux/hid.h> #include <linux/hidraw.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/container_of.h> #define MAX_REPORT 16 struct winwing_led { struct led_classdev cdev; struct hid_device *hdev; int number; }; struct winwing_led_info { int number; int max_brightness; const char *led_name; }; static struct winwing_led_info led_info[4] = { { 0, 255, "left" }, { 1, 255, "right" }, { 2, 255, "left_right" }, { 3, 255, "logo" }, }; struct winwing_drv_data { struct hid_device *hdev; const __u8 *remap; __u8 *report_buf; struct mutex lock; unsigned int num_leds; struct winwing_led leds[]; }; static int winwing_led_write(struct led_classdev *cdev, enum led_brightness br) { struct winwing_led *led = container_of(cdev, struct winwing_led, cdev); struct winwing_drv_data *data = hid_get_drvdata(led->hdev); __u8 *buf = data->report_buf; int ret; mutex_lock(&data->lock); // 构造与用户态一致的输出报告 buf[0] = 0x02; buf[1] = 0xf0; buf[2] = 0xbe; buf[3] = 0x00; buf[4] = 0x00; buf[5] = 0x03; buf[6] = 0x49; buf[7] = led->number; buf[8] = br; buf[9] = 0x00; buf[10] = 0; buf[11] = 0; buf[12] = 0; buf[13] = 0; ret = hid_hw_output_report(led->hdev, buf, 14); mutex_unlock(&data->lock); return ret; } static int winwing_init_led(struct hid_device *hdev, struct input_dev *input) { struct winwing_drv_data *data; struct winwing_led *led; int ret; int i; data = hid_get_drvdata(hdev); if (!data) return -EINVAL; data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); if (!data->report_buf) return -ENOMEM; for (i = 0; i < ARRAY_SIZE(led_info); i++) { struct winwing_led_info *info = &led_info[i]; led = &data->leds[i]; led->hdev = hdev; led->number = info->number; led->cdev.max_brightness = info->max_brightness; led->cdev.brightness_set_blocking = winwing_led_write; led->cdev.flags = LED_HW_PLUGGABLE; led->cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s::%s", dev_name(&input->dev), info->led_name); ret = devm_led_classdev_register(&hdev->dev, &led->cdev); if (ret) { hid_err(hdev, "Failed to register LED %d\n", i); return ret; } } hid_info(hdev, "LEDs initialized successfully\n"); return 0; } static int winwing_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct winwing_drv_data *data; // 分配足够的内存容纳4个LED结构体 size_t data_size = struct_size(data, leds, ARRAY_SIZE(led_info)); int ret; ret = hid_parse(hdev); if (ret) { hid_err(hdev, "parse failed\n"); return ret; } data = devm_kzalloc(&hdev->dev, data_size, GFP_KERNEL); if (!data) return -ENOMEM; hid_set_drvdata(hdev, data); // 初始化互斥锁 mutex_init(&data->lock); ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); return ret; } return 0; } static int winwing_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { int ret; ret = winwing_init_led(hdev, hidinput->input); if (ret) hid_err(hdev, "LED init failed\n"); return ret; } static int winwing_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *raw_data, int size) { struct winwing_drv_data *data = hid_get_drvdata(hdev); if (!data) return -EINVAL; if (data->remap) { int src, dst; int index_src, index_dst; __u8 mask_src, mask_dst; int i = 0; while (data->remap[i] < 128) { src = data->remap[i++]; dst = data->remap[i++]; index_src = src / 8 + 1; index_dst = dst / 8 + 1; mask_src = 1 << (src % 8); mask_dst = 1 << (dst % 8); if ((raw_data[index_src] & mask_src) != 0) { raw_data[index_dst] |= mask_dst; } else { raw_data[index_dst] &= ~mask_dst; } } } if (size >= 15) { /* Skip buttons 32 .. 63 */ memmove(raw_data + 5, raw_data + 9, 6); /* Clear the padding */ memset(raw_data + 11, 0, 4); } return 0; } static const struct hid_device_id winwing_devices[] = { { HID_USB_DEVICE(0x4098, 0xbef0) }, /* Metall rudder */ {} }; MODULE_DEVICE_TABLE(hid, winwing_devices); static struct hid_driver winwing_driver = { .name = "winwing-rudder", .id_table = winwing_devices, .probe = winwing_probe, .input_configured = winwing_input_configured, .raw_event = winwing_raw_event, }; module_hid_driver(winwing_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("HID driver for WinWing Metall rudder with LED control");
关键修改说明
- 内存分配修复:使用
ARRAY_SIZE(led_info)计算LED数量,确保分配足够的内存容纳所有LED结构体,避免越界。 - 互斥锁初始化:在
winwing_probe中添加mutex_init(&data->lock),确保互斥锁可以正常使用。 - 容器宏替换:使用
container_of(cdev, struct winwing_led, cdev)获取父结构体指针,这是Linux内核的标准写法,避免强制转换的风险。 - 日志优化:调整日志级别,使用
hid_info打印正常初始化信息,错误信息仅在实际失败时打印。 - 循环优化:使用
ARRAY_SIZE(led_info)代替硬编码的4,提升代码可维护性。
验证方法
- 编译并加载修改后的驱动模块。
- 查看LED子系统节点:
ls /sys/class/leds/,应该能看到四个以你的设备命名的LED节点(如inputX::left等)。 - 尝试控制LED亮度:
echo 120 > /sys/class/leds/inputX::logo/brightness,检查LED是否正常响应。 - 同时验证HID输入事件是否正常:使用
evtest设备节点,确认舵踏板的输入事件可以正常接收。
内容的提问来源于stack exchange,提问作者NeNo
相关产品推荐
相关产品推荐

