Rockchip RK3399 - component框架
我们在分析RK3399 DRM
驱动过程中,涉及到了component
框架内容,因此这里我们穿插一节内容,专门对component
框架进行介绍。
一、component
概述
1.1 背景
linux
内核中的驱动,需要有一定的加载顺序,用来解决驱动之间的依赖问题。虽然说linux
内核有定传统的驱动优先级,用来定义驱动的先后顺序,但是不足以更加细分的加载。
有的驱动可以独立加载而不依赖于其他驱动,但是在一个比较庞大的驱动面前,细分驱动的加载就比较重要了,因为这些庞大的驱动,环环相扣,一环出了问题就影响整个驱动的顺利走完。
所以component
架构构建功能系统就包括两方面的作用:
- 保证系统安装了所有的组件;
- 规定了系统各组件初始化的顺序;
1.2 介绍
component
架构在linux
内核中现在主要的应用是用来构建display-subsystem
,一个显示子系统由显示控制器(vop
)、接口控制器(mipi
,lvds
,hdmi
等)、液晶背光,电源等多个独立的功能单元构成。而把这些功能单元构成一个系统,就需要这些功能单元间有一定的协同配合。
如在扫描设备树时,每一个设备节点依次被注册到系统中(一般将设备节点转换为platform_device
,然后使用platform_device_register
注册),而只有当所有显示子系统相关的设备节点都注册到系统中时,整个显示系统才能正常工作。
如果没有component
架构的参与,在用platform_device_register
函数注册每个设备节点时,其platform_driver
驱动的probe
函数中会执行了一系列的初始化工作,而此时系统相关的其它设备节点可能还没有注册到内核中,这样可能就会导致一些问题出现。
在component
架构的参下,可以保证在显示子系统的所有功能单元都注册后,才按照一定顺序执行初始化操作。
二、component
核心数据结构
component
架构的实现位于drivers/base/component.c
,其主要涉及到的数据结构有struct component
、struct aggregate_device
、struct component_match
。
2.1 关系图
component
架构上述结构可以用拼乐高积木的过程来理解:
- 一个一个形态各异的积木就用
struct component
来表示; struct aggregate_device
就是要拼接成的东西(系统)(例如想拼成一辆车或一个房子);struct component_match
就是你手里的图纸;
根据图纸,就可以在一个个积木(component
)中,找出需要的积木,然后拼成一个想要的作品(aggregate_device
)。
如果对应到display-subsystem
中的话,:
struct component
对应的就是显示子系统的各个功能单元,每个component
都会和一个platform_device
关联起来;struct aggregate_device
对应的就是显示子系统;
为了更加形象的表示struct component
、struct aggregate_device
、struct component_match
之间的关系,我们绘制了如下关系框图:
刚开始component
会将自己注册进入系统,并尝试唤醒aggregate_device
来统筹(component
也不了解自己是不是最后一个注册的,所有有义务唤醒aggregate_device
).
最后aggregate_device
把自己注册进入系统,根据aggregate_device
自己定义的匹配方法找到所有的component
,调用component
的bind
调函数.
2.2 struct component
struct component
用来表示系统组件,定义在drivers/base/component.c
;
struct component {
struct list_head node;
struct aggregate_device *adev;
bool bound;
const struct component_ops *ops;
int subcomponent;
struct device *dev;
};
其中:
node
: 链表节点,用于将当前节点添加到全局链表component_list
,链表component_list
保存了注册到系统中的所有component
;adev
:用于保存与该组件匹配的aggregate_device
;bound
:表示组件已经执行了bind
操作,当操作集ops
中的bind
函数被执行,该标志位设置为true
;ops
:组件可执行的初始化操作;dev
:组件所属的device
;
2.2.1 全局链表component_list
全局链表component_list
定义如下:
static LIST_HEAD(component_list);
2.2.2 struct component_ops
struct component_ops
定义在include/linux/component.h
,用于表示component
可执行的初始化操作;
/**
* struct component_ops - callbacks for component drivers
*
* Components are registered with component_add() and unregistered with
* component_del().
*/
struct component_ops {
/**
* @bind:
*
* Called through component_bind_all() when the aggregate driver is
* ready to bind the overall driver.
*/
int (*bind)(struct device *comp, struct device *master,
void *master_data);
/**
* @unbind:
*
* Called through component_unbind_all() when the aggregate driver is
* ready to bind the overall driver, or when component_bind_all() fails
* part-ways through and needs to unbind some already bound components.
*/
void (*unbind)(struct device *comp, struct device *master,
void *master_data);
};
该结构体中包含两个回调函数:
bind
:执行组件的绑定操作;当调用component_bind_all
函数时,回调该函数;unbind
:执行组件的解绑操作;当调用component_unbind_all
函数时,回调该函数;
2.3 struct aggregate_device
struct aggregate_device
表示需要构建的系统,定义在drivers/base/component.c
;
struct aggregate_device {
struct list_head node;
bool bound;
const struct component_master_ops *ops;
struct device *parent;
struct component_match *match;
};
其中:
node
:链表节点,用于将当前节点添加到全局链表aggregate_devices
,链表aggregate_devices
保存了注册到系统中的所有aggregate_device
;bound
:表示系统已经执行了bind
操作,当操作集ops
中的bind
函数被执行,该标志位设置为true
;ops
:aggregate_device
可执行的初始化操作;parent
:aggregate_device
所属的device
;match
:该aggregate_device
用到的component_match
,aggregate_device
应用该match
在component_list
链表中找到适配于自己的component
组件,并将所有匹配的component
保存到该结构体的中;
2.3.1 全局链表aggregate_devices
全局链表aggregate_devices
定义如下:
static LIST_HEAD(aggregate_devices);
2.3.2 struct component_master_ops
struct component_master_ops
定义在include/linux/component.h
,用于表示aggregate_device
可执行的初始化操作;
/**
* struct component_master_ops - callback for the aggregate driver
*
* Aggregate drivers are registered with component_master_add_with_match() and
* unregistered with component_master_del().
*/
struct component_master_ops {
/**
* @bind:
*
* Called when all components or the aggregate driver, as specified in
* the match list passed to component_master_add_with_match(), are
* ready. Usually there are 3 steps to bind an aggregate driver:
*
* 1. Allocate a structure for the aggregate driver.
*
* 2. Bind all components to the aggregate driver by calling
* component_bind_all() with the aggregate driver structure as opaque
* pointer data.
*
* 3. Register the aggregate driver with the subsystem to publish its
* interfaces.
*
* Note that the lifetime of the aggregate driver does not align with
* any of the underlying &struct device instances. Therefore devm cannot
* be used and all resources acquired or allocated in this callback must
* be explicitly released in the @unbind callback.
*/
int (*bind)(struct device *master);
/**
* @unbind:
*
* Called when either the aggregate driver, using
* component_master_del(), or one of its components, using
* component_del(), is unregistered.
*/
void (*unbind)(struct device *master);
};
该结构体中包含两个回调函数:
bind
:执行aggregate_device
的绑定操作;当component
或aggregate_device
注册时,回调该函数;unbind
:执行aggregate_device
的解绑操作;当调用component_master_del
或使用component_del
时,回调该函数;
2.4 struct component_match
struct component_match
是一个用于匹配的对象,用于匹配aggregate_device
和component
,因为系统中有很多component
,一个aggregate_device
并不是统筹所有的component
,而是统筹有一定特征的component
,所以需要一个匹配的工具,那就是component_match
。
其定义在drivers/base/component.c
;
struct component_match {
size_t alloc;
size_t num;
struct component_match_array *compare;
};
其中:
alloc
:用于记录分配的compare
数组大小;num
:用于记录系统实际需要的组件(compnents
)数量,更准确地说是保存compare
数组有效component_match_array
的个数;compare
:指向一个数组,保存一个系统(aggregate_device
)中需要的所有组件(compnents
),数组每个有效条目代表了一个需要安装的组件;
alloc
与num
主要用于内存的动态分配与管理,compare
主要用于aggregate_device
和component
的匹配。
2.4.1 struct component_match_array
struct component_match_array
定义在drivers/base/component.c
,描述与aggregate_device
匹配的component
;
struct component_match_array {
void *data;
int (*compare)(struct device *, void *);
int (*compare_typed)(struct device *, int, void *);
void (*release)(struct device *, void *);
struct component *component;
bool duplicate;
};
其中:
data
: 保存了compare
、compare_typed
用到的匹配数据;compare
:用于实现aggregate_device
匹配component
,compare
函数指针指定了匹配的规则;compare_typed
:用于实现aggregate_device
匹配component
,compare_typed
函数指针指定了匹配的规则;component
:保存与aggregate_devices
匹配的component
;
三、component
核心API
3.1 component
注册
如开头所说,驱动都是通过probe
进入加载的,但是probe
并没有特别明确的加载顺序,做为display-subsystem
的设备驱动,需要在probe
函数中将自己抽象为component
设备注册进系统,然后等待aggregate_device
来统筹每个conponent
的加载顺序,现在就来看看component
如何加载。
component_add
函数为设备dev
向系统注册一个component
,函数定义在drivers/base/component.c
;
static int __component_add(struct device *dev, const struct component_ops *ops,
int subcomponent) // 设置为0
{
struct component *component;
int ret;
// 动态分配一个struct component
component = kzalloc(sizeof(*component), GFP_KERNEL);
if (!component)
return -ENOMEM;
component->ops = ops;
component->dev = dev;
component->subcomponent = subcomponent;
dev_dbg(dev, "adding component (ops %ps)\n", ops);
// 获取互斥锁
mutex_lock(&component_mutex);
// 将当前component添加到全局链表component_list
list_add_tail(&component->node, &component_list);
// 尝试启动和component相关的aggregate_device
ret = try_to_bring_up_masters(component);
if (ret < 0) {
if (component->adev)
remove_component(component->adev, component);
list_del(&component->node);
kfree(component);
}
// 释放互斥锁
mutex_unlock(&component_mutex);
return ret < 0 ? ret : 0;
}
/**
* component_add - register a component
* @dev: component device
* @ops: component callbacks
*
* Register a new component for @dev. Functions in @ops will be called when the
* aggregate driver is ready to bind the overall driver by calling
* component_bind_all(). See also &struct component_ops.
*
* The component needs to be unregistered at driver unload/disconnect by
* calling component_del().
*
* See also component_add_typed() for a variant that allows multipled different
* components on the same device.
*/
int component_add(struct device *dev, const struct component_ops *ops)
{
return __component_add(dev, ops, 0);
}
函数主要步骤如下:
- 调用
kzalloc
动态分配一个struct component
; - 初始化组件成员:
ops
、dev
、subcomponent
; - 将当前组件添加到全局链表
component_list
; - 调用
try_to_bring_up_masters
函数尝试启动和component
相关的aggregate_device
;
try_to_bring_up_masters
用于启动和component
相关的aggregate_device
,实现如下;
static int try_to_bring_up_masters(struct component *component)
{
struct aggregate_device *adev;
int ret = 0;
// 遍历aggregate_devices链表
list_for_each_entry(adev, &aggregate_devices, node) {
// 当前adev尚未执行bind操作
if (!adev->bound) {
// 尝试启动aggregate_device
ret = try_to_bring_up_aggregate_device(adev, component);
if (ret != 0)
break;
}
}
return ret;
}
遍历aggregate_device
并调用try_to_bring_up_aggregate_device
尝试启动aggregate_device
,这个函数后面介绍。
3.2 component_match
注册
一个aggregate_device
对应一个component_match
,一个component_match
对应一组component_match_array
,使用component_match_add_release
来构建一个带release
函数的component_match
,函数定义在drivers/base/component.c
;
static void __component_match_add(struct device *parent,
struct component_match **matchptr,
void (*release)(struct device *, void *),
int (*compare)(struct device *, void *),
int (*compare_typed)(struct device *, int, void *),
void *compare_data)
{
struct component_match *match = *matchptr;
if (IS_ERR(match))
return;
// 未指定match,动态分配一个match
if (!match) {
match = devres_alloc(devm_component_match_release,
sizeof(*match), GFP_KERNEL);
if (!match) {
*matchptr = ERR_PTR(-ENOMEM);
return;
}
devres_add(parent, match);
*matchptr = match;
}
// 如果一组`component_match_array`已经满了,或者空的,那么就新增16个空位
if (match->num == match->alloc) {
size_t new_size = match->alloc + 16;
int ret;
// 动态扩容
ret = component_match_realloc(match, new_size);
if (ret) {
*matchptr = ERR_PTR(ret);
return;
}
}
// 填充一个`component_match_array`,里面包含了很重要的`compare`函数和`compare`数据等
match->compare[match->num].compare = compare;
match->compare[match->num].compare_typed = compare_typed;
match->compare[match->num].release = release;
match->compare[match->num].data = compare_data;
match->compare[match->num].component = NULL;
// 记录compare数组有效`component_match_array`的个数
match->num++;
}
/**
* component_match_add_release - add a component match entry with release callback
* @parent: parent device of the aggregate driver
* @matchptr: pointer to the list of component matches
* @release: release function for @compare_data
* @compare: compare function to match against all components
* @compare_data: opaque pointer passed to the @compare function
*
* Adds a new component match to the list stored in @matchptr, which the
* aggregate driver needs to function. The list of component matches pointed to
* by @matchptr must be initialized to NULL before adding the first match. This
* only matches against components added with component_add().
*
* The allocated match list in @matchptr is automatically released using devm
* actions, where upon @release will be called to free any references held by
* @compare_data, e.g. when @compare_data is a &device_node that must be
* released with of_node_put().
*
* See also component_match_add() and component_match_add_typed().
*/
void component_match_add_release(struct device *parent,
struct component_match **matchptr,
void (*release)(struct device *, void *),
int (*compare)(struct device *, void *), void *compare_data)
{
__component_match_add(parent, matchptr, release, compare, NULL,
compare_data);
}
具体步骤如下:
- 如果传进来的
component_match
对象是NULL
,那么意味着需要创建一个新的component_match
对象; - 如果一组
component_match_array
已经满了,或者空的,那么就新增16个空位; - 填充一个
component_match_array
,里面包含了很重要的compare
、release
函数和compare
数据等;
component_match_array
到底是什么,可以说一项component_match_array
对应一个component
,因此通过component_match_array
可以找到component
。
就这样,通过component_match_add
就可以往component_match
里面增加一个个的component_match_array
。
需要注意的是:在这一步中,component_match_array
里的component
还没有指定,但是指定了每个component
的compare
函数,在aggregate_device
注册的时候,遍历全局链表component_list
,调用compare
函数,将符合的要求的component
加入component_match_array
。
3.3 aggregate_device
注册
aggregate_device
作为一个统筹的角色,在所有component
都被注册进系统后,就可以将aggregate_device
注册进系统,component_master_add_with_match
函数用于向系统注册一个aggregate_device
。
函数定义在drivers/base/component.c
,可以看到传入的参数是aggregate_device
所属的设备,aggregate_device
的操作集合,以及刚刚构建好的component_match
函数,这正好对应上了:一个aggregate_device
对应一个component_match
。
/**
* component_master_add_with_match - register an aggregate driver
* @parent: parent device of the aggregate driver
* @ops: callbacks for the aggregate driver
* @match: component match list for the aggregate driver
*
* Registers a new aggregate driver consisting of the components added to @match
* by calling one of the component_match_add() functions. Once all components in
* @match are available, it will be assembled by calling
* &component_master_ops.bind from @ops. Must be unregistered by calling
* component_master_del().
*/
int component_master_add_with_match(struct device *parent,
const struct component_master_ops *ops,
struct component_match *match)
{
struct aggregate_device *adev;
int ret;
/* Reallocate the match array for its true size */
ret = component_match_realloc(match, match->num);
if (ret)
return ret;
// 动态分配一个struct aggregate_device
adev = kzalloc(sizeof(*adev), GFP_KERNEL);
if (!adev)
return -ENOMEM;
adev->parent = parent;
adev->ops = ops;
adev->match = match;
// debugfs文件系统相关
component_debugfs_add(adev);
/* Add to the list of available aggregate devices. */
mutex_lock(&component_mutex);
// 将当前caggregate_device添加到全局链表aggregate_devices
list_add(&adev->node, &aggregate_devices);
// 尝试启动aggregate_device
ret = try_to_bring_up_aggregate_device(adev, NULL);
if (ret < 0)
free_aggregate_device(adev);
mutex_unlock(&component_mutex);
return ret < 0 ? ret : 0;
}
函数主要步骤如下:
- 将
aggregate_device
加进系统之前重新规划component_match
指向的数组component_match_array
,修改成真实存在的数量,因为要知道我们通过component_match_add_typed
一下子申请了16个空位,但是加进去的不一定有16个component
。 - 调用
kzalloc
动态分配一个struct aggregate_device
; - 初始化组件成员:
ops
、match
、parent
; debugfs
系统增加内容;- 将当前
aggregate_device
添加到全局链表aggregate_devices
; - 调用
try_to_bring_up_masters
函数尝试启动aggregate_device
;
3.3.1 component_match_realloc
component_match_realloc
函数用于动态扩容(即为match
->compare
重新分配内存),其中num
为需要分配的数组的长度,函数定义在drivers/base/component.c
;
static int component_match_realloc(struct component_match *match, size_t num)
{
struct component_match_array *new;
// 如果已经分配了长度为num的数组,则不需要扩容
if (match->alloc == num)
return 0;
// 动态分配数组,数组长度为num
new = kmalloc_array(num, sizeof(*new), GFP_KERNEL);
if (!new)
return -ENOMEM;
// 如果已经存在,将数据拷贝到新分配的数组中
if (match->compare) {
memcpy(new, match->compare, sizeof(*new) *
min(match->num, num));
// 释放之前的内存
kfree(match->compare);
}
// 保存分配的数组
match->compare = new;
// 记录分配的compare数组大小
match->alloc = num;
return 0;
}
3.3.2 component_debugfs_add
component_debugfs_add
函数用于在debugfs
文件系统创建以m
->parent
设备命名的文件:
static void component_debugfs_add(struct aggregate_device *m)
{
debugfs_create_file(dev_name(m->parent), 0444, component_debugfs_dir, m,
&component_devices_fops);
}
core_initcall(component_debug_init);
其中:
- 文件名为
dev_name(m->parent)
; 0444
描述了该文件应该具有的访问权限;component_debugfs_dir
表示应该保存该文件的目录,比如/sys/kernel/debug/device_component
;m
将被保存在产生的inode
结构的i_private
字段中;component_devices_fops
是一组实现该文件行为的文件操作函数。
component_debugfs_dir
在component_debug_init
函数中被初始化:
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了