Linux设备树-component组件使用说明

1.简述

在实际linux驱动开发过程中,解决设备树节点对应的驱动初始化依赖问题,使用component框架是比较常见 的方法,component框架核心是将存在依赖的驱动,拆分成“组件”, 由核心层统一管理初始化顺序, 确保被依赖组件,先完成初始化,再初始化依赖组件

2.component框架原理

component框架通过“匹配” + “聚合”机制工作,其工作机制流程如下:

2.1 组件注册

每个驱动模块都注册成一个“component”,并声明自己的匹配原则(比如非常常见的设备树compatible属性)。

2.2 组件聚合

所有匹配到的component会被聚合到一个“Master(主组件)”中。

2.3 有序初始化

Master会先初始化所有“被依赖的子组件”, 再调用自己的初始化函数,完成初始化,从根本上保证依赖的顺序。

3.参考使用

以全志F1C200S作为参考进行,解析。

3.1 被依赖模块

以下这个模块其实也是被其它模块依赖,同时也依赖其它模块,是中间

//soc下面, fe0
    fe0: display-frontend@1e00000 {

        compatible = "allwinner,suniv-f1c100s-display-frontend";
        reg = <0x01e00000 0x20000>;
        interrupts = <30>;
        clocks = <&ccu CLK_BUS_DE_FE>, <&ccu CLK_DE_FE>,
             <&ccu CLK_DRAM_DE_FE>;
        clock-names = "ahb", "mod",
                  "ram";
        resets = <&ccu RST_BUS_DE_FE>;
        status = "disabled";

        ports {
            #address-cells = <1>;
            #size-cells = <0>;

            fe0_out: port@1 {
                #address-cells = <1>;
                #size-cells = <0>;
                reg = <1>;

                fe0_out_be0: endpoint@0 {
                    reg = <0>;
                    remote-endpoint = <&be0_in_fe0>;
                };
            };
        };
    };
//这个其实是一个中间件,同时依赖于&be0_in_fe0 节点

//compatible = "allwinner,suniv-f1c100s-display-frontend"; //对应probe
static int sun4i_frontend_probe(struct platform_device *pdev)
{
    return component_add(&pdev->dev, &sun4i_frontend_ops);
}

static const struct component_ops sun4i_frontend_ops = {
    .bind   = sun4i_frontend_bind,
    .unbind = sun4i_frontend_unbind,
};

//这个component模块,进行解析
// 传递进入dev, 调用这个被依赖模块的master, 数据指针
static int sun4i_frontend_bind(struct device *dev, struct device *master,
             void *data)
{
    //从device 获取到,本模块形成的platform_device
    struct platform_device *pdev = to_platform_device(dev);
    struct sun4i_frontend *frontend;
    struct drm_device *drm = data;
    struct sun4i_drv *drv = drm->dev_private;
    struct resource *res;
    void __iomem *regs;

    frontend = devm_kzalloc(dev, sizeof(*frontend), GFP_KERNEL);
    if (!frontend)
        return -ENOMEM;
    //保存这个frontend 到 本模块的platform_device,使用get函数获取,后续可以使用
    dev_set_drvdata(dev, frontend);
    frontend->dev = dev;
    frontend->node = dev->of_node;

    frontend->data = of_device_get_match_data(dev);
    if (!frontend->data)
        return -ENODEV;
    //1.映射寄存器(从设备树reg属性,获取地址,这个是硬件寄存器地址)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    //2.转换成内核的虚拟地址
    regs = devm_ioremap_resource(dev, res);
    if (IS_ERR(regs))
        return PTR_ERR(regs);

    frontend->regs = devm_regmap_init_mmio(dev, regs,
                           &sun4i_frontend_regmap_config);
    if (IS_ERR(frontend->regs)) {
        dev_err(dev, "Couldn't create the frontend regmap\n");
        return PTR_ERR(frontend->regs);
    }

    frontend->reset = devm_reset_control_get(dev, NULL);
    if (IS_ERR(frontend->reset)) {
        dev_err(dev, "Couldn't get our reset line\n");
        return PTR_ERR(frontend->reset);
    }

    frontend->bus_clk = devm_clk_get(dev, "ahb");
    if (IS_ERR(frontend->bus_clk)) {
        dev_err(dev, "Couldn't get our bus clock\n");
        return PTR_ERR(frontend->bus_clk);
    }
.... //时钟解析,省略部分


    list_add_tail(&frontend->list, &drv->frontend_list);
    pm_runtime_enable(dev);

    return 0;
}

static void sun4i_frontend_unbind(struct device *dev, struct device *master,
                void *data)
{
    struct sun4i_frontend *frontend = dev_get_drvdata(dev);

    list_del(&frontend->list);
    pm_runtime_force_suspend(dev);
}


static int sun4i_frontend_probe(struct platform_device *pdev)
{
//将本设备,注册为component (核心步骤)
    return component_add(&pdev->dev, &sun4i_frontend_ops);
}
static int sun4i_frontend_remove(struct platform_device *pdev)
{
//移除掉本component
    component_del(&pdev->dev, &sun4i_frontend_ops);
    return 0;
}

3.2 componet master组件

这个模块是需要最后初始化的,是需要依赖其它模块的初始化后进行。 这个模块需要注册为Component Master, 明确依赖的子组件列表,并且在master_bind 中完成 自身模块的初始化工作(其依赖的模块都已经被初始化了!!)

设备树:

//这个de节点,是master节点了,依赖 &fe0节点,也就是上面的节点
    de: display-engine {
        compatible = "allwinner,suniv-f1c100s-display-engine";
        allwinner,pipelines = <&fe0>;
        status = "disabled";
    };
//对应的probe,进行解析 de 节点
// { .compatible = "allwinner,suniv-f1c100s-display-engine" }
    static int sun4i_drv_probe(struct platform_device *pdev)
{
    struct component_match *match = NULL;
    struct device_node *np = pdev->dev.of_node, *endpoint;
    struct endpoint_list list;
    int i, ret, count = 0;

    INIT_KFIFO(list.fifo);

    for (i = 0;; i++) {
        struct device_node *pipeline = of_parse_phandle(np,
                                "allwinner,pipelines",
                                i);
        if (!pipeline)
            break;

        kfifo_put(&list.fifo, pipeline);
    }

    while (kfifo_get(&list.fifo, &endpoint)) {
        /* process this endpoint */
        ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match,
                          endpoint);

        /* sun4i_drv_add_endpoints can fail to allocate memory */
        if (ret < 0)
            return ret;

        count += ret;
    }

    if (count)
    //注册为Comonent Master,并聚合所有匹配的子组件,(component_master_add() )
    //内部会先初始化所有子组件(也就是它依赖的组件),在调用 master_bind
        return component_master_add_with_match(&pdev->dev,
                               &sun4i_drv_master_ops,
                               match);
    else
        return 0;
}

上面完成了Component Master的注册,然后开始实现bind unbind函数。

static int sun4i_drv_bind(struct device *dev)
{
    struct drm_device *drm;
    struct sun4i_drv *drv;
    int ret;

    drm = drm_dev_alloc(&sun4i_drv_driver, dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
    if (!drv) {
        ret = -ENOMEM;
        goto free_drm;
    }
//设置本platform_device 的 私有driver data----比如:方便在unbind中获取,然后进行相关处理
    dev_set_drvdata(dev, drm);
    drm->dev_private = drv;
    INIT_LIST_HEAD(&drv->frontend_list);
    INIT_LIST_HEAD(&drv->engine_list);
    INIT_LIST_HEAD(&drv->tcon_list);

...
//绑定所有的
    ret = component_bind_all(drm->dev, drm);
    if (ret) {
        dev_err(drm->dev, "Couldn't bind all pipelines components\n");
        goto cleanup_mode_config;
    }


    return 0;

finish_poll:
    drm_kms_helper_poll_fini(drm);
cleanup_mode_config:
    drm_mode_config_cleanup(drm);
    of_reserved_mem_device_release(dev);
free_drm:
    drm_dev_put(drm);
    return ret;
}

static void sun4i_drv_unbind(struct device *dev)
{
    struct drm_device *drm = dev_get_drvdata(dev);

    drm_dev_unregister(drm);
    drm_kms_helper_poll_fini(drm);
    drm_atomic_helper_shutdown(drm);
    drm_mode_config_cleanup(drm);
//与bind相对应
    component_unbind_all(dev, NULL);
    of_reserved_mem_device_release(dev);

    drm_dev_put(drm);
}

4.总结

1.内核启动后,先扫描设备树,注册依赖节点为 component

2.匹配到master节点,注册为 Master

3.Component核心层检测到Master依赖,先调用被依赖模块的 bind方法(初始化)

4.被依赖模块初始化完成后,核心层调用 master_bind,这个时候,被依赖都已经初始化就绪。

对比:

相比module_init顺序(依赖模块加载顺序,不可靠),platform_set_drvdata(手动传递状态,容易储出错),Component

框架通过内核层统一管理依赖,避免手动维护顺序,并且可以很好适配设备树,可通过of_node灵活解析依赖关系。