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

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");

关键修改说明

  1. 内存分配修复:使用ARRAY_SIZE(led_info)计算LED数量,确保分配足够的内存容纳所有LED结构体,避免越界。
  2. 互斥锁初始化:在winwing_probe中添加mutex_init(&data->lock),确保互斥锁可以正常使用。
  3. 容器宏替换:使用container_of(cdev, struct winwing_led, cdev)获取父结构体指针,这是Linux内核的标准写法,避免强制转换的风险。
  4. 日志优化:调整日志级别,使用hid_info打印正常初始化信息,错误信息仅在实际失败时打印。
  5. 循环优化:使用ARRAY_SIZE(led_info)代替硬编码的4,提升代码可维护性。

验证方法

  1. 编译并加载修改后的驱动模块。
  2. 查看LED子系统节点:ls /sys/class/leds/,应该能看到四个以你的设备命名的LED节点(如inputX::left等)。
  3. 尝试控制LED亮度:echo 120 > /sys/class/leds/inputX::logo/brightness,检查LED是否正常响应。
  4. 同时验证HID输入事件是否正常:使用evtest设备节点,确认舵踏板的输入事件可以正常接收。

内容的提问来源于stack exchange,提问作者NeNo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 21:55:55