4. Linux 内核驱动框架:字符设备驱动基础、platform 驱动模型、设备树简介
在网关外设驱动开发中,理解 Linux 内核的驱动框架是挂载 SPI 与 I2C 设备的前提。本章聚焦三个核心层次:字符设备驱动作为用户空间与内核交互的基本接口,platform 驱动模型作为非枚举总线的设备-驱动匹配机制,以及设备树作为硬件描述语言在嵌入式平台中的标准化用法。
4.1 字符设备驱动基础
字符设备(char device)是 Linux 中最简单的设备类型,以字节流方式读写,典型代表包括串口、GPIO、SPI 控制器等。字符设备驱动的核心是 struct file_operations 结构体,它定义了用户空间 open/read/write/ioctl 等系统调用在内核中的实现。
4.1.1 核心数据结构与注册流程
| 组件 | 作用 | 关键函数/宏 |
|---|---|---|
struct cdev |
内核中表示字符设备的对象 | cdev_init(), cdev_add() |
dev_t |
设备号(主设备号 + 次设备号) | MKDEV(), register_chrdev_region() |
struct file_operations |
设备操作函数表 | .open, .read, .write, .unlocked_ioctl |
class_create() |
在 sysfs 中创建设备类 | device_create() 自动生成设备节点 |
标准注册流程如下:
- 分配
dev_t设备号(静态或动态)。 - 初始化
struct cdev并关联file_operations。 - 调用
cdev_add()将设备加入内核。 - 创建
class和device,自动生成/dev/节点。
// 字符设备驱动注册示例(简化)
static int __init my_driver_init(void)
{
dev_t devno;
int ret;
// 1. 分配设备号
ret = alloc_chrdev_region(&devno, 0, 1, "my_device");
if (ret < 0) return ret;
// 2. 初始化 cdev
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
// 3. 添加 cdev 到内核
ret = cdev_add(&my_cdev, devno, 1);
if (ret) goto err_cdev;
// 4. 创建类与设备节点
my_class = class_create(THIS_MODULE, "my_class");
device_create(my_class, NULL, devno, NULL, "my_device");
return 0;
err_cdev:
unregister_chrdev_region(devno, 1);
return ret;
}
file_operations 中的 .open 和 .release,否则用户空间无法正常打开设备文件。对于 SPI/I2C 设备,.unlocked_ioctl 常用于传递 SPI 模式、时钟频率等配置参数。
4.2 platform 驱动模型
在 Linux 内核中,并非所有设备都挂载在 PCI、USB 等可枚举总线上。对于 SoC 内部集成的 SPI 控制器、I2C 适配器、GPIO 控制器等,Linux 引入了 platform 驱动模型(也称为“平台总线”)。它使用虚拟总线 platform_bus 将设备与驱动进行匹配。
4.2.1 platform 设备与驱动分离
platform 模型的核心思想是设备与驱动分离:设备描述“有什么硬件”,驱动描述“如何操作硬件”。匹配由内核自动完成,匹配依据是 name 字段或设备树中的 compatible 属性。
| 组件 | 结构体 | 关键字段 |
|---|---|---|
| platform 设备 | struct platform_device |
.name, .id, .num_resources, .resource |
| platform 驱动 | struct platform_driver |
.driver.name, .probe, .remove, .id_table |
| 匹配方式 | 设备树 / 传统板级代码 | of_match_table 或 id_table |
// platform 驱动模板
static const struct of_device_id my_of_match[] = {
{ .compatible = "vendor,my-spi-controller" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
static int my_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
// 获取 I/O 内存资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 注册字符设备或 SPI 控制器
dev_info(&pdev->dev, "device probed successfully\n");
return 0;
}
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_spi_controller",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
struct spi_driver,I2C 设备驱动应使用 struct i2c_driver。
4.3 设备树(Device Tree)简介
设备树(Device Tree, DT)是一种描述硬件配置的数据结构,以树状节点形式表示 CPU、内存、外设、中断、GPIO、时钟等硬件资源。它替代了传统的板级代码(board-*.c),实现了内核代码与硬件描述的解耦。
4.3.1 设备树文件体系
| 文件类型 | 扩展名 | 说明 |
|---|---|---|
| 设备树源文件 | .dts |
可读的文本格式,描述硬件 |
| 设备树头文件 | .dtsi |
公共 SoC 定义,被 .dts 包含 |
| 设备树二进制 | .dtb |
编译后的二进制,由 bootloader 加载 |
| 设备树覆盖 | .dtbo |
运行时叠加的硬件片段 |
4.3.2 设备树节点与属性
每个设备树节点包含 compatible(驱动匹配)、reg(地址/大小)、interrupts(中断号)等属性。以下是一个 SPI 控制器的设备树节点示例:
// 设备树节点示例:SPI 控制器
spi0: spi@ff010000 {
compatible = "vendor,spi-controller";
reg = <0xff010000 0x1000>; // 基地址 + 大小
interrupts = <0 32 4>; // 中断类型 编号 触发方式
clocks = <&clkc 15>; // 时钟引用
#address-cells = <1>;
#size-cells = <0>;
// 挂载在 SPI 总线上的从设备
spi_device0: sensor@0 {
compatible = "vendor,temperature-sensor";
reg = <0>; // 片选编号 0
spi-max-frequency = <1000000>; // 最大 SPI 时钟 1MHz
};
};
of_match_table 与设备树节点的 compatible 属性进行字符串匹配。匹配成功后,内核将设备树中的 reg、interrupts 等资源自动传递给驱动的 .probe() 函数。
4.3.3 设备树在驱动开发中的典型用法
在驱动代码中,通过 of_* 系列 API 读取设备树属性:
// 驱动中读取设备树属性
static int my_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
u32 max_freq;
int irq;
// 读取 spi-max-frequency 属性
if (of_property_read_u32(np, "spi-max-frequency", &max_freq)) {
dev_err(dev, "missing spi-max-frequency\n");
return -EINVAL;
}
// 获取中断号
irq = irq_of_parse_and_map(np, 0);
if (!irq) {
dev_err(dev, "failed to get irq\n");
return -EINVAL;
}
dev_info(dev, "max freq: %u Hz, irq: %d\n", max_freq, irq);
return 0;
}
- 设备树中的
reg属性对应platform_get_resource(pdev, IORESOURCE_MEM, n)。 interrupts属性对应platform_get_irq(pdev, n)或irq_of_parse_and_map()。- 自定义属性(如
spi-max-frequency)使用of_property_read_u32/index读取。 - 设备树覆盖(.dtbo)允许在不重新编译内核的情况下添加新硬件,适合网关产品的模块化扩展。
4.4 三者的协同关系
在网关外设驱动开发中,字符设备、platform 模型、设备树三者协同工作:
- 设备树描述硬件拓扑和资源(地址、中断、时钟)。
- platform 驱动通过
of_match_table匹配设备树节点,在.probe()中获取资源并初始化硬件。 - 字符设备作为用户空间的访问接口,通过
file_operations提供open/read/write/ioctl操作。
对于 SPI 和 I2C 设备,platform 驱动通常用于实现控制器驱动(如 SPI 控制器、I2C 适配器),而具体的外设驱动(如温度传感器、ADC)则使用 spi_driver 或 i2c_driver 框架,这些内容将在后续章节中详细展开。