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

news2025/8/13 7:59:02

相关信息

内核版本:Linux version 4.9.56
驱动文件:lichee\linux-4.9\drivers\video\backlight\backlight.c

驱动作用

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

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

在这里插入图片描述

源码分析

一、驱动初始化

  1. 完成背光设备逻辑类的创建
  2. 初始化用于通知背光设备驱动加载卸载的事件
  3. 为该逻辑类设定五个背光控制相关的设备节点
/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;
}
关键解析

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

  1. Linux 最常用的是通过结构体嵌套的方式,实现以某个成员的内存地址计算出结构体的首地址,从而访问到其它的成员。
  2. struct device 是直接嵌入到 struct backlight_device 里面,就可以通过 struct device 找到对应 struct backlight_device。
  3. 应用层通过 /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");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/14961.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

闲人闲谈PS之三十二——业务工程预算和PS成本计划

惯例闲话&#xff1a;年底&#xff0c;又要开始忙了&#xff0c;今年这一年&#xff0c;收获还不错&#xff0c;至少规划了第三年实现的目标——工程行业彩虹图&#xff0c;在今年居然奇迹般的实现了&#xff0c;看样子闲人还是保守了。还是应验了那句话&#xff0c;只要标准化…

React插槽

在开发中&#xff0c;我们抽取了一个组件&#xff0c;但是为了让这个组件具备更强的通用性&#xff0c;我们不能将组件中的内容限制为固定的div、span等等这些元素。我们应该让使用者可以决定某一块区域到底存放什么内容。这里可以使用插槽。 而在React中&#xff0c;我们可以通…

Spring Cloud Circuit Breaker 使用示例

Spring Cloud Circuit Breaker 使用示例 作者&#xff1a; Grey 原文地址&#xff1a; 博客园&#xff1a;Spring Cloud Circuit Breaker 使用示例 CSDN&#xff1a;Spring Cloud Circuit Breaker 使用示例 说明 Spring Cloud Circuit breaker提供了一个跨越不同断路器实现…

留学Essay写作怎么注意具体结构?

留学生都知道Essay一般分为Introduction,Main Body和Conclusion这三个部分&#xff0c;Introduction说明了文章的整体内容&#xff0c;但本身并没有提出任何论点&#xff1b;Conclusion通常简要地概括了正文的要点&#xff0c;并建议进一步研究给出一些或结论性的想法&#xff…

Springboot实现ENC加密

目录1. 为什么要用ENC加密2. jasypt实现ENC加密1. 实现流程2. 说明1. 自定义加密秘钥1. 盐、前缀、后缀2. 自定义加密方案2. 部署方案1. 为什么要用ENC加密 以下是未经过加密的数据库配置&#xff0c;密码均是采用明文密码&#xff0c;很容易导致数据库泄露。 spring:datasou…

gitlab 简单优化 gitlab cpu高,内存高 gitlab 负载飙高

1.首先要说得是&#xff0c;优化不能解决根本问题&#xff0c;机器小水管&#xff0c;再优化还是不行。 我感觉4核8g应该可以&#xff0c;截图这机器是阿里云送的免费一个月 4核8g 内存&#xff0c;不知是不是送得问题&#xff0c;感觉也是hold不住。 负载逛逛升&#xff0c;8…

【springboot】19、数据库操作

文章目录基本说明默认数据源HikariDataSource切换数据源为Druid基本说明 这篇文章介绍如何在springboot的项目中进行数据库的连接&#xff0c;完成数据库操作。 默认数据源HikariDataSource HikariDataSource是springboot的默认数据源&#xff0c;性能十分优秀&#xff0c;我…

【Hack The Box】windows练习-- love

HTB 学习笔记 【Hack The Box】windows练习-- love &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年9月7日&#x1f334; &#x1f36d…

【freeRTOS】操作系统之四-事件标志组

事件标志组是实现多任务同步的有效机制之一。 ​ 搞个全局变量不是更简单&#xff1f;其实不然&#xff0c;在裸机编程时&#xff0c;使用全局变量的确比较方便&#xff0c;但是在加上 RTOS 后就是另一种情况了。 使用全局变量相比事件标志组主要有如下三个问题&#xff1a; …

人工智能 AI 绘画 AI绘制的图片 ? 简介的版权,以及如何使用图像生成AI 绘画 ?

人工智能 AI 绘画 AI绘制的图片 ? 简介的版权&#xff0c;以及如何使用图像生成AI 绘画 ? 我认为&#xff0c;许多人认为工作的绘画是人类独有的。 然而&#xff0c;在最新的&#xff0c;大赦国际还可以在工作的绘画和绘画创作大赦国际已经生一个接一个。和有些人感到惊奇的…

Docker从入门到精通,一文详解

一、Docker简介 1、背景 物理机时代 一个物理机上安装操作系统&#xff0c;然后直接运行我们的软件。也就是说你电脑上直接跑了一个软件&#xff0c;并没有开虚拟机什么的&#xff0c;资源极其浪费。 缺点 部署慢成本高 虚拟机都不开&#xff0c;直接上物理机部署应用&#x…

libusb系列-006-Qt下使用libusb1.0.9源码

libusb系列-006-Qt下使用libusb1.0.9源码 文章目录libusb系列-006-Qt下使用libusb1.0.9源码摘要添加宏添加源文件编译文件测试libusb工程源码关键字&#xff1a; Debian、 Linux、 Qt、 libusb、 源码内容背景&#xff1a; 最近项目终于切到Linux下开发了&#xff0c;所以最近的…

生产者消费者问题实践(头歌实验)第1关:生产者消费者问题实践,第2关:进程互斥和同步。

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 题目&#xff1a; 第1关&#xff1a;生产者消费者问题实践 任务&#xff1a; 相关知识 代码&#xff1a; 效果截图&#xff1a; 第2关&#xff1a;进程互斥和同步…

Linux的命令行

命令行的格式 command [-options] [parameter] options和parameter并不是每一个的命令都需要的 ls命令 直接的ls 查看当前文件目录下的所有文件以及文件夹 ls -a 查看所有的同时&#xff0c;查看隐藏的文件&#xff0c;在Linux中以.开头的文件都会被隐藏起来 ls -l 以…

【Java学习笔记】第四章 面向对象编程三部曲(上)

【Java学习笔记】第四章 面向对象编程三部曲&#xff08;上&#xff09; 【Java学习笔记】第四章 面向对象编程三部曲&#xff08;中&#xff09; 【Java学习笔记】第四章 面向对象编程三部曲&#xff08;下&#xff09; 文章目录4.面向对象编程&#xff08;上&#xff09;4.…

Kafka 插件并创建 Kafka Producer 发送

相关说明 启动测试前清空所有数据。每次测试先把所有数据写入 Kafka&#xff0c;再加载 Kafka 插件同步数据到 DolphinDB 中。目的是将同步数据的压力全部集中到 Kafka 插件。以 Kafka 插件从收到第一批数据到收到最后一批数据的时间差作为同步数据的总耗时。 测试流程 加载 …

公网远程连接内网Everything实现快速搜索私有云文件

企业外派出差&#xff0c;已经是稀松平常的事&#xff0c;通常出差的同事都会带一个优盘或移动硬盘。但优盘和硬盘是离线设备&#xff0c;所存储的文件数据无法及时更新&#xff0c;因此能够连接公司主机获得最新文件才是出差的首选。可是公司主机位于内网&#xff0c;且面对浩…

【搭建NextCloud私有云盘服务】采用docker在linux上进行部署,内含nextCloud移植(迁移服务器)方法

1、前言 完成的效果&#xff1a; 在linux上搭建NextCloud云盘服务&#xff0c;可以通过域名访问到云盘服务&#xff0c;并且安装有SSL证书&#xff0c;可进行https访问。 例如&#xff1a; 服务器公网ip为47.110.66.88 域名为&#xff1a;test.huahua.com 可直接通过访问https…

Java#10(String 类的构造方法和练习)

目录 一.String类的构造方法 1.public String()空参构造 2.public String(char[ ] ch2);(对堆区已有的值没有办法复用,数据多会浪费内存空间,而直接赋值如果已有相同数据可以复用,不会在浪费太多内存) 3.public String(byte[ ] bytes) 二.字符串的比较 1.前提基础: 比较…

解决使用svg绘制后下载图片以及下载svg内部嵌套image图片失败的问题。

在使用svg进行图形绘制之后&#xff0c;可能需要下载已经绘制的svg图片&#xff0c;我们可能会遇到以下两种情况&#xff1a; 情况1&#xff1a; <svg width"640" height"400" xmlns"http://www.w3.org/2000/svg" id"svgColumn">…