【i.MX6ULL】platform总线

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) 三种即可。

  1. 无设备树
if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

这种是没有使用设备树的匹配方法。

  1. 名字匹配
return (strcmp(pdev->name, drv->name) == 0);

这种是以上匹配方式都不满足进行名字匹配。

  1. 设备树匹配
    接下来详细看一下 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函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

点灯大师~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值