从内核配置 LED 触发器的参数 Space

Configure Parameters of LED Trigger from Kernel Space

我正在做一个嵌入式项目。我们的主板使用 Linux 内核 v3.16.7。我正在努力支持几个外围 LED 监控 activity。我已经成功地将引导程序修改为 load the drivers and create sysfs entries in /sys/class/leds/, which is great. I've also attached a oneshot trigger 到 LED,这样我就可以从 /sys/class/leds/actled1\:green/echo 1 > shot 并且 LED 闪烁。正是我想要的。

但是,我想在启动过程中实例化驱动程序时为每个 LED 配置延迟,但我不清楚该怎么做。驱动程序在 /sys/class/leds/actled1\:green/ 中创建名为 delay_ondelay_off 的 sysfs 条目,我可以从用户 space 写信给它们来配置延迟,但应该可以设置它们实例化期间来自内核 space 的初始值。我还希望能够设置 invert 参数(这只是另一个 sysfs 条目,就像延迟一样)。

从内核实例化驱动时如何配置led触发器的参数space?

下面是我如何实例化 LED GPIO。首先,我设置了所需的结构:

static struct gpio_led my_leds[] __initdata = {
    {
        .name = "actled1:green",
        .default_trigger = "oneshot"
        .gpio = ACTIVITY_LED_GPIO_BASE + 0,
        .active_low = true,
    },
    {
        .name = "actled2:red",
        .default_trigger = "oneshot"
        .gpio = ACTIVITY_LED_GPIO_BASE + 1,
        .active_low = true,
    },
};

static struct gpio_led_platform_data my_leds_pdata __initdata = {
    .num_leds = ARRAY_SIZE(my_leds),
    .leds = my_leds,
};

然后,我调用这个函数来创建平台设备:

static int __init setup_my_leds (void)
{
    struct platform_device *pdev;
    int ret;

    pdev = platform_device_alloc("leds-gpio", -1);
    if (!pdev) {
        return -ENOMEM;
    }

    ret = platform_device_add_data(pdev,
                    &my_leds_pdata,
                    sizeof(my_leds_pdata));

    if (ret < 0) {
        platform_device_put(pdev);
        return ret;
    }

    ret = platform_device_add(pdev);
    if (ret < 0) {
        platform_device_put(pdev);
        return ret;
    }

    return 0;
}

gpio_led 结构的定义在 include/linux/leds.h line 327, and the definition for gpio_led_platform_data is in line 341 of the same file. The definition of platform_device_add_data is in drivers/base/platform.c line 284.

查看 oneshot 触发器的来源可能会有用 (drivers/leds/trigger/ledtrig-oneshot.c) in order to answer the question. Also relevant is the "leds-gpio" driver (drivers/leds/leds-gpio.c)。

我怀疑答案在 drivers/base/platform.c and the associated documentation 中的某处,但我没有看到任何处理我需要的数据的函数。


解决我无意中遗漏的一些信息:

  1. 引导加载程序设置内核参数,我们无法修改引导加载程序。没关系;我要设置的值是常量,我可以将它们硬编码。
  2. 驱动程序在编译时嵌入到内核中(并且,我推测,由引导加载程序加载),而不是稍后使用 modprobe 加载 .ko
  3. 我喜欢一种设置任意触发参数的通用方法,而不仅仅是 oneshot 的 delay_on / delay_off。例如,oneshot 的 invert 参数。
  4. 我完全可以修改 oneshot / 创建新触发器。事实上,一旦我让它与 oneshot 一起工作,我将需要 创建一个新的触发器来扩展 oneshot(这也是我需要设置任意参数的原因)。

如您在 ledtrig-oneshot.c 中所见,延迟始终使用 DEFAULT_DELAY 进行初始化。不幸的是,如果您希望能够在启动时配置不同的值,这是您必须实现的机制..

有几个问题,我想我已经找到了解决方案,但是即使你提供了很多信息,也有一些遗漏,所以我会列举所有可能的情况,所以请耐心等待...

(1)获取要设置的初始值。我想你已经想通了,但是......你可以从内核 cmdline 解析中得到这些(例如,你将值添加到 /boot/grub2/grub.cfg 为 myleds.delay_on=...。如果你通过 modprobe,你设置了一个模块参数。这些也可以是一个配置文件,如 myleds.config_file=/etc/sysconfig/myleds.conf

(2) 您可以将它们设置在您的 setup_my_leds 中 [除了顽固的 oneshot_trig_activate--which 我们会尽快处理]。来自 drivers/base/platform.c:

/**
 * arch_setup_pdev_archdata - Allow manipulation of archdata before its used
 * @pdev: platform device
 *
 * This is called before platform_device_add() such that any pdev_archdata may
 * be setup before the platform_notifier is called.  So if a user needs to
 * manipulate any relevant information in the pdev_archdata they can do:
 *
 *  platform_device_alloc()
 *  ... manipulate ...
 *  platform_device_add()
 *
 * And if they don't care they can just call platform_device_register() and
 * everything will just work out.
 */

因此,考虑到这一点,让我们稍微更改一下您的设置函数:

static int __init setup_my_leds (void)
{
    struct platform_device *pdev;
    int ret;

    // get initial values you want to set, possibly storing away for later use
    my_leds_get_init_values(...);

    pdev = platform_device_alloc("leds-gpio", -1);
    if (!pdev) {
        return -ENOMEM;
    }

    // Choice (1): set your initial values in my_leds_pdata here
    my_leds_set_init_values(&my_leds_pdata);

    // NOTE: just does kmemdup and sets pdev->dev.platform_data
    ret = platform_device_add_data(pdev,
                    &my_leds_pdata,
                    sizeof(my_leds_pdata));

    if (ret < 0) {
        platform_device_put(pdev);
        return ret;
    }

    // Choice (2): set your initial values in pdev->dev.platform_data here
    my_leds_set_init_values(pdev->dev.platform_data);

    ret = platform_device_add(pdev);
    if (ret < 0) {
        platform_device_put(pdev);
        return ret;
    }

    return 0;
}

(3) 不幸的是,由于您使用的是 .default_trigger = "oneshot",因此上述数据将在 drivers/leds/trigger/ledtrig-oneshot.c 中被 oneshot_trig_activate 破坏。所以,我们需要处理这个问题。

选项 (A):假设您可以根据自己的选择重建整个内核,只需修改 ledtrig-oneshot.c 中的 oneshot_trig_activate 并删除使用 DEFAULT_DELAY 的行。这只有在您知道被您系统中可能需要默认值的任何其他东西使用时才真正有用。

选项 (B):如果不允许修改 ledtrig-oneshot.c,但允许向 drivers/leds/trigger 添加新触发器,则将文件复制到(例如)ledtrig-oneshot2.c并在那里进行更改。您需要将 .name 更改为 .name = "oneshot2"。 [当然,在 vi 中 :-)] 的简单方法是 :%s/oneshot/oneshot2/g。为此,您还需要在 Kconfig 和 Makefile 中添加一个新条目。然后,更改您的结构定义以使用新的 driver: .default_trigger = "oneshot2"

选项 (C):假设您不能[或不想]触摸 drivers/leds/trigger 目录,将 ledtrig-oneshot.c 复制到您的 driver 目录 [适当重命名].从上面的选项 (B) 进行编辑。在你的 Makefile 中使用一些技巧,你可以让它同时构建 my_led_driver.ko ledtrig-oneshot2.ko。您需要修改您的 Kconfig,可能会为 LED 触发器 driver 添加一个 depends on LED_TRIGGERS。您也可以将两者放入单独的子目录中,单独的 Makefile/Kconfig 可能更简单:my_led/my_drivermy_led/my_trigger

选项 (C) 会在前期做更多的工作,但从长远来看可能会更干净、更便携 运行。当然,你可以对 proof-of-concept 执行选项 (A),然后执行选项 (B),然后按选项 (C) 执行 "final ship"。

另一种设置初始值的方法:记住 my_leds_get_init_values 的注释是 possibly storing away for later use。您可以更改 oneshot2_trig_activate 来调用它,而不是使用 DEFAULT_DELAY。我不太喜欢这个,更喜欢简单地消除 oneshot_trig_activate 的冒犯行为的解决方案。但是,通过一些测试,您可能会发现这是您必须采用的方式。

希望以上方法有效。如果没有,请使用其他信息 and/or 限制编辑您的问题 [并向我发送评论],我很乐意更新我的答案 [我已经 drivers 40+]。

更新: 好的,这里是一个完全注释和修改的 LED 触发器 driver,您可以将其用作 drivers/led/trigger/ledtrig-oneshot.c 的替代品。

因为 invert 参数可以 而不是 直接通过您在设置函数中有权访问的任何标准结构[即它存储在触发器 driver] 内的私有结构中,删除 "Choice (1)" 和 "Choice (2)"。我们将在 [modified] oneshot_trig_activate.

中一次设置它们

此外,您需要的初始化参数必须由 my_leds_get_init_values 设置并存储为 globals,以便触发器 driver 可以找到它们。也就是说,没有办法干净地执行此操作(例如,使用指向传递的私有结构的指针),因为您在设置中可以访问的结构没有用于此的字段。有关此问题的讨论,请参阅触发器 driver 的顶部。

我的第一步是用描述性注释对基础 driver 进行注释。除了K&R style for copyright和一个one-liner,里面没有任何评论。我的注释是 ANSI ("//") 注释。

如果我接手 driver,我会添加这些并保留它们。但是,根据内核样式指南,我的评论级别可能会被认为是 "over-commenting",并且可能会被考虑"cruft",特别是 driver 就是这么简单。

下一步是添加必要的更改。所有具有 additions/changes 的地方都标有以 "C:" 开头的注释块。这些是要看的重要地方。请注意,这些评论是离开 in 的合法候选者。在其他更复杂的driver中,评论的水平由作者决定。 "C:"只是为了给你突出显示的地方。

有了注释,直线通读现在可能更容易了。此外,diff -u 也可能有帮助。如果您在 git 下拥有一切,那就更好了。

因为这一切,我会删除 "Option (A)" [直接修改原始文件],只做 "Option (B)" 或 "Option (C)"。

三ger driver 使用所有 static 定义,因此我之前建议的全局编辑 不需要 。我确实做了 .name = "myled_oneshot";,所以你需要将它与 .default_trigger = "myled_oneshot"; 匹配。请随意使用 my_leds_whatever 以与您现有的命名约定保持一致。当我为自己做这个时,我通常使用我的姓名首字母,所以它变成 ce_leds_whatever--YMMV

无论如何,这是 整个 修改后的触发器 driver。请注意,我已经完成了编辑,但我 没有 尝试 compile/build 它。

/*
 * One-shot LED Trigger
 *
 * Copyright 2012, Fabio Baltieri <fabio.baltieri@gmail.com>
 *
 * Based on ledtrig-timer.c by Richard Purdie <rpurdie@openedhand.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/ctype.h>
#include <linux/slab.h>
#include <linux/leds.h>
#include "../leds.h"

// C: we need to get access to the init data populated by the setup function
// we have the "clean way" with a struct definition inside a header file and
// the "dirty way" using three separate int globals
// in either case, the externs referenced here must be defined in the "my_leds"
// driver as global

// C: the "clean way"
// (1) requires that we have a path to the .h (e.g. -I<whatever)
// (2) this would be easier/preferable for the "Option (C)"
// (3) once done, easily extensible [probably not a consideration here]
#ifdef MYLED_USESTRUCT
#include "whatever/myled_init.h"
extern struct myled_init myled_init;

// C: the "ugly way"
// (1) no need to use a separate .h file
// (2) three separate global variables is wasteful
// (3) more than three, and we really should consider the "struct"
#else
extern int myled_init_delay_on;
extern int myled_init_delay_off;
extern int myled_init_invert;
#endif

#define DEFAULT_DELAY 100

// oneshot trigger driver private data
struct oneshot_trig_data {
    unsigned int invert;                // current invert state
};

// arm oneshot sequence from sysfs write to shot file
static ssize_t led_shot(struct device *dev,
        struct device_attribute *attr, const char *buf, size_t size)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);
    struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data;

    led_blink_set_oneshot(led_cdev,
            &led_cdev->blink_delay_on, &led_cdev->blink_delay_off,
            oneshot_data->invert);

    /* content is ignored */
    return size;
}

// show invert state for "cat invert"
static ssize_t led_invert_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);
    struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data;

    return sprintf(buf, "%u\n", oneshot_data->invert);
}

// set invert from sysfs write to invert file (e.g. echo 1 > invert)
static ssize_t led_invert_store(struct device *dev,
        struct device_attribute *attr, const char *buf, size_t size)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);
    struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data;
    unsigned long state;
    int ret;

    ret = kstrtoul(buf, 0, &state);
    if (ret)
        return ret;

    oneshot_data->invert = !!state;

    if (oneshot_data->invert)
        led_set_brightness_async(led_cdev, LED_FULL);
    else
        led_set_brightness_async(led_cdev, LED_OFF);

    return size;
}

// show delay_on state for "cat delay_on"
static ssize_t led_delay_on_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);

    return sprintf(buf, "%lu\n", led_cdev->blink_delay_on);
}

// set delay_on from sysfs write to delay_on file (e.g. echo 20 > delay_on)
static ssize_t led_delay_on_store(struct device *dev,
        struct device_attribute *attr, const char *buf, size_t size)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);
    unsigned long state;
    int ret;

    ret = kstrtoul(buf, 0, &state);
    if (ret)
        return ret;

    led_cdev->blink_delay_on = state;

    return size;
}

// show delay_off state for "cat delay_off"
static ssize_t led_delay_off_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);

    return sprintf(buf, "%lu\n", led_cdev->blink_delay_off);
}

// set delay_off from sysfs write to delay_off file (e.g. echo 20 > delay_off)
static ssize_t led_delay_off_store(struct device *dev,
        struct device_attribute *attr, const char *buf, size_t size)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);
    unsigned long state;
    int ret;

    ret = kstrtoul(buf, 0, &state);
    if (ret)
        return ret;

    led_cdev->blink_delay_off = state;

    return size;
}

// these are the "attribute" definitions -- one for each sysfs entry
// pointers to these show up in the above functions as the "attr" argument
static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store);
static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store);
static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
static DEVICE_ATTR(shot, 0200, NULL, led_shot);

// activate the trigger device
static void oneshot_trig_activate(struct led_classdev *led_cdev)
{
    struct oneshot_trig_data *oneshot_data;
    int rc;

    // create an instance of the private data we need
    oneshot_data = kzalloc(sizeof(*oneshot_data), GFP_KERNEL);
    if (!oneshot_data)
        return;

    // save the pointer in the led class struct so it's available to other
    // functions above
    led_cdev->trigger_data = oneshot_data;

    // attach the sysfs entries
    rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
    if (rc)
        goto err_out_trig_data;
    rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
    if (rc)
        goto err_out_delayon;
    rc = device_create_file(led_cdev->dev, &dev_attr_invert);
    if (rc)
        goto err_out_delayoff;
    rc = device_create_file(led_cdev->dev, &dev_attr_shot);
    if (rc)
        goto err_out_invert;

    // C: this is what the driver used to do
#if 0
    led_cdev->blink_delay_on = DEFAULT_DELAY;
    led_cdev->blink_delay_off = DEFAULT_DELAY;
#endif

    led_cdev->activated = true;

    // C: from here to the return is what the modified driver must do

#ifdef MYLED_USESTRUCT
    led_cdev->blink_delay_on = myled_init.delay_on;
    led_cdev->blink_delay_off = myled_init.delay_off;
    oneshot_data->invert = myled_init.invert;
#else
    led_cdev->blink_delay_on = myled_init_delay_on;
    led_cdev->blink_delay_off = myled_init_delay_off;
    oneshot_data->invert = myled_init_invert;
#endif

    // C: if invert is off, nothing to do -- just like before
    // if invert is set, we implement this as if we just got an instantaneous
    // write to the sysfs "invert" file (which would call led_invert_store
    // above)

    // C: this is a direct rip-off of the above led_invert_store function which
    // we can _not_ call here directly because we don't have access to the
    // data it needs for its arguments [at least, not conveniently]
    // so, we extract the one line we actually need
    if (oneshot_data->invert)
        led_set_brightness_async(led_cdev, LED_FULL);

    return;

    // release everything if an error occurs
err_out_invert:
    device_remove_file(led_cdev->dev, &dev_attr_invert);
err_out_delayoff:
    device_remove_file(led_cdev->dev, &dev_attr_delay_off);
err_out_delayon:
    device_remove_file(led_cdev->dev, &dev_attr_delay_on);
err_out_trig_data:
    kfree(led_cdev->trigger_data);
}

// deactivate the trigger device
static void oneshot_trig_deactivate(struct led_classdev *led_cdev)
{
    struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data;

    // release/destroy all the sysfs entries [and free the private data]
    if (led_cdev->activated) {
        device_remove_file(led_cdev->dev, &dev_attr_delay_on);
        device_remove_file(led_cdev->dev, &dev_attr_delay_off);
        device_remove_file(led_cdev->dev, &dev_attr_invert);
        device_remove_file(led_cdev->dev, &dev_attr_shot);
        kfree(oneshot_data);
        led_cdev->activated = false;
    }

    /* Stop blinking */
    led_set_brightness(led_cdev, LED_OFF);
}

// definition/control for trigger device registration
// C: changed the name to "myled_oneshot"
static struct led_trigger oneshot_led_trigger = {
    .name     = "myled_oneshot",
    .activate = oneshot_trig_activate,
    .deactivate = oneshot_trig_deactivate,
};

// module init function -- register the trigger device
static int __init oneshot_trig_init(void)
{
    return led_trigger_register(&oneshot_led_trigger);
}

// module exit function -- unregister the trigger device
static void __exit oneshot_trig_exit(void)
{
    led_trigger_unregister(&oneshot_led_trigger);
}

module_init(oneshot_trig_init);
module_exit(oneshot_trig_exit);

MODULE_AUTHOR("Fabio Baltieri <fabio.baltieri@gmail.com>");
MODULE_DESCRIPTION("One-shot LED trigger");
MODULE_LICENSE("GPL");

正如 Craig 回答的那样,它应该来自内核命令行选项,但是嵌入式系统可能存在问题,其中引导加载程序传递命令行参数并且无法修改引导加载程序,它们通常是 OTP 。在那种情况下,我只看到 2 个选项

  1. 内核初始化函数中的硬编码

  2. as mac 地址存储在 eeprom 中供 nic 驱动程序读取,如果值可以存储在闪存 (nor) 中并在启动时读取该值。这可以在内核引导期间创建 mtd 分区后完成。