Linux内核LED子系统触发器分析

JASON0916Linux驱动 2024-07-02 15:52:47 5421阅读 举报


在《Linux内核自带的leds-gpio代码分析 - 电子网 (dianziwang.net)》这篇文章中有介绍当一个新的LED的驱动被加载后,如果对应的平台设备和平台驱动匹配,就会执行驱动中的probe函数。添加完LED的字符设备后,会遍历触发器链表,比较是否有名字相同的触发器与设备树中给的触发器名字是否相同,如果相同就会调用触发器的active函数。

好,那这里我们就先以ledtrig-timer.c来分析:

假如我们的设备树文件中启用了这个触发器:linux,default-trigger = "timer";

	leds: leds {
		compatible = "gpio-leds";
		rgb_led_r: rgb-led-r {
			gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_LOW>;
			linux,default-trigger = "timer";
			linux,delay-reg = <0>;   		// 延时注册
			linux,blink-delay-on = <500>; 	// 打开时间
			linux,blink-delay-off = <500>;	// 关闭时间
		};
		rgb_led_g: rgb-led-g {
			gpios = <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
			linux,default-trigger = "timer";
			linux,delay-reg = <100>;   		// 延时注册
			linux,blink-delay-on = <1000>; 
			linux,blink-delay-off = <1000>;
		};
		rgb_led_b: rgb-led-b {
			gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_LOW>;
			linux,default-trigger = "timer";
			linux,delay-reg = <100>;  		// 延时注册
			linux,blink-delay-on = <1500>; 
			linux,blink-delay-off = <1500>;
		};
	};

调用触发器的active函数:

static void timer_trig_activate(struct led_classdev *led_cdev)
{
    int rc;

    led_cdev->trigger_data = NULL;

    rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
    if (rc)
        return;
    rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
    if (rc)
        goto err_out_delayon;

    led_blink_set(led_cdev, &led_cdev->blink_delay_on,
              &led_cdev->blink_delay_off);
    led_cdev->activated = true;

    return;

err_out_delayon:
    device_remove_file(led_cdev->dev, &dev_attr_delay_on);
}

这里首先在LED的sys目录下添加了两个属性文件,然后调用led_blink_set来设置亮和灭的时间。

这两个属性文件又来源于上面的定义:

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 struct attribute *timer_trig_attrs[] = {
	&dev_attr_delay_on.attr,
	&dev_attr_delay_off.attr,
	NULL
};
ATTRIBUTE_GROUPS(timer_trig);

我们看下DEVICE_ATTR是什么?

内核根目录/include/linux/device.h文件中有定义:

#define DEVICE_ATTR(_name, _mode, _show, _store) \
	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

在Documentation/driver-model/device.txt文档中有对于这个的介绍:

ˉˉˉˉˉˉˉˉˉˉˉˉˉ
    Attributes are declared using a macro called DEVICE_ATTR:
    #define DEVICE_ATTR(name, mode, show, store)

    意思是说,宏 DEVICE_ATTR 用来声明 设备属性文件的 属性(听起来像绕口令)。宏的各参数含义如下:
    name    属性文件的名称
    mode    属性文件的权限。这个权限值为类似于 0644 这样的 4 位数,分别表示 SUID/GUID+User+Group+Others
    show    从该属性文件读数据时调用的函数
    store    向该属性文件写数据时调用的函数
    宏 __ATTR(_name, _mode, _show, _store) 的定义:
    #define __ATTR(_name, _mode, _show, _store) { \
        .attr = {.name = __stringify(_name), .mode = _mode }, \
        .show = _show, \
        .store = _store, \
    }

 结构体 device_attribute 的定义:

    struct device_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
        ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
    };

由此可见, 宏 DEVICE_ATTR的作用实际上是在声明并初始化一个 device_attribute结构体。
    该结构体名称为 dev_attr_##_name (这里使用了预处理命令中 粘贴操作符 的概念)。

回到我们LED定时器触发这里,我们把 static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); 展开:

struct device_attribute dev_attr_delay_on = __ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store)

进一步展开得到:

    struct device_attribute dev_attr_delay_on = { \
.attr = {.name = __stringify(delay_on), .mode = 0644}, \
.show = led_delay_on_show, \
.store = led_delay_on_store, \
}

同理,我们把 static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store);展开:

struct device_attribute dev_attr_delay_off = __ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store)

进一步展开得到:

    struct device_attribute dev_attr_delay_off = { \
.attr = {.name = __stringify(delay_off), .mode = 0644}, \
.show = led_delay_off_show, \
.store = led_delay_off_store, \
}

这两个属性放到了一个属性组里,我们来看下这个属性组,也是个宏定义:

static struct attribute *timer_trig_attrs[] = {
	&dev_attr_delay_on.attr,
	&dev_attr_delay_off.attr,
	NULL
};
ATTRIBUTE_GROUPS(timer_trig);

ATTRIBUTE_GROUPS的原型在include\linux\sysfs.h:

#define __ATTRIBUTE_GROUPS(_name)				\
static const struct attribute_group *_name##_groups[] = {	\
	&_name##_group,						\
	NULL,							\
}

#define ATTRIBUTE_GROUPS(_name)					\
static const struct attribute_group _name##_group = {		\
	.attrs = _name##_attrs,					\
};								\
__ATTRIBUTE_GROUPS(_name)

展开就是:

#define __ATTRIBUTE_GROUPS(timer_trig)				\
static const struct attribute_group *timer_trig_groups[] = { \
&timer_trig_group, \
NULL, \ } #define ATTRIBUTE_GROUPS(timer_trig) \
static const struct attribute_group timer_trig_group = { \
.attrs = timer_trig_attrs, \
}; \ __ATTRIBUTE_GROUPS(timer_trig)

那这些属性文件到底在哪里创建呢?我们来分析一下看看是不是这样的,当我们注册触发器的时候会与LED设备链表中的设备节点中的触发器名字做比较,如果相同就会设置触发器,进而创建相关的属性文件。

module_led_trigger(timer_led_trigger);
	module_driver(__led_trigger, led_trigger_register, \
		      led_trigger_unregister)
int led_trigger_register(struct led_trigger *trig)
{
	struct led_classdev *led_cdev;
	struct led_trigger *_trig;

	rwlock_init(&trig->leddev_list_lock);
	INIT_LIST_HEAD(&trig->led_cdevs);

	down_write(&triggers_list_lock);
	/* Make sure the trigger's name isn't already in use */
	list_for_each_entry(_trig, &trigger_list, next_trig) {
		if (!strcmp(_trig->name, trig->name)) {
			up_write(&triggers_list_lock);
			return -EEXIST;
		}
	}
	/* Add to the list of led triggers */
	list_add_tail(&trig->next_trig, &trigger_list);
	up_write(&triggers_list_lock);

	/* Register with any LEDs that have this as a default trigger */
	down_read(&leds_list_lock);
	list_for_each_entry(led_cdev, &leds_list, node) {
		down_write(&led_cdev->trigger_lock);
		if (!led_cdev->trigger && led_cdev->default_trigger &&
			    !strcmp(led_cdev->default_trigger, trig->name))
			led_trigger_set(led_cdev, trig);
		up_write(&led_cdev->trigger_lock);
	}
	up_read(&leds_list_lock);

	return 0;
}

这里把该触发器挂到了触发器列表,然后与LED设备列表里的节点比较触发器的名字,如果匹配则设置这个LED设备的触发器。

int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
	unsigned long flags;
	char *event = NULL;
	char *envp[2];
	const char *name;
	int ret;

	if (!led_cdev->trigger && !trig)
		return 0;

	name = trig ? trig->name : "none";
	event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name);

	/* Remove any existing trigger */
	if (led_cdev->trigger) {
		write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
		list_del(&led_cdev->trig_list);
		write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
			flags);
		cancel_work_sync(&led_cdev->set_brightness_work);
		led_stop_software_blink(led_cdev);
		if (led_cdev->trigger->deactivate)
			led_cdev->trigger->deactivate(led_cdev);
		device_remove_groups(led_cdev->dev, led_cdev->trigger->groups);
		led_cdev->trigger = NULL;
		led_cdev->trigger_data = NULL;
		led_cdev->activated = false;
		led_set_brightness(led_cdev, LED_OFF);
	}
	if (trig) {
		write_lock_irqsave(&trig->leddev_list_lock, flags);
		list_add_tail(&led_cdev->trig_list, &trig->led_cdevs);
		write_unlock_irqrestore(&trig->leddev_list_lock, flags);
		led_cdev->trigger = trig;

		if (trig->activate)
			ret = trig->activate(led_cdev);
		else
			ret = 0;

		if (ret)
			goto err_activate;

		ret = device_add_groups(led_cdev->dev, trig->groups);
		if (ret) {
			dev_err(led_cdev->dev, "Failed to add trigger attributes\n");
			goto err_add_groups;
		}
	}

	if (event) {
		envp[0] = event;
		envp[1] = NULL;
		if (kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp))
			dev_err(led_cdev->dev,
				"%s: Error sending uevent\n", __func__);
		kfree(event);
	}

	return 0;

err_add_groups:

	if (trig->deactivate)
		trig->deactivate(led_cdev);
err_activate:

	write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
	list_del(&led_cdev->trig_list);
	write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, flags);
	led_cdev->trigger = NULL;
	led_cdev->trigger_data = NULL;
	led_set_brightness(led_cdev, LED_OFF);
	kfree(event);

	return ret;
}

trig->activate(led_cdev); 调用触发器的active函数,在本文开头已经介绍过了,这里不展开,我们看下面的device_add_groups(led_cdev->dev, trig->groups);接着调用了sysfs_create_groups(&dev->kobj, groups);

int sysfs_create_groups(struct kobject *kobj,
			const struct attribute_group **groups)
{
	int error = 0;
	int i;

	if (!groups)
		return 0;

	for (i = 0; groups[i]; i++) {
		error = sysfs_create_group(kobj, groups[i]);
		if (error) {
			while (--i >= 0)
				sysfs_remove_group(kobj, groups[i]);
			break;
		}
	}
	return error;
}

这个groups就是timer_trig_groups,里面只有一个数组元素timer_trig_group

static const struct attribute_group *timer_trig_groups[] = {	\
	&timer_trig_group,						\
	NULL,							\
}

sysfs_create_group调用internal_create_group(kobj, 0, grp);

static int internal_create_group(struct kobject *kobj, int update,
				 const struct attribute_group *grp)
{
	struct kernfs_node *kn;
	kuid_t uid;
	kgid_t gid;
	int error;

	BUG_ON(!kobj || (!update && !kobj->sd));

	/* Updates may happen before the object has been instantiated */
	if (unlikely(update && !kobj->sd))
		return -EINVAL;
	if (!grp->attrs && !grp->bin_attrs) {
		WARN(1, "sysfs: (bin_)attrs not set by subsystem for group: %s/%s\n",
			kobj->name, grp->name ?: "");
		return -EINVAL;
	}
	kobject_get_ownership(kobj, &uid, &gid);
	if (grp->name) {
		if (update) {
			kn = kernfs_find_and_get(kobj->sd, grp->name);
			if (!kn) {
				pr_warn("Can't update unknown attr grp name: %s/%s\n",
					kobj->name, grp->name);
				return -EINVAL;
			}
		} else {
			kn = kernfs_create_dir_ns(kobj->sd, grp->name,  //创建组目录
						  S_IRWXU | S_IRUGO | S_IXUGO,
						  uid, gid, kobj, NULL);
			if (IS_ERR(kn)) {
				if (PTR_ERR(kn) == -EEXIST)
					sysfs_warn_dup(kobj->sd, grp->name);
				return PTR_ERR(kn);
			}
		}
	} else
		kn = kobj->sd;
	kernfs_get(kn);
	error = create_files(kn, kobj, uid, gid, grp, update);//创建属性文件
	if (error) {
		if (grp->name)
			kernfs_remove(kn);
	}
	kernfs_put(kn);

	if (grp->name && update)
		kernfs_put(kn);

	return error;
}

在这里面可以看到创建了组的目录和属性文件。

static int create_files(struct kernfs_node *parent, struct kobject *kobj,
			kuid_t uid, kgid_t gid,
			const struct attribute_group *grp, int update)
{
	struct attribute *const *attr;
	struct bin_attribute *const *bin_attr;
	int error = 0, i;

	if (grp->attrs) {
		for (i = 0, attr = grp->attrs; *attr && !error; i++, attr++) {
			umode_t mode = (*attr)->mode;

			/*
			 * In update mode, we're changing the permissions or
			 * visibility.  Do this by first removing then
			 * re-adding (if required) the file.
			 */
			if (update)
				kernfs_remove_by_name(parent, (*attr)->name);
			if (grp->is_visible) {
				mode = grp->is_visible(kobj, *attr, i);
				if (!mode)
					continue;
			}

			WARN(mode & ~(SYSFS_PREALLOC | 0664),
			     "Attribute %s: Invalid permissions 0%o\n",
			     (*attr)->name, mode);

			mode &= SYSFS_PREALLOC | 0664;
			error = sysfs_add_file_mode_ns(parent, *attr, false,
						       mode, uid, gid, NULL);
			if (unlikely(error))
				break;
		}
		if (error) {
			remove_files(parent, grp);
			goto exit;
		}
	}

	if (grp->bin_attrs) {
		for (i = 0, bin_attr = grp->bin_attrs; *bin_attr; i++, bin_attr++) {
			umode_t mode = (*bin_attr)->attr.mode;

			if (update)
				kernfs_remove_by_name(parent,
						(*bin_attr)->attr.name);
			if (grp->is_bin_visible) {
				mode = grp->is_bin_visible(kobj, *bin_attr, i);
				if (!mode)
					continue;
			}

			WARN(mode & ~(SYSFS_PREALLOC | 0664),
			     "Attribute %s: Invalid permissions 0%o\n",
			     (*bin_attr)->attr.name, mode);

			mode &= SYSFS_PREALLOC | 0664;
			error = sysfs_add_file_mode_ns(parent,
					&(*bin_attr)->attr, true,
					mode,
					uid, gid, NULL);
			if (error)
				break;
		}
		if (error)
			remove_files(parent, grp);
	}
exit:
	return error;
}

创建完后我们就可以在对应的LED的文件目录下看到两个属性文件:

root@RK356X:/# ls /sys/class/leds/
mmc0::  rgb-led-b  rgb-led-g  rgb-led-r
root@RK356X:/# ls /sys/class/leds/rgb-led-b
brightness  delay_on  max_brightness  subsystem  uevent
delay_off   device    power    trigger

我们看到还多出来一些属性文件,这些属性文件是在注册LED设备的时候随之自动创建的。

然后就可以在命令行中对这些属性文件进行读写操作:

定时触发
 echo timer > /sys/class/leds/rgb-led-b/trigger
echo 5000 > /sys/class/leds/rgb-led-b/delay_on
echo 5000 > /sys/class/leds/rgb-led-b/delay_off
心跳触发
echo heartbeat > /sys/class/leds/rgb-led-b/trigger
echo 100 > /sys/class/leds/rgb-led-b/delay_on
echo 200 > /sys/class/leds/rgb-led-b/delay_off
亮灭控制
 echo 0 > /sys/class/leds/rgb-led-b/brightness 亮
echo 1 > /sys/class/leds/rgb-led-b/brightness  灭

echo timer > /sys/class/leds/led1/trigger
这一句会调用
led_trigger_store()->
   led_trigger_set()->
     trigger->activate(led_cdev);
从而调用ledtrig-timer.c文件里的timer_trig_activate()
echo 100 > /sys/class/leds/reb-led-b/deay_on 这一句会调用led_delay_on_store -> led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off); 来设置闪烁关闭的时间
#关闭蓝灯  实际是移除了存在的触发器,并把亮度设置成关闭 led_trigger_remove=》led_trigger_set(led_cdev, NULL);=》led_set_brightness(led_cdev, LED_OFF);
rk3566_tspi:/ # echo "none" > /sys/class/leds/rgb-led-b/trigger  
#关闭绿灯
rk3566_tspi:/ # echo "none" > /sys/class/leds/rgb-led-g/trigger
#关闭红灯
rk3566_tspi:/ # echo "none" > /sys/class/leds/rgb-led-r/trigger
#常亮蓝灯  添加ledtrig-timer.c这个触发器,在defon_trig_activate=》led_set_brightness_nosleep(led_cdev, led_cdev->max_brightness);设置为最大亮度
rk3566_tspi:/ # echo "default-on" > /sys/class/leds/rgb-led-b/trigger
#常亮绿灯
rk3566_tspi:/ # echo "default-on" > /sys/class/leds/rgb-led-g/trigger
#常亮红灯
rk3566_tspi:/ # echo "default-on" > /sys/class/leds/rgb-led-r/trigger
#常亮蓝灯
rk3566_tspi:/ # echo "heartbeat" > /sys/class/leds/rgb-led-b/trigger
#常亮绿灯
rk3566_tspi:/ # echo "heartbeat" > /sys/class/leds/rgb-led-g/trigger
#常亮红灯
rk3566_tspi:/ # echo "heartbeat" > /sys/class/leds/rgb-led-r/trigger
#常亮蓝灯
rk3566_tspi:/ # echo "timer" > /sys/class/leds/rgb-led-b/trigger
#常亮绿灯
rk3566_tspi:/ # echo "timer" > /sys/class/leds/rgb-led-g/trigger
#常亮红灯
rk3566_tspi:/ # echo "timer" > /sys/class/leds/rgb-led-r/trigger



标签: #Linux#

版权声明:
作者:JASON0916
链接:https://www.dianziwang.net/p/893cd0527d84e.html
来源:Linux驱动
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以点击 “举报”


登录 后发表评论
0条评论
还没有人评论过~