从用户空间到 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 依次完成:
- 将引脚复用为 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 保证内核态与用户态之间的数据安全拷贝。
网硕互联帮助中心





评论前必须登录!
注册