C语言 从内核空间配置LED触发器参数



我正在做一个嵌入式项目。我们的主板使用的是Linux内核v3.16.7。我正在支持几个外围led监控活动。我已经成功地修改了引导过程,以便在/sys/class/leds/中加载驱动程序并创建sysfs条目,这非常棒。我还附加了一个触发到led,所以我可以从/sys/class/leds/actled1:green/echo 1 > shot和led闪烁。正是我想要的。

但是,我想在启动过程中实例化驱动程序时配置每个LED的延迟,并且我不清楚如何做到这一点。驱动程序在/sys/class/leds/actled1:green/中创建名为delay_ondelay_off的sysfs条目,我可以从用户空间向它们写入以配置延迟,但是应该可以在实例化期间从内核空间设置它们的初始值。我还希望能够设置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第327行,gpio_led_platform_data的定义在同一文件的第341行。platform_device_add_data的定义在drivers/base/platform.c第284行。

为了回答这个问题,查看一次触发(drivers/leds/trigger/ledtrig-oneshot.c)的源可能是有用的。同样相关的是"led -gpio"驱动程序(drivers/leds/leds-gpio.c)。

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


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

  1. 引导加载程序设置内核参数,我们不能修改引导加载程序。这很好;我想要设置的值是常量,我可以硬编码它们。
  2. 驱动程序在编译时被嵌入内核(并且,我假设,由引导加载程序加载),而不是稍后用modprobe加载.ko。我希望有一种通用的方法来设置任意的触发参数,而不仅仅是单一的delay_on/delay_off。例如,oneshot的invert参数
  3. 我完全可以修改/创建新的触发器。事实上,一旦我得到它的工作与一拍,我将需要创建一个新的触发器扩展一拍(这也是为什么我需要设置任意参数)。

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

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

(2)你可以在setup_my_leds中设置它们[除了oneshot_trig_activate的顽固性-我们将很快处理]。Fromdrivers/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中添加一个新条目。然后,更改结构定义以使用新的驱动程序:.default_trigger = "oneshot2"

选项(C):假设你不能[或不想]触摸drivers/leds/trigger目录,复制ledtrig-oneshot.c到你的驱动程序目录[重命名适当]。根据上面的选项(B)进行编辑。通过在Makefile中使用一些技巧,您可以让它同时构建my_led_driver.koledtrig-oneshot2.ko。您需要修改Kconfig,可能需要为led触发器驱动程序添加depends on LED_TRIGGERS。您还可以将这两个文件放在单独的子目录中,单独的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参数可以直接通过你在setup函数中可以访问的任何标准结构体传递[即它存储在触发器驱动程序内部的私有结构体中],删除"Choice(1)"one_answers"Choice(2)"。我们将在[modified]oneshot_trig_activate中一次设置它们。

此外,您想要的init参数必须由my_leds_get_init_values设置并存储为全局,以便触发器驱动程序可以找到它们。也就是说,没有办法干净地做到这一点(例如,使用一个指向私有结构的指针来传递),因为你在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, "%un", 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, "%lun", 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, "%lun", 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");

在ledtrig_oneshot .c中可以看到,延迟总是用DEFAULT_DELAY初始化。不幸的是,如果你想在启动时配置一个不同的值,这是一个你必须实现的机制。

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

  1. 内核init函数中的硬编码

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