从用户空间到 GPIO 寄存器:IMX6ULL 杂项设备 LED 驱动完整解析
这篇博客整理自我们之前逐行拆解的 LED 驱动代码。它实现的是:用户空间向 /dev/led_misc 写入 0 或 1,驱动通过 GPIO 寄存器控制 IMX6ULL 上 LED 的亮灭 。代码没有硬编码寄存器地址,而是通过设备树节点和 of_iomap 完成地址映射。
一、为什么用 misc 设备?
Linux 设备分为字符设备、块设备和网络设备。LED 属于典型的字符设备:按字节流读写,不需要随机寻址。
传统字符设备需要自己申请主设备号、创建设备节点、管理 cdev。而 misc 设备是字符设备的简化版本 ,所有 misc 设备共享主设备号 10,只需指定次设备号和设备名,注册后内核会自动在 /dev 下生成设备节点。
对于 LED、蜂鸣器、按键这类功能简单的外设,misc 框架能省去大量模板代码。
二、设备树:硬件信息的"说明书"
驱动里最关键的转变是:不再硬编码寄存器地址 。我们在设备树中定义一个 /myled0 节点,用 reg 属性描述四个寄存器:
/myled0 {
reg = <0x020E0068 0x04 /* 引脚复用控制寄存器 */
0x020E02F4 0x04 /* 引脚电气属性寄存器 */
0x0209C004 0x04 /* GPIO1_GDIR 方向寄存器 */
0x0209C000 0x04>; /* GPIO1_DR 数据寄存器 */
};
reg 里每一对 <地址 长度> 是一段。驱动通过 of_iomap(plednode, index) 按索引取出对应段,完成物理地址到内核虚拟地址的映射。
三、驱动初始化:注册设备 + 配置硬件
模块加载时,led_misc_init 依次完成:
- 注册 misc 设备 :
misc_register(&led_misc)生成/dev/led_misc。 - 查找设备树节点 :
of_find_node_by_path("/myled0")定位硬件描述。 - 映射四个寄存器 :通过
of_iomap分别映射复用控制、PAD 配置、方向、数据寄存器。 - 初始化 GPIO :
- 将引脚复用为 GPIO 模式;
- 配置 PAD 电气属性;
- 设置 GPIO1_IO03 为输出;
- 默认输出高电平,LED 熄灭。
四、led_write:用户空间到硬件的桥梁
这是整个驱动的核心交互函数。它的原型是:
static ssize_t led_write(struct file *fp, const char __user *puser, size_t n, loff_t *off)
这些参数不是驱动自己传的 ,而是内核 VFS 层在用户调用 write() 时自动填充的:
| 参数 | 来源 |
|---|---|
fp |
内核根据文件描述符找到的 struct file |
puser |
用户空间 write(fd, buf, count) 中的 buf |
n |
用户空间 write 中的 count |
off |
内核维护的文件读写偏移 |
调用链如下:
用户: write(fd, &val, sizeof(val))
↓
glibc: 触发 write 系统调用
↓
内核: sys_write → vfs_write → fops->write
↓
驱动: led_write(fp, puser, n, off)
函数内部通过 copy_from_user 从用户态拷贝数据,然后根据值操作 GPIO 数据寄存器的 bit3:
- 写入
1:清除 bit3,输出低电平,点亮 LED; - 写入
0:置位 bit3,输出高电平,熄灭 LED。
五、file_operations 与函数指针回调
驱动通过 file_operations 把 led_write 挂到 .write 上:
static struct file_operations fops = {
.owner = THIS_MODULE,
.write = led_write,
};
fops 是一组函数指针的集合,定义了设备对外暴露的操作接口。 用户空间调用 write() 时,VFS 沿着指针找到你的 led_write 并传入参数。这就是 Linux 驱动中回调函数 + 函数指针的核心设计模式。
六、代码改进建议
| 问题 | 建议 |
|---|---|
失败返回 -1 |
改为返回 -EFAULT |
成功返回 0 |
建议返回 n,表示实际写入字节数 |
of_iomap 无检查 |
每次映射后判断返回值是否为 NULL |
| 退出未释放映射 | led_misc_exit 中应对四个地址调用 iounmap |
| 节点引用未释放 | 用完后调用 of_node_put(plednode) |
七、测试方法
insmod led_misc.ko
ls /dev/led_misc
echo -ne "\x01" > /dev/led_misc # 点亮
echo -ne "\x00" > /dev/led_misc # 熄灭
rmmod led_misc
八、总结
这套代码串联了 Linux 驱动开发的几个核心概念:
- misc 框架简化字符设备注册;
- 设备树实现硬件描述与驱动代码解耦;
of_iomap完成物理地址到虚拟地址的安全映射;file_operations通过函数指针建立用户空间与驱动的交互通道;copy_from_user保证内核态与用户态之间的数据安全拷贝。