本文基于kernel 5.10版本,在进行DRM显示框架的分析过程中,需要对component框架比较了解,顺便学习了。

一、背景

linux内核中的驱动,需要有一定的加载顺序,用来解决驱动之间的依赖问题。虽然说linux内核有定传统的驱动优先级,用来定义驱动的先后顺序,但是不足以更加细分的加载。

有的驱动可以独立加载而不依赖于其他驱动,但是在一个比较庞大的驱动面前,细分驱动的加载就比较重要了,因为这些庞大的驱动,环环相扣,一环出了问题就影响整个驱动的顺利走完。因此component架构一般用于DRM显示驱动架构和ALSA音频驱动架构。

个人认为component并不是和DRM和ALSA绑定的,更多的是一种内核基础设施,自己编写的驱动如有依赖关系,完全可以使用上component这个工具。

二、component介绍

component架构主要的逻辑编写在内核源码的driver/base/component.c

最后系统应该呈现出来的是这样的组织:

上面呈现了3种对象:component、master、component_match

可以看到无论是master还是component都有他们的操作集ops。

在component架构中,master在一个复杂驱动(drm或者alsa)中被看成在一个统筹的角色,各个component表示在这个复杂驱动中不可或缺的组件。开始各个component各自注册进系统,并且尝试唤醒master来统筹(因为component都不知道自己是不是最后一个注册的,所以都有责任去尝试唤醒master),最后master将自己注册进系统,然后根据master自己定义的对比方法找到匹配的component,调用他们的回调,通常这些回调包含了component自己的关键步骤。

下面将一步一步分解上面的过程。

三、component注册

如开头所说,驱动都是以probe进入加载的,但是probe们并没有特别明确的加载顺序,因此进入probe的时候,可以使用component架构,将自己抽象成component注册进系统,然后等待master来统筹每个conponent的加载顺序,现在就来看看component如何加载。

使用component_add()注册进系统使用component_del()从系统中注销

所谓的注册和注销,无非就是内核的组织形式。在这里。component是以链表形式去组织的,在系统启动后就已经静态注册了两个链表头,用来组织master和component。

/*driver/base/component.c*/

static LIST_HEAD(component_list);

static LIST_HEAD(masters);

关键是上面的3步

①内存上申请component对象

②初始化component,主要是component绑定一个操作集合,里面有bind和unbind回调,一般在bind里面包含了关于这个component需要被安排做的事情,这个五星级重要。

③将component加入component_list链表,形成组织

其实还有最后一步,那就是尝试去唤醒master来回调各个componnet的bind。其实componnet的注册还是挺简单的,简单一句话就是自己管自己,将自己注册好,至于什么时候bind回调能执行,并不清楚。经过componnet_add那么componnet链表中就出出现了第一个对象了,且这个对象包含一个操作集合,包含了bind回调。

最后,还发现了注册componnet的时候还需要传入一个device对象,证明了component是属于某个对象的,那样通过componnet完全可以找到被抽象的设备。

使用例子:

二、component_match的构建

component_match是一个用于匹配的对象,用于匹配master和componnet,因为系统中有很多componnet,一个master并不是统筹全系统的componnet,而是统筹有一定特征的coponent,所以需要一个匹配的工具,那就是component_match。

如总框架图:

一个master对应一个component_match,一个component_match对应一组component_match_array.

使用component_match_add来构建一个component_match和一组component_match_array

static inline void component_match_add(struct device *master,

struct component_match **matchptr,

int (*compare)(struct device *, void *), void *compare_data)

①如果传进来的component_match对象是NULL,那么意味着需要创建一个新的

②如果一组component_match_array已经满了,或者是空的,那么就新增16个空位

③填充一个component_match_array,里面包含了很重要的compare函数和compare 数据

component_match_array到底是什么,可以说一项component_match_array对应一个component,因此看到了通过component_match_array可以找到component。

就这样,通过component_match_add就可以往component_match里面增加一个个的component_match_array。至于有什么用,在后面再来分析。

使用例子:

第一个参数仅仅只是devm自动管理架构使用的,其余三个参数才是我们最需要关注的。分别是component_match、compare对比函数,compare数据。在这里可以看到,如果要对比的话,此处采用的方法是比较设备的设备树节点。

三、master的注册

master作为一个统筹的角色,在所有component都被注册进系统后,就可以将master注册进系统,

通过component_master_add_with_match()将master注册进系统

int component_master_add_with_match(struct device *dev,

const struct component_master_ops *ops,

struct component_match *match)

可以看到传入的参数是master依附的设备,master的操作集合,以及刚刚构建好的component_match函数,这正好对应上了:一个master对应一个component_match。

接下来看看master是如何注册的:

①将master加进系统之前重新规划component_match指向的数组,修改成真实存在的数量,因为要知道我们一下子申请了16个空位,但是加进去的不一定有16个component。

②内存申请master对象,并且绑定依附的设备,绑定master操作集,绑定match

③debugfs系统增加内容

④将master加进master链表,也就是总图的第二个链表

⑤尝试唤醒master

可以看到无论是component还是master都会尝试唤醒master。

使用例子:

一定是component_match填充完之后才进行master的注册

四、如何唤醒master?

这个是整个component架构的重头戏,正是这个流程使得各个bind回调能按顺序执行。

1、当component注册时候尝试唤醒master

当某个component注册时候尝试去唤醒master的时候,会遍历master链表。find_coponents()对每个master下面维护的component_match指向的component_match_array数组遍历,因为component和component_match_array一一对应,如果发现component_match_array已经绑定了component,那么就跳过,如果发现一个孤立的component_match_array,就会调用find_component()遍历component链表,如果发现已经绑定了master了就跳过,如果发现一个孤立的coponent,就调用孤立的component_match_array里面保存的compare对比函数来查看这个component是否符合要求(这个根据compare函数的逻辑),如果符合要求,就找到了一个component,将component和master关联,将component和component_match_array关联。

找到一个master所有的component后会调用master的bind函数。最后在master的bind函数里面调用component_bind_all()完成对各个component的bind顺序执行。

其实整个系统可能就只有两个master,DRM和ALSA的,只是为了方便理解,未经过考证。

2.当master注册时候如何唤醒master?

当master注册时候,并不需要遍历master链表,因为当master注册的时候,他知道自己是master,不需要关注其他master,只需要关注自己维护有哪些componnet,因此会遍历component链表。知道到有符合条件的component最后也是调用master的bind函数。

五、master的bind函数如何按顺序执行各个component的bind函数?

通常在master的bind函数里面调用一个接口component_bind_all()

int component_bind_all(struct device *master_dev, void *data)

首先通过设备找到对应的master,因为所有的对象都有关联了。再按照master→component_match_array→componnet的顺序找到componnet,然后就能调用component的bind函数

六、关于设备树的ports节点如何书写

上面说到component_match_add即构建一项又一项的component_match_array,除了用component_match_add()写死构建外,还可以使用设备树的ports节点来灵活更改。所以一般再抽象成master的驱动里面会又一段解析这种设备树节点的代码。此处以xilinx DRM的master来举例子:

首先从master的设备数节点里面寻找“ports”属性,使用of_parse_phandle寻找指定的设备数节点(标号pl_disp_port),这个节点完全可以写在其他地方,然后通过parent找到了父节点(名字drm-pl-disp),然后将这个父节点加入component_match_array。最后根据ports下port指定的remote-endpoints 找到了另一端的remote-endpoint,加入component_match_array就好比两个接头。

七、总结

component_match_add 对应component_match_array的构建

component__add 对应component的链表构建

component_master_add_with_match对应master的链表构建即master bind的执行

component_bind_all()是master最后执行各个bind的接口 查看系统已有组件:cat /sys/kernel/debug/device_component/XX

精彩链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。