宿主机 : 虚拟机 Ubuntu 16.04 LTS / X64
目标板[底板]: Tiny4412SDK - 1506
目标板[核心板]: Tiny4412 - 1412
LINUX内核: 4.12.0
交叉编译器: gcc-arm-none-eabi-5_4-2016q3
日期: 2017-7-22 12:08:02
作者: SY
Tiny4412
开发板上有4个LED
自定义指示灯,我们使用LED1
作为Heartbeat
心跳。只要系统正常运行,小灯便永不停息,就像人的心跳一样!
对于LINUX
来说,最好的方式是:只需一种驱动便可适配多个设备,驱动可以自由的管理设备的安装、卸载、打开、关闭等操作。
设备树本质上和xml
文件类似,主要用来被内核做字符串解析,是一个静态文本。就像安卓的页面配置文件activity_main.xml
用来描述页面的布局,元素等。
在LINUX 3.5
以后,都是使用内核设备树配置外设。也就是说,很多驱动在LINUX
内核源码层次,不用修改,只要配置dts
,然后在menuconfig
中打开就行了。
root@ubuntu:/opt/linux-4.12# more arch/arm/boot/dts/exynos4412-tiny4412.dts
leds {
compatible = "gpio-leds";
led1 {
label = "led1";
gpios = <&gpm4 0 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "heartbeat";
};
led2 {
label = "led2";
gpios = <&gpm4 1 GPIO_ACTIVE_LOW>;
default-state = "off";
};
led3 {
label = "led3";
gpios = <&gpm4 2 GPIO_ACTIVE_LOW>;
default-state = "off";
};
led4 {
label = "led4";
gpios = <&gpm4 3 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "mmc0";
};
};
本次主要分析led1
:
compatible = "gpio-leds";
主要匹配:./drivers/leds/leds-gpio.c
static const struct of_device_id of_gpio_leds_match[] = {
{ .compatible = "gpio-leds", },
{},
};
led
在probe
时,将设备树中的匹配到的节点全部进行初始化。
属性label
在下面解释:
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
ret = fwnode_property_read_string(child, "label", &led.name);
}
gpios
在下面解释: static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
GPIOD_ASIS,
led.name); <--
devm_fwnode_get_index_gpiod_from_child <--
for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
if (con_id)
snprintf(prop_name, sizeof(prop_name), "%s-%s",
con_id, gpio_suffixes[i]);
else
snprintf(prop_name, sizeof(prop_name), "%s",
gpio_suffixes[i]);
desc = fwnode_get_named_gpiod(child, prop_name, index, flags,
label);
if (!IS_ERR(desc) || (PTR_ERR(desc) != -ENOENT))
break;
}
static const char * const gpio_suffixes[] = { "gpios", "gpio" };
也就是说属性gpios也可以写成gpio,内核也可以识别
}
default-state = "off"
在下面解释:static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
if (!fwnode_property_read_string(child, "default-state",
&state)) {
if (!strcmp(state, "keep"))
led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
else if (!strcmp(state, "on"))
led.default_state = LEDS_GPIO_DEFSTATE_ON;
else
led.default_state = LEDS_GPIO_DEFSTATE_OFF;
}
}
linux,default-trigger
在下面解释:static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
fwnode_property_read_string(child, "linux,default-trigger",
&led.default_trigger);
}
menuconfig
root@ubuntu:/opt/linux-4.12# make menuconfig
Device Drivers --->
[*] LED Support --->
<*> LED Class Support
<*> LED Support for GPIO connected LEDs
[*] LED Trigger support --->
<*> LED Heartbeat Trigger
支持三种配置方式:
< >
:不支持
:以模块方式加载
<*>
:编译进内核
我们采用从后向前分析的方式,我们的目的是让LED1
实现心跳,首先要找到实现心跳的驱动位置:
./drivers/leds/trigger/ledtrig-heartbeat.c
module_init(heartbeat_trig_init); -->
static int __init heartbeat_trig_init(void)
{
int rc = led_trigger_register(&heartbeat_led_trigger);
if (!rc) {
atomic_notifier_chain_register(&panic_notifier_list,
&heartbeat_panic_nb);
register_reboot_notifier(&heartbeat_reboot_nb);
}
return rc;
}
static struct led_trigger heartbeat_led_trigger = {
.name = "heartbeat",
.activate = heartbeat_trig_activate,
.deactivate = heartbeat_trig_deactivate,
};
而led_trigger_register
在./drivers/leds/led-triggers.c
中定义。
int led_trigger_register(struct led_trigger *trig)
{
...
/* Add to the list of led triggers */
list_add_tail(&trig->next_trig, &trigger_list);
[1] 这样 heartbeat_led_trigger 被添加到链表中,通过name字段来标识
/* Register with any LEDs that have this as a default trigger */
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);
}
[2] 遍历leds_list链表,那么肯定有地方要添加元素到链表,找到函数
leds_list <--
of_led_classdev_register <--
devm_of_led_classdev_register <--
create_gpio_led <--
gpio_led_probe <--
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
.shutdown = gpio_led_shutdown,
.driver = {
.name = "leds-gpio",
.of_match_table = of_gpio_leds_match,
},
};
module_platform_driver(gpio_led_driver); <--
/* module_platform_driver() - Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*/
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
}
[1]
struct led_trigger {
/* Trigger Properties */
const char *name;
void (*activate)(struct led_classdev *led_cdev);
void (*deactivate)(struct led_classdev *led_cdev);
/* LEDs under control by this trigger (for simple triggers) */
rwlock_t leddev_list_lock;
struct list_head led_cdevs;
/* Link to next registered trigger */
struct list_head next_trig;
};
[2]
./drivers/leds/led-class.c
int of_led_classdev_register(struct device *parent, struct device_node *np,
struct led_classdev *led_cdev)
{
...
list_add_tail(&led_cdev->node, &leds_list);
}
我们在顺序执行流程整理一下:[分支A]
module_platform_driver(gpio_led_driver);
gpio_led_driver -->
gpio_led_probe -->
gpio_leds_create -->
create_gpio_led -->
devm_of_led_classdev_register -->
of_led_classdev_register -->
list_add_tail(&led_cdev->node, &leds_list); # leds_list链表产生了节点
再来看另一个分支:[分支B]
module_init(heartbeat_trig_init);
heartbeat_trig_init -->
led_trigger_register -->
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);
} # 遍历leds_list链表
这样两个分支便产生了关联,分支A先执行,然后分支B执行。其实也很好理解,分支A在做底层的初始化和配置工作,分支B是中间接口层,向下连接底层驱动,向上提供访问接口。
如果要控制LED
,需要调用函数:
static struct led_trigger heartbeat_led_trigger = {
.name = "heartbeat",
.activate = heartbeat_trig_activate,
.deactivate = heartbeat_trig_deactivate,
};
通过搜索,找到./drivers/leds/led-triggers.c
中的函数:
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
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);
led_cdev->trigger = NULL;
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) # 使生效
trig->activate(led_cdev);
}
}
搜索led_trigger_set
的调用者:
ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
led_trigger_set(led_cdev, trig);
}
搜索led_trigger_store
的调用者:
#ifdef CONFIG_LEDS_TRIGGERS
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
static struct attribute *led_trigger_attrs[] = {
&dev_attr_trigger.attr,
NULL,
};
static const struct attribute_group led_trigger_group = {
.attrs = led_trigger_attrs,
};
#endif
参考[1],上述代码可以修改为以下形式:
static device_attribute dev_attr_trigger = {
.attr = {.trigger = __stringify(trigger), \
.mode = VERIFY_OCTAL_PERMISSIONS(0644) }, \
.show = led_trigger_show, \
.store = led_trigger_store, \
};
static struct attribute *led_trigger_attrs[] = {
&dev_attr_trigger.attr,
NULL,
};
static const struct attribute_group led_trigger_group = {
.attrs = led_trigger_attrs,
};
[1]
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define __ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _show, \
.store = _store, \
}
搜索led_trigger_group
调用者:
static const struct attribute_group *led_groups[] = {
&led_group,
#ifdef CONFIG_LEDS_TRIGGERS
&led_trigger_group,
#endif
NULL,
};
搜索led_groups
调用者:
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "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;
}
subsys_initcall(leds_init);
再次总结:led-class.c
为led
类向外提供的通用接口,调用顺序:
module_platform_driver(gpio_led_driver); --> # ./drivers/leds/leds-gpio.c
module_init(heartbeat_trig_init); --> # ./drivers/leds/trigger/heartbeat.c
led_trigger_set(); --> # ./drivers/leds/led-triggers.c
subsys_initcall(leds_init); --> # ./drivers/leds/led-class.c
对于LED
心跳来说,咚咚两下,就是说需要使用状态机驱动,在切换状态时,修改定时器的定时周期即可。
最核心的执行体为:led_heartbeat_function
创建定时器:
static void heartbeat_trig_activate(struct led_classdev *led_cdev)
{
setup_timer(&heartbeat_data->timer,
led_heartbeat_function, (unsigned long) led_cdev);
}
只执行一次,将led_heartbeat_function
作为定时器溢出的回调函数,用来切换状态机。
修改定时器:
static void led_heartbeat_function(unsigned long data)
{
mod_timer(&heartbeat_data->timer, jiffies + delay);
}
每次定时器溢出,执行一次。
关闭定时器:
static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
{
del_timer_sync(&heartbeat_data->timer);
}
永远不会执行,心跳永远不会停止。
LED
打开时机:
led_trigger_store -->
ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
list_for_each_entry(trig, &trigger_list, next_trig) {
if (sysfs_streq(buf, trig->name)) {
down_write(&led_cdev->trigger_lock);
led_trigger_set(led_cdev, trig); -->
up_write(&led_cdev->trigger_lock);
up_read(&triggers_list_lock);
goto unlock;
}
}
}
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
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)
trig->activate(led_cdev); -->
}
}
static void heartbeat_trig_activate(struct led_classdev *led_cdev)
{
setup_timer(&heartbeat_data->timer,
led_heartbeat_function, (unsigned long) led_cdev); --> # Open HeartBeat
led_heartbeat_function(heartbeat_data->timer.data);
}
LED
会在 /sys/bus/platform/devices/leds/leds/
目录生成文件。读写文件可以操作 LED
设备了。
查看触发器:
[root@TINY4412:~]# cat /sys/bus/platform/devices/leds/leds/led4/trigger
none kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock usbport usb-gadget usb-host [mmc0] mmc1 heartbeat
[xxx]
表示当前触发器被选中,当前设备被读写时, LED
会有相应动作。
LED4
的触发器为 mmc0
,mmc0
表示 SD卡
。 当我们拔掉 SD卡
时, LED4
开始闪烁。
上述列表中的触发器都是可以配置的。