platform设备与驱动的注册与匹配

这段时间在搞嵌入式相关的知识,其中很多地方涉及到了驱动编程,对驱动程序文件的结构有所了解,但是其原理不是很清楚,因此本文主要对platform的相关流程进行介绍,Linux内核代码版本为5.7.19。

1.platform

在关注驱动程序注册的过程前,首先需要明确什么是platform,什么样的设备称为platform device。
基于分层的思想,Linux的设备与驱动进行分离,分为设备、总线、驱动三个层次,多个设备与多个驱动通过总线相连,总线将设备与驱动进行绑定,系统每注册一个设备,会去寻找匹配的驱动;每注册一个驱动的同时,也会寻找匹配的设备,匹配的过程由总线完成。也就是说,设备与驱动的注册过程其实是分开的
一般情况下,Linux设备与驱动挂载在同一总线例如PCI、SPI等,但是对于嵌入式的SOC等外设,却没有总线进行挂载。因此在Linxu2.6以后,引入了虚拟总线platform总线,相应的设备称为platform device,驱动则是platform driver。

2.总线、设备与驱动

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u64		platform_dma_mask;
	struct device_dma_parameters dma_parms;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

设备中的信息主要包括设备名、设备号等基本信息。

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};

驱动主要包括调用的函数,例如probe()、shutdown()、remove()等,参数均包含platform_device,与其本身的特性相符合。

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.dma_configure	= platform_dma_configure,
	.pm		= &platform_dev_pm_ops,
};

Linux为platform总线定义了一个bus_type的实例,match函数负责驱动与device的匹配。

3.设备注册

static int __init uart8250_init(void)
{
	return platform_device_register(&uart8250_device);
}

在Linux下任选某一platform设备,查看其初始化函数。这里以uart8250为例。发现执行了platform_device_register()函数。
进入该函数:

/**
 * platform_device_register - add a platform-level device
 * @pdev: platform device we're adding
 */
int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	setup_pdev_dma_masks(pdev);
	return platform_device_add(pdev);
}

该函数的作用是初始化platform_device中的device结构,设置DMA mask,将设备挂载到platform总线上。

int platform_device_add(struct platform_device *pdev)
{
	u32 i;
	int ret;

	if (!pdev)
		return -EINVAL;

	if (!pdev->dev.parent)
		pdev->dev.parent = &platform_bus;//设置父节点

	pdev->dev.bus = &platform_bus_type; //设置总线
	//根据设备的ID,修改或者设置设备的名称
	switch (pdev->id) {
	//对于多个同名的设备,使用ID区分
	default:
		//将数据写入到dev中的kobject对象
		dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
		break;
	case PLATFORM_DEVID_NONE:
		dev_set_name(&pdev->dev, "%s", pdev->name);
		break;
	case PLATFORM_DEVID_AUTO:
		/*
		 * Automatically allocated device ID. We mark it as such so
		 * that we remember it must be freed, and we append a suffix
		 * to avoid namespace collision with explicit IDs.
		 */
		ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
		if (ret < 0)
			goto err_out;
		pdev->id = ret;
		pdev->id_auto = true;
		dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
		break;
	}

	for (i = 0; i < pdev->num_resources; i++) {
		struct resource *p, *r = &pdev->resource[i];

		if (r->name == NULL)
			r->name = dev_name(&pdev->dev);

		p = r->parent;
		if (!p) {
			if (resource_type(r) == IORESOURCE_MEM)
				p = &iomem_resource;
			else if (resource_type(r) == IORESOURCE_IO)
				p = &ioport_resource;
		}

		if (p) {
			ret = insert_resource(p, r);
			if (ret) {
				dev_err(&pdev->dev, "failed to claim resource %d: %pR\n", i, r);
				goto failed;
			}
		}
	}

	pr_debug("Registering platform device '%s'. Parent at %s\n",
		 dev_name(&pdev->dev), dev_name(pdev->dev.parent));
	//将device变量添加到内核中
	ret = device_add(&pdev->dev);
	if (ret == 0)
		return ret;

 failed:
	if (pdev->id_auto) {
		ida_simple_remove(&platform_devid_ida, pdev->id);
		pdev->id = PLATFORM_DEVID_AUTO;
	}

	while (i--) {
		struct resource *r = &pdev->resource[i];
		if (r->parent)
			release_resource(r);
	}

 err_out:
	return ret;
}

这是一个完整的device注册过程。

4.驱动注册

任选一个驱动注册程序,其初始化函数为:

static struct platform_driver locomo_device_driver = {
	.probe		= locomo_probe,
	.remove		= locomo_remove,
	
	.driver		= {
		.name	= "locomo",
	},
};

static int __init locomo_init(void)
{
	int ret = bus_register(&locomo_bus_type);
	if (ret == 0)
		platform_driver_register(&locomo_device_driver);
	return ret;
}

该函数调用了platform_driver_register()函数,继续寻找调用路径,发现该函数定义为__platform_driver_register()函数:

#define platform_driver_register(drv) \
	__platform_driver_register(drv, THIS_MODULE)

int __platform_driver_register(struct platform_driver *drv,
				struct module *owner)
{
	drv->driver.owner = owner;
	drv->driver.bus = &platform_bus_type;
	drv->driver.probe = platform_drv_probe;
	drv->driver.remove = platform_drv_remove;
	drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}

在函数中,设置了该驱动所在的总线,为platform_bus_type,同时设定了platform_driver内置的driver的probe、remove、shutdown函数。最后将驱动添加到内核中。

static int platform_drv_probe(struct device *_dev)
{
	struct platform_driver *drv = to_platform_driver(_dev->driver);
	struct platform_device *dev = to_platform_device(_dev);
	int ret;

	ret = of_clk_set_defaults(_dev->of_node, false);
	if (ret < 0)
		return ret;

	ret = dev_pm_domain_attach(_dev, true);
	if (ret)
		goto out;

	if (drv->probe) {
		ret = drv->probe(dev);
		if (ret)
			dev_pm_domain_detach(_dev, true);
	}

out:
	if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
		dev_warn(_dev, "probe deferral not supported\n");
		ret = -ENXIO;
	}

	return ret;
}

进入platform_drv_probe()查看,将会执行了drv->probe()函数,这也是驱动程序本身的probe()函数。remove()函数与shutdown()函数与之类似。

设备与驱动的匹配

设备与驱动的匹配都是由bus总线完成的。设备与驱动在注册的过程中,都会引发总线调用match()函数来寻找该设备(或驱动)匹配的驱动(或设备),如果存在则将双方绑定。如果是设备先注册,设备在挂载到到总线上时,无法匹配对应的驱动。当接下来注册驱动时,由于设备已被注册,那么总线会先对设备与驱动进行绑定,再调用驱动中的probe()函数等。如果是驱动先注册,则probe()函数会等到设备注册成功并匹配绑定后才会调用。
设备注册时的匹配:

int platform_device_add(struct platform_device *pdev)
	int device_add(struct device *dev)
		void bus_probe_device(struct device *dev)
			void device_initial_probe(struct device *dev)
				static int __device_attach(struct device *dev, bool allow_async)
					static int __device_attach_driver(struct device_driver *drv, void *_data)
						static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)

这就是设备注册时对match()函数的调用过程。

int driver_register(struct device_driver *drv)
	int bus_add_driver(struct device_driver *drv)
		int driver_attach(struct device_driver *drv)
			static int __driver_attach(struct device *dev, void *data)
				static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)

其基本流程与设备注册类似,最后都会在driver_match_device()中调用bus总线的platform_match()函数进行设备与驱动的匹配。其匹配函数如下:

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_driver_match_device()函数进行匹配,该方法主要对Linux中的设备树中的设备进行匹配;
接着,对ACPI标准的驱动、设备进行匹配。
然后,查看驱动的id_table是否有该设备的名称。这样是因为一个驱动可能支持多个设备,所以建表来进行存储支持的所有设备。
最后,比较设备名与驱动名是否相等。
在驱动与设备匹配完毕后,会调用驱动的probe()函数,其调用过程为:

static int __driver_attach(struct device *dev, void *data)
	int device_driver_attach(struct device_driver *drv, struct device *dev)
		int driver_probe_device(struct device_driver *drv, struct device *dev)
			static int really_probe(struct device *dev, struct device_driver *drv)
				dev->bus->probe(dev);

最后调用bus总线的probe()函数,也就是之前分析的platform_drv_probe()函数。同样,当设备注册后,也会先与驱动匹配,如果匹配完成,则同样会调用driver_probe_device()函数。这也说明,只有当驱动与设备匹配完成后,驱动的probe()函数才会调用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值