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

C 从内核空间配置LED触发器的参数,c,linux,linux-kernel,linux-device-driver,kernel-module,C,Linux,Linux Kernel,Linux Device Driver,Kernel Module,我在做一个嵌入式项目。我们的主板使用Linux内核v3.16.7。我正在研究支持两个外围LED来监控活动。我已经成功地将引导过程修改为in/sys/class/led/,这非常好。我还将a连接到led上,因此我可以从/sys/class/led/actled1\:绿色/中拍摄echo 1>镜头,并且led闪烁。正是我想要的 但是,我想在引导期间实例化驱动程序时为每个LED配置延迟,我不清楚如何做到这一点。驱动程序在/sys/class/led/actled1\:green/中创建sysfs条目,

我在做一个嵌入式项目。我们的主板使用Linux内核v3.16.7。我正在研究支持两个外围LED来监控活动。我已经成功地将引导过程修改为in
/sys/class/led/
,这非常好。我还将a连接到led上,因此我可以从
/sys/class/led/actled1\:绿色/
中拍摄
echo 1>镜头,并且led闪烁。正是我想要的

但是,我想在引导期间实例化驱动程序时为每个LED配置延迟,我不清楚如何做到这一点。驱动程序在
/sys/class/led/actled1\:green/
中创建sysfs条目,称为
delay\u on
delay\u 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;
}
static int\uuuu init setup\u my\u指示灯(无效)
{
结构平台设备*pdev;
int ret;
pdev=平台、设备、所有(“LED gpio”、-1);
如果(!pdev){
return-ENOMEM;
}
ret=平台设备添加数据(pdev,
&我的数据,
sizeof(my_LED_pdata));
如果(ret<0){
平台设备输出(pdev);
返回ret;
}
ret=平台设备添加(pdev);
如果(ret<0){
平台设备输出(pdev);
返回ret;
}
返回0;
}
gpio_led
struct的定义在中,而
gpio_led_平台_数据的定义在中。
platform\u device\u add\u data
的定义在中

为了回答这个问题,查看oneshot触发器()的源代码可能会很有用。与此相关的还有“LED gpio”驱动程序()

我怀疑答案在和相关联的函数中,但我没有看到任何处理我需要的数据的函数


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

  • 引导加载程序设置内核参数,我们不能修改引导加载程序。那很好;我想设置的值是常量,我可以硬编码它们
  • 驱动程序在编译时被烘焙到内核中(我猜是由引导加载程序加载的),而不是稍后使用modprobe加载
    .ko
  • 我想要一种设置任意触发参数的通用方法,而不仅仅是oneshot的
    delay\u on
    /
    delay\u off
    。例如,oneshot的
    invert
    参数
  • 我完全可以修改oneshot/创建新触发器。事实上,一旦我使用oneshot,我需要创建一个新的触发器来扩展oneshot(这也是我需要设置任意参数的原因)

  • 如中所示,延迟总是使用
    DEFAULT\u delay
    初始化。不幸的是,如果您想在启动时配置不同的值,这是一种必须实现的机制。

    有一些问题,我想我已经找到了解决方案,但即使您提供了大量信息,也缺少一些内容,因此我将列举所有可能的场景,请耐心等待

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

    (2) 您可以在设置“我的”指示灯中设置它们[除了oneshot\u trig\u 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;
    }
    

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

  • 内核init函数中的硬编码

  • 由于mac地址存储在eeprom中,以便nic驱动程序读取,因此,如果值可以存储在闪存(nor)中,则在引导时读取该值。这可以在内核引导期间创建mtd分区后完成


  • 设备树、ACPI或内置设备属性是您的选择。在任何情况下,您都必须为驱动程序支持的属性编写代码。我很乐意为oneshot编写自己的修改,但如果我编写了一个新函数(例如,
    set\u delay\u on(int)
    ),我如何从我的内核空间实例化代码中调用它?我实际上没有驱动程序或任何东西的句柄。你是一个救命恩人。这个答案非常有用。我将研究你提到的一些事情并向你汇报。好的,我看到这允许我设置
    延迟
    延迟
    using平台数据。我可以使用此技术设置其他参数,如
    invert
    吗?我这样问是因为它看起来是直接设置结构的显式提到的
    delay\u on
    delay\u off
    字段,但是
    invert
    字段不是该结构的一部分(也不是其他触发器的任意参数)。因此,此方法是否仅限于配置
    delay\u on
    delay\u off
    参数?@WoodrowBarlow您是正确的(例如,invert是
    /*
     * 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");