在《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