Linux platform驱动与设备树匹配开发实战 - 第1节

1.1 内核模块基础

Linux 内核采用模块化设计,允许在运行时动态加载和卸载功能代码,这种可扩展的代码单元称为内核模块。驱动开发者通过编写内核模块,将设备驱动、文件系统、协议栈等组件插入内核,无需重新编译整个内核或重启系统。

1.1.1 模块的入口与出口

每个内核模块必须包含两个宏:

  • module_init() — 指定模块加载时调用的初始化函数。
  • module_exit() — 指定模块卸载时调用的清理函数。

基本框架如下:

#include <linux/module.h>
#include <linux/kernel.h>

static int __init my_driver_init(void)
{
    printk(KERN_INFO "My driver loaded\n");
    return 0;
}

static void __exit my_driver_exit(void)
{
    printk(KERN_INFO "My driver unloaded\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Engineer");
MODULE_DESCRIPTION("A minimal kernel module example");

1.1.2 模块编译与依赖

内核模块的编译依赖当前运行内核的构建系统,通常使用 Makefile 调用内核的 Kbuild 框架:

obj-m := my_driver.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean
提示:模块加载使用 insmod,卸载使用 rmmod,查看已加载模块使用 lsmod。模块间若存在符号依赖,需按顺序加载。

1.2 驱动分类:字符、块与网络

Linux 驱动按设备与内核的数据交换方式分为三大类,下表总结了它们的核心差异:

驱动类型 数据交换方式 典型设备 设备节点 访问接口
字符设备 字节流,顺序访问 串口、GPIO、I2C、SPI /dev/xxx open/read/write/ioctl
块设备 块(512B/4KB),随机访问 硬盘、eMMC、SD卡、NVMe /dev/xxx 文件系统 + submit_bio
网络设备 数据包(sk_buff),异步收发 以太网、WiFi、CAN、USB网卡 无设备节点 net_device_ops + socket

1.2.1 字符设备驱动

字符设备是嵌入式开发中最常见的驱动类型。它通过 struct file_operations 向用户空间暴露操作接口,内核使用主设备号与次设备号进行标识。

static struct file_operations my_fops = {
    .owner   = THIS_MODULE,
    .open    = my_open,
    .release = my_release,
    .read    = my_read,
    .write   = my_write,
    .unlocked_ioctl = my_ioctl,
};

1.2.2 块设备驱动

块设备驱动面向存储介质,以块为单位进行读写,内核通过 I/O 调度器 合并与排序请求。块设备驱动需要实现 struct block_device_operations,并注册 gendisk 结构。

1.2.3 网络设备驱动

网络设备驱动不通过设备节点访问,而是通过内核网络协议栈。驱动需注册 struct net_device,并实现 struct net_device_ops 中的 ndo_start_xmit 等回调函数。

重点:本课程聚焦 Platform 驱动,它属于字符设备驱动范畴,但具有独特的设备与驱动匹配机制,是 SoC 内部外设驱动的标准实现方式。

1.3 Platform 驱动在 Linux 驱动模型中的位置

Linux 驱动模型以 设备(device)、驱动(driver)、总线(bus) 三者为核心。设备与驱动通过总线进行匹配,匹配成功后调用驱动的 probe 函数完成初始化。

1.3.1 驱动模型三层结构

  • 总线(bus):负责匹配设备与驱动,提供匹配回调函数。常见总线有 platform_bus、i2c_bus、spi_bus、pci_bus 等。
  • 设备(device):描述硬件资源(地址、中断、时钟、DMA),可静态定义或由设备树生成。
  • 驱动(driver):实现硬件操作逻辑,包含 probe、remove、suspend、resume 等回调。
Linux 驱动模型:总线 — 设备 — 驱动 总线 (Bus) 设备 (Device) 资源描述 / 设备树节点 驱动 (Driver) probe / remove / 操作函数 匹配成功 → probe Platform 总线 Platform 总线是虚拟总线,用于管理 SoC 内部没有硬件总线的设备(如 UART、I2C 控制器、DMA) 匹配依据:设备树 compatible 属性 / 设备名 / ACPI ID

1.3.2 Platform 驱动的独特地位

Platform 驱动是 Linux 驱动模型中最灵活、最广泛的驱动形式,尤其适用于嵌入式 SoC 内部外设。其核心特征包括:

  • 虚拟总线:Platform 总线是内核软件虚拟的总线,不依赖硬件电气连接。
  • 资源传递:通过 struct platform_device 传递内存地址、中断号、DMA 通道等资源。
  • 设备树匹配:现代内核通过设备树(Device Tree)的 compatible 属性与驱动进行匹配,实现硬件描述与驱动代码的解耦。
  • 热插拔支持:部分 Platform 设备支持运行时动态添加/移除。
Platform 驱动匹配流程 设备树 (.dts) 内核解析 platform_device platform_driver 匹配 (match) probe() 调用

1.3.3 与其他驱动类型的关系

Platform 驱动并非独立于三大驱动类型之外,而是位于字符/块/网络驱动之上的一层封装。一个 Platform 驱动最终仍会注册为字符设备、块设备或网络设备,但它的设备发现和资源获取由 Platform 总线机制完成。

驱动类型 是否可基于 Platform 典型场景
字符设备 是(最常见) UART、GPIO、I2C控制器、SPI控制器
块设备 是 NAND Flash控制器、SDHCI控制器
网络设备 是 以太网 MAC(如 dwmac、fec)
注意:Platform 驱动不适用于 PCI、USB、I2C、SPI 等具有硬件总线的设备,这些设备使用各自的总线驱动模型。Platform 专为“没有硬件总线”的 SoC 内部外设设计。

1.4 本章小结

  • 内核模块是 Linux 驱动的基本载体,使用 module_init/module_exit 管理生命周期。
  • 驱动按数据交换方式分为字符、块、网络三类,字符驱动在嵌入式开发中占比最高。
  • Linux 驱动模型以总线为核心,实现设备与驱动的解耦匹配。
  • Platform 驱动是 SoC 内部外设的标准驱动形式,通过设备树 compatible 属性完成匹配,是现代嵌入式 Linux 驱动开发的核心技能。