Rockchip RK3399 - component框架

发布时间 2023-09-07 00:18:18作者: 大奥特曼打小怪兽

我们在分析RK3399 DRM驱动过程中,涉及到了component框架内容,因此这里我们穿插一节内容,专门对component框架进行介绍。

一、component概述

1.1 背景

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

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

所以component架构构建功能系统就包括两方面的作用:

  • 保证系统安装了所有的组件;
  • 规定了系统各组件初始化的顺序;

1.2 介绍

component架构在linux内核中现在主要的应用是用来构建display-subsystem,一个显示子系统由LCD控制器(vop)、接口控制器(mipilvdshdmi等)、液晶背光,电源等多个独立的功能单元构成。而把这些功能单元构成一个系统,就需要这些功能单元间有一定的协同配合。

如在扫描设备树时,每一个设备节点依次被注册到系统中(一般用platform_device_register)。而只有当所有显示子系统相关的设备节点都注册到系统中时,整个显示系统才能正常工作。

如果没有component架构的参与,在用platform_device_register函数注册每个设备节点时,在其驱动的probe函数中。就已经执行了一系列的初始化工作,而此时系统相关的其它设备节点可能还没有注册到内核中,这样可能就会导致一些问题出现。

component架构的参下,可以保证在显示子系统的所有功能单元都注册后,才按照一定顺序执行初始化操作。

二、component核心数据结构

component架构的实现位于drivers/base/component.c,其主要涉及到的数据结构有struct componentstruct aggregate_devicestruct component_match

2.1 关系图

component架构上述结构可以用拼乐高积木的过程来理解:

  • 一个一个形态各异的积木就用struct component来表示;
  • struct aggregate_device就是要拼接成的东西(系统)(例如想拼成一辆车或一个房子);
  • struct component_match就是你手里的图纸;

根据图纸,就可以在一个个积木(component)中,找出需要的积木,然后拼成一个想要的作品(aggregate_device)。

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

/**
 * 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);
};

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
  • opsaggregate_device可执行的初始化和退出操作;
  • match:该aggregate_device用到的component_matchaggregate_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

/**
 * 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:执行绑定操作;当componentaggregate_device注册时,回调该函数;
  • unbind:执行解绑操作;当调用component_master_del或使用component_del时,回调该函数;

2.4 struct component_match

struct component_match用来匹配系统需要的组件,并规定了组件的初始化顺序。定义在drivers/base/component.c

struct component_match {
        size_t alloc;
        size_t num;
        struct component_match_array *compare;
};

其中:

  • alloc:用于记录分配的compare数组大小;
  • num:用于记录系统实际需要的组件(compnents)数量。当num不等于alloc时,会引发compare数组的空间扩展分配;
  • compare:指向一个数组,保存一个系统(aggregate_device)中需要的所有组件(compnents)。每个数组条目代表了一个需要安装的组件;

allocnum主要用于内存的动态分配与管理,compare主要用于aggregate_devicecomponent的匹配。

2.4.1 struct component_match_array

struct component_match_array定义在drivers/base/component.c,描述与aggregate_device匹配的compnent

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: 保存了comparecompare_typed用到的匹配数据;
  • compare:用于实现aggregate_device匹配componentcompare函数指针指定了匹配的规则;
  • compare_typed:用于实现aggregate_device匹配componentcompare_typed函数指针指定了匹配的规则;
  • component:保存与aggregate_devices匹配的component

三、component核心API

3.1 component注册

component_add函数为设备dev向系统注册一个comonent,函数定义在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
  • 初始化组件成员:opsdevsubcomponent
  • 将当前组件添加到全局链表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 aggregate_device注册

component_master_add_with_match函数用于向系统注册一个aggregate_device,函数定义在drivers/base/component.c

/**
 * 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 重新分配match->compare数组的内存,以适应实际大小。 */
        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;
}

函数主要步骤如下:

  • 调用kzalloc动态分配一个struct aggregate_device
  • 初始化组件成员:opsmatchparent
  • 将当前aggregate_device添加到全局链表aggregate_devices
  • 调用try_to_bring_up_masters函数尝试启动aggregate_device
3.2.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;

        //  实际大小 == 期望大小,则不需要扩容
        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.2.2 try_to_bring_up_aggregate_device

try_to_bring_up_aggregate_device函数用于尝试启动aggregate_device

  • 如果componentNULL,表示对aggregate_device进行操作;
  • 如果component不为NULL,表示需要检查并确保component存在,才能启动aggregate_device设备;

函数定义如下:

/*
 * Try to bring up an aggregate device.  If component is NULL, we're interested
 * in this aggregate device, otherwise it's a component which must be present
 * to try and bring up the aggregate device.
 *
 * Returns 1 for successful bringup, 0 if not ready, or -ve errno.
 */
static int try_to_bring_up_aggregate_device(struct aggregate_device *adev,
        struct component *component)
{
        int ret;

        dev_dbg(adev->parent, "trying to bring up adev\n");

    	// 遍历adev->match->compare数组,校验每一个匹配规则是否指定了与adev匹配的omponent,如果都指定了返回0
        if (find_components(adev)) {
                dev_dbg(adev->parent, "master has incomplete components\n");
                return 0;
        }

    	// 如果指定了component,但是component所属的adev不是adev,表示该component与adev不匹配,直接返回
        if (component && component->adev != adev) {
                dev_dbg(adev->parent, "master is not for this component (%s)\n",
                        dev_name(component->dev));
                return 0;
        }

    	// 打开一个资源组
        if (!devres_open_group(adev->parent, adev, GFP_KERNEL))
                return -ENOMEM;

        /* Found all components,执行aggregate_device的绑定操作 */
        ret = adev->ops->bind(adev->parent);
        if (ret < 0) {
                devres_release_group(adev->parent, NULL);
                if (ret != -EPROBE_DEFER)
                        dev_info(adev->parent, "adev bind failed: %d\n", ret);
                return ret;
        }

    	// 关闭资源组
        devres_close_group(adev->parent, NULL);
        // 更新标志位
        adev->bound = true;
        return 1;
}

这里我们看一下find_components函数,函数:

  • 首先遍历match->compare数组,元素类型为struct component_match_array
    • 然后遍历component_list链表,元素类型为struct component
      • 查找每一个component_match_array是否指定了与adev匹配的component

如果每一个component_match_array都指定了与adev匹配的component则返回0,否则返回-ENXIO

static int find_components(struct aggregate_device *adev)
{
        struct component_match *match = adev->match;
        size_t i;
        int ret = 0;

        /*
         * Scan the array of match functions and attach
         * any components which are found to this adev.
         */
        for (i = 0; i < match->num; i++) {
            	// 获取component匹配规则
                struct component_match_array *mc = &match->compare[i];
                struct component *c;

                dev_dbg(adev->parent, "Looking for component %zu\n", i);
				
            	// 如果指定了匹配的component,直接下一个component匹配规则
                if (match->compare[i].component)
                        continue;
	
            	// 遍历component_list链表找到与adev匹配的component,参数mc指定了匹配规则
                c = find_component(adev, mc);
                // 如果没找到与adev匹配的component、则c=NULL
                if (!c) {
                        ret = -ENXIO;
                        break;
                }

                dev_dbg(adev->parent, "found component %s, duplicate %u\n",
                        dev_name(c->dev), !!c->adev);

                /* Attach this component to the adev */
                match->compare[i].duplicate = !!c->adev;
            	// 保存匹配的component
                match->compare[i].component = c;
                // 更新compponent所属的adev
                c->adev = adev;
        }
        return ret;
}

find_component会遍历component_list链表,找到第一个与adev匹配的component并返回,如果找不到返回NULL

static struct component *find_component(struct aggregate_device *adev,
        struct component_match_array *mc)
{
        struct component *c;
		// 遍历component_list链表中的component
        list_for_each_entry(c, &component_list, node) {
	            // 如果指定了c->adev,但是c->adev不是adev,表示该component与adev不匹配,继续
                if (c->adev && c->adev != adev)
                        continue;
				// 指定了compare函数,匹配成功直接返回当前component
                if (mc->compare && mc->compare(c->dev, mc->data))
                        return c;

            	// 如果指定了compare_typed函数,匹配成功直接返回当前component
                if (mc->compare_typed &&
                    mc->compare_typed(c->dev, c->subcomponent, mc->data))
                        return c;
        }

        return NULL;
}

3.3 match注册

component_match_add函数填充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;

        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;
        }

        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;
                }
        }

        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_add_typed - add a component match entry for a typed component
 * @parent: parent device of the aggregate driver
 * @matchptr: pointer to the list of component matches
 * @compare_typed: compare function to match against all typed 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_typed().
 *
 * The allocated match list in @matchptr is automatically released using devm
 * actions.
 *
 * See also component_match_add_release() and component_match_add_typed().
 */
void component_match_add_typed(struct device *parent,
        struct component_match **matchptr,
        int (*compare_typed)(struct device *, int, void *), void *compare_data)
{
        __component_match_add(parent, matchptr, NULL, NULL, compare_typed,
                              compare_data);
}

参考文章

[1] linux component组件架构分析

[2] kernel - component组件用法

[3] linux内核compoent框架分析和使用