二、设备-驱动 匹配机制探究

前言

Linux总线和设备都会注册到总线驱动上,然后在添加新驱动时,会进行匹配查找,将适合的设备和驱动进行绑定,并将设备和驱动注册显示在sys的层级结构种去。

当在使用i2c设备驱动时,遇到一个问题,一般会将定义 i2c_device_id 结构,注册到驱动的 .id_table 中去。再顶一个of_device_id,用于设备树风格的设备驱动匹配。按理来说,如果使用设备树匹配风格,那么不定义 i2c_device_id 结构应该也是可以的。但是在将 i2c_device_id 结构删除后,会发现驱动加载后,probe函数不会被执行。查看网上的说法,是i2c设备驱动必须要定义这个结构,否则就无法匹配。因此跟着跟了一下的注册匹配的流程。发现确实如此。

驱动注册流程

驱动时通过 module_i2c_driver 宏定义进行注册,跟下去发现调用的是 i2c_register_driver 函数。

i2c_register_driver
	driver_register
		bus_add_driver
			driver_attach
				bus_for_each_dev
				driver_match_device
				driver_probe_device
					really_probe
						dev->bus->probe / drv->probe
						driver_bound
						driver_deferred_probe_trigger
							deferred_probe_work_func
								bus_probe_device
									device_initial_probe
										__device_attach
											device_bind_driver
												__device_attach_driver
													driver_match_device
													driver_probe_device
														really_probe

以上就是驱动注册的调用流程,驱动会执行驱动总线的match函数,来判断驱动和设备是否匹配。使用i2c设备驱动,那么应该就是使用i2c bus的match函数了。

i2c bus match

driver_match_device 驱动的中:

static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

调用的总线的match函数,在i2c总线中,应该是:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);

i2c_device_match:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}

这里会执行三钟方式的匹配,实际只有第一种和第三种有效果。第一种是使用设备树风格的匹配。实际调用:

static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
					   const struct device_node *node)
{
	const struct of_device_id *best_match = NULL;
	int score, best_score = 0;

	if (!matches)
		return NULL;

	for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
		score = __of_device_is_compatible(node, matches->compatible,
						  matches->type, matches->name);
		if (score > best_score) {
			best_match = matches;
			best_score = score;
		}
	}

	return best_match;
}

依次比较 compatible , type, name 属性,匹配上的会加分,最终得到一个综合得分:

static int __of_device_is_compatible(const struct device_node *device,
				     const char *compat, const char *type, const char *name)
{
	struct property *prop;
	const char *cp;
	int index = 0, score = 0;

	/* Compatible match has highest priority */
	if (compat && compat[0]) {
		prop = __of_find_property(device, "compatible", NULL);
		for (cp = of_prop_next_string(prop, NULL); cp;
		     cp = of_prop_next_string(prop, cp), index++) {
			if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
				score = INT_MAX/2 - (index << 2);
				break;
			}
		}
		if (!score)
			return 0;
	}

	/* Matching type is better than matching name */
	if (type && type[0]) {
		if (!device->type || of_node_cmp(type, device->type))
			return 0;
		score += 2;
	}

	/* Matching name is a bit better than not */
	if (name && name[0]) {
		if (!device->name || of_node_cmp(name, device->name))
			return 0;
		score++;
	}

	return score;
}

选择得分最高的作为最佳匹配。

当设备树匹配失败时,使用id_table 方式匹配。id_table 方式,只匹配id_table 中的name字段和 i2c client 的name。这里i2c client的name应该就是设备树中申明的设备的名称。因此,在声明id_table时,这里不应该使用和compitable属性一样的字符串,应该使用和设备树中设备节点名称一致的字符串。
比如,设备树中如下定义:

    htu21d: htu21d@40 {
        status = "okay";
        compatible = "se,htu21d";
        reg = <0x40>;
    };

那么使用id_table应该这样定义:

static struct i2c_device_id htu21d_id_table[] = {
    {"htu21d", 0}, /* error style: "se,htu21d" */
    { },
};
MODULE_DEVICE_TABLE(i2c, htu21d_id_table);

使用设备树匹配 of_device_id应该这样定义:

static struct of_device_id htu21d_of_match[] = {
    {.compatible = "se,htu21d"},
    { },
};
MODULE_DEVICE_TABLE(of, htu21d_of_match);

做实验,可以尝试,只是用id_table进行匹配,会发现,当使用 “se,htu21d” 字符串作为 id_table 的name字段时,匹配是无法成功的,即probe函数不会执行。

分析到这,似乎只使用of_match_table 是可行的。为什么probe不执行呢?接着往下看。

执行 probe

在match成功后,将执行really_probe,其中如果 dev->bus->probe 存在,那么会执行 bus->probe,如果不存在,才会执行 drv->probe 函数。由于是使用的i2c bus驱动注册的,那么将使用i2c bus的probe函数。

问题就出在这儿。

i2c_device_probe 中:

	driver = to_i2c_driver(dev->driver);
	if (!driver->probe || !driver->id_table)
		return -ENODEV;

将对驱动的 probe 函数指针和 id_table 指针进行判断,其中一个为
NULL,那么将直接返回 -ENODEV ,不会继续后面的流程,去执行 driver->probe 函数。因为它要在 driver->probe 中传入匹配的 id_table 项。

显然这是不合理的。如果是使用虚拟总线设备,那么只定义 of_device_id 是没有问题的。因为它将跳过 bus->probe,直接执行 drv->probe,不会有这个问题出现。虽然bus->probe 最终也会去调用 drv->probe。

查阅最新的内核 6.1.2,在 i2c-core-base.c 中,对应的4.14版本文件应该是 i2c-core.c 文件,其中的 i2c_device_probe 函数已经做了修改:

	if (!driver->id_table &&
	    !acpi_driver_match_device(dev, dev->driver) &&
	    !i2c_of_match_device(dev->driver->of_match_table, client)) {
		status = -ENODEV;
		goto put_sync_adapter;
	}

如果 id_table 为空时,还会检查 of_match_table ,如果都不匹配,才会返回失败。
并且,如果定义了 probe_new,那么也不需要 id_table 作为参数传递给 probe函数参数了。将使用新版的probe函数:

	if (driver->probe_new)
		status = driver->probe_new(client);
	else if (driver->probe)
		status = driver->probe(client,
				       i2c_match_id(driver->id_table, client));
	else
		status = -EINVAL;

那么在4.14版本中,id_table 属于是强制的。那么我们可以不使用它,但是给他提供一个空的定义,这样可以避免匹配失败的问题,匹配还是使用of_match_table。

2025/06/13:在6.6内核上,之前的驱动编译不过了。发现 probe_new 已经把 probe 取代了:

struct i2c_driver {
	unsigned int class;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *client);
	void (*remove)(struct i2c_client *client);

probe 函数指针的申明,已经去调了 id_table参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值