1、platform 平台驱动模型简介
设备驱动的分离,有总线(bus)、驱动(driver)和设备(device)模型,比如 I2C、 SPI、 USB 等总线。 但是在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题, Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device。
2、platform 总线
Linux系统内核使用 bus_type 结构体表示总线,此结构体定义在文件 include/linux/device.h, bus_type 结构体内容如下:
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv); /* 此函数非常非常重要 */
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
match 函数,此函数很重要,单词 match 的意思就是“匹配、相配”,因此 此函数就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。
match 函数有两个参数: dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。
platform 总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.c, platform 总线定义如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。我们来看一下驱动和设备是如何匹配的, platform_match 函数定义在文件 drivers/base/platform.c 中,函数内容如下所示:
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)
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);
}
驱动和设备的匹配有四种方法,我们依次来看一下:
- 第一种匹配方式, OF 类型的匹配,也就是设备树采用的匹配方式, of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。 device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。
- 第二种匹配方式, ACPI 匹配方式。
- 第三种匹配方式, id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。
- 第四种匹配方式,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。
3、match过程
platform_match函数函数路径:./drivers/base/platform.c
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)
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);
}
-
struct platform_device *pdev = to_platform_device(dev)
使用to_platform_device得到struct device *dev所在的结构体起始地址,也即结构体:struct platform_device
-
struct platform_driver *pdrv = to_platform_driver(drv)
使用to_platform_driver得到struct device_driver *drv所在的结构体起始地址,也即结构体:struct platform_driver
紧接着下面是5种设备与驱动匹配方式,只需关注 if(of_driver_match_device(dev, drv))、if (pdrv->id_table)、eturn (strcmp(pdev->name, drv->name) == 0) 三种即可。
- 无设备树
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
这种是没有使用设备树的匹配方法。
- 名字匹配
return (strcmp(pdev->name, drv->name) == 0);
这种是以上匹配方式都不满足进行名字匹配。
- 设备树匹配
接下来详细看一下of_driver_match_device(dev, drv)
函数内容。该函数定义在:./include/linux/of_device.h 文件中。
/**
* of_driver_match_device - Tell if a driver's of_match_table matches a device.
* @drv: the device_driver structure to test
* @dev: the device structure to match against
*/
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;
}
紧接着又调用函数:of_match_device,该函数定义在:./drivers/of/device.c 中。
/**
* of_match_device - Tell if a struct device matches an of_device_id list
* @ids: array of of device match structures to search in
* @dev: the of device structure to match against
*
* Used by a driver to check whether an platform_device present in the
* system is in its list of supported devices.
*/
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);
}
紧接着又调用函数:of_match_node,通过该函数名大概能得知要进行设备树相关匹配,该函数定义在:./drivers/of/base.c 中。
/**
* of_match_node - Tell if a device_node has a matching of_match structure
* @matches: array of of device match structures to search in
* @node: the of device structure to match against
*
* Low level utility function used by device matching.
*/
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,该函数定义在:./drivers/of/base.c 中。
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;
}
在该函数中依次遍历结构体 struct of_device_id 的成员:name、type、compatible 值与设备树中对应的
name、type、compatible属性进行匹配,函数 __of_device_is_compatible
定义在:./drivers/of/base.c 中。
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;
}
通过观察函数得知,匹配次序为:compatible、type、name,led的设备树信息如下:
gpio_led {
compatible = "alientek,gpio_led";
pinctrl-name = "defaule";
pinctrl-0 = <&pinctrl_gpio_led>;
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
可知在设备树中只有 compatible 一个属性,因此在编写led驱动时,在初始化结构体struct of_device_id时,只初始化成员:compatible 即可,将成员compatible初始化成与设备树对应的名字: "alientek,gpio_led"就可以实现platform设备与platform驱动的匹配。
总结: 整体的函数调用关系为:
platform_match
-> of_driver_match_device
-> of_match_device
-> of_match_node
-> __of_match_node
-> __of_device_is_compatible
-> ...
总线驱动和设备匹配函数调用关系
首先使用 driver_register函数注册驱动:
driver_register
-> bus_add_driver
-> driver_attach //查找bus下所有设备,找到预期的。
-> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)
-> __driver_attach /* 每个设备都调用此函数,
查看每个设备是否与驱动匹配成功 */
-> driver_match_device //检查是否匹配
-> driver_probe_device
-> really_probe
-> dev->bus->probe(dev); /* 执行 driver 的 prode函数 */
向总线注册驱动的时候,会检查当前总线下的所有设备,有没有与此驱动匹配的设备,如果有的话就执行驱动里面的 probe函数。