相關信息
內(nèi)核版本:Linux version 4.9.56
驅動文件:lichee\linux-4.9\drivers\video\backlight\backlight.c
驅動作用
- 對上,面對應用層提供統(tǒng)一的設備節(jié)點入口
- 同級,面對驅動層提供設備驅動加載卸載通知事件,以及背光控制接口。
- 對下,面對硬件層提供背光控制調(diào)節(jié)的回調(diào)接口
- 監(jiān)聽 frambuffer 事件, 實現(xiàn)清屏聯(lián)動背光控制
- 監(jiān)聽系統(tǒng)休眠喚醒,實現(xiàn)休眠喚醒背光聯(lián)動控制
控制背光的來源:應用訪問、事件聯(lián)動、休眠喚醒
源碼分析
一、驅動初始化
- 完成背光設備邏輯類的創(chuàng)建
- 初始化用于通知背光設備驅動加載卸載的事件
- 為該邏輯類設定五個背光控制相關的設備節(jié)點
/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)
{
// 創(chuàng)建背光設備邏輯類: /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);
}
// 面對應用層提供統(tǒng)一的設備節(jié)點入口(預先設置好節(jié)點名稱和可讀寫權限)
backlight_class->dev_groups = bl_device_groups;
// 監(jiān)聽系統(tǒng)的休眠喚醒回調(diào)
backlight_class->pm = &backlight_class_dev_pm_ops;
// 初始化背光設備鏈表,用于被外部驅動查詢獲取
INIT_LIST_HEAD(&backlight_dev_list);
mutex_init(&backlight_dev_list_mutex);
// 初始化內(nèi)核通知鏈, 用于通知驅動層背光設備注冊或卸載事件
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)
宏定義展開,取其中一個設備節(jié)點的例子: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,那么調(diào)用此接口后將會產(chǎn)生如下幾個設備節(jié)點:
背光設備類型:/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:私有數(shù)據(jù) ops:背光控制調(diào)節(jié)的回調(diào) 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;
// 創(chuàng)建背光設備
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);
// 設置設備邏輯類為背光設備邏輯類, 在驅動初始化時完成的創(chuàng)建
// 這里要注意, struct device 是直接嵌入到 struct backlight_device 里面的
new_bd->dev.class = backlight_class;
new_bd->dev.parent = parent;
new_bd->dev.release = bl_device_release;
// 設置名稱, 體現(xiàn)在: /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;
}
// 將此設備注冊到系統(tǒng)里面,里面會創(chuàng)建 backlight_class 預設的那幾個設備節(jié)點
rc = device_register(&new_bd->dev);
if (rc) {
put_device(&new_bd->dev);
return ERR_PTR(rc);
}
// 注冊監(jiān)聽 frambuffer 的事件通知鏈
rc = backlight_register_fb(new_bd);
if (rc) {
device_unregister(&new_bd->dev);
return ERR_PTR(rc);
}
// 指定背光控制接口
new_bd->ops = ops;
// 將此設備加入到鏈表內(nèi), 便于外部查詢獲取
mutex_lock(&backlight_dev_list_mutex);
list_add(&new_bd->entry, &backlight_dev_list);
mutex_unlock(&backlight_dev_list_mutex);
// 發(fā)出事件通知, 有新的背光設備被注冊到系統(tǒng)中
blocking_notifier_call_chain(&backlight_notifier, BACKLIGHT_REGISTERED, new_bd);
return new_bd;
}
EXPORT_SYMBOL(backlight_device_register);
三、應用層進行背光調(diào)節(jié)路徑
代碼解析
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;
// 字符串轉換為數(shù)值
rc = kstrtoul(buf, 0, &brightness);
if (rc)
return rc;
// 調(diào)用設置背光接口, 該接口也被開放給其它驅動程序調(diào)用
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;
// 有多個路徑調(diào)用, 存在并發(fā)調(diào)用, 借助互斥鎖保護
mutex_lock(&bd->ops_lock);
// 檢查是否設置了回調(diào)
if (bd->ops) {
// 檢查所設置的背光數(shù)值是否超出最大值
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;
// 從這里可以看出, 最終調(diào)用的背光設備驅動的 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;
}
關鍵解析
這里最主要的是搞清楚是如何區(qū)分應用層操作哪個背光設備(這部分實現(xiàn)很有意思,其原理會另寫文章解析)。
- Linux 最常用的是通過結構體嵌套的方式,實現(xiàn)以某個成員的內(nèi)存地址計算出結構體的首地址,從而訪問到其它的成員。
- struct device 是直接嵌入到 struct backlight_device 里面,就可以通過 struct device 找到對應 struct backlight_device。
- 應用層通過 /sys/class/backlight/xxx 來訪問 xxx 背光設備,就可確定 device, 根據(jù) device 找出 backlight_device,調(diào)用對應背光設備驅動的 ops 成員。
struct backlight_device {
......
struct device dev;
......
};
#define to_backlight_device(obj) container_of(obj, struct backlight_device, dev)
四、事件聯(lián)動進行背光調(diào)節(jié)路徑
在背光設備驅動注冊的時候,就調(diào)用 backlight_register_fb 來監(jiān)聽 fb 事件。
Xorg 就通過 FBIOBLANK 指令實現(xiàn)熄屏,但若沒有對應的背光設備接口,就會出現(xiàn)顯示屏全黑但是背光還亮著的問題。文章來源:http://www.zghlxwxcb.cn/news/detail-672800.html
代碼解析
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;
}
五、最簡單的背光設備驅動文章來源地址http://www.zghlxwxcb.cn/news/detail-672800.html
#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 子系統(tǒng)分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!