Linux platform总线驱动基础知识

Linux platform总线驱动基础知识


platform总线驱动模型

  在Linux2.6版本以后的设备驱动编写时,一般需关心三部分内容,即 总线(bus)设备(device)驱动(driver) 三个实体。总线是设备和驱动之间的桥梁,Linux通过总线将设备与驱动两部分绑定起来。当向系统注册一个驱动时,总线会寻找与之匹配的设备,如果有的话会将两者联系起来;相反的,当向系统注册一个设备时,总线会在驱动中查找有没有与之匹配的设备,如果有的话也将两者联系起来。

  在真实的项目开发中,一般Linux设备和驱动通常挂接在一类总线上,但还是有一部分外设不依赖于类似I2C、SPI、USB等此类总线。基于此,产生了称为platform的一类总线,相应的设备称为platform_device,驱动称为platform_driver。platform_device并不是和Linux字符设备、块设备、网络设备并列的概念,而是一种附加手段。类似于将SoC内部集成的I2C、PWM、LCD等控制器归纳为platform_device。

平台总线注册和匹配方式

  在Linux设备驱动中,总线相对比较重要,它主要完成匹配设备和驱动,每当有新的设备或新的驱动加入到platform总线上时,总线便会调用platform_math函数对新增的设备进行配对。

  Linux内核通过platform_bus_type来描述总线。

\drivers\base\platform.c
struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.probe		= platform_probe,
	.remove		= platform_remove,
	.shutdown	= platform_shutdown,
	.dma_configure	= platform_dma_configure,
	.pm		= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

  在上述结构体中,match函数指针相对重要,该函数指针指向的函数将负责实现platform总线驱动和playform总线设备的匹配过程。对于每个驱动总线,他都必须实例化该函数指针。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);
}

  platform总线在Linux内核启动时自动进行注册,即内核自动调用函数platform_bus_init完成注册。

  platform总线提供了四种存在先后顺序的匹配方式:设备树>ACPI匹配模式>id_table方式>字符串比较

\drivers\base\platform.c
int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();

	error = device_register(&platform_bus);
	if (error) {
		put_device(&platform_bus);
		return error;
	}
	error =  bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);
	of_platform_register_reconfig_notifier();
	return error;
}

  在上述platform总线初始化中,bus_register函数完成向Linux内核注册platform平台总线。

平台设备(platform_device

  platform_device主要是为驱动程序提供设备信息,设备信息包括硬件信息软件信息两部分。

  • 硬件信息

    硬件信息主要是驱动程序使用到的寄存器、中断号、内存、IO资源等。

  • 软件信息

    包括I2C设备的涉笔地址、片选设备的片选信号、以太网的MAC地址等信息。

  platform设备主要维护platform_device结构体,结构体原型如下所示。

\include\linux\platform_device.h
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;
};

结构体常用成员含义:

  • name

    设备名称,需要于platform_driver的name值一致,否则纵向将匹配不成功。

  • id

    id号,用于给插入该总线相同name的设备进行编号,如果只有一个设备,填入-1。

  • dev

    内嵌的device结构体。Linux内核采用大量的面向对象的编程思想,platform_device通过继承该结构体,可复用他的相关代码,方便管理平台设备。其中成员platform_data,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等)。

  • num_resources

    表示资源的数量,当结构体成员resources存放的是数组时,一般记录resources数组的个数,内核通过宏定义ARRAY_SIZE计算数组的个数。

  • resources

    平台设备提供给驱动的资源、信息,例如寄存器。

  • id_entry

    平台总线提供的另一种匹配方式,原理是比较字符串。

platform_device 涉及的常用函数

\drivers\base\platform.c
/*
 * 平台设备注册,将设备挂接在platform总线上
 * [pdev] platform_device类型结构体指针
 */
int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	setup_pdev_dma_masks(pdev);
	return platform_device_add(pdev);
}

/*
 * 平台设备注销
 * [pdev] platform_device类型结构体指针
 */
void platform_device_unregister(struct platform_device *pdev)
{
	platform_device_del(pdev);
	platform_device_put(pdev);
}

  在platform_device结构体中resource结构体相对比较重要,这是实现驱动和设备分离的关键,它主要用于描述硬件信息,包括终端号、寄存器物理地址等,其结构体原型如下:

\include\linux\ioport.h
struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	unsigned long desc;
	struct resource *parent, *sibling, *child;
};

结构体常用成员变量含义:

  • start

    资源的开始值,对于I/O内存来说是起始的内存地址,对于中断资源来说是起始的中断号,对于DMA资源来说是起始的DMA通道号

  • end

    资源的结束值。

  • flags

    用于指定该资源的类型,在Linux中,资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型,最常见的有以下几种:

资源宏定义描述
IORESOURCE_IO用于IO地址空间,对应于IO端口映射方式
IORESOURCE_MEM用于外设的可直接寻址的地址空间
IORESOURCE_IRQ用于指定该设备使用某个中断号
IORESOURCE_DMA用于指定使用的DMA通道

  当有的设备可能有多个资源,通常使用platform_get_resource函数来获取资源,原型如下:

\drivers\base\platform.c
/*
 * 获取设备信息
 * [dev] 指定要获取哪个平台设备的资源;
 * [type] 指定获取资源的类型,如IORESOURCE_MEM、IORESOURCE_IO等;
 * [num] 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
 */
struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num)
{
	u32 i;

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

		if (type == resource_type(r) && num-- == 0)
			return r;
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);

平台驱动(platform_driver

  platform驱动核心为platform_driver结构体,结构体内部包含多个函数指针,用于对platform驱动进行管理,如下所示:

\include\linux\platform_device.h
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

    函数指针,当驱动与设备匹配成功后probe函数将会执行。驱动开发者需要在驱动程序中初始化该函数指针,一般通过该函数对设备进行一系列的初始化操作,类似于字符设备驱动中的init函数。

  • remove

    函数指针,用于移除某个平台时调用,驱动开发者需要在驱动程序中初始化该函数指针,该函数实现的操作,通常是probe函数实现操作的逆过程,类似于字符设备驱动中的exit函数。

  • driver

    Linux设备模型中用于抽象驱动的device_driver结构体,platform_driver继承该结构体,也就获取了设备模型驱动对象的特性。

  • id_table

    id_table是个表,元素类型为platform_device_id类型,表示该驱动能够兼容的设备类型。

platform_driver常用函数

\include\linux\platform_device.h
#define platform_driver_register(drv) \
	__platform_driver_register(drv, THIS_MODULE)
\driver\base\platform.c
/*
 * 注册平台驱动
 * [drv] platform_driver类型结构体指针
 * [owner] 一般为THIS_MODULE
 */
int __platform_driver_register(struct platform_driver *drv,
				struct module *owner)
{
	drv->driver.owner = owner;
	drv->driver.bus = &platform_bus_type;

	return driver_register(&drv->driver);
}

/*
 * 注销平台驱动
 * [drv] platform_driver类型结构体指针
 */
void platform_driver_unregister(struct platform_driver *drv)
{
	driver_unregister(&drv->driver);
}

参考


《Linux设备驱动开发详解 基于最新的Linux4.0内核》

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.pdf》

《[野火]i.MX Linux开发实战指南》

[驱动注册]platform_driver_register()与platform_device_register()

🎪

🔊

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值