【分析笔记】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/backlight
backlight_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/XXX
dev_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.c
int 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_device
bd = 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");
【分析笔记】Linux 4.9 backlight 子系统分析的更多相关文章
- Linux下的Backlight子系统(二)【转】
转自:http://blog.csdn.net/weiqing1981127/article/details/8515847 版权所有,转载必须说明转自 http://my.csdn.net/weiq ...
- Linux下的Backlight子系统(一)【转】
转自:http://blog.csdn.net/weiqing1981127/article/details/8511676 版权所有,转载必须说明转自 http://my.csdn.net/weiq ...
- Linux内核内存管理子系统分析【转】
本文转载自:http://blog.csdn.net/coding__madman/article/details/51298718 版权声明:本文为博主原创文章,未经博主允许不得转载. 还是那张熟悉 ...
- 【分析笔记】NXP PCF85263 设备驱动分析笔记
驱动移植 供应商无法提供相应的驱动程序,不过在 linux 最新的内核倒是有一份 pcf85363 的驱动,看代码并核对寄存器功能,是可以兼容 pcf85263 芯片.只是我们用的内核比较老 linu ...
- backlight 子系统(转载)
http://blog.csdn.net/weiqing1981127/article/details/8511676 Linux下的Backlight子系统(一) http://blog.csd ...
- 【分析笔记】Linux input 子系统原理分析
一.input 子系统简介 输入子系统主要用于支持各种输入设备,可大大简化这类设备驱动的开发难度.以下为个人的理解,可能不同的内核版本会略有差异,在这里分析的内核为 linux-4.9. 无论在 Li ...
- Linux input子系统分析
输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见.同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分 ...
- Linux kernel的中断子系统之(七):GIC代码分析
返回目录:<ARM-Linux中断系统>. 总结: 原文地址:<linux kernel的中断子系统之(七):GIC代码分析> 参考代码:http://elixir.free- ...
- Linux内核分析第七周学习笔记——Linux内核如何装载和启动一个可执行程序
Linux内核分析第七周学习笔记--Linux内核如何装载和启动一个可执行程序 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study. ...
随机推荐
- 禁止eslint对指定代码检测
有时候我们引入外部文件的API时,eslint无法识别,编译的时候就会出现warn eslint是可以禁用对指定代码的检测: 单行注释 let map = new BMap.Map('map') // ...
- Windows版CheatSheet——一键显示当前程序快捷键列表
Windows系统上的各种软件有太多的快捷键,想要记住是几乎不可能的,推荐一个一键显示当前软件快捷键的软件,在使用其他程序的时候,只要按下Ctrl+`就可以理解弹出该软件的所有快捷键列表,还支持收藏功 ...
- 第2-2-4章 常见组件与中台化-常用组件服务介绍-分布式ID-附Snowflake雪花算法的代码实现
目录 2.3 分布式ID 2.3.1 功能概述 2.3.2 应用场景 2.3.3 使用说明 2.3.4 项目截图 2.3.5 Snowflake雪花算法的代码实现 2.3 分布式ID 2.3.1 功能 ...
- Kubernetes核心技术Pod
Kubernetes核心技术Pod Pod概述 Pod是K8S系统中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型,也是在K8S上运行容器化应用的资源对象,其它的资源对象 ...
- 推荐三个实用的 Go 开发工具
孙悟空在花果山称王的时候,特意去了一趟东海,在那里淘到了如意金箍棒.因为身为一个山大王,怎么能没有一件趁手的兵器呢? 作为程序员的我们也一样,除了我们的傍身武器 Ctrl C + V 之外,还要不停的 ...
- Docker遇见golang https://www.jianshu.com/p/37693eb8f646
golang logo 在我国古代,传说天庭中有种种天兵天将,有看守四大天门的(docker0网桥),有负责传话的门将(REST API),有负责人间和天界联络的,如财神爷,土地公等(NAT,DNS) ...
- RabbitMq了解
RibbitMQ MQ优势 MQ的三大主要作用: 应用解耦.异步提速.流量削锋 应用解耦 系统的耦合性越高,容错性就越低,可维护性就越低: 解耦: 如果其中一个系统服务宕机,那么系统的其他服务将也无法 ...
- cv2.imread opencv读取不到图片问题
解决办法 cv2.imread 路径中包含中文,改为英文 其他 这个问题解决了很久,都属于库的问题
- 关于python3调用matplotlib中文乱码问题
问题描述 我用来绘制柱形图,横坐标上面的数据, 但是网上大部分说的都是更改横纵坐标标签的乱码问题,而不是横坐标数据乱码问题 解决办法 更改横纵坐标上标签的中文不乱码 import matplotlib ...
- 【Java EE】Day12 XML、约束(DTD、Schema)、解析方式、Jsoup、选择器(Selector、XPath)
一.XML介绍 1.概述 Extensible Markup Language--可扩展标记语言 标记语言 :标签构成 可扩展:可以自定义标签 2.功能 存储数据 作为配置文件使用 作为数据载体在网络 ...