第九课Linux设备驱动的软件架构思想

在编写设备驱动代码时,若将设备信息如寄存器地址都写在驱动代码中,当换一个硬件平台时就需要重新修改驱动代码进行适配。是否有办法将设备信息从驱动中剥离出来,并让驱动通过某种标准方法获取设备相关的平台信息?

Linux总线、设备和驱动模型:驱动只负责驱动,设备只管设备,总线负责匹配设备和驱动,驱动可以标准方式获取板级信息。如下:
在这里插入图片描述
Linux设备驱动模型的分层设计:
所有的设备的file_operations结构体的实现、I/O模型处理等部分的代码实现几乎都一样,可以将这些代码提炼出来作为一个中间层(核心层),开发人员只需完成一些简单的工作即可,如输入事件的获取和报告。
在这里插入图片描述
Linux设备驱动模型的分隔设计:
假设通过SPI总线访问某外设,CPU的名字叫xxx1,SPI外设的名字叫yyy1。当访问yyy1时,需通过操作CPU xxx1上的SPI控制器实现。当操作CPU xxx2上的SPI控制器访问外设xxx1时,又需要再实现一套驱动代码。即任何一个SPI外设的驱动代码都与CPU相关,如果又N个不同的YYY外设在M个不同的CPU XXX上运行,就需要M*N份代码。

对主机控制器驱动和外设驱动进行分隔后,主动控制器驱动就不需要关心外设,而外设也不需要关心主机,外设与主机之间通过核心层的通用API进行数据传输,并且可任意组合。

在这里插入图片描述

platform总线框架

platform总线的实现主要在以下几个文件中完成:

  1. drivers/base/platform.c:实现了总线、对外接口的操作
  2. include/linux/platform_device.h:定义了platform设备结构体,声明一系列对外接口

总线

内核专门定义了一个总线类型的全局变量platform_bus_type,在该变量中定义了设备和驱动的匹配规则、电源管理、设备属性等操作。

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs, /* 设备属性结构体 */
	.match		= platform_match, /* 设备与驱动匹配函数 */
	.uevent		= platform_uevent, /*  */
	.pm		= &platform_dev_pm_ops, /* 电源管理结构体 */
};

主要说下platform_match()函数:
从这里可以看出,设备和驱动的匹配方式有三种:

  1. 设备树匹配
  2. id_table匹配
  3. 驱动和设备的名字匹配
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);

	/* Attempt an OF style match first */
	if (of_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);
}

在完成总线的类型定义后,还需将该总线注册到系统中,下面这个函数应该是在内核启动时就会被调用,从而完成总线的初始化工作。

int __init platform_bus_init(void)
{
	int error;
	early_platform_cleanup();
	error = device_register(&platform_bus);
	if (error)
		return error;
	error =  bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);
	return error;
}

驱动

platform的驱动结构体如下,该结构体需由开发者实现:

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;
};

其中:

  • probe接口会在驱动和设备匹配成功后被调用,主要完成设备号申请、设备资源获取、各类文件节点的创建等。
  • remove接口则会在卸载驱动时被调用,完成的工作刚好与probe相反。
  • driver字段主要关心设备树表of_match_table,虽然该字段中也定义了proberemove等接口,但这些接口最终都会调用驱动platform_driver中的接口
  • id_table字段用于设备和驱动的匹配。

在完成上述驱动结构体的定义后,可通过这两个函数向总线注册/卸载驱动:

/* 向总线注册驱动 */
int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;
	return driver_register(&drv->driver);
}
/* 从总线卸载驱动 */
void platform_driver_unregister(struct platform_driver *drv)
{
	driver_unregister(&drv->driver);
}

设备

向总线注册设备时,可以使用设备树方式,也可以通过硬编码的方式。

硬编码的方式是指在内核代码中实现设备信息的注册,如下所示。

设备结构体:

struct platform_device {
	const char	* name; /* 设备名称,可用于设备和驱动的匹配 */
	int		id;
	struct device	dev;
	u32		num_resources; /* 指示resource字段的数量 */
	struct resource	* resource; /* 设备的资源信息 */
	const struct platform_device_id	*id_entry; /* 用于id_table匹配方式 */
	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;
	/* arch specific additions */
	struct pdev_archdata	archdata;
};

开发者需要实现struct resource结构体,并完成struct platform_device结构体的定义。

然后,可通过这两个函数完成设备的注册和卸载:

int platform_device_register(struct platform_device *);
void platform_device_unregister(struct platform_device *);

platform总线的整体框架如下:
在这里插入图片描述

MISC驱动

misc意思是混合、杂项的,也就是说MISC驱动专门负责一些杂项工作,像有些外设无法进行分类时可以使用MISC驱动框架。实际上,MISC驱动框架完全基于platform总线框架实现,只是将一些只有在驱动和设备匹配成功后probe函数中完成的设备号申请、文件节点创建等工作给提前完成了。

MISC驱动框架提前申请了主设备号10和256个次设备号,任何使用该框架的设备其主设备号都是10,只需要通过MISC框架提供的接口申请次设备号即可。

MISC框架就不详细介绍了,直接根据下图学习即可:
在这里插入图片描述

I2C驱动框架

Linux的I2C驱动框架分为三层:

  • 主机适配器层:基于platform总线,完成I2C控制器的驱动,比如I2C总线时序、启动、停止、应答等。
  • API接口层:为设备驱动层提供统一的接口以完成数据的收发,使得具体设备和I2C控制器分离。
  • 设备驱动层:基于i2c_bus_type总线实现具体设备的驱动,如MPU6050、EEPROM等。

I2C驱动框架如下:
在这里插入图片描述

SPI驱动框架

与I2C驱动框架一样,SPI驱动框架也分为三层:

  • 主机适配器层:基于platform总线,完成SPI控制器的驱动,比如总线时序等。
  • API接口层:为设备驱动层提供统一的接口以完成数据的收发,使得具体设备和SPI控制器分离。
  • 设备驱动层:基于spi_bus_type总线实现具体设备的驱动。

SPI驱动框架如下:
在这里插入图片描述

UART驱动框架

UART驱动基于platform总线实现,大致框架如下:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值