i.mx6ull驱动开发|23-linux下的驱动分离与分层——platform平台驱动模型(代码片段)

Mculover666 Mculover666     2022-11-29     114

关键词:

一、Linux驱动的分离

1. 为什么需要驱动分离?

在嵌入式开发中,无论处理器如何更换,外设模块的操作都是一致的,比如有三个不同的平台都要驱动MPU6050传感器,最简单的方法是针对每个平台都写一份驱动:

显然这种处理方式太low了,MPU6050都是使用I2C接口操作的,对于不同的平台,只是I2C操作方式不一样,所以这里可以将I2C接口抽象出来,给不同的平台用自己的库函数适配:

这样多种平台就可以共用同一份MPU6050驱动:

2. Linux内核中的驱动分离

在Linux内核中,一般SOC的主机控制器驱动已经由半导体厂家写好了,比如这里imx6ull的i2c控制器驱动已经由NXP写好了。

而对于具体的设备,比如MPU6050等设备,其驱动程序也由设备厂商写好了。

我们要做的是提供设备信息即可,比如:设备连接到了哪个I2C接口上?支持的速率是多少?设备在总线上的从机地址是多少?等等。

这样设计之下,SOC的外设驱动只负责外设驱动,某个设备的驱动只负责设备驱动,使用时只需要让内核将二者联系起来即可。


当我们向系统注册一个驱动的时候,内核就会在右侧的设备中查找有没有匹配的设备,同样,当我们向系统中注册一个设备的时候,内核就会在左侧的驱动中查找有没有匹配的驱动。

这个就是Linux内核中的总线(bus)、驱动(driver)、设备(device)模型,也称之为Linux内核中的驱动分离。

3. Linux内核中的驱动分层

分层的目的是为了在不同的层处理不同的内容

以input子系统为例,input子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸板等。

  • 设备原始驱动层:负责获取输入设备的原始值。
  • input核心层:处理各种IO模型,并且提供file_operations操作集合。

这样分层设计之后,在编写输入设别的驱动时,只需要考虑到如何上报输入事件即可,至于如何处理这些上报的输入事件,是上层需要做的事情,无需关注。

二、platform平台驱动模型

1. 为什么需要platform?

Linux内核中的驱动程序分离为:总线(bus)、驱动(driver)、设备(device)模型,但是有些SOC中有些外设是没有总线这个概念的。

为了解决此问题,Linux内核中提出了platform平台模型作为虚拟总线,相应的有platform_driver和platform_device。

2. platform总线

2.1. platform总线的类型——bus_type结构体

Linux内核中使用bus_type结构体表示总线,定义在文件include/linux/device.h,如下:

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函数指针,所以每一条总线都必须实现此函数。

int (*match)(struct device *dev, struct device_driver *drv);

可以看到,match函数有两个参数:dev和drv。

  • dev:device类型,表示设备
  • drv:device_driver类型,表示驱动

2.2. platform总线

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,
	.pm		= &platform_dev_pm_ops,
;
EXPORT_SYMBOL_GPL(platform_bus_type);

platform_bus_type就是platform平台总线,其中 platform_match 就是匹配函数。

2.3. 驱动和设备如何匹配

platform_match 函数定义在drivers/base/platform.c中,

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
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);

从代码中可以看到,设备和驱动的匹配有四种方法。

(1)OF类型的匹配(设备树采用的匹配方式):根据设备节点的 compatible 属性和驱动的of_match_table表进行匹配;
(2)ACPI匹配方式
(3)id_table匹配
(4)直接比较驱动和设备的name字段是否相等

3. platform驱动(重点)

3.1. platform_driver结构体

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

(1)probe函数

当驱动与设备匹配成功以后probe函数就会执行,由驱动的提供者编写。

(2)remove函数

驱动卸载的时候会执行

(3)driver成员

device_driver相当于基类,提供了最基础的驱动框架,platform_driver继承了这个基类。

(4)id_table表

(5)of_match_table表:设备树匹配表

3.2. platform驱动可用API

(1)向内核注册一个platform驱动

/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
				struct module *owner);

/*
 * use a macro to avoid include chaining to get THIS_MODULE
 */
#define platform_driver_register(drv) \\
	__platform_driver_register(drv, THIS_MODULE)

(2)从内核卸载一个platform驱动

extern void platform_driver_unregister(struct platform_driver *);

4. platform设备

如果内核支持设备树,platform设备用设备树描述。Linux内核启动的时候会从设备树中读取信息,然后将其组织成platform_device结构体形式。

如果内核不支持设备树,可以直接用platform_device结构体描述。

i.mx6ull驱动开发|36-注册spilcd为framebuffer设备并使用lvgl测试(代码片段)

...取更简洁清爽的阅读体验,请移步我的个人博客网站:i.MX6ULL驱动开发|36-注册spilcd为framebuffer设备并使用lvgl测试。一、准备工作i.MX6ULL驱动开发|34-基于SPI框架驱动spilcd(st7789)(mculover666.cn)i.MX6ULL驱动开发|35-NXPLCD控制器Framebuffer驱... 查看详情

i.mx6ull驱动开发|36-注册spilcd为framebuffer设备并使用lvgl测试(代码片段)

...取更简洁清爽的阅读体验,请移步我的个人博客网站:i.MX6ULL驱动开发|36-注册spilcd为framebuffer设备并使用lvgl测试。一、准备工作i.MX6ULL驱动开发|34-基于SPI框架驱动spilcd(st7789)(mculover666.cn)i.MX6ULL驱动开发|35-NXPLCD控制器Framebuffer驱... 查看详情

i.mx6ull驱动开发|30-使用ec204g网卡(移植移远gobinet驱动)(代码片段)

一、EC20EC20在Linux下的驱动架构:二、Linux内核中USB驱动的修改与配置1.EC20USB驱动修改默认插上之后没有出来ttyUSB设备,需要在内核中添加EC20的USB设备信息。1.1.添加USB设备信息修改文件drivers/usb/serial/option.c。(1)o... 查看详情

i.mx6ull驱动开发|35-nxplcd控制器framebuffer驱动浅读

...取更简洁清爽的阅读体验,请移步我的个人博客网站:i.MX6ULL驱动开发|35-NXPLCD控制器Framebuffer驱动浅读。一、FramebufferFramebuffer为帧缓存,或者也可以叫做显存,开辟于RAM中。如下图所示,LCD控制器周而复始的从Framebuffer中逐一取... 查看详情

i.mx6ull驱动开发|35-nxplcd控制器framebuffer驱动浅读

...取更简洁清爽的阅读体验,请移步我的个人博客网站:i.MX6ULL驱动开发|35-NXPLCD控制器Framebuffer驱动浅读。一、FramebufferFramebuffer为帧缓存,或者也可以叫做显存,开辟于RAM中。如下图所示,LCD控制器周而复始的从Framebuffer中逐一取... 查看详情

i.mx6ull驱动开发|34-基于spi框架驱动spilcd(st7789)(代码片段)

...更干净清爽的阅读体验,可以访问我的个人博客网站:i.MX6ULL驱动开发|34-基于SPI框架驱动spilcd(st7789)。一、驱动编写思路(1)编写spi驱动框架,检查probe是否可以正常挂载;(2)在probe设备中,解析设备树gpio信息、屏幕参数... 查看详情

i.mx6ull驱动开发|08-基于pinctrl子系统和gpio子系统点亮led(代码片段)

前置知识i.MX6ULL驱动开发|03-基于字符设备驱动框架点亮LEDi.MX6ULL驱动开发|06-pinctrl子系统i.MX6ULL驱动开发|07-gpio子系统一、编写基本设备驱动模块#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>#include<linu 查看详情

i.mx6ull驱动开发|24-基于platform平台驱动模型点亮led(代码片段)

一、编写基本设备驱动模块编写驱动模块源码:#include<linux/module.h>#include<linux/init.h>staticint__initplatform_led_init(void)return0;staticvoid__exitplatform_led_exit(void)module_init(platform_led_init 查看详情

i.mx6ull驱动开发|28-使用ft5426多点电容触摸(代码片段)

一、多点触摸协议(MT)input子系统下的多点触摸协议称为MT协议,其文档为:Documentation/input/multitouch-protocol.txt。MT协议被分为两种类型,取决于硬件的兼容性:TypeA:适用于触摸点不能被区分或者追踪&#... 查看详情

i.mx6ull驱动开发|26-linux内核的rtc驱动(代码片段)

一、RTC时间查看与设置1.内核启动日志查看Linux内核启动时与RTC相关的日志:dmesg|greprtc2.查看与设置当前系统时间(1)查看当前时间:date(2)设置当前时间date-s"2022-07-0213:47:00"3.设置当前时间到RTC外... 查看详情

i.mx6ull驱动开发|26-linux内核的rtc驱动(代码片段)

一、RTC时间查看与设置1.内核启动日志查看Linux内核启动时与RTC相关的日志:dmesg|greprtc2.查看与设置当前系统时间(1)查看当前时间:date(2)设置当前时间date-s"2022-07-0213:47:00"3.设置当前时间到RTC外... 查看详情

i.mx6ull驱动开发|25-基于linux自带的key驱动检测按键(代码片段)

一、Linux内核自带的驱动Linux内核已经集成了采用platform框架编写的KEY驱动·,无需我们自己编写,只要按照要求在设备树文件中添加相应的LED节点即可。1.如何使能(1)在内核源码目录中,打开menuconfig进行配... 查看详情

i.mx6ull驱动开发|16-基于uart驱动框架发送/接收串口数据(代码片段)

i.MX6ULL在SOC级别的UART外设驱动已经由原厂编写好了,我们只需要在设备树中添加对应的节点即可使用。一、在设备树添加节点1.设置UART3引脚在iomucx节点中添加uart3子节点:pinctrl_uart3:uart3grp fsl,pins=< MX6UL_PAD_UART3_TX_DATA... 查看详情

i.mx6ull驱动开发|31-linux内核网络设备驱动框架(代码片段)

一、Linux网络设备驱动整体架构网络设备是完成用户数据包在网络媒介上发送和接收的设备,它将上层协议传递下来的数据包,以特定的媒介访问控制方式进行发送,并将接收到的数据包传递给上层协议。Linux系统对... 查看详情

i.mx6ull驱动开发|29-使用usbwifi网卡(rtl8188eu)(代码片段)

一、USB无线网卡插到电脑上看下型号是RealtekRTL8188EU版本:二、添加驱动到Linux中1.realtek驱动源码使用正点原子资料包中的源码:2.删除内核自带的RTL8192CU驱动根据正点原子教程描述,linux内核自带的驱动经过测试不稳... 查看详情

i.mx6ull驱动开发|32-手动编写一个虚拟网卡设备(代码片段)

一、Linux内核网络驱动处理流程1.net_device结构体的申请与释放net_device结构体实例可以通过动态申请:#definealloc_netdev(sizeof_priv,name,name_assign_type,setup)\\ alloc_netdev_mqs(sizeof_priv,name,name_assign_type,setup,1,1)#define 查看详情

i.mx6ull驱动开发|32-手动编写一个虚拟网卡设备(代码片段)

一、Linux内核网络驱动处理流程1.net_device结构体的申请与释放net_device结构体实例可以通过动态申请:#definealloc_netdev(sizeof_priv,name,name_assign_type,setup)\\ alloc_netdev_mqs(sizeof_priv,name,name_assign_type,setup,1,1)#define 查看详情

i.mx6ull驱动开发二十七:块设备(代码片段)

参考:【块设备】通用块层structbio详解|zzm(aliez22.github.io)一、Linux中块设备驱动框架二、块设备基本概念1、扇区的概念来自硬件,扇区是硬件最小操作单位。2、块的概念来自文件系统,是文件系统数据处理的最小单位... 查看详情