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灵活解析依赖关系。
您还没有登录,请您登录后发表评论。