【简单嵌入式】 Linux 内核驱动
📦 一、先看完整编译安装流程(心中有图)
在动手写代码前,先搞清楚整个构建链:
# 1. 配置内核(生成 .config)
make defconfig # 或 make menuconfig
# 2. 编译内核镜像(以 ARM64 为例)
make Image -j$(nproc)
# 3. 编译模块
make modules -j$(nproc)
# 4. 安装模块到目标根文件系统(可选)
make modules_install INSTALL_MOD_PATH=/path/to/rootfs
# 5. 手动复制内核镜像(嵌入式常用)
cp arch/arm64/boot/Image /path/to/rootfs/boot/
💡 注意:make install 在桌面 Linux 上可用,但在交叉编译嵌入式系统时,通常手动复制Image 和 .dtb 文件。
🗂️ 二、内核配置系统:Kconfig 是如何“找”到你的驱动的?
Linux 内核使用 Kconfig + Kbuild 构建系统。很多人以为只要写了 Kconfig 就能自动生效——大错特错!
✅ 正确做法(必须手动!):
假设你要添加一个新驱动目录:drivers/mydrv/
- 1. 创建子目录的 Kconfig
# drivers/mydrv/Kconfig
config MYDRV
tristate "My Awesome Driver"
help
This is my driver.
- 2. 在上层 Kconfig 中显式引入
比如在 drivers/Kconfig 末尾加上:
source "drivers/mydrv/Kconfig"
⚠️ 如果你忘了这一步,即使写好了 Kconfig,menuconfig 里也完全看不到你的选项!Kconfig 不会自动递归扫描子目录,必须靠 source 显式引入。
🛠️ 三、开发环境拓扑(别再搞混了!)
| |
|---|
| 主机(Host) | |
| 编译容器/虚拟机 | |
| 开发板(Target) | |
常见挂载方式:
- • 主机代码目录 → 挂载到编译环境
/opt/linux-src - • 开发板根文件系统 → 挂载到编译环境
/mnt/rootfs
这样,make modules_install INSTALL_MOD_PATH=/mnt/rootfs 就能直接部署。
🌳 四、设备树三剑客:.dtsi、.dts、.dtb 到底谁改谁?
示例:你在 RK3588 开发板上接了一个 PCF85063,就在你的 .dts 文件里加:
&i2c6 {
status = "okay";
pcf85063: rtc@51 {
compatible = "nxp,pcf85063";
reg = <0x51>;
quartz-load-femtofarads = <12500>; // 晶振负载电容
};
};
🔧 五、驱动代码分层设计:两类函数,各司其职
以 drivers/rtc/rtc-pcf85063.c 为例,所有优秀驱动都遵循 “面向芯片” vs “面向系统” 的分层思想。
第一类:面向芯片的函数(Chip-Oriented)
核心问题:怎么操作 PCF85063 这个硬件?
这些函数直接和寄存器打交道,处理 BCD 编码、I2C 通信、中断标志等。
| |
|---|
pcf85063_rtc_read_time | 从寄存器读 BCD 时间 → 转为 struct rtc_time |
pcf85063_rtc_set_time | |
pcf85063_rtc_set_alarm | |
pcf85063_ioctl | 处理 RTC_VL_READ,检测是否掉电(查 OS 位) |
pcf85063_load_capacitance | |
✅ 特点:调用 regmap_read/write,懂芯片手册,处理硬件细节。
第二类:面向系统的函数(System-Oriented)
核心问题:怎么让 Linux 内核认识这个硬件?
这些函数对接内核框架,注册设备,处理探测、电源管理等。
| |
|---|
pcf85063_rtc_ops | 关键桥梁! |
pcf85063_probe | 设备探测入口:初始化 regmap、注册 rtc、申请中断 |
pcf85063_rtc_handle_irq | |
pcf85063_driver | I2C 驱动描述符(含匹配表 + probe 函数) |
module_i2c_driver(...) | |
✅ 特点:使用 devm_*、of_match_table、rtc_register_device 等内核标准 API。
🌉 关键桥梁:pcf85063_rtc_ops
static conststruct rtc_class_ops pcf85063_rtc_ops = {
.read_time = pcf85063_rtc_read_time, // ← 芯片层
.set_time = pcf85063_rtc_set_time, // ← 芯片层
.read_alarm = pcf85063_rtc_read_alarm,
.set_alarm = pcf85063_rtc_set_alarm,
.ioctl = pcf85063_ioctl,
};
- • 对内核 RTC 子系统:它只看到
.read_time(),不知道背后是 I2C 还是 SPI。 - • 对 PCF85063 芯片:
pcf85063_rtc_read_time() 只关心寄存器地址和 BCD 转换。
这就是经典的 适配器模式(Adapter Pattern) —— 解耦硬件与系统!
🧩 六、驱动注册:i2c_driver 结构体详解
staticstruct i2c_driver pcf85063_driver = {
.driver = {
.name = "rtc-pcf85063",
.of_match_table = of_match_ptr(pcf85063_of_match),
},
.probe_new = pcf85063_probe,
.id_table = pcf85063_ids,
};
module_i2c_driver(pcf85063_driver);
逐行解读:
- •
.name → 内核中驱动的名字,影响 /sys/bus/i2c/drivers/ 目录名。 - •
.of_match_table → 设备树匹配表,现代嵌入式 Linux 的标配:
static conststruct of_device_id pcf85063_of_match[] = {
{ .compatible = "nxp,pcf85063a" },
{ .compatible = "microcrystal,rv8263" },
{ }
};
- •
.probe_new → 设备匹配成功后调用的初始化函数(新版 API)。 - •
module_i2c_driver() → 自动完成 i2c_add_driver(),省去 init/exit 模板代码。
⚙️ 七、Kconfig 与 Makefile 配置
1. drivers/rtc/Kconfig
config RTC_DRV_PCF85063
tristate "NXP PCF85063"
select REGMAP_I2C
help
Support for the PCF85063 RTC chip.
This driver can also be built as a module (rtc-pcf85063.ko).
- •
tristate → 支持 y(内置)、m(模块)、n(不编译) - •
select REGMAP_I2C → 自动依赖 regmap I2C 子系统
2. drivers/rtc/Makefile
obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o
3. 上层集成(drivers/Kconfig & Makefile)
# drivers/Kconfig
source "drivers/rtc/Kconfig"
# drivers/Makefile
obj-$(CONFIG_RTC_LIB) += rtc/
注意:RTC_LIB 是一个布尔开关,控制是否编译整个 rtc/ 子目录。
✅ 总结:写一个驱动,你只需要做这几件事
- 2. 写系统接口函数(probe、ops 结构体)
- 3. 配置 Kconfig + Makefile(别忘了
source!)