刚开始做显示驱动的时候就一直接触到component组件,DRM中的各个模块使用component来管理。最近学习了一下这一块源码,彻底明白了component的机制。
为什么引入component机制
为什么会引入这个这个机制呢?
display和camera等这种框架会涉及到很多模块,像高通处理中,里面有MDP,mipi,hdmi,edp等等其他模块;模块之间会存在依赖,就需要考虑加载顺序。虽然内核也提供了像core_initcall,arch_initcall,late_initcall这些接口控制加载驱动的顺序,但是使用不够灵活。
而component就可以达到这种控制整个流程的作用,
Component工作机制
Master component
- 通过设备树控制各个component的连接关系,即模块的加载顺序
- 解析设备树节点,并调用component_match_add,添加对应节点(add顺序决定模块bind顺序)
- 创建struct component_master_ops,调用component_master_add_with_match创建master;并在component_master_ops->bind函数中调用component_bind_all;
Slave component
创建struct component_ops,调用component_add,将slave component添加进component框架。
当master解析到的所有节点均已component add,则会调用master_ops->bind中component_bind_all,按照设备树中解析的顺序依次执行从设备中component_ops的bind。
上面说的可能不是很清楚,来个例子:
假设现在有五个设备,一个master_component,和四个component;master_compoent中ports中使用了两个component,component1和component2;
Cmt1连接cmt3;cmt2连接cmt4;期望执行顺序为
设备树除了要配置必要的一些参数外,还需要配置port的节点(port可以有多个),port节点包含endpoint节点;endpoint节点中包含remote-endpoint节点,remote-endpoint节点指向所要连接节点中port中的endpoint节点(此处有点绕),比如cmt1中的remote-endpoint = <&cmt3_in>;cmt3_in为cmt3中port的endpoint节点;
完整设备树结构:
代码如下:
master_cmt: master_component {
compatible = "master,component";
status = "okay";
ports = <&cmt1 &cmt2>; //使用了component1和component2
};
cmt1: component1 {
compatible = "slave1,component";
status = "okay";
port {
cmt1_out:endpoint {
remote-endpoint = <&cmt3_in>; //连接component3
};
};
};
cmt2: component2 {
compatible = "slave2,component";
status = "okay";
port {
cmt2_out:endpoint {
remote-endpoint = <&cmt4_in>;//连接component4
};
};
};
cmt3: component3 {
compatible = "slave3,component";
status = "okay";
port {
cmt3_in:endpoint {
//remote-endpoint = <&cmt1_out>;//未连接下一个从设备可不设置
};
};
};
cmt4: component4 {
compatible = "slave4,component";
status = "okay";
port {
cmt4_in:endpoint {
//remote-endpoint = <&cmt2_out>;//未连接下一个从设备可不设置
};
};
};
驱动代码:
master component probe的函数
static const struct component_master_ops master_cmt_ops = {
.bind = master_cmt_bind,
.unbind = master_cmt_unbind,
};
int master_cmt_probe(struct platform_device *pdev)
{
struct component_match *match = NULL;
struct device_node *np = NULL;
struct device_node *ep = NULL, *remote = NULL;
struct device *dev = &pdev->dev;
int i = 0;
pr_err("%s entry\n", __func__);
for(i = 0; ; i++) {
//获取master component节点中ports所指向的节点,第一次循环拿到cmt1,第二次循环拿到cmt2
np = of_parse_phandle(dev->of_node, "ports", i);
if (!np) {
break;
}
component_match_add(dev, &match, compare_of, np); //获取到cmt1或cmt2后添加到component框架中
pr_err("%s add component:%s\n", __func__, np->name);
for_each_endpoint_of_node(np, ep) //此函数用于获取np节点中所有endpoint节点,这里不管cmt1或者cmt2均只有一个endpoint节点
{
remote = of_graph_get_remote_port_parent(ep); //获取endpoint节点中remote-endpoint所指向节点的父节点,如果remote-endpoint = <&cmt3_in> 则remote为cmt3
if(!remote || !of_device_is_available(remote))
{
pr_err("remote device is not available\n");
of_node_put(remote);
continue;
}
else if (!of_device_is_available(remote->parent)) {
pr_err("parent device of %s is not available\n",
remote->full_name);
of_node_put(remote);
continue;
}
component_match_add(dev, &match, compare_of, remote); //将获取到的remote添加进component框架
pr_err("%s add component:%s\n", __func__, remote->name);
of_node_put(remote);
}
}
return component_master_add_with_match(&pdev->dev, &master_cmt_ops, match); //此函数用于创建master,用于调用bind函数指针
}
Slave component驱动
static const struct component_ops slave_cmt_ops = {
.bind = slave_cmt_bind,
.unbind = slave_cmt_unbind,
};
int slave_cmt_probe(struct platform_device *pdev)
{
int ret = 0;
pr_err("%s entry %s\n", __func__, pdev->dev.of_node->name);
ret = component_add(&pdev->dev, &slave_cmt_ops);//将设备添加进component用于与master中添加的component进行匹配,当所有slave component全部add,就会调用master中的bind函数指针
pr_err("%s component %s add ret %d\n", __func__, pdev->dev.of_node->name, ret);
return ret;
}
详细代码已经上传github,地址https://github.com/chaochaofeng/LinuxComponent
这里只是利用一个简单的例子大致介绍了component的工作机制和使用方式,没有分析其内部源码,后面会对源码进行剖析。有问题可以指出,互相交流学习一下。