title: platform总线模型 tags: [Linux驱动, platform, 设备驱动分离, 总线设备驱动, 设备树, 嵌入式] created: 2026-09-17 updated: 2026-09-17 pdf_ref:
关联知识: [[02-嵌入式Linux内核基础/06-设备模型与驱动框架]] | [[03-Linux驱动开发核心/02-设备树语法与实战]]
Linux系统是一个成熟、庞大、复杂的操作系统,代码的重用性非常重要。驱动程序占用了Linux内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码增加,内核文件数量将庞大到无法接受。
传统驱动的痛点:
假设有三个平台A、B、C,每个平台都有MPU6050这个I2C传感器。按传统方式,每个平台都要写一个MPU6050驱动,导致大量重复代码:
传统方式:
A平台I2C主机驱动 + MPU6050驱动
B平台I2C主机驱动 + MPU6050驱动
C平台I2C主机驱动 + MPU6050驱动
改进方案——驱动分离:
将主机驱动和设备驱动分隔开来,每个平台的I2C控制器提供统一接口,每个设备只提供一个驱动程序:
改进后:
A平台I2C主机驱动 ─┐
B平台I2C主机驱动 ─┼── 统一接口API ── MPU6050驱动
C平台I2C主机驱动 ─┘
驱动分离的核心思想:
这就是Linux中的总线(bus)、驱动(driver)和设备(device)模型。
SOC中有些外设(如GPIO、定时器等)没有物理总线概念,但又要使用总线-驱动-设备模型。为解决此问题,Linux提出了platform虚拟总线:
graph TB
subgraph "platform总线模型"
Bus["platform_bus<br/>(虚拟总线)"]
subgraph "设备侧"
D1["platform_device<br/>name: xxx-gpio<br/>resource: 寄存器/中断"]
D2["platform_device<br/>name: yyy-i2c<br/>resource: 寄存器/中断"]
D3["设备树节点<br/>compatible = xxx-gpio"]
end
subgraph "驱动侧"
DR1["platform_driver<br/>name: xxx<br/>probe: xxx_probe"]
DR2["platform_driver<br/>name: yyy<br/>probe: yyy_probe"]
end
D1 -->|"匹配(name)"| Bus
D2 -->|"匹配(name)"| Bus
D3 -->|"匹配(compatible)"| Bus
Bus -->|"匹配(name)"| DR1
Bus -->|"匹配(name)"| DR2
end
Bus -->|"调用probe()"| DR1
Bus -->|"调用probe()"| DR2
关键组件:
drivers/base/platform.c| 特性 | platform_device | platform_driver |
|---|---|---|
| 作用 | 描述设备硬件信息 | 实现驱动逻辑 |
| 定义位置 | include/linux/platform_device.h |
include/linux/platform_device.h |
| 关键成员 | name, resource, num_resources | probe, remove, driver, id_table |
| 注册函数 | platform_device_register() |
platform_driver_register() |
| 设备树方式 | 无需手动编写,内核自动解析 | 需实现of_match_table |
| 无设备树方式 | 需手动定义并注册 | 需定义id_table或name匹配 |
platform总线的匹配函数platform_match()定义在drivers/base/platform.c中,匹配优先级如下:
匹配顺序(从高到低):
1. driver_override强制匹配 → 直接比较名字
2. OF设备树匹配 → compatible属性匹配
3. ACPI匹配 → ACPI表匹配
4. id_table匹配 → platform_device_id数组匹配
5. name匹配 → 直接比较name字段
/* 文件: include/linux/platform_device.h */
struct platform_device {
const char *name; /* 设备名字,用于与驱动name字段匹配 */
int id; /* 设备ID,-1表示自动分配 */
bool id_auto; /* 是否自动分配ID */
struct device dev; /* 内嵌的device结构体 */
u32 num_resources; /* 资源数量 */
struct resource *resource; /* 资源数组(寄存器、中断等) */
const struct platform_device_id *id_entry; /* id_table匹配项 */
char *driver_override; /* 强制绑定的驱动名 */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
关键字段说明:
| 字段 | 说明 |
|---|---|
name |
设备名字,必须与驱动的name字段相同才能匹配 |
dev |
内嵌的device结构体,包含设备的基础信息 |
resource |
资源数组,描述寄存器地址、中断号等硬件信息 |
num_resources |
resource数组的元素个数 |
driver_override |
强制绑定指定驱动,优先级最高 |
/* 文件: 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; /* 基类device_driver */
const struct platform_device_id *id_table; /* id_table匹配表 */
bool prevent_deferred_probe; /* 阻止延迟探测 */
};
关键字段说明:
| 字段 | 说明 |
|---|---|
probe |
驱动与设备匹配成功后自动调用,是驱动的核心入口 |
remove |
卸载驱动时调用,用于释放资源 |
driver |
基类device_driver,包含name、of_match_table等 |
id_table |
用于传统name匹配方式 |
/* 文件: include/linux/device.h */
struct device_driver {
const char *name; /* 驱动名字 */
struct bus_type *bus; /* 所属总线 */
struct module *owner; /* 模块拥有者 */
const char *mod_name; /* 内建模块名 */
bool suppress_bind_attrs; /* 禁用sysfs绑定/解绑 */
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; /* 驱动私有数据 */
};
/* 文件: include/linux/mod_devicetable.h */
struct of_device_id {
char name[32]; /* 设备名(较少使用) */
char type[32]; /* 设备类型(较少使用) */
char compatible[128]; /* 兼容属性,设备树匹配的关键字段 */
const void *data; /* 私有数据指针 */
};
compatible字段是最关键的,设备树中每个设备节点的compatible属性会与of_match_table中每个项目的compatible成员进行比较。
/* 文件: include/linux/ioport.h */
struct resource {
resource_size_t start; /* 资源起始地址 */
resource_size_t end; /* 资源结束地址 */
const char *name; /* 资源名字 */
unsigned long flags; /* 资源类型标志 */
struct resource *parent, *sibling, *child; /* 资源树 */
};
资源类型标志:
/* 文件: include/linux/ioport.h */
#define IORESOURCE_BITS 0x000000ff /* 总线特定位 */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* 资源类型掩码 */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O端口 */
#define IORESOURCE_MEM 0x00000200 /* 内存资源 */
#define IORESOURCE_REG 0x00000300 /* 寄存器偏移 */
#define IORESOURCE_IRQ 0x00000400 /* 中断资源 */
#define IORESOURCE_DMA 0x00000800 /* DMA资源 */
#define IORESOURCE_BUS 0x00001000 /* 总线资源 */
在设备树中,设备节点通过compatible属性描述设备信息:
/* IMX6ULL LED设备节点示例 */
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled"; /* 匹配驱动的关键属性 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
驱动中通过of_match_table声明兼容的设备:
/* 匹配表定义 */
static const struct of_device_id leds_of_match[] = {
{ .compatible = "atkalpha-gpioled" }, /* 兼容属性 */
{ /* Sentinel - 最后一个必须为空 */ }
};
/* 声明设备表,使内核能识别 */
MODULE_DEVICE_TABLE(of, leds_of_match);
/* platform_driver中引用匹配表 */
static struct platform_driver leds_platform_driver = {
.driver = {
.name = "imx6ul-led", /* 传统name匹配 */
.of_match_table = leds_of_match, /* 设备树匹配 */
},
.probe = leds_probe,
.remove = leds_remove,
};
flowchart TD
A["驱动/设备注册到platform总线"] --> B{"匹配函数 platform_match()"}
B --> C{"1. driver_override<br/>是否设置?"}
C -->|"是"| D["强制匹配driver_override指定的驱动"]
C -->|"否"| E{"2. OF设备树匹配<br/>of_driver_match_device()"}
E -->|"设备有compatible<br/>驱动有of_match_table"| F["比较compatible属性"]
F -->|"匹配成功"| G["调用probe()"]
F -->|"不匹配"| H{"3. ACPI匹配"}
E -->|"无设备树支持"| H
H -->|"ACPI系统"| I["ACPI表匹配"]
H -->|"非ACPI系统"| J{"4. id_table匹配<br/>platform_match_id()"}
J -->|"驱动有id_table"| K["遍历id_table比较name"]
K -->|"匹配成功"| G
K -->|"不匹配"| L{"5. name匹配<br/>strcmp(pdev->name, drv->name)"}
J -->|"驱动无id_table"| L
L -->|"name相同"| G
L -->|"name不同"| M["匹配失败"]
G --> N["probe函数执行<br/>驱动正式工作"]
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LEDDEV_CNT 1 /* 设备号长度 */
#define LEDDEV_NAME "platled" /* 设备名字 */
#define LEDOFF 0
#define LEDON 1
/* 设备结构体 */
struct leddev_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
};
struct leddev_dev leddev; /* led设备 */
/* LED打开/关闭 */
void led0_switch(u8 sta)
{
/* 根据sta控制LED */
}
/* 打开设备 */
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &leddev; /* 设置私有数据 */
return 0;
}
/* 向设备写数据 */
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if (retvalue < 0) {
return -EFAULT;
}
ledstat = databuf[0];
if (ledstat == LEDON) {
led0_switch(LEDON);
} else if (ledstat == LEDOFF) {
led0_switch(LEDOFF);
}
return 0;
}
/* 设备操作函数 */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/*
* probe函数 - 驱动与设备匹配成功后执行
*/
static int xxx_probe(struct platform_device *dev)
{
/* 初始化LED */
/* 注册字符设备驱动 */
cdev_init(&leddev.cdev, &led_fops);
/* 其他初始化工作 */
return 0;
}
/*
* remove函数 - 卸载platform驱动时执行
*/
static int xxx_remove(struct platform_device *dev)
{
cdev_del(&leddev.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");
MODULE_AUTHOR("zuozhongkai");
probe函数是platform驱动的核心,在设备与驱动匹配成功后自动调用。主要完成以下工作:
static int xxx_probe(struct platform_device *dev)
{
/* 1. 获取资源 */
struct resource *mem;
mem = platform_get_resource(dev, IORESOURCE_MEM, 0);
if (!mem) {
dev_err(&dev->dev, "No MEM resource\n");
return -ENXIO;
}
/* 2. 内存映射 */
void __iomem *base;
base = devm_ioremap_resource(&dev->dev, mem);
if (IS_ERR(base)) {
return PTR_ERR(base);
}
/* 3. 获取中断号 */
int irq;
irq = platform_get_irq(dev, 0);
if (irq < 0) {
dev_err(&dev->dev, "No IRQ resource\n");
return irq;
}
/* 4. 初始化硬件 */
/* 5. 注册字符设备驱动 */
/* 6. 创建设备节点 */
dev_info(&dev->dev, "Driver probed successfully\n");
return 0;
}
remove函数在卸载驱动时执行,负责释放所有资源:
static int xxx_remove(struct platform_device *dev)
{
/* 1. 释放内存映射 */
iounmap(base);
/* 2. 删除cdev */
cdev_del(&leddev.cdev);
/* 3. 注销设备号 */
unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
/* 4. 删除设备 */
device_destroy(leddev.class, leddev.devid);
/* 5. 删除类 */
class_destroy(leddev.class);
dev_info(&dev->dev, "Driver removed\n");
return 0;
}
/* 驱动模块加载 - 向内核注册platform驱动 */
static int __init xxxdriver_init(void)
{
return platform_driver_register(&xxx_driver);
}
/* 驱动模块卸载 - 从内核注销platform驱动 */
static void __exit xxxdriver_exit(void)
{
platform_driver_unregister(&xxx_driver);
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/* 寄存器地址定义 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
#define REGISTER_LENGTH 4
/* 释放platform设备模块时执行 */
static void led_release(struct device *dev)
{
printk("led device released!\r\n");
}
/* 设备资源信息 - LED0使用的所有寄存器 */
static struct resource led_resources[] = {
[0] = {
.start = CCM_CCGR1_BASE,
.end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[1] = {
.start = SW_MUX_GPIO1_IO03_BASE,
.end = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[2] = {
.start = SW_PAD_GPIO1_IO03_BASE,
.end = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[3] = {
.start = GPIO1_DR_BASE,
.end = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[4] = {
.start = GPIO1_GDIR_BASE,
.end = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
};
/* platform设备结构体 */
static struct platform_device leddevice = {
.name = "imx6ul-led",
.id = -1,
.dev = {
.release = &led_release,
},
.num_resources = ARRAY_SIZE(led_resources),
.resource = led_resources,
};
/* 设备模块加载 */
static int __init leddevice_init(void)
{
return platform_device_register(&leddevice);
}
/* 设备模块注销 */
static void __exit leddevice_exit(void)
{
platform_device_unregister(&leddevice);
}
module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
逐行解释:
| 行号 | 说明 |
|---|---|
| 37-42 | 定义LED使用的寄存器物理地址 |
| 48-51 | 设备释放回调函数,设备移除时调用 |
| 56-82 | led_resources数组,描述5个寄存器的内存资源 |
| 88-96 | platform设备结构体,name必须与驱动name匹配 |
| 103-106 | 模块加载函数,调用platform_device_register()注册设备 |
| 113-116 | 模块卸载函数,调用platform_device_unregister()注销设备 |
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LEDDEV_CNT 1 /* 设备号长度 */
#define LEDDEV_NAME "platled" /* 设备名字 */
#define LEDOFF 0
#define LEDON 1
/* 寄存器虚拟地址 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* leddev设备结构体 */
struct leddev_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
};
struct leddev_dev leddev; /* led设备 */
/* LED打开/关闭 */
void led0_switch(u8 sta)
{
u32 val = 0;
if (sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
} else if (sta == LEDOFF) {
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
}
}
/* 打开设备 */
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &leddev;
return 0;
}
/* 向设备写数据 */
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if (retvalue < 0) {
return -EFAULT;
}
ledstat = databuf[0];
if (ledstat == LEDON) {
led0_switch(LEDON);
} else if (ledstat == LEDOFF) {
led0_switch(LEDOFF);
}
return 0;
}
/* 设备操作函数 */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/*
* probe函数 - 当驱动与设备匹配成功后执行
*/
static int led_probe(struct platform_device *dev)
{
int i = 0;
int ressize[5];
u32 val = 0;
struct resource *ledsource[5];
printk("led driver and device has matched!\r\n");
/* 1、获取资源 */
for (i = 0; i < 5; i++) {
ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
if (!ledsource[i]) {
dev_err(&dev->dev, "No MEM resource for always on\n");
return -ENXIO;
}
ressize[i] = resource_size(ledsource[i]);
}
/* 2、初始化LED */
/* 寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val, IMX6U_CCM_CCGR1);
/* 设置GPIO1_IO03复用功能 */
writel(5, SW_MUX_GPIO1_IO03);
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val, GPIO1_GDIR);
/* 默认关闭LED1 */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
/* 3、注册字符设备驱动 */
if (leddev.major) {
leddev.devid = MKDEV(leddev.major, 0);
register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
} else {
alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
leddev.major = MAJOR(leddev.devid);
}
/* 4、初始化cdev */
leddev.cdev.owner = THIS_MODULE;
cdev_init(&leddev.cdev, &led_fops);
/* 5、添加一个cdev */
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
/* 6、创建类 */
leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
if (IS_ERR(leddev.class)) {
return PTR_ERR(leddev.class);
}
/* 7、创建设备 */
leddev.device = device_create(leddev.class, NULL, leddev.devid,
NULL, LEDDEV_NAME);
if (IS_ERR(leddev.device)) {
return PTR_ERR(leddev.device);
}
return 0;
}
/*
* remove函数 - 卸载platform驱动时执行
*/
static int led_remove(struct platform_device *dev)
{
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
cdev_del(&leddev.cdev);
unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
device_destroy(leddev.class, leddev.devid);
class_destroy(leddev.class);
return 0;
}
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led",
},
.probe = led_probe,
.remove = led_remove,
};
/* 驱动模块加载 */
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
/* 驱动模块卸载 */
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
逐行解释:
| 行号 | 说明 |
|---|---|
| 40-44 | 定义寄存器虚拟地址指针 |
| 47-55 | 设备结构体,包含设备号、cdev、类、设备等 |
| 62-74 | LED开关函数,通过操作GPIO数据寄存器控制LED |
| 83-87 | open函数,设置私有数据指针 |
| 97-115 | write函数,从用户空间读取数据控制LED |
| 130-206 | probe函数,匹配成功后执行:获取资源→映射寄存器→初始化GPIO→注册字符设备 |
| 213-226 | remove函数,卸载时释放所有资源 |
| 229-235 | platform_driver结构体,name必须与设备name匹配 |
| 242-245 | 模块加载函数,调用platform_driver_register() |
| 252-255 | 模块卸载函数,调用platform_driver_unregister() |
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LEDDEV_CNT 1
#define LEDDEV_NAME "dtsplatled"
#define LEDOFF 0
#define LEDON 1
struct leddev_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
struct device_node *node; /* LED设备节点 */
int led0; /* LED灯GPIO标号 */
};
struct leddev_dev leddev;
void led0_switch(u8 sta)
{
if (sta == LEDON)
gpio_set_value(leddev.led0, 0);
else if (sta == LEDOFF)
gpio_set_value(leddev.led0, 1);
}
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &leddev;
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[2];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if (retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0];
if (ledstat == LEDON) {
led0_switch(LEDON);
} else if (ledstat == LEDOFF) {
led0_switch(LEDOFF);
}
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/*
* probe函数 - 设备树匹配成功后执行
*/
static int led_probe(struct platform_device *dev)
{
printk("led driver and device was matched!\r\n");
/* 1、设置设备号 */
if (leddev.major) {
leddev.devid = MKDEV(leddev.major, 0);
register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
} else {
alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
leddev.major = MAJOR(leddev.devid);
}
/* 2、注册设备 */
cdev_init(&leddev.cdev, &led_fops);
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
/* 3、创建类 */
leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
if (IS_ERR(leddev.class)) {
return PTR_ERR(leddev.class);
}
/* 4、创建设备 */
leddev.device = device_create(leddev.class, NULL, leddev.devid,
NULL, LEDDEV_NAME);
if (IS_ERR(leddev.device)) {
return PTR_ERR(leddev.device);
}
/* 5、初始化IO */
leddev.node = of_find_node_by_path("/gpioled");
if (leddev.node == NULL) {
printk("gpioled node nost find!\r\n");
return -EINVAL;
}
leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0);
if (leddev.led0 < 0) {
printk("can't get led-gpio\r\n");
return -EINVAL;
}
gpio_request(leddev.led0, "led0");
gpio_direction_output(leddev.led0, 1);
return 0;
}
static int led_remove(struct platform_device *dev)
{
gpio_set_value(leddev.led0, 1);
cdev_del(&leddev.cdev);
unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
device_destroy(leddev.class, leddev.devid);
class_destroy(leddev.class);
return 0;
}
/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "atkalpha-gpioled" },
{ /* Sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-led",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
与无设备树方式的区别:
| 特性 | 无设备树方式 | 设备树方式 |
|---|---|---|
| 设备信息 | 手动编写platform_device | 设备树节点自动解析 |
| 匹配方式 | name字段匹配 | compatible属性匹配 |
| 获取资源 | platform_get_resource() |
of_find_node_by_path() + of_get_named_gpio() |
| 内存映射 | ioremap() |
of_iomap() 或 devm_ioremap_resource() |
| 需要文件 | leddevice.c + leddriver.c | 仅leddriver.c |
在设备树中添加LED设备节点(以IMX6ULL为例):
/* 在根节点下添加 */
/ {
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
};
/* 在iomuxc节点下添加pinctrl */
&iomuxc {
pinctrl_led: ledgrp {
fsl,pins = <
MX6ULL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
};
Makefile:
KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := leddriver.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译命令:
# 编译驱动模块
make -j32
# 编译测试APP
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
# 拷贝文件到开发板
scp leddriver.ko root@192.168.1.232:/lib/modules/4.1.15/
scp ledApp root@192.168.1.232:/lib/modules/4.1.15/
# 开发板上执行
cd /lib/modules/4.1.15/
# 第一次加载需要执行depmod
depmod
# 加载驱动模块
modprobe leddriver.ko
# 查看驱动是否加载成功
ls /sys/bus/platform/drivers/imx6ul-led/
# 查看设备是否加载成功
ls /sys/bus/platform/devices/
# 测试LED
./ledApp /dev/dtsplatled 1 # 打开LED
./ledApp /dev/dtsplatled 0 # 关闭LED
# 卸载驱动
rmmod leddriver.ko
验证要点:
"led driver and device was matched!"/sys/bus/platform/drivers/下存在imx6ul-led/sys/bus/platform/devices/下存在设备节点| 特性 | IMX6ULL | STM32 (Linux) | RK3568 |
|---|---|---|---|
| 平台总线 | platform虚拟总线 | platform虚拟总线 | platform虚拟总线 |
| 设备树支持 | 完整支持 | 部分支持 | 完整支持 |
| 匹配方式 | OF/name | OF/name/ACPI | OF/name |
| 资源获取 | platform_get_resource() |
platform_get_resource() |
platform_get_resource() |
| GPIO管理 | of_get_named_gpio() |
of_get_named_gpio() |
gpiod_get() |
| 时钟管理 | devm_clk_get() |
devm_clk_get() |
devm_clk_get() |
| 设备树示例 | compatible = "atkalpha-gpioled" |
compatible = "st,stm32-led" |
compatible = "rockchip,gpio-led" |
差异点:
&gpio1引用gpiod接口考察点:驱动分离与分层
参考答案:
platform总线模型的核心思想是驱动分离——将设备信息与驱动逻辑分离,通过总线进行匹配。
为什么需要:
架构组成:
考察点:匹配机制
参考答案:
platform_match()函数按以下优先级依次匹配:
| 优先级 | 匹配方式 | 说明 |
|---|---|---|
| 1 | driver_override |
强制绑定指定驱动,优先级最高 |
| 2 | OF设备树匹配 | 比较compatible属性,最常用 |
| 3 | ACPI匹配 | 用于ACPI系统 |
| 4 | id_table匹配 | 比较platform_device_id数组 |
| 5 | name匹配 | 直接比较name字段,最简单 |
代码位置:drivers/base/platform.c中的platform_match()函数
实际应用:现代Linux驱动通常同时支持OF匹配和name匹配,以保证兼容性。
考察点:驱动核心逻辑
参考答案:
执行时机:当设备与驱动通过总线匹配成功后,platform_match()调用driver_probe_device(),最终执行probe()函数。
probe函数应该完成:
platform_get_resource()获取寄存器、中断等ioremap()或devm_ioremap_resource()register_chrdev_region()、cdev_init()、cdev_add()class_create()、device_create()request_irq()(如需要)注意:probe函数中不能执行阻塞操作。
考察点:设备树API
参考答案:
| 资源类型 | API函数 | 示例 |
|---|---|---|
| 设备节点 | of_find_node_by_path() |
of_find_node_by_path("/gpioled") |
| GPIO | of_get_named_gpio() |
of_get_named_gpio(node, "led-gpio", 0) |
| 寄存器 | of_iomap() |
of_iomap(node, 0) |
| 中断 | platform_get_irq() |
platform_get_irq(dev, 0) |
| 时钟 | devm_clk_get() |
devm_clk_get(&dev->dev, "ipg") |
| 属性值 | of_property_read_u32() |
of_property_read_u32(node, "reg", &val) |
推荐使用devm_系列函数,它们会在设备移除时自动释放资源。
考察点:驱动模型理解
参考答案:
关系:platform驱动是框架,字符设备驱动是实现。
类比:
platform驱动 = 外壳(管理框架)
字符设备驱动 = 内核(具体功能)
代码体现:
static int xxx_probe(struct platform_device *dev) {
/* platform框架的probe函数 */
/* 在这里面实现字符设备驱动 */
cdev_init(&xxxdev.cdev, &xxx_fops); /* 字符设备 */
cdev_add(&xxxdev.cdev, ...);
class_create(...);
device_create(...);
return 0;
}
总结:platform驱动是字符设备驱动的一种封装形式,目的是实现驱动的分离与分层。字符设备驱动的核心逻辑仍然在probe函数中实现。