驱动和设备如何配对?
无论是向内核注册设备还是驱动,最终均会出发一个设备和驱动配对的过程,今天把这个如何配对的过程详细的解析下:
背景
遇到了一个问题,内核启动到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和驱动写成一样的即可,无须关系具体的处理流程。