内核驱动和设备匹配函数platform_match(struct device *dev, struct device_driver *drv) 解析

驱动和设备如何配对?

无论是向内核注册设备还是驱动,最终均会出发一个设备和驱动配对的过程,今天把这个如何配对的过程详细的解析下:

背景

遇到了一个问题,内核启动到usb初始化控制器时,单板卡死,触发了3秒看门狗复位了。修改代码关闭看门狗,重启之后,依然卡死,但时不会重启了。怀疑是ubs控制器初始化失败,影响到了时钟中断。这里具体不讲解这个故障,在解决这个故障的时候,发现dts中并没有和ubs驱动相匹配的节点,但是usb驱动依然被调用了probe,probe时,写ubs控制器的内存引发故障。所以,本文主要思考dts节点和驱动如何匹配的问题,对于写内存失败,不解析。

ubs控制器的dts节点

	usb@210000 {
		#address-cells = <0x1>;
		#size-cells = <0x0>;
		compatible = "fsl-usb2-mph-v2.5", "fsl-usb2-mph";
		dr_mode = "host";
		fsl,iommu-parent = <0x32>;
		fsl,liodn = <0x229>;
		fsl,liodn-reg = <0x33 0x520>;
		fsl,usb-erratum-a007792;
		interrupts = <0x2c 0x2 0x0 0x0>;
		phy_type = "utmi_dual";
		port0;
		reg = <0x210000 0x1000>;
		sleep = <0x10 0x20>;
	};

usb驱动初始化

内核驱动、设备初始化流程:

[    2.164234] Call Trace:
[    2.166700] [c0000000790df610] [c0000000008c2d88] .dump_stack+0xac/0xec (unreliable)
[    2.174487] [c0000000790df6a0] [c00000000069f730] .fsl_ehci_drv_probe+0x2c/0x4f0
[    2.181919] [c0000000790df740] [c000000000461f94] .platform_drv_probe+0x74/0xf8
[    2.189259] [c0000000790df7d0] [c00000000045fe3c] .driver_probe_device+0x2e8/0x388
[    2.196860] [c0000000790df870] [c00000000045ffd0] .__driver_attach+0xf4/0xf8
[    2.203937] [c0000000790df900] [c00000000045d49c] .bus_for_each_dev+0x84/0xe4
[    2.211101] [c0000000790df9a0] [c00000000045f5f0] .driver_attach+0x24/0x38
[    2.218004] [c0000000790dfa10] [c00000000045f028] .bus_add_driver+0x254/0x2d0
[    2.225168] [c0000000790dfab0] [c0000000004608b4] .driver_register+0x8c/0x158
[    2.232333] [c0000000790dfb30] [c000000000461e94] .__platform_driver_register+0x48/0x5c
[    2.240372] [c0000000790dfba0] [c000000000c26658] .ehci_fsl_init+0x90/0xc4
[    2.247279] [c0000000790dfc20] [c000000000001958] .do_one_initcall+0x5c/0x1d0
[    2.254449] [c0000000790dfd00] [c000000000be2eb8] .kernel_init_freeable+0x238/0x310
[    2.262138] [c0000000790dfdb0] [c0000000000021c4] .kernel_init+0x1c/0xe58
[    2.268954] [c0000000790dfe30] [c0000000000009dc] .ret_from_kernel_thread+0x58/0x7c

从我之前的理解,内核驱动和dts设备节点的配对,主要就是依据 compatible 字符串,同时也支持其他的方式,从本例子看驱动中并未在指定compatible字符串。

static struct platform_driver ehci_fsl_driver = {
	.probe = fsl_ehci_drv_probe,
	.remove = fsl_ehci_drv_remove,
	.shutdown = usb_hcd_platform_shutdown,
	.driver = {
		.name = "fsl-ehci",
		.pm = EHCI_FSL_PM_OPS,
	},
};

所以从 platform_match 函数入手,这个函数是负责判断驱动和设备是否配对。解析这个函数的实现。

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override){
        printk("error dev->driver_override %s  drv_name %s \n", pdev->driver_override, drv->name);
        return !strcmp(pdev->driver_override, drv->name);
    }

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

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

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

从上面函数可以看出,主要支持了五种条件匹配。5中方式存在优先级的概念,从上到下,成功就返回,不在往下判断,分别看下。

pdev->driver_override
如果pdev->driver_override 定义了,那么比较这个值和驱动的名字。本利中驱动的名字是  "fsl-ehci",  但是设备的driver_override来自于哪里呢?感觉按照目前的内核驱动编写方式,应该是来自于dts的,但是没有发现dts中有这个属性,所以加打印看看。
compatible 字符串 ,目前最流行的方式。
if (of_driver_match_device(dev, drv))
	return 1;


static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}

const struct of_device_id *of_match_device(const struct of_device_id *matches,
				   const struct device *dev)
{
	if ((!matches) || (!dev->of_node))
		return NULL;
	return of_match_node(matches, dev->of_node);
}


const struct of_device_id *of_match_node(const struct of_device_id *matches,
				 const struct device_node *node)
{
	const struct of_device_id *match;
	unsigned long flags;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	match = __of_match_node(matches, node);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return match;
}

跟踪流程,下面这两个关键的函数,__of_match_node 会根据__of_device_is_compatible计算出来的得分,选择最匹配的。

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

所以,__of_device_is_compatible函数是一个关键。将会计算得分。

/**
 * __of_device_is_compatible() - Check if the node matches given constraints
 * @device: pointer to node
 * @compat: required compatible string, NULL or "" for any match
 * @type: required device_type value, NULL or "" for any match
 * @name: required node name, NULL or "" for any match
 *
 * Checks if the given @compat, @type and @name strings match the
 * properties of the given @device. A constraints can be skipped by
 * passing NULL or an empty string as the constraint.
 *
 * Returns 0 for no match, and a positive integer on match. The return
 * value is a relative score with larger values indicating better
 * matches. The score is weighted for the most specific compatible value
 * to get the highest score. Matching type is next, followed by matching
 * name. Practically speaking, this results in the following priority
 * order for matches:
 *
 * 1. specific compatible && type && name
 * 2. specific compatible && type
 * 3. specific compatible && name
 * 4. specific compatible
 * 5. general compatible && type && name
 * 6. general compatible && type
 * 7. general compatible && name
 * 8. general compatible
 * 9. type && name
 * 10. type
 * 11. 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;
}

这个函数主要判断驱动的 of_device_id 罗列的选项是否和设备相匹配。同样也存在优先级,首先判断的是compatible字符串。其次是type 和name。

我们发现,这个usb的驱动中并没有of_device_id,但是我们不妨随便看个其他的驱动。如下

static const struct of_device_id at91_udc_dt_ids[] = {
	{
		.compatible = "atmel,at91rm9200-udc",
		.data = &at91rm9200_udc_caps,
	},
	{
		.compatible = "atmel,at91sam9260-udc",
		.data = &at91sam9260_udc_caps,
	},
	{
		.compatible = "atmel,at91sam9261-udc",
		.data = &at91sam9261_udc_caps,
	},
	{
		.compatible = "atmel,at91sam9263-udc",
		.data = &at91sam9263_udc_caps,
	},
	{ /* sentinel */ }
};

static struct platform_driver at91_udc_driver = {
	.remove		= at91udc_remove,
	.shutdown	= at91udc_shutdown,
	.suspend	= at91udc_suspend,
	.resume		= at91udc_resume,
	.driver		= {
		.name	= (char *) driver_name,
		.of_match_table	= at91_udc_dt_ids,
	},
};

从__of_device_is_compatible的前几行可以看出来,首先会调用of_prop_next_string读取dts中compatible的字符串,然后和of_device_id中罗列的进行比较。如果找到,就返回。

本利的驱动中并没有of_match_table表,所以of_driver_match_device返回为NULL.

ACPI style match

不知道是什么,也不想看。略过。。。。

pdrv->id_table
if (pdrv->id_table)
	return platform_match_id(pdrv->id_table, pdev) != NULL;

入参分别是驱动的platform_device_id 和 设备。从下面的首先看,逐个比较驱动的platform_device_id表中的name和设备的name,相同了就返回id。否则返回为NULL,匹配失败了。

static const struct platform_device_id *platform_match_id(
		const struct platform_device_id *id,
		struct platform_device *pdev)
{
	while (id->name[0]) {
		if (strcmp(pdev->name, id->name) == 0) {
			pdev->id_entry = id;
			return id;
		}
		id++;
	}
	return NULL;
}
最后比较驱动的名字和设备的名字。
return (strcmp(pdev->name, drv->name) == 0);

其实,这部分流程是多数内核开发者无需关系的,内核已经将流程标准化。我们使用即可。同样,我们会使用compatible,dts和驱动写成一样的即可,无须关系具体的处理流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值