Linux设备驱动probe过程(四)


之前整理了Linux设备驱动probe的一些流程,文章链接如下:

  1. 非热插拔设备驱动probe过程(一)
  2. Linux设备驱动probe过程(二)
  3. Linux设备驱动probe过程(三)

本篇文章作为系列的最有一环,介绍一种涉及多模块,且各模块需要按照一定顺序初始化的情况,比如display和camera。在所有相关硬件模块初始化完成前,不能进行用户节点的注册,避免造成画面内容不正确问题。

本文章节设计为:

  1. Componets概述,介绍component基本用法和概念。
  2. Component示例,选用示例进一步介绍component的应用;
  3. 系列回顾。

1. Component 概述

Component框架是为了设备驱动能够按照一定顺序初始化而提出的架构。Linux中复杂的子系统一般由多个设备模块组成,而内核加载每个模块时间和顺序不定,通过component框架可以保证设备初始化加载前,所依赖的设备全部加载完毕。

Component包含两个基本概念:mastercomponent。master是设备树中的 “超级设备(superdevice)”,负责管理该超级设备下的"普通"设备。component是由master管理的普通设备,需要先初始化。

master在设备树中一般包含有ports子节点,存有该master应该关联的普通设备,如"ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>;"。

display-subsystem {
	compatible = "fsl,imx-display-subsystem";
	ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>;
};

component是普通的设备节点,其下有与master的ports属性值一样的节点,如ipu1_die0的节点。通过字段名,把超级设备与普通设备关联起来,之后就可以通过框架来进行管理了。

ipu1: ipu@02400000 {
	...
	ipu1_di0: port@2 {
		#address-cells = <1>;
		#size-cells = <0>;
		reg = <2>;
		...	
		ipu1_di0_hdmi: hdmi-endpoint {
			remote-endpoint = <&hdmi_mux_0>;
		};
		...
	};
};

1.1 主要函数

1.1.1 component_add

注册component设备,将新创建的component加入到component_list全局链表中。component注册时会遍历master链表,查找关联的master是否已经注册,若已注册则触发master bind调用。

int component_add(struct device *dev, const struct component_ops *ops)
{
	return __component_add(dev, ops, 0);
}

static int __component_add(struct device *dev, const struct component_ops *ops, int subcomponent)
{
	struct component *component;
	int ret;

	component = kzalloc(sizeof(*component), GFP_KERNEL);
	if (!component)
		return -ENOMEM;

	component->ops = ops;
	component->dev = dev;
	component->subcomponent = subcomponent;

	mutex_lock(&component_mutex);
	list_add_tail(&component->node, &component_list);

	ret = try_to_bring_up_masters(component);
	if (ret < 0) {
		if (component->master)
			remove_component(component->master, component);
		list_del(&component->node);

		kfree(component);
	}
	mutex_unlock(&component_mutex);

	return ret < 0 ? ret : 0;
}

static int try_to_bring_up_master(struct master *master,
	struct component *component)
{
	...
	if (component && component->master != master) {
		dev_dbg(master->dev, "master is not for this component (%s)\n",
			dev_name(component->dev));
		return 0;
	}

	ret = master->ops->bind(master->dev);
	...
}

1.1.2 drm_of_component_match_add

创建component_match,用于接下来component master的创建。

void drm_of_component_match_add(struct device *master,
				struct component_match **matchptr,
				int (*compare)(struct device *, void *),
				struct device_node *node)
{
	of_node_get(node);
	component_match_add_release(master, matchptr, drm_release_of,
				    compare, node);
}

void component_match_add_release(struct device *master,
	struct component_match **matchptr,
	void (*release)(struct device *, void *),
	int (*compare)(struct device *, void *), void *compare_data)
{
	__component_match_add(master, matchptr, release, compare, NULL,
			      compare_data);
}

static void __component_match_add(struct device *master,
	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(master, match);

		*matchptr = match;
	}

	if (match->num == match->alloc) {
		size_t new_size = match->alloc + 16;
		int ret;

		ret = component_match_realloc(master, 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拓扑为:
在这里插入图片描述

1.1.3 component_master_add_with_match

注册component master,若master所依赖component都已注册且匹配成功,则触发master bind函数调用。

int component_master_add_with_match(struct device *dev,
	const struct component_master_ops *ops,
	struct component_match *match)
{
	struct master *master;
	int ret;
	...
	master = kzalloc(sizeof(*master), GFP_KERNEL);
	if (!master)
		return -ENOMEM;

	master->dev = dev;
	master->ops = ops;
	master->match = match;

	component_master_debugfs_add(master);

	/* Add to the list of available masters. */
	mutex_lock(&component_mutex);
	list_add(&master->node, &masters);

	ret = try_to_bring_up_master(master, NULL);

	if (ret < 0)
		free_master(master);

	mutex_unlock(&component_mutex);

	return ret < 0 ? ret : 0;
}

1.1.4 find_components

master根据自己的match条件对注册的component进行match检查。match失败,对注册的下一个component进行match;match成功时更新对应match项,然后换下一个注册的component进行match。直到master的所有match项都尝试match一次,即完成一次match条件检查。

主要步骤:

  1. 遍历component_list中注册的所有component设备,调用compore函数进行匹配;
  2. 若其中component匹配成功,将component和master关联起来。
static int find_components(struct master *master)
{
	struct component_match *match = master->match;
	size_t i;
	int ret = 0;

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

		dev_dbg(master->dev, "Looking for component %zu\n", i);

		if (match->compare[i].component)
			continue;

		c = find_component(master, mc);
		if (!c) {
			ret = -ENXIO;
			break;
		}

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

		/* Attach this component to the master */
		match->compare[i].duplicate = !!c->master;
		match->compare[i].component = c;
		c->master = master;
	}
	return ret;
}

1.2 Component 初始化

初始化分为两部分:

  • master执行probe时调用component_master_add_with_match函数将自己注册到component框架中。
  • component执行probe时使用component_add函数将自己注册到component框架中。

master和component注册先后顺序并无要求,可随意顺序。每一个设备加入到框架中,框架就尝试进行匹配,当master匹配上所有component后,会调用master的bind回调,开始按顺序进行初始化。保证了当所有子设备全部probe成功后再执行初始化操作。

1.3 小结

component常用于cisp和drm架构,用来处理内核模块加载/卸载顺序,保证最后加载的模块在需要先加载的模块都加载后加载。
component有两个概念:master和component。master是超级设备,通过设备树管控多个component加载/卸载顺序,保证所有组件正常加载/卸载。

conponent通过维护两个全局的组件链表masterscomponent_list管理注册的master/component组件对象。

  1. device对象通过component_add()将自己的device结构和component_ops注册到component_list。
  2. component框架尝试bring up masters链表中的各master对象。如果master以来的组件都已经加载完,这时的master可以加载了,会执行自己的bind()绑定自己的component。
  3. master bind完成后,调用关联的compoent bind函数进行component bind调用。

有两种方式触发component框架进行bind流程:
一是master添加到component框架,同时这个master匹配的component都已经注册到component框架,这时master尝试bring up自己,触发bind处理流程。
一是component添加到component框架,同时这个component的加入使component框架中注册的一个或多个master的匹配条件得到满足,这时这些满足条件的master将逐个触发bind处理流程。

无论是master的注册还是component的注册触发bind流程,触发bind流程的条件都是:master的match条件得到满足。

master注册后调用try_to_bring_up_master()检查match条件,尝试启动bind流程;
component注册后调用try_to_bring_up_masters()逐个检查注册到框架的master的match条件,尝试启动bind流程。

2. Component 示例

这里举一个dp和dpu的component使用示例,双方通过dts ports进行关联:
在这里插入图片描述

2.1 dp driver

DP driver是指用于支持dp协议的驱动,区别与HTMI,dp硬件接口形态定义为:
在这里插入图片描述

DP接收从DPU传过来的数据,数据包括data、hsync、vsync等,这些数据输入到DP的video sampler中,sampler采用stream(pixel) clock进行采样,pixel clock是从SOC PLL提供的,通常为600MHZ,一个像素时钟周期传输一个像素的数据;

2.1.1 dp component register

dp作为component设备,在probe过程中调用component_add()函数注册为component。

static int xx_dptx_probe(struct platform_device *pdev)
{
	...
	for (i = 0; i < MAX_DPU_NUM; i++) {
		ret = component_add(dev, &xx_dptx_component_ops);
		if (ret) {
			goto probe_fail;
		}
	}
	...
}

static const struct component_ops xx_dptx_component_ops = {
	.bind = xx_dptx_comp_bind,
	.unbind = xx_dptx_comp_unbind,
};

通常component probe中只进行dts parse、reg remap、中断注册、硬件初始化等和自身模块有关部分。与架构耦合部分会剥离到component_ops.bind()中,待master完成初始化后再进行。

2.2 dpu driver

drm架构主体部分,实现接收外部模块的display提交,进行拼接、缩放、overlay等操作后,转换为支持硬件显示的dpi格式。

2.2.1 dpu master register

dpu作为component master设备,在probe过程中调用component_master_add_with_match()函数注册为master设备。

static int komeda_platform_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct component_match *match = NULL;
	struct device_node *child;
	...
	for_each_available_child_of_node(dev->of_node, child) {
		if (of_node_cmp(child->name, "pipeline") != 0)
			continue;

		/* add connector */
		komeda_add_slave(dev, &match, child, KOMEDA_OF_PORT_OUTPUT, 0);
		komeda_add_slave(dev, &match, child, KOMEDA_OF_PORT_OUTPUT, 1);
	}

	return component_master_add_with_match(dev, &komeda_master_ops, match);
}

static void komeda_add_slave(struct device *master,
			     struct component_match **match,
			     struct device_node *np,
			     u32 port, u32 endpoint)
{
	struct device_node *remote;

	remote = of_graph_get_remote_node(np, port, endpoint);
	if (remote) {
		drm_of_component_match_add(master, match, compare_of, remote);
		of_node_put(remote);
	}
}

可以看到dpu master driver中,probe函数只进行了component master的注册,其余所有初始化均放到了bind()调用中。

2.3 加载顺序

通过解析System.map,找到dp和dpu的__init函数,实际加载顺序为:

  1. dpu probe,注册component master;
  2. dp probe,dp初始化,注册component;
  3. master 匹配成功触发dpu bind;
  4. dpu初始化;
  5. 调用component_bind_all(),触发dp bind()调用;
  6. 执行dp bind调用,初始化drm encoder、connector架构部分代码;
  7. 返回dpu bind,调用drm_dev_register()完成drm注册;
  8. 用户open对应card和fb节点进行显示buffer提交。

展开拓扑图为:
在这里插入图片描述

3. 系列回顾

本系列分4篇文章介绍了:build-in方式的标准设备初始化、ko方式打包的设备初始化、热插拔设备初始化、多模块设备初始化。至此设备驱动初始化系列就阶段告一段落了,期待接下来的更新吧。

Linux系统中,i2c设备驱动程序通常会使用i2c核心框架提供的API,以下是一般的i2c驱动代码框架: ```c #include <linux/module.h> #include <linux/i2c.h> static int my_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { // 初始化i2c设备 // 创建字符设备节点 // 设置私有数据 return 0; } static int my_i2c_remove(struct i2c_client *client) { // 删除字符设备节点 // 释放私有数据 return 0; } static const struct i2c_device_id my_i2c_id[] = { { "my_i2c_device", 0 }, { }, }; MODULE_DEVICE_TABLE(i2c, my_i2c_id); static struct i2c_driver my_i2c_driver = { .driver = { .name = "my_i2c_device", .owner = THIS_MODULE, }, .probe = my_i2c_probe, .remove = my_i2c_remove, .id_table = my_i2c_id, }; static int __init my_i2c_init(void) { return i2c_add_driver(&my_i2c_driver); } static void __exit my_i2c_exit(void) { i2c_del_driver(&my_i2c_driver); } module_init(my_i2c_init); module_exit(my_i2c_exit); MODULE_AUTHOR("author"); MODULE_DESCRIPTION("i2c driver for my i2c device"); MODULE_LICENSE("GPL"); ``` 以上代码定义了一个名为`my_i2c_device`的i2c设备驱动程序,其中包含了设备的`probe`和`remove`函数,以及设备的标识信息和驱动程序的初始化和卸载函数。在`probe`函数中,需要完成i2c设备的初始化工作,包括创建字符设备节点和设置私有数据。在`remove`函数中,需要释放相关资源,包括删除字符设备节点和释放私有数据。最后,通过`i2c_add_driver`和`i2c_del_driver`函数将驱动程序注册到i2c核心框架中。 需要注意的是,以上代码仅为框架示例,具体的驱动程序实现会因不同的i2c设备而异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值