gpt4 book ai didi

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

转载 作者:IT王子 更新时间:2023-10-29 00:07:37 25 4
gpt4 key购买 nike

我正在做一个嵌入式项目。我们的主板使用的是 Linux 内核 v3.16.7。我正在努力支持几个监控事件的外围 LED。我已成功将启动程序修改为 load the drivers and create sysfs entries/sys/class/leds/ ,这很棒。我还附上了 oneshot trigger到 LED 以便我可以 echo 1 > shot从内部 /sys/class/leds/actled1\:green/并且 LED 闪烁。正是我想要的。

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

当我从内核空间实例化驱动程序时,如何配置 LED 触发器的参数?

下面是我如何实例化 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 ,以及 gpio_led_platform_data 的定义在 line 341 of the same file . platform_device_add_data的定义在 drivers/base/platform.c line 284 .

查看 oneshot 触发器的来源 ( drivers/leds/trigger/ledtrig-oneshot.c ) 以回答问题可能很有用。同样相关的是“leds-gpio”驱动程序( drivers/leds/leds-gpio.c )。

我怀疑答案在 drivers/base/platform.c 中的某个地方和相关的 documentation ,但我没有看到任何处理我需要的数据的函数。

为了解决我无意中遗漏的一些信息:
  • 引导加载程序设置内核参数,我们无法修改引导加载程序。没关系;我想设置的值是常量,我可以将它们硬编码。
  • 驱动程序在编译时被烘焙到内核中(并且,我认为,由引导加载程序加载)而不是加载 .ko稍后使用 modprobe。
  • 我想要一种设置任意触发参数的通用方法,而不仅仅是 oneshot 的 delay_on/delay_off .例如,oneshot 的 invert范围。
  • 我完全可以修改oneshot/创建新触发器。事实上,一旦我让它与 oneshot 一起工作,我将需要创建一个新的触发器来扩展 oneshot(这也是我需要设置任意参数的原因)。
  • 最佳答案

    有一些问题,我想我已经找到了解决方案,但是即使您提供了大量信息,也缺少一些东西,所以我会列举所有可能的情况,所以请耐心等待......

    (1) 获取要设置的初始值。我想你已经想通了,但是......你可以从内核 cmdline 解析中得到这些(例如,你将值添加到/boot/grub2/grub.cfg 作为 myleds.delay_on=... 。如果你通过 modprobe 加载, 你设置了一个模块参数。这些也可以是一个配置文件,如 myleds.config_file=/etc/sysconfig/myleds.conf
    (2) 您可以将它们设置在 setup_my_leds 中 [oneshot_trig_activate 的顽固性除外——我们很快就会处理]。来自 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" ,以上数据会被 oneshot_trig_activate炸掉在 drivers/leds/trigger/ledtrig-oneshot.c .所以,我们需要解决这个问题。

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

    选项(B):如果不允许修改 ledtrig-oneshot.c ,但允许向 drivers/leds/trigger 添加新的触发器, 将文件复制到(例如) ledtrig-oneshot2.c并在那里进行更改。您需要更改 .name.name = "oneshot2" .简单的方法 [在 vi 中,当然 :-)] 是 :%s/oneshot/oneshot2/g .为此,您还需要在 Kconfig 和 Makefile 中添加一个新条目。然后,更改结构定义以使用新驱动程序: .default_trigger = "oneshot2"
    选项 (C):假设您不能(或不想)触摸 drivers/leds/trigger目录,复制 ledtrig-oneshot.c到您的驱动程序目录[根据需要重命名]。从上面的选项 (B) 中进行编辑。在你的 Makefile 中使用一些技巧,你可以让它同时构建 my_led_driver.koledtrig-oneshot2.ko .你需要修改你的 Kconfig,可能添加一个 depends on LED_TRIGGERS用于 LED 触发驱动器。您也可以将两者放在单独的子目录中,单独的 Makefile/Kconfig 可能更简单: my_led/my_drivermy_led/my_trigger
    选项 (C) 需要更多的前期工作,但从长远来看可能更干净、更便携。当然,你可以做选项(A)的概念验证,然后做选项(B),然后做“最终船”作为选项(C)。

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

    希望以上方法能奏效。如果没有,请使用其他信息和/或限制编辑您的问题 [并向我发送评论],我很乐意更新我的答案 [我一直在做 40 多个驱动程序]。

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

    因为 invert参数不能直接通过您在设置函数中可以访问的任何标准结构[即它存储在触发器驱动程序内的私有(private)结构中],删除“选择(1)”和“选择(2)”。我们将在 [已修改] oneshot_trig_activate 中一次性设置它们。 .

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

    我的第一步是用描述性注释对基本驱动程序进行注释。里面没有任何评论,除了版权的 K&R 风格和单行文字。我的注释是 ANSI(“//”)注释。

    如果我接管驱动程序,我会添加这些并将它们保留。但是,根据内核样式指南,我的评论级别可能会被视为“过度评论”,并且可能会被视为“粗俗”,特别是对于驱动程序这是直截了当吗。

    下一步是添加必要的更改。所有添加/更改的地方都标有以“C:”开头的注释块。这些是重要的地方。请注意,这些评论是可以留下的合法候选。在其他更复杂的驱动程序中,评论的级别取决于作者。 “C:”只是为您突出显示位置。

    有了注释,直线阅读现在可能更容易。还有一个 diff -u也可能有帮助。如果您拥有 git 下的所有信息,这样就更好了。

    因此,我将删除“选项(A)”[直接修改原始文件]并仅执行“选项(B)”或“选项(C)”。

    触发器驱动程序使用所有 static定义,因此不需要我之前建议的全局编辑。我确实做了 .name = "myled_oneshot"; ,因此您需要将其与 .default_trigger = "myled_oneshot"; 匹配.欢迎使用 my_leds_whatever与您现有的命名约定保持一致。当我自己做这个时,我通常使用我的首字母,所以它变成了 ce_leds_whatever --YMMV

    无论如何,这是整个修改后的触发器驱动程序。请注意,我已经完成了编辑,但我还没有尝试编译/构建它。
    /*
    * 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");

    关于从内核空间配置 LED 触发器的参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33310236/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com