第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > 【分析笔记】Linux 4.9 backlight 子系统分析

【分析笔记】Linux 4.9 backlight 子系统分析

时间:2019-07-22 09:16:39

相关推荐

【分析笔记】Linux 4.9 backlight 子系统分析

相关信息

内核版本:Linux version 4.9.56

驱动文件:lichee\linux-4.9\drivers\video\backlight\backlight.c

驱动作用

对上,面对应用层提供统一的设备节点入口同级,面对驱动层提供设备驱动加载卸载通知事件,以及背光控制接口。对下,面对硬件层提供背光控制调节的回调接口监听 frambuffer 事件, 实现清屏联动背光控制监听系统休眠唤醒,实现休眠唤醒背光联动控制

控制背光的来源:应用访问、事件联动、休眠唤醒

源码分析

一、驱动初始化

完成背光设备逻辑类的创建初始化用于通知背光设备驱动加载卸载的事件为该逻辑类设定五个背光控制相关的设备节点

/sys/class/backlight/xxx/type/sys/class/backlight/xxx/bl_power/sys/class/backlight/xxx/brightness/sys/class/backlight/xxx/max_brightness/sys/class/backlight/xxx/actual_brightness

代码解析

static int __init backlight_class_init(void){// 创建背光设备逻辑类: /sys/class/backlightbacklight_class = class_create(THIS_MODULE, "backlight");if (IS_ERR(backlight_class)) {pr_warn("Unable to create backlight class; errno = %ld\n", PTR_ERR(backlight_class));return PTR_ERR(backlight_class);}// 面对应用层提供统一的设备节点入口(预先设置好节点名称和可读写权限)backlight_class->dev_groups = bl_device_groups;// 监听系统的休眠唤醒回调backlight_class->pm = &backlight_class_dev_pm_ops;// 初始化背光设备链表,用于被外部驱动查询获取INIT_LIST_HEAD(&backlight_dev_list);mutex_init(&backlight_dev_list_mutex);// 初始化内核通知链, 用于通知驱动层背光设备注册或卸载事件BLOCKING_INIT_NOTIFIER_HEAD(&backlight_notifier);return 0;}

ATTRIBUTE_GROUPS 宏定义解析:

直接搜索源码是找不到 bl_device_groups 关键词的,实际该变量是通过宏进行定义

static struct attribute *bl_device_attrs[] = {&dev_attr_bl_power.attr,&dev_attr_brightness.attr,&dev_attr_actual_brightness.attr,&dev_attr_max_brightness.attr,&dev_attr_type.attr,NULL,};ATTRIBUTE_GROUPS(bl_device);

宏定义: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)

宏定义展开:ATTRIBUTE_GROUPS(bl_device)

#define __ATTRIBUTE_GROUPS(bl_device)\static const struct attribute_group *bl_device_groups[] = {\&bl_device_group,\NULL,\}#define ATTRIBUTE_GROUPS(bl_device)\static const struct attribute_group bl_device_group = {\.attrs = bl_device_attrs,\};\__ATTRIBUTE_GROUPS(bl_device)

宏替换后的效果

static struct attribute *bl_device_attrs[] = {&dev_attr_bl_power.attr,&dev_attr_brightness.attr,&dev_attr_actual_brightness.attr,&dev_attr_max_brightness.attr,&dev_attr_type.attr,NULL,};static const struct attribute_group bl_device_group = {.attrs = bl_device_attrs,};static const struct attribute_group *bl_device_groups[] = {&bl_device_group,NULL,};

DEVICE_ATTR_RW 宏定义解析:

static ssize_t brightness_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t count){int rc;struct backlight_device *bd = to_backlight_device(dev);unsigned long brightness;rc = kstrtoul(buf, 0, &brightness);if (rc)return rc;rc = backlight_device_set_brightness(bd, brightness);return rc ? rc : count;}static DEVICE_ATTR_RW(brightness);

宏定义:include\linux\device.h include\linux\sysfs.h

#define DEVICE_ATTR_RW(_name) \struct device_attribute dev_attr_##_name = __ATTR_RW(_name)#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO),\_name##_show, _name##_store)

宏定义展开,取其中一个设备节点的例子:static DEVICE_ATTR_RW(brightness)

#define DEVICE_ATTR_RW(brightness) \struct device_attribute dev_attr_brightness = __ATTR_RW(brightness)#define __ATTR_RW(brightness) __ATTR(brightness, (S_IWUSR | S_IRUGO),\brightness_show, brightness_store)#define __ATTR(brightness, (S_IWUSR | S_IRUGO), brightness_show, brightness_store) {\.attr = {.name = __stringify(brightness),\.mode = VERIFY_OCTAL_PERMISSIONS((S_IWUSR | S_IRUGO)) },\.show= brightness_show,\.store= brightness_store,\}

宏替换后的效果

static struct device_attribute dev_attr_brightness = {.attr = {.name = __stringify(brightness),.mode = VERIFY_OCTAL_PERMISSIONS((S_IWUSR | S_IRUGO)) },.show= brightness_show,.store= brightness_store,}

二、背光设备驱动注册

假如 name=sunxi,那么调用此接口后将会产生如下几个设备节点:

背光设备类型:/sys/class/backlight/sunxi/type

背光电源控制:/sys/class/backlight/sunxi/bl_power

背光亮度设置:/sys/class/backlight/sunxi/brightness

最大背光亮度:/sys/class/backlight/sunxi/max_brightness

真实背光亮度:/sys/class/backlight/sunxi/actual_brightness

// name:背光设备名称parent:父设备devdata:私有数据ops:背光控制调节的回调props:默认的背光属性struct backlight_device *backlight_device_register(const char *name, struct device *parent, void *devdata, const struct backlight_ops *ops, const struct backlight_properties *props){struct backlight_device *new_bd;int rc;// 创建背光设备new_bd = kzalloc(sizeof(struct backlight_device), GFP_KERNEL);if (!new_bd)return ERR_PTR(-ENOMEM);mutex_init(&new_bd->update_lock);mutex_init(&new_bd->ops_lock);// 设置设备逻辑类为背光设备逻辑类, 在驱动初始化时完成的创建// 这里要注意, struct device 是直接嵌入到 struct backlight_device 里面的new_bd->dev.class = backlight_class;new_bd->dev.parent = parent;new_bd->dev.release = bl_device_release;// 设置名称, 体现在: /sys/class/backlight/XXXdev_set_name(&new_bd->dev, "%s", name);dev_set_drvdata(&new_bd->dev, devdata);// 如果有指定的背光属性, 则拷贝并作为默认的背光属性if (props) {memcpy(&new_bd->props, props, sizeof(struct backlight_properties));if (props->type <= 0 || props->type >= BACKLIGHT_TYPE_MAX) {WARN(1, "%s: invalid backlight type", name);new_bd->props.type = BACKLIGHT_RAW;}} else {new_bd->props.type = BACKLIGHT_RAW;}// 将此设备注册到系统里面,里面会创建 backlight_class 预设的那几个设备节点rc = device_register(&new_bd->dev);if (rc) {put_device(&new_bd->dev);return ERR_PTR(rc);}// 注册监听 frambuffer 的事件通知链rc = backlight_register_fb(new_bd);if (rc) {device_unregister(&new_bd->dev);return ERR_PTR(rc);}// 指定背光控制接口new_bd->ops = ops;// 将此设备加入到链表内, 便于外部查询获取mutex_lock(&backlight_dev_list_mutex);list_add(&new_bd->entry, &backlight_dev_list);mutex_unlock(&backlight_dev_list_mutex);// 发出事件通知, 有新的背光设备被注册到系统中blocking_notifier_call_chain(&backlight_notifier, BACKLIGHT_REGISTERED, new_bd);return new_bd;}EXPORT_SYMBOL(backlight_device_register);

三、应用层进行背光调节路径

代码解析

static ssize_t brightness_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t count){int rc;// 通过 dev 计算出 backlight_device 地址, 取得对应背光设备对象struct backlight_device *bd = to_backlight_device(dev);unsigned long brightness;// 字符串转换为数值rc = kstrtoul(buf, 0, &brightness);if (rc)return rc;// 调用设置背光接口, 该接口也被开放给其它驱动程序调用rc = backlight_device_set_brightness(bd, brightness);return rc ? rc : count;}static DEVICE_ATTR_RW(brightness);int backlight_device_set_brightness(struct backlight_device *bd,unsigned long brightness){int rc = -ENXIO;// 有多个路径调用, 存在并发调用, 借助互斥锁保护mutex_lock(&bd->ops_lock);// 检查是否设置了回调if (bd->ops) {// 检查所设置的背光数值是否超出最大值if (brightness > bd->props.max_brightness)rc = -EINVAL;else {// 填充要设置的背光值并更新bd->props.brightness = brightness;backlight_update_status(bd);rc = 0;}}mutex_unlock(&bd->ops_lock);backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS);return rc;}EXPORT_SYMBOL(backlight_device_set_brightness);static inline int backlight_update_status(struct backlight_device *bd){int ret = -ENOENT;// 从这里可以看出, 最终调用的背光设备驱动的 ops->update_status() 接口mutex_lock(&bd->update_lock);if (bd->ops && bd->ops->update_status)ret = bd->ops->update_status(bd);mutex_unlock(&bd->update_lock);return ret;}

关键解析

这里最主要的是搞清楚是如何区分应用层操作哪个背光设备(这部分实现很有意思,其原理会另写文章解析)。

Linux 最常用的是通过结构体嵌套的方式,实现以某个成员的内存地址计算出结构体的首地址,从而访问到其它的成员。struct device 是直接嵌入到 struct backlight_device 里面,就可以通过 struct device 找到对应 struct backlight_device。应用层通过 /sys/class/backlight/xxx 来访问 xxx 背光设备,就可确定 device, 根据 device 找出 backlight_device,调用对应背光设备驱动的 ops 成员。

struct backlight_device {......struct device dev;......};#define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)

四、事件联动进行背光调节路径

在背光设备驱动注册的时候,就调用 backlight_register_fb 来监听 fb 事件。

Xorg 就通过 FBIOBLANK 指令实现熄屏,但若没有对应的背光设备接口,就会出现显示屏全黑但是背光还亮着的问题。

代码解析

static int backlight_register_fb(struct backlight_device *bd){// 当有背光memset(&bd->fb_notif, 0, sizeof(bd->fb_notif));bd->fb_notif.notifier_call = fb_notifier_callback;return fb_register_client(&bd->fb_notif);}// drivers\video\fbdev\core\fb_notify.cint fb_register_client(struct notifier_block *nb){return blocking_notifier_chain_register(&fb_notifier_list, nb);}EXPORT_SYMBOL(fb_register_client);static int fb_notifier_callback(struct notifier_block *self,unsigned long event, void *data){struct backlight_device *bd;struct fb_event *evdata = data;int node = evdata->info->node;int fb_blank = 0;// 仅对显示空白事件感兴趣if (event != FB_EVENT_BLANK && event != FB_EVENT_CONBLANK)return 0;// 一样的操作, 通过 device 找到 backlight_devicebd = container_of(self, struct backlight_device, fb_notif);mutex_lock(&bd->ops_lock);if (bd->ops)if (!bd->ops->check_fb ||bd->ops->check_fb(bd, evdata->info)) {fb_blank = *(int *)evdata->data;if (fb_blank == FB_BLANK_UNBLANK && !bd->fb_bl_on[node]) {bd->fb_bl_on[node] = true;if (!bd->use_count++) {// 如果需要正常显示,则亮起背光bd->props.state &= ~BL_CORE_FBBLANK;bd->props.fb_blank = FB_BLANK_UNBLANK;backlight_update_status(bd);}} else if (fb_blank != FB_BLANK_UNBLANK && bd->fb_bl_on[node]) {bd->fb_bl_on[node] = false;if (!(--bd->use_count)) {// 如果显示空白画面,则熄灭背光bd->props.state |= BL_CORE_FBBLANK;bd->props.fb_blank = fb_blank;backlight_update_status(bd);}}}mutex_unlock(&bd->ops_lock);return 0;}

五、最简单的背光设备驱动

#include <linux/kernel.h>#include <linux/types.h>#include <linux/module.h>#include <linux/init.h>#include <linux/err.h>#include <linux/device.h>#include <linux/platform_device.h>#include <linux/backlight.h>static struct backlight_device *bdev = NULL;// 更新背光亮度static int mybl_bl_ops_update_bl(struct backlight_device *bdev){//......return 0;}// 获取背光亮度static int mybl_bl_ops_get_brightness(struct backlight_device *bdev){//......return brightness;}static const struct backlight_ops mybl_bl_ops = {.update_status= mybl_bl_ops_update_bl,.get_brightness= mybl_bl_ops_get_brightness,};static int mybl_probe(struct platform_device *pdev){bdev = backlight_device_register("mydriver", &pdev->dev, NULL, &mybl_bl_ops, NULL);if(IS_ERR(bdev)){return -1;}return 0;}static int mybl_remove(struct platform_device *pdev){backlight_device_unregister(bdev);return 0;}static const struct of_device_id mybl_ids[] = {{.compatible = "mybl"},{}};static struct platform_driver mybl_driver = {.probe= mybl_probe,.remove= mybl_remove,.driver= {.owner= THIS_MODULE,.name = "mybl",.of_match_table= mybl_ids,},};module_platform_driver(mybl_driver);MODULE_LICENSE("GPL");

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。