3. Linux设备驱动模型与Sitara外设驱动框架
在 Sitara(AM335x/AM437x/AM57xx 等)平台上写外设驱动,如果只盯着“寄存器读写”而忽略 Linux 设备驱动模型,代码往往能跑通却难以维护:设备与驱动耦合、资源写死、无法热插拔、sysfs 里看不到任何信息。本节把 Linux 设备驱动模型与 Sitara 外设驱动的组织方式串起来,重点讲清字符设备、平台设备、设备树匹配、注册与探测流程,以及 sysfs/procfs 接口的作用。
一、设备驱动模型要解决的核心问题
Linux 设备驱动模型(LDM)用三个核心对象描述系统:总线(bus)、设备(device)、驱动(driver)。总线负责把设备和驱动“撮合”到一起,匹配成功后调用驱动的 probe 函数。对 Sitara 而言,绝大多数片上外设(UART、I2C、SPI、GPIO、McSPI、EHRPWM、ADC 等)都挂在 platform 这条虚拟总线上,因此它们属于平台设备。
与之相对的是字符设备:它面向用户空间提供 /dev/xxx 节点,通过 file_operations 中的 open/read/write/ioctl 等回调与用户交互。二者并不冲突——一个 Sitara 外设驱动通常“上半身”是平台驱动(负责资源、时钟、中断、寄存器映射),“下半身”再注册一个字符设备(或 input、iio、netdev 等子系统设备)暴露给应用。理解这个分层,是读懂 Sitara 内核源码树里 drivers/ 目录结构的前提。
二、平台设备与设备树匹配
在 ARM Sitara 上,平台设备不再像老式内核那样在板级文件中用 platform_device 静态注册,而是由设备树(DTS)描述。内核启动时解析 DTS,为每个带 compatible 属性的节点创建 platform_device。驱动侧则用 of_device_id 表声明自己支持的 compatible 字符串,二者一致即匹配成功。
/* 设备树片段:am335x 上的一个自定义外设 */
mydev0: mydev@4804c000 {
compatible = "ti,am335x-mydev";
reg = <0x4804c000 0x1000>;
interrupts = <72>;
clocks = <&l4_wkup_clkctrl AM3_L4_WKUP_MYDEV_CLKCTRL 0>;
status = "okay";
};
/* 驱动侧匹配表 */
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "ti,am335x-mydev" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mydev_of_match);
匹配的优先级顺序是:of_device_id(设备树)→ acpi_device_id → id_table → name。Sitara 上基本只用第一种。注意 compatible 推荐写成“厂商,型号”形式,例如 ti,am335x-mydev,避免与通用字符串冲突。
三、驱动注册与探测流程
平台驱动通过 module_platform_driver() 宏注册,展开后等价于在模块加载时调用 platform_driver_register(),卸载时调用 platform_driver_unregister()。注册后内核会遍历 platform 总线上的设备,对每个设备调用 bus_match,匹配成功则调用驱动的 probe。
probe 函数是驱动真正“干活”的地方,典型顺序为:获取设备树资源(platform_get_resource / devm_ioremap_resource)→ 使能时钟(devm_clk_get + clk_prepare_enable)→ 申请中断(platform_get_irq + devm_request_irq)→ 初始化硬件寄存器 → 注册字符设备或子系统设备。使用 devm_ 前缀的接口可自动释放资源,probe 失败时无需手写 goto 清理链,这是 Sitara 驱动推荐的写法。
下面这张图把“设备树 → 平台设备 → 总线匹配 → probe → 字符设备节点”的完整链路画出来,同时标出 sysfs/procfs 的观察点,便于对照源码理解。
四、字符设备注册的两种方式
老式接口 register_chrdev(major, name, fops) 一次占用 0~255 全部次设备号,简单但浪费;现代写法是“申请设备号 + 初始化 cdev + 添加”三步:
static dev_t devno;
static struct cdev mydev_cdev;
static struct class *mydev_class;
/* 1. 动态申请主次设备号 */
alloc_chrdev_region(&devno, 0, 1, "mydev");
/* 2. 绑定 fops */
cdev_init(&mydev_cdev, &mydev_fops);
mydev_cdev.owner = THIS_MODULE;
cdev_add(&mydev_cdev, devno, 1);
/* 3. 创建设备节点 /dev/mydev0 */
mydev_class = class_create(THIS_MODULE, "mydev");
device_create(mydev_class, NULL, devno, NULL, "mydev0");
这样 /dev/mydev0 会自动出现,且主设备号由内核动态分配,避免冲突。若希望固定主设备号,可在 DTS 或模块参数中指定,但 Sitara 上更推荐动态分配。
五、sysfs 与 procfs 接口
sysfs 挂载在 /sys,是设备驱动模型对用户空间的“结构化视图”。对平台设备,可在 /sys/bus/platform/devices/ 下看到设备名,在 /sys/bus/platform/drivers/ 下看到驱动名,二者通过符号链接关联。驱动若用 DEVICE_ATTR 宏定义属性,还会在设备目录下生成可读写文件,例如查看版本号、复位设备。
procfs 挂载在 /proc,更偏向运行时状态。调试 Sitara 外设时最常用的几个文件是:/proc/interrupts(确认中断号与触发次数)、/proc/iomem(确认寄存器物理地址映射)、/proc/devices(确认字符设备主设备号)、/proc/device-tree/(查看内核实际解析到的设备树节点)。这些接口不替代 probe 逻辑,但能快速判断“驱动到底有没有匹配上、资源有没有拿到”。
/proc/device-tree/ 确认节点存在且 status 为 okay,再看 dmesg | grep mydev 确认 probe 是否执行,最后看 /proc/devices 与 /dev 确认字符设备是否注册成功。六、Sitara 外设驱动在内核中的组织方式
以 AM335x 为例,内核源码中与 Sitara 外设相关的目录大致如下:arch/arm/boot/dts/am335x-*.dts 描述板级设备树;drivers/tty/serial/8250/8250_omap.c 是 UART;drivers/i2c/busses/i2c-omap.c 是 I2C;drivers/spi/spi-omap2-mcspi.c 是 McSPI;drivers/gpio/gpio-omap.c 是 GPIO;drivers/pwm/pwm-tiehrpwm.c 是 EHRPWM。它们都遵循“平台驱动 + 子系统框架”的模式:平台驱动负责 Sitara 特有的寄存器与时钟,子系统框架(tty、i2c、spi、gpio、pwm)负责向上提供统一接口。
因此,当你要为一块 Sitara 板卡新增外设时,正确做法不是从零写一个字符设备,而是先判断它属于哪个子系统:如果是串口就挂到 8250 框架,如果是传感器就挂到 i2c 或 iio,如果是自定义逻辑则用平台驱动 + 字符设备。这样既能复用内核已有框架,也能让 sysfs/procfs 自动获得可观测性。