Linux设备驱动probe过程(四)
之前整理了Linux设备驱动probe的一些流程,文章链接如下:
本篇文章作为系列的最有一环,介绍一种涉及多模块,且各模块需要按照一定顺序初始化的情况,比如display和camera。在所有相关硬件模块初始化完成前,不能进行用户节点的注册,避免造成画面内容不正确问题。
本文章节设计为:
- Componets概述,介绍component基本用法和概念。
- Component示例,选用示例进一步介绍component的应用;
- 系列回顾。
1. Component 概述
Component框架是为了设备驱动能够按照一定顺序初始化而提出的架构。Linux中复杂的子系统一般由多个设备模块组成,而内核加载每个模块时间和顺序不定,通过component框架可以保证设备初始化加载前,所依赖的设备全部加载完毕。
Component包含两个基本概念:master和component。master是设备树中的 “超级设备(superdevice)”,负责管理该超级设备下的"普通"设备。component是由master管理的普通设备,需要先初始化。
master在设备树中一般包含有ports子节点,存有该master应该关联的普通设备,如"ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>;"。
display-subsystem {
compatible = "fsl,imx-display-subsystem";
ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>;
};
component是普通的设备节点,其下有与master的ports属性值一样的节点,如ipu1_die0的节点。通过字段名,把超级设备与普通设备关联起来,之后就可以通过框架来进行管理了。
ipu1: ipu@02400000 {
...
ipu1_di0: port@2 {
#address-cells = <1>;
#size-cells = <0>;
reg = <2>;
...
ipu1_di0_hdmi: hdmi-endpoint {
remote-endpoint = <&hdmi_mux_0>;
};
...
};
};
1.1 主要函数
1.1.1 component_add
注册component设备,将新创建的component加入到component_list
全局链表中。component注册时会遍历master链表,查找关联的master是否已经注册,若已注册则触发master bind调用。
int component_add(struct device *dev, const struct component_ops *ops)
{
return __component_add(dev, ops, 0);
}
static int __component_add(struct device *dev, const struct component_ops *ops, int subcomponent)
{
struct component *component;
int ret;
component = kzalloc(sizeof(*component), GFP_KERNEL);
if (!component)
return -ENOMEM;
component->ops = ops;
component->dev = dev;
component->subcomponent = subcomponent;
mutex_lock(&component_mutex);
list_add_tail(&component->node, &component_list);
ret = try_to_bring_up_masters(component);
if (ret < 0) {
if (component->master)
remove_component(component->master, component);
list_del(&component->node);
kfree(component);
}
mutex_unlock(&component_mutex);
return ret < 0 ? ret : 0;
}
static int try_to_bring_up_master(struct master *master,
struct component *component)
{
...
if (component && component->master != master) {
dev_dbg(master->dev, "master is not for this component (%s)\n",
dev_name(component->dev));
return 0;
}
ret = master->ops->bind(master->dev);
...
}
1.1.2 drm_of_component_match_add
创建component_match,用于接下来component master的创建。
void drm_of_component_match_add(struct device *master,
struct component_match **matchptr,
int (*compare)(struct device *, void *),
struct device_node *node)
{
of_node_get(node);
component_match_add_release(master, matchptr, drm_release_of,
compare, node);
}
void component_match_add_release(struct device *master,
struct component_match **matchptr,
void (*release)(struct device *, void *),
int (*compare)(struct device *, void *), void *compare_data)
{
__component_match_add(master, matchptr, release, compare, NULL,
compare_data);
}
static void __component_match_add(struct device *master,
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;
if (!match) {
match = devres_alloc(devm_component_match_release,
sizeof(*match), GFP_KERNEL);
if (!match) {
*matchptr = ERR_PTR(-ENOMEM);
return;
}
devres_add(master, match);
*matchptr = match;
}
if (match->num == match->alloc) {
size_t new_size = match->alloc + 16;
int ret;
ret = component_match_realloc(master, match, new_size);
if (ret) {
*matchptr = ERR_PTR(ret);
return;
}
}
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;
match->num++;
}
创建的component_match拓扑为:
1.1.3 component_master_add_with_match
注册component master,若master所依赖component都已注册且匹配成功,则触发master bind函数调用。
int component_master_add_with_match(struct device *dev,
const struct component_master_ops *ops,
struct component_match *match)
{
struct master *master;
int ret;
...
master = kzalloc(sizeof(*master), GFP_KERNEL);
if (!master)
return -ENOMEM;
master->dev = dev;
master->ops = ops;
master->match = match;
component_master_debugfs_add(master);
/* Add to the list of available masters. */
mutex_lock(&component_mutex);
list_add(&master->node, &masters);
ret = try_to_bring_up_master(master, NULL);
if (ret < 0)
free_master(master);
mutex_unlock(&component_mutex);
return ret < 0 ? ret : 0;
}
1.1.4 find_components
master根据自己的match条件对注册的component进行match检查。match失败,对注册的下一个component进行match;match成功时更新对应match项,然后换下一个注册的component进行match。直到master的所有match项都尝试match一次,即完成一次match条件检查。
主要步骤:
- 遍历component_list中注册的所有component设备,调用compore函数进行匹配;
- 若其中component匹配成功,将component和master关联起来。
static int find_components(struct master *master)
{
struct component_match *match = master->match;
size_t i;
int ret = 0;
/*
* Scan the array of match functions and attach
* any components which are found to this master.
*/
for (i = 0; i < match->num; i++) {
struct component_match_array *mc = &match->compare[i];
struct component *c;
dev_dbg(master->dev, "Looking for component %zu\n", i);
if (match->compare[i].component)
continue;
c = find_component(master, mc);
if (!c) {
ret = -ENXIO;
break;
}
dev_dbg(master->dev, "found component %s, duplicate %u\n", dev_name(c->dev), !!c->master);
/* Attach this component to the master */
match->compare[i].duplicate = !!c->master;
match->compare[i].component = c;
c->master = master;
}
return ret;
}
1.2 Component 初始化
初始化分为两部分:
- master执行probe时调用
component_master_add_with_match
函数将自己注册到component框架中。 - component执行probe时使用
component_add
函数将自己注册到component框架中。
master和component注册先后顺序并无要求,可随意顺序。每一个设备加入到框架中,框架就尝试进行匹配,当master匹配上所有component后,会调用master的bind回调,开始按顺序进行初始化。保证了当所有子设备全部probe成功后再执行初始化操作。
1.3 小结
component常用于cisp和drm架构,用来处理内核模块加载/卸载顺序,保证最后加载的模块在需要先加载的模块都加载后加载。
component有两个概念:master和component。master是超级设备,通过设备树管控多个component加载/卸载顺序,保证所有组件正常加载/卸载。
conponent通过维护两个全局的组件链表masters
和component_list
管理注册的master/component组件对象。
- device对象通过
component_add()
将自己的device结构和component_ops注册到component_list。 - component框架尝试bring up masters链表中的各master对象。如果master以来的组件都已经加载完,这时的master可以加载了,会执行自己的bind()绑定自己的component。
- master bind完成后,调用关联的compoent bind函数进行component bind调用。
有两种方式触发component框架进行bind流程:
一是master添加到component框架,同时这个master匹配的component都已经注册到component框架,这时master尝试bring up自己,触发bind处理流程。
一是component添加到component框架,同时这个component的加入使component框架中注册的一个或多个master的匹配条件得到满足,这时这些满足条件的master将逐个触发bind处理流程。
无论是master的注册还是component的注册触发bind流程,触发bind流程的条件都是:master的match条件得到满足。
master注册后调用try_to_bring_up_master()
检查match条件,尝试启动bind流程;
component注册后调用try_to_bring_up_masters()
逐个检查注册到框架的master的match条件,尝试启动bind流程。
2. Component 示例
这里举一个dp和dpu的component使用示例,双方通过dts ports进行关联:
2.1 dp driver
DP driver是指用于支持dp协议的驱动,区别与HTMI,dp硬件接口形态定义为:
DP接收从DPU传过来的数据,数据包括data、hsync、vsync等,这些数据输入到DP的video sampler中,sampler采用stream(pixel) clock进行采样,pixel clock是从SOC PLL提供的,通常为600MHZ,一个像素时钟周期传输一个像素的数据;
2.1.1 dp component register
dp作为component设备,在probe过程中调用component_add()
函数注册为component。
static int xx_dptx_probe(struct platform_device *pdev)
{
...
for (i = 0; i < MAX_DPU_NUM; i++) {
ret = component_add(dev, &xx_dptx_component_ops);
if (ret) {
goto probe_fail;
}
}
...
}
static const struct component_ops xx_dptx_component_ops = {
.bind = xx_dptx_comp_bind,
.unbind = xx_dptx_comp_unbind,
};
通常component probe中只进行dts parse、reg remap、中断注册、硬件初始化等和自身模块有关部分。与架构耦合部分会剥离到component_ops.bind()
中,待master完成初始化后再进行。
2.2 dpu driver
drm架构主体部分,实现接收外部模块的display提交,进行拼接、缩放、overlay等操作后,转换为支持硬件显示的dpi格式。
2.2.1 dpu master register
dpu作为component master设备,在probe过程中调用component_master_add_with_match()
函数注册为master设备。
static int komeda_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct component_match *match = NULL;
struct device_node *child;
...
for_each_available_child_of_node(dev->of_node, child) {
if (of_node_cmp(child->name, "pipeline") != 0)
continue;
/* add connector */
komeda_add_slave(dev, &match, child, KOMEDA_OF_PORT_OUTPUT, 0);
komeda_add_slave(dev, &match, child, KOMEDA_OF_PORT_OUTPUT, 1);
}
return component_master_add_with_match(dev, &komeda_master_ops, match);
}
static void komeda_add_slave(struct device *master,
struct component_match **match,
struct device_node *np,
u32 port, u32 endpoint)
{
struct device_node *remote;
remote = of_graph_get_remote_node(np, port, endpoint);
if (remote) {
drm_of_component_match_add(master, match, compare_of, remote);
of_node_put(remote);
}
}
可以看到dpu master driver中,probe函数只进行了component master的注册,其余所有初始化均放到了bind()
调用中。
2.3 加载顺序
通过解析System.map,找到dp和dpu的__init函数,实际加载顺序为:
- dpu probe,注册component master;
- dp probe,dp初始化,注册component;
- master 匹配成功触发dpu bind;
- dpu初始化;
- 调用
component_bind_all()
,触发dp bind()调用; - 执行dp bind调用,初始化drm encoder、connector架构部分代码;
- 返回dpu bind,调用
drm_dev_register()
完成drm注册; - 用户open对应card和fb节点进行显示buffer提交。
展开拓扑图为:
3. 系列回顾
本系列分4篇文章介绍了:build-in方式的标准设备初始化、ko方式打包的设备初始化、热插拔设备初始化、多模块设备初始化。至此设备驱动初始化系列
就阶段告一段落了,期待接下来的更新吧。