目录
[Linux 驱动的分离与分层](#Linux 驱动的分离与分层)
[platform 平台驱动模型](#platform 平台驱动模型)
[platform 总线](#platform 总线)
[bus_type 结构体](#bus_type 结构体)
[platform 总线](#platform 总线)
[platform 驱动](#platform 驱动)
[platform_driver 结构体](#platform_driver 结构体)
[device_driver 结构体](#device_driver 结构体)
[platform_driver_register 函数](#platform_driver_register 函数)
[platform_driver_unregister 函数](#platform_driver_unregister 函数)
[platform 驱动框架](#platform 驱动框架)
[platform 设备](#platform 设备)
[platform_device_register 函数](#platform_device_register 函数)
[platform_device_unregister 函数](#platform_device_unregister 函数)
[platform 设备信息框架](#platform 设备信息框架)
Linux 驱动的分离与分层
驱动的分隔与分离
驱动的分隔,就是将主机驱动和设备驱动分隔开来,比如 I2C、 SPI 等等都会采用驱动分隔的方式来简化驱动的开发。

Linux 中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。
驱动的分离,是指将驱动程序中与硬件相关的部分和与硬件无关的部分分开实现。
主要分离方式
设备与驱动分离
- 设备(device):描述硬件资源(如寄存器地址、中断号等)
- 驱动(driver):包含操作硬件的具体实现
- 通过总线(bus)将两者匹配起来
输入子系统分离
- 输入核心(input core)处理通用输入逻辑
- 输入驱动(input driver)处理具体硬件操作
- 输入事件(input event)处理事件上报
平台设备驱动分离
-
平台设备(platform_device)描述平台相关资源
-
平台驱动(platform_driver)实现硬件操作
驱动的分层
驱动的分层,是指将驱动程序按照功能或抽象层次进行分层实现。
Linux 驱动通常可以分为以下几个主要层次:

以input(输入子系统)为例,
- input 子系统,负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸等,
- 最底层的就是设备原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给 input 核心层。
- input 核心层会处理各种 IO 模型,并且提供 file_operations 操作集合。
- 在编写输入设备驱动的时候,只需要处理好输入事件的上报即可。
platform 平台驱动模型
Linux 提出了 platform 这个虚拟总线,相应的就有platform_driver和 platform_device。
platform 总线
Linux系统内核使用bus_type 结构体表示总线,bus_type 结构体定义在文件 include/linux/device.h。
bus_type 结构体
bus_type 结构体内容如下:
cpp
/**
* struct bus_type - 总线类型结构体
*
* 表示Linux内核中的一条总线类型,用于管理设备与驱动的匹配和交互
*/
struct bus_type {
/* 总线基本信息 */
const char *name; /* 总线名称(如"pci", "usb", "platform"等) */
const char *dev_name; /* 用于设备枚举的默认名称 */
struct device *dev_root; /* 总线设备的根设备 */
/* 属性相关 */
struct device_attribute *dev_attrs; /* 总线设备的默认属性 */
const struct attribute_group **bus_groups; /* 总线自身的属性组(在sysfs中显示) */
const struct attribute_group **dev_groups; /* 总线上设备的默认属性组 */
const struct attribute_group **drv_groups; /* 总线上驱动的默认属性组 */
/* 核心操作函数 */
int (*match)(struct device *dev, struct device_driver *drv);
/* 匹配设备与驱动的关键函数,返回1表示匹配成功 */
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; /* 电源管理操作集 */
/* IOMMU相关 */
const struct iommu_ops *iommu_ops; /* IOMMU操作函数集 */
/* 私有数据 */
struct subsys_private *p; /* 总线私有数据,由内核内部使用 */
struct lock_class_key lock_key; /* 锁类键,用于锁调试 */
};
其中,match 函数就是完成设备和驱动之间匹配的:
-
总线最重要的函数之一
-
负责检查设备(device)是否可以被驱动(device_driver)支持
-
通常通过比较设备ID和驱动支持的ID表来实现
match 函数有两个参数: dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。
platform 总线
platform 总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.c。
platform 总线定义如下:
cpp
/**
* platform_bus_type - 平台总线类型实例
*
* Linux内核中用于管理平台设备(platform device)和平台驱动(platform driver)的总线类型
* 平台总线用于那些不连接在传统物理总线上的SoC外设和集成设备
*/
struct bus_type platform_bus_type = {
.name = "platform", /* 总线名称,在sysfs中显示为/sys/bus/platform */
/* 平台设备的默认属性组 */
.dev_groups = platform_dev_groups,
/* 关键操作函数:平台设备与驱动的匹配函数 */
.match = platform_match,
.uevent = platform_uevent,
/* 处理平台设备的热插拔事件,生成用户空间uevent */
/* 电源管理操作集 */
.pm = &platform_dev_pm_ops,
};
platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。
platform_match函数
platform_match 函数定义在文件 drivers/base/platform.c 中,函数内容如下所示:
cpp
/**
* platform_match - 平台设备与驱动的匹配函数
* @dev: 待匹配的设备
* @drv: 待匹配的驱动
*
* 这个函数实现了平台设备与平台驱动的匹配逻辑,按照以下优先级顺序进行匹配:
* 1. 首先检查driver_override强制绑定
* 2. 然后尝试设备树(OF)风格匹配
* 3. 接着尝试ACPI风格匹配
* 4. 再尝试ID表匹配
* 5. 最后回退到名称匹配
*
* 返回1表示匹配成功,0表示匹配失败
*/
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);
/*
* 1. 检查driver_override - 当设置了driver_override时,
* 只绑定到名称完全匹配的驱动(用于强制指定驱动)
*/
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/*
* 2. 首先尝试设备树(OF)风格匹配
* 检查设备树节点是否与驱动中of_match_table匹配
*/
if (of_driver_match_device(dev, drv))
return 1;
/*
* 3. 尝试ACPI风格匹配
* 检查ACPI设备ID是否与驱动中acpi_match_table匹配
*/
if (acpi_driver_match_device(dev, drv))
return 1;
/*
* 4. 尝试使用驱动的id_table进行匹配
* 比较驱动的id_table和设备的name/id
*/
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/*
* 5. 最后回退到简单的名称匹配
* 直接比较设备名称和驱动名称
*/
return (strcmp(pdev->name, drv->name) == 0);
}
驱动和设备的匹配有四种方法:
- 设备树(OF)风格匹配:设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。
- ACPI风格匹配
- ID表匹配:每个 platform_driver 结构体有一个 id_table成员变量,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。
- 名称匹配:如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
platform 驱动
platform_driver 结构体表示platform 驱 动 , 此结构体定义在文件include/linux/platform_device.h
中。
platform_driver 结构体
platform_driver 结构体内容如下:
cpp
/**
* struct platform_driver - 平台设备驱动结构体
*/
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); // 设备挂起函数(传统PM)
int (*resume)(struct platform_device *); // 设备恢复函数(传统PM)
struct device_driver driver; // 内嵌的标准驱动结构(含name/owner/pm等)
const struct platform_device_id *id_table; // 驱动支持的设备ID表(传统匹配方式)
bool prevent_deferred_probe; // 是否禁止延迟探测
};
- probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行。
- driver 成员,为 device_driver 结构体变量,相当于基类,提供了最基础的驱动框架。
- id_table 是个表( 也就是数组) ,每个元素的类型为 platform_device_id。
platform_device_id 结构体内容如下:
cpp
struct platform_device_id {
char name[PLATFORM_NAME_SIZE]; /* 设备名称,最大长度为PLATFORM_NAME_SIZE(通常为20) */
kernel_ulong_t driver_data; /* 驱动私有数据,可通过platform_get_device_id()获取 */
};
device_driver 结构体
device_driver 结构体定义在 include/linux/device.h, device_driver 结构体内容如下:
cpp
/**
* struct device_driver - 核心设备驱动结构体
*
* 表示Linux设备模型中的一个设备驱动程序
*/
struct device_driver {
const char *name; /* 驱动名称,用于匹配设备 */
struct bus_type *bus; /* 所属总线类型 */
struct module *owner; /* 所属模块(THIS_MODULE) */
const char *mod_name; /* 内置模块名称 */
bool suppress_bind_attrs; /* 禁用sysfs中的bind/unbind属性 */
/* 设备匹配表 */
const struct of_device_id *of_match_table; /* 设备树匹配表 */
const struct acpi_device_id *acpi_match_table; /* ACPI匹配表 */
/* 驱动操作函数 */
int (*probe)(struct device *dev); /* 设备探测函数 */
int (*remove)(struct device *dev); /* 设备移除函数 */
void (*shutdown)(struct device *dev); /* 设备关闭函数 */
int (*suspend)(struct device *dev, pm_message_t state); /* 设备挂起函数 */
int (*resume)(struct device *dev); /* 设备恢复函数 */
const struct attribute_group **groups; /* 默认属性组 */
const struct dev_pm_ops *pm; /* 电源管理操作集 */
struct driver_private *p; /* 驱动私有数据 */
};
其中, of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型。
of_device_id 结构体定义在文件 include/linux/mod_devicetable.h 中,内容如下:
cpp
struct of_device_id {
char name[32]; /* 传统设备名称(逐步淘汰) */
char type[32]; /* 传统设备类型(逐步淘汰) */
char compatible[128]; /* 设备树节点必须包含的兼容性字符串 */
const void *data; /* 传递给驱动的私有数据 */
};
对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。
在编写 platform 驱动的时候,首先定义一个 platform_driver 结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe函数就会执行,具体的驱动程序在 probe 函数里面编写,比如字符设备驱动等等。
platform_driver_register 函数
当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动。
platform_driver_register 函数原型如下所示:
cpp
int platform_driver_register (struct platform_driver *driver)
- driver:要注册的 platform 驱动。
- 返回值: 负数,失败; 0,成功。
platform_driver_unregister 函数
还需要在驱动卸载函数中,通过 platform_driver_unregister 函数卸载 platform 驱动, platform_driver_unregister 函数原型如下:
cpp
void platform_driver_unregister(struct platform_driver *drv)
- drv:要卸载的 platform 驱动。
- 返回值: 无。
platform 驱动框架
platform 驱动框架如下所示:
cpp
/* 设备结构体 */
struct xxx_dev {
struct cdev cdev;
/* 设备结构体其他具体内容 */
};
struct xxx_dev xxxdev; /* 定义个设备结构体变量 */
static int xxx_open(struct inode *inode, struct file *filp)
{
/* 函数具体内容 */
return 0;
}
static ssize_t xxx_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
/* 函数具体内容 */
return 0;
}
/*
* 字符设备驱动操作集
*/
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.write = xxx_write,
};
/*
* platform 驱动的 probe 函数
* 驱动与设备匹配成功以后此函数就会执行
*/
static int xxx_probe(struct platform_device *dev)
{
......
cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
/* 函数具体内容 */
return 0;
}
static int xxx_remove(struct platform_device *dev)
{
......
cdev_del(&xxxdev.cdev);/* 删除 cdev */
/* 函数具体内容 */
return 0;
}
/* 匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx-gpio" },
{ /* Sentinel */ }
};
/*
* platform 平台驱动结构体
*/
static struct platform_driver xxx_driver = {
.driver = {
.name = "xxx",
.of_match_table = xxx_of_match,
},
.probe = xxx_probe,
.remove = xxx_remove,
};
/* 驱动模块加载 */
static int __init xxxdriver_init(void)
{
return platform_driver_register(&xxx_driver);
}
/* 驱动模块卸载 */
static void __exit xxxdriver_exit(void)
{
platform_driver_unregister(&xxx_driver);
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
platform 驱动还是传统的字符设备驱动、块设备驱动或网络设备驱动,只是套上了一张"platform" 的皮,目的是为了使用总线、驱动和设备这个驱动模型来实现驱动的分离与分层。
platform 设备
platform_device结构体
platform_device 这个结构体表示 platform 设备,如果内核支持设备树,就是用设备树来描述设备。
platform_device 结构体定义在文件include/linux/platform_device.h 中,结构体内容如下:
cpp
/**
* struct platform_device - 平台设备结构体
*
* 表示一个不连接在传统硬件总线上的设备(如SoC内置外设)
*/
struct platform_device {
const char *name; /* 设备名称,用于与驱动匹配 */
int id; /* 设备实例ID(-1表示单个实例) */
bool id_auto; /* 是否自动分配ID */
struct device dev; /* 内嵌的标准设备结构 */
u32 num_resources; /* 资源数量 */
struct resource *resource; /* 设备资源数组(内存/I/O/中断等) */
const struct platform_device_id *id_entry; /* 设备ID条目(传统匹配方式) */
char *driver_override; /* 强制指定驱动名称 */
struct mfd_cell *mfd_cell; /* 如果是MFD子设备,指向父单元 */
struct pdev_archdata archdata; /* 架构特定数据 */
};
其中:
- name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设备就无法匹配到对应的驱动。
- num_resources 表示资源数量。
- resource 表示资源,也就是设备信息,比如外设寄存器等。
resource结构体
Linux 内核使用 resource结构体表示资源, resource 结构体内容如下:
cpp
/**
* struct resource - 硬件资源描述结构体
*
* 描述设备使用的硬件资源,包括内存区域、I/O端口、中断号等
*/
struct resource {
resource_size_t start; /* 资源起始地址/中断号 */
resource_size_t end; /* 资源结束地址/中断号 */
const char *name; /* 资源名称(可选) */
unsigned long flags; /* 资源类型和属性标志 */
/* 资源树管理指针(内核内部使用) */
struct resource *parent; /* 父资源 */
struct resource *sibling; /* 兄弟资源 */
struct resource *child; /* 子资源 */
};
- start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址。
- name 表示资源名字。
- flags 表示资源类型。
可选的资源类型,都定义在了文件include/linux/ioport.h 里面,如下所示:
cpp
#define IORESOURCE_IO 0x00000100 /* IO端口资源 */
#define IORESOURCE_MEM 0x00000200 /* 内存区域资源 */
#define IORESOURCE_IRQ 0x00000400 /* 中断资源 */
#define IORESOURCE_DMA 0x00000800 /* DMA通道 */
#define IORESOURCE_BUSY 0x80000000 /* 资源已分配 */
#define IORESOURCE_CACHEABLE 0x00000001 /* 可缓存内存 */
#define IORESOURCE_READONLY 0x00000002 /* 只读内存 */
在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中。
platform_device_register 函数
platform_device_register 函数原型如下所示:
cpp
int platform_device_register(struct platform_device *pdev)
- pdev:要注册的 platform 设备。
- 返回值: 负数,失败; 0,成功。
platform_device_unregister 函数
如果不再使用 platform 的话,可以通过 platform_device_unregister 函数注销掉相应的 platform设备。
platform_device_unregister 函数原型如下
cpp
void platform_device_unregister(struct platform_device *pdev)
- pdev:要注销的 platform 设备。
- 返回值: 无。
platform 设备信息框架
当 Linux 内核支持了设备树以后,就不需要用户手动去注册 platform 设备了。
因为设备信息都放到了设备树中去描述, Linux 内核启动的时候会从设备树中读取设备信息,然后将其组织成 platform_device 形式,
platform 设备信息框架如下所示:
cpp
/* 寄存器地址定义*/
#define PERIPH1_REGISTER_BASE (0X20000000) /* 外设1寄存器首地址 */
#define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设2寄存器首地址 */
#define REGISTER_LENGTH 4
/* 资源 */
static struct resource xxx_resources[] = {
[0] = {
.start = PERIPH1_REGISTER_BASE,
.end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[1] = {
.start = PERIPH2_REGISTER_BASE,
.end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
};
/* platform 设备结构体 */
static struct platform_device xxxdevice = {
.name = "xxx-gpio",
.id = -1,
.num_resources = ARRAY_SIZE(xxx_resources),
.resource = xxx_resources,
};
/* 设备模块加载 */
static int __init xxxdevice_init(void)
{
return platform_device_register(&xxxdevice);
}
/* 设备模块注销 */
static void __exit xxxdevice_exit(void)
{
platform_device_unregister(&xxxdevice);
}
module_init(xxxdevice_init);
module_exit(xxxdevice_exit);
MODULE_LICENSE("GPL");