led子系统分析

本文基于 Linux Kernel 4.4.179 版本
led子系统是 linux kernel中最简单的,由此开始…


1 概述

以下文字来自 {kernel}\Documentation\leds\leds-class.txt 文件的简单翻译:

  1. LED类以最简单的形式允许从用户空间控制LED设备。 LED类出现在/sys/class/leds/目录中。LED的最大亮度在max_brightness文件中定义。brightness文件用来设置LED的亮度(取值为0-max_brightness)。大多数LED设备不支持硬件亮度,因此brightness设置为非0时打开。
  2. 该类还实现了LED触发器的可选功能。 触发器是内核的led事件的来源。 触发器可以是简单的,也可以是复杂的。一个简单的触发器是不可配置的,旨在以最少的附加代码插入现有子系统。例如ide-disk,nand-disk和sharpsl-charge触发器。 禁用led触发器后,代码即可优化。
  3. 所有LED都可以使用复杂的触发器,这些触发器具有LED特定的参数,并且每个LED都可以工作。timer触发器就是一个例子。timer触发器将定期在"LED_OFF"和当前亮度设置之间更改LED亮度。可以通过 /sys/class/leds/<device>/delay_ {on,off}(单位:ms)指定“打开”和“关闭”时间。您可以独立于计时器触发器来更改LED的亮度值。 但是,如果将亮度值设置为LED_OFF,则也会禁用timer器触发。
  4. 您可以类似于选择IO调度程序的方式来更改触发器(通过/sys/class/leds/ <device>/trigger)。 一旦选择了给定的触发器,触发器特定的参数就会出现在/sys/class/leds/<device>中。
  5. 设计哲学:基本的设计理念是简单性。LED是简单的设备,目的是保留少量代码,以提供尽可能多的功能。 在建议增强功能时,请记住这一点。

2 核心源码和数据结构

2.1 核心源码

主要源码位于 {kernel}/drivers/leds/ 目录下,核心源码文件如下:

 led-core.c         		 // led操作的通用逻辑代码
 led-class.c         		 // 实现led class,给用户空间操作led提供接口
 led-triggers.c		         // 维护所有的触发器

2.2核心数据结构

led_classdev 结构:

struct led_classdev {
	const char		*name;						// Led的名字  
	enum led_brightness	 brightness;			//Led亮度
	enum led_brightness	 max_brightness;		//led最大亮度 
	int			 flags;
	
	/* Set LED brightness level */
	/* Must not sleep, use a workqueue if needed */
	/* 用于设置亮度的函数指针 */
	void	 (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);	
	int     (*brightness_set_sync)(struct led_classdev *led_cdev, enum led_brightness brightness);
	/* Get LED brightness level */
	/* 用于获取亮度的函数指针 */
	enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);	
	/* 用来设置闪烁时点亮和熄灭时长 */
	int		(*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off);
	struct device		*dev;
	/* 对于一些复杂的led设备,可以自定义一些class属性文件接口 */		
	const struct attribute_group	**groups;

	struct list_head	 node;			/* LED Device list */
	const char		*default_trigger;	/* Trigger to use */

	unsigned long		 blink_delay_on, blink_delay_off;	// 闪烁的开关时间
	struct timer_list	 blink_timer;						// 闪烁的定时器链表
	int			 blink_brightness;							// 闪烁的亮度
	void			(*flash_resume)(struct led_classdev *led_cdev);

	struct work_struct	set_brightness_work;
	int			delayed_set_value;

#ifdef CONFIG_LEDS_TRIGGERS
	/* Protects the trigger data below */
	struct rw_semaphore	 trigger_lock;						// trigger的锁

	struct led_trigger	*trigger;							// Led的trigger
	struct list_head	 trig_list;							// trigger链表
	void			*trigger_data;							// trigger数据
	/* true if activated - deactivate routine uses it to do cleanup */
	bool			activated;								// trigger激活的标志
#endif

	/* Ensures consistent access to the LED Flash Class device */
	struct mutex		led_access;
};

brightness_set / brightness_set_sync:这两个函数指针是由led设备驱动实现的,涉及具体的led设备的硬件操作。如,对于gpio控制的led,就是设置高低电平。一般情况使用 brightness_set;
blink_set:这个函数指针是由led设备驱动实现的,涉及具体的led设备的硬件操作。如果硬件不支持,可以通过软件触发器来实现闪烁功能;
node:将 led_classdev 结构体加入 leds_list 链表的 node;
default_trigger:led_classdev 使用的trigger的名字,通过这个名字在trigger_list中找到对应的默认trigger;
trig_list:将 led_classdev 结构体加入 led_trigger.led_cdevs 链表的 node,用来表示这个触发器可以支持哪些led设备;

led_trigger 结构:

struct led_trigger {
	/* Trigger Properties */
	const char	 *name;
	void		(*activate)(struct led_classdev *led_cdev);		// 激活trigger
	void		(*deactivate)(struct led_classdev *led_cdev);	// 关闭trigger

	/* LEDs under control by this trigger (for simple triggers) */
	rwlock_t	  leddev_list_lock;
	struct list_head  led_cdevs;								// 该trigger支持的led设备链表 

	/* Link to next registered trigger */
	struct list_head  next_trig;
};

activate / deactivate:这两个函数指针是由各自触发器实现;
next_trig:将 led_trigger 结构体加入 trigger_list 链表的 node。


3 软件框图

在这里插入图片描述

4 源码分析

主要涉及定时器和工作队列。

4.1 初始化

主要就是创建leds class,赋值 led_groups。

static int __init leds_init(void)
{
	leds_class = class_create(THIS_MODULE, "leds");		// 创建/sys/class/leds/
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
	leds_class->pm = &leds_class_dev_pm_ops;
	leds_class->dev_groups = led_groups;
	return 0;
}

static const struct attribute_group *led_groups[] = {
	&led_group,
#ifdef CONFIG_LEDS_TRIGGERS
	&led_trigger_group,
#endif
	NULL,
};

led_groups[]:实现了led设备通用的3个属性文件:brightness、max_brightness、trigger。
leds_init() 只是创建了led class,还没有创建上面3个属性文件。这3个属性文件是在注册led设备驱动(led_classdev_register)时,为每个led设备都要创建这3个属性文件。

配置led亮灭: echo 1 > brightness

brightness_store(dev, attr, buf, size)
	/* 操作全局变量 mutex_lock */
	led_set_brightness(led_cdev, state);
        /* 闪烁处理 */
        led_cdev->delayed_set_value = brightness;
        if (brightness == LED_OFF)
            schedule_work(&led_cdev->set_brightness_work);
 		
        led_set_brightness_async
            led_cdev->brightness_set			// 调用到led设备驱动实现的操作led亮灭的函数
        led_set_brightness_sync
            led_cdev->brightness_set_sync

该函数有2个功能:

  1. 调用 led设备驱动 注册的控制led亮灭的函数,进行led的亮灭控制。
  2. 如果配置led灭,会调度工作队列运行,删除定时器,停止闪烁。

4.2 注册led设备

/* 注册 led_classdev 结构体 */
led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
    led_cdev->dev = device_create_with_groups       // 创建led设备的属性文件,包含了3个通用的属文件和某些led设备特有的属性文件
	list_add_tail(&led_cdev->node, &leds_list);     // led设备 加入led链表
	led_cdev->flags |= SET_BRIGHTNESS_ASYNC;        // 决定调用的是led_cdev->brightness_set  -> 这个函数由led设备驱动实现
    led_init_core()                                  // 初始化工作队列和定时器,处理led的闪烁
    led_trigger_set_default()
        list_for_each_entry                             // 遍历 trigger_list, 取出trig,如果与默认一致,则设置
            led_trigger_set(led_cdev, trig);

该注册函数主要完成如下操作:

  1. 创建led设备的属性文件,包含了3个通用的属文件和某些led设备特有的属性文件;
  2. 将led设备加入led链表
  3. 初始化工作队列和定时器,处理led的闪烁;
  4. 设置默认触发器。

4.3 注册触发器

/* 注册 led_trigger 结构体 */
led_trigger_register(struct led_trigger *trig)
    list_for_each_entry(&trigger_list)                  // 遍历 trigger_list,检测名字是否重复
    strcmp(_trig->name, trig->name)
    list_add_tail(&trig->next_trig, &trigger_list);     // trig 加入 trigger_list
    list_for_each_entry(&leds_list)                     // 遍历led设备, 注册trig(trigger空 && default_trigger设置 && trig没有注册)
        led_trigger_set(led_cdev, trig)                         // 给 led 注册 trig (trigger_lock)
            list_add_tail(&led_cdev->trig_list, &trig->led_cdevs);  // led_cdev 加入 trig 设备链表(leddev_list_lock)
            led_cdev->trigger = trig; 
            trig->activate(led_cdev);                               // 注册触发器结构体中的激活函数

该注册函数主要完成如下操作:

  1. 遍历触发器链表,检测触发器是否重复;
  2. 将该触发器加入触发器链表
  3. 遍历led设备链表,该触发器为led设备的默认触发器,则设置。

配置触发器: echo timer > trigger

led_trigger_store()
    list_for_each_entry(&trigger_list)                  // 遍历触发器链表
    led_trigger_set(led_cdev, trig)                         // 给led设备注册触发器 

/* 打印所有的触发器,当前设备使用的触发器用[%s] */
led_trigger_show()

4.4 总结

  1. 使用了3个链表:
    1.1 led设备链表(leds_list),注册led设备(led_classdev_register)时,加入该链表;
    1.2 触发器链表(trigger_list),注册触发器(led_trigger_register)时,加入该链表;
    1.3 触发器支持的设备链表(led_trigger.led_cdevs),给led设置触发器(led_trigger_set)时,将led设备加入该链表。
  2. 定时器:
    2.1 注册led设备时,创建了定时器处理函数(led_timer_function);
    2.2 注册timer触发器时,若某led设备的默认触发器是timer,则会调用到timer_trig_activate()激活定时器,启动定时器函数;
    2.3 用户空间操作 “echo timer > trigger”,也会调用到timer_trig_activate()函数。
led_classdev_register()
	led_init_core()
		/* set_brightness_delayed -> del_timer_sync -> brightness_set(0) */
		INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
		/* led_timer_function -> brightness_set -> mod_timer */
		setup_timer(&led_cdev->blink_timer, led_timer_function, (unsigned long)led_cdev);

led_trigger_register(struct led_trigger *trig)
	led_trigger_set(led_cdev, trig)
            trig->activate(led_cdev)
            	timer_trig_activate()
				    led_blink_set()
				        del_timer_sync(&led_cdev->blink_timer);
				        led_blink_setup()
				            led_set_software_blink()
				                led_cdev->brightness_set()
				                mod_timer(&led_cdev->blink_timer, jiffies + 1);   // 1 jiffiess后,启动定时器函数
				                   
/* 定时器切换灯状态 */
led_timer_function()
    /* 切换亮灭参数 */
    brightness = led_get_brightness(led_cdev);              // led_cdev->brightness
    /* brightness, 当前的亮度状态:亮 -> 灭 */
    if (!brightness)                                        // 亮
        if (delayed_set_value)                                  // 更新亮度值
			led_cdev->blink_brightness = delayed_set_value;
			delayed_set_value = 0;
        brightness = led_cdev->blink_brightness;
        delay = led_cdev->blink_delay_on;
    else                                                    // 灭
        led_cdev->blink_brightness = brightness;
        brightness = LED_OFF;
        delay = led_cdev->blink_delay_off;
    /* 切换灯状态 */
    led_set_brightness_async(brightness)
        led_cdev->brightness = brightness;
        led_cdev->brightness_set
    mod_timer(jiffies + msecs_to_jiffies(delay))
  1. 工作队列
    3.1 注册led设备时,创建了工作队列处理函数(set_brightness_delayed);
    3.2 用户空间操作 “echo 0 > brightness”,从led class中删除触发器,调度工作队列运行,删除定时器,关闭led。
    3.3 用户空间操作 “echo null > trigger”,也是进行上面的操作。
brightness_store(dev, attr, buf, size)
	if (state == LED_OFF)
		led_trigger_remove(led_cdev);
			led_trigger_set(led_cdev, NULL);
	led_set_brightness(led_cdev, state);
        if (brightness == LED_OFF)
            schedule_work(&led_cdev->set_brightness_work);
              |
            set_brightness_delayed()
        		del_timer_sync()
        		brightness_set(0)

4.5 gpio触发器

gpio触发器:就是通过gpio的高低电平控制led的亮灭。

  1. led设备配置gpio触发器,会在 /sys/class/leds/ 目录下会生成 gpio 、 inverted和desired_brightness属性文件,以及会初始化工作队列;
  2. 写gpio属性文件,指定gpio编号。会注册一个gpio中断,当gpio pin电平变化时,运行gpio中断函数(gpio_trig_irq)调度gpio工作队列;
  3. 在工作队列中,获取gpio pin的电平值,并根据inverted,配置led的亮灭。

5 led设备驱动

对于驱动开发者来说,一般都是编写led设备驱动,实现led亮灭控制函数,按照框架代码的要求注册到链表中即可。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/of_gpio.h>

#define LED_DRV_NAME "led_gpio"

struct rk39xx_led_platdata {
    int  gpio;
    int  flags;
    const char *name;
    char *def_trigger;
};

struct rk39xx_gpio_led {
    struct led_classdev         cdev;
    struct rk39xx_led_platdata  *pdata;
};

static inline struct rk39xx_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
    return platform_get_drvdata(dev);
}

static inline struct rk39xx_gpio_led *to_gpio(struct led_classdev *pled_cdev)
{
    return container_of(pled_cdev, struct rk39xx_gpio_led, cdev);
}

/* 控制led亮灭 */
static void rk39xx_led_set(struct led_classdev *pled_cdev, enum led_brightness value)
{
    struct rk39xx_gpio_led *pled = to_gpio(pled_cdev);
    struct rk39xx_led_platdata *pd  = pled->pdata;
    int state = (value ? 1 : 0) ^ (pd->flags & OF_GPIO_ACTIVE_LOW);
    
    gpio_set_value(pd->gpio, state);
}

static int rk39xx_led_probe(struct platform_device *pdev)
{
    int ret;
    struct device_node *np = pdev->dev.of_node;
    struct rk39xx_led_platdata *pdata;
    struct rk39xx_gpio_led *pled;
    int  led_gpio;
    enum of_gpio_flags gpio_flags;
    unsigned long flags = GPIOF_OUT_INIT_LOW;

    /* 平台数据,保存硬件信息 */
    pdata = devm_kzalloc(&pdev->dev, sizeof(struct rk39xx_led_platdata), GFP_KERNEL);
    if (!pdata)
    {
        return -ENOMEM;
    }
    
    pdata->name  = np->name;
    led_gpio = of_get_gpio_flags(np, 0, &gpio_flags);
    if (!gpio_is_valid(led_gpio))
    {
        dev_err(&pdev->dev, "%s: %d is invalid\n", pdata->name, led_gpio); 
        return -ENODEV;
    }
    pdata->gpio  = led_gpio;
    pdata->flags = gpio_flags;
    
    /* 低电平有效, 初始化为高电平 */
    if (pdata->flags & OF_GPIO_ACTIVE_LOW)
    {
        flags = GPIOF_OUT_INIT_HIGH;
    }
    
    ret = devm_gpio_request_one(&pdev->dev, pdata->gpio, flags, pdata->name);
    if (ret < 0) 
    {
        dev_err(&pdev->dev, "requesting led gpio error: %d\n", ret);
        return ret;
    }
    
    pled = devm_kzalloc(&pdev->dev, sizeof(struct rk39xx_gpio_led), GFP_KERNEL);
    if (!pled)
    {
        return -ENOMEM;
    }

    /* 将led存入pdev->dev->driver_data */
    platform_set_drvdata(pdev, pled);
    
    pled->cdev.brightness_set = rk39xx_led_set;
    //pled->cdev.default_trigger = pdata->def_trigger;
    pled->cdev.name   = pdata->name;
    pled->pdata       = pdata;

    /* 注册LED设备 */
    ret = led_classdev_register(&pdev->dev, &pled->cdev);
    if (ret < 0)
    {
        dev_err(&pdev->dev, "led_classdev_register failed\n");
        return ret;
    }

    return 0;
}

static int rk39xx_led_remove (struct platform_device *pdev)
{
    struct rk39xx_gpio_led *pled = pdev_to_gpio(pdev);

    led_classdev_unregister(&pled->cdev);

    return 0;
}

static const struct of_device_id led_of_match[] = {
    { .compatible = "th,led" },
    { /* sentinel */ }
};

MODULE_DEVICE_TABLE(of, led_of_match);

static struct platform_driver rk39xx_led_driver = {
    .probe  = rk39xx_led_probe,
    .remove = rk39xx_led_remove,
    .driver = {
        .name  = LED_DRV_NAME,
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(led_of_match),
    },
};

module_platform_driver(rk39xx_led_driver);

MODULE_AUTHOR("qulei <qulei159@126.com>");
MODULE_DESCRIPTION("rk39xx Leds driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:rk39xx_led");
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值