【黑金云课堂】FPGA技术教程Linux开发:系统驱动/UIO驱动
一、Linux驱动概述
驱动本质
Linux 驱动运行在内核空间,作为硬件、操作系统、上层应用的通信桥梁;应用通过 open/read/write 库函数发起系统调用,陷入内核后由驱动操作硬件,区分用户空间、内核空间。

驱动分类
- 字符设备:按字节串行访问(LED、按键等,本教程核心)。
- 块设备:块为单位随机读写(硬盘、SD 卡)。
- 网络设备:专用数据包接口,不使用标准文件读写函数。
二、驱动开发(LED实操案例)
2.1 字符设备驱动开发
整体开发步骤:
查看硬件手册 / 原理图 → 修改设备树 → 编写驱动框架代码 → 编译驱动(Petalinux工程内置 / 独立 Makefile) → 编写上层测试程序 → 开发板运行调试。
核心知识点:
-
file_operations结构体
-
设备号管理
每个设备文件都有主次设备号major,主设备号是唯一的,每个主设备下有次设备号minor,次设备号在这个主设备下也是唯一的。在Linux系统下输入cat /proc/devices命令可以查看已被注册的主设备号。
示例:
int major = 200;
int minor = 0;
dev_t devid = MKDEV(major, minor); //通过主次设备号获取设备号
/* 向内核注册设备号 */
register_chrdev_region(devid, 1, “xxx-dev”);
dev_t devid; //设备号
/* 申请设备号 */
alloc_chrdev_region(&devid, 0, 1, "xxx-dev");
- 字符设备注册流程
① 使用cdev结构体来定义一个字符设备,它定义在include/linux/cdev.h中
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
② cdev结构体变量定义后需要用cdev_init()函数初始化,函数原型为
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
③ 初始化字符结构体变量后,便可以用这个变量向Linux系统注册字符设备。注册函数 cdev_add原型为:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
④ 字符设备的注销函数是 cdev_del 函数,原型为:
void cdev_del(struct cdev *p)
- 驱动模块出入口
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
- 设备节点自动创建
类的创建和删除:
创建设备前需要先创建类,设备是在类下面创建的。类的结构体struct class结构体定义在include/linux/device/class.h中。
使用函数class_create()来创建类,原型为:
struct class * class_create(const char *name);
使用函数class_destroy()来删除类,原型为:
void class_destroy(const struct class *cls)
设备节点的创建和删除:
创建类后,使用device_create()函数在类下面创建设备,原型为:
device_create(const struct class *cls, struct device *parent, dev_t devt,
void *drvdata, const char *fmt, ...);
使用函数device_destroy()来删除设备节点,原型为:
void device_destroy(const struct class *cls, dev_t devt);
2.2 platform驱动框架
驱动分离思想:
Platform 虚拟总线实现硬件资源(SOC 底层)与业务设备(LED / 按键)分离。
- SOC 厂商统一实现底层 GPIO 硬件驱动
- 外设厂商仅编写设备业务逻辑,通过统一接口调用底层资源
- 更换硬件平台仅替换底层 SOC 驱动,上层设备代码无需大幅修改
platform三大核心组件:
-
platform_bus 虚拟总线
内核内置platform_bus_type,提供 match 匹配逻辑,自动绑定同名字的设备与驱动。 -
platform_driver 驱动
核心成员probe(设备匹配成功执行初始化)、remove(卸载释放资源),通过platform_driver_register/unregister完成注册注销。 -
platform_device 设备
描述硬件资源(寄存器地址、引脚),支持手动注册结构体或设备树替代;驱动通过platform_get_resource()读取硬件资源。
三、UIO驱动概述
UIO 核心思想是轻量内核驱动,业务逻辑放在用户态,适合 FPGA 自定义 IP、工业 IO 卡这类只需要访问硬件内存、处理中断的设备;
内核只做基础资源管理,降低内核模块 bug 风险。内核暴露 /dev/uioX 字符设备、/sys/class/uio/uioX sysfs 节点给应用程序。
四、UIO 核心框架 uio_core
核心数据结构uio_info
struct uio_info {
struct uio_device *uio_dev;
const char *name;
const char *version;
struct uio_mem mem[MAX_UIO_MAPS];
struct uio_port port[MAX_UIO_PORT_REGIONS];
long irq;
unsigned long irq_flags;
void *priv;
irqreturn_t (*handler)(int irq, struct uio_info *dev_info);
int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);
int (*open)(struct uio_info *info, struct inode *inode);
int (*release)(struct uio_info *info, struct inode *inode);
int (*irqcontrol)(struct uio_info *info, s32 irq_on);
};
核心数据结构uio_device
struct uio_device {
struct module *owner;
struct devicedev;
int minor;
atomic_t event;
struct fasync_struct *async_queue;
wait_queue_head_t wait;
struct uio_info *info;
struct mutexinfo_lock;
struct kobject *map_dir;
struct kobject *portio_dir;
};
讲解 UIO 设备注册 API __uio_register_device;
内核实现 uio 文件操作集合(open/read/mmap/poll 等);
中断分为顶半部处理回调、底半部线程,中断事件通知用户空间。
五、通用 UIO 设备驱动 uio_pdrv_genirq
内核自带的 platform 通用 UIO 驱动,无需自定义编写内核模块,通过设备树或 platform_data 配置即可使用。
实现了中断 handler、中断控制回调;支持设备树 OF 匹配,通过模块参数 of_id 匹配设备。
六、FPGA 自定义 IP 的 UIO 实操
以 PWM 自定义 IP 为例,给出寄存器地址表;
讲解完整使用流程:
- 开启内核 UIO 配置
- 设置
of_id参数 - 修改设备树设置
compatible = "generic‑uio" - 编写用户态测试程序
- 板卡运行测试,在用户空间操作 FPGA 寄存器、捕获中断
💡 完整视频讲解可前往 ALINX 视频号进行直播回看
更多细节欢迎关注我们黑金云课堂全年免费直播课,
黑金云课堂八月直播日历 我们将在每周二、三、四,同步推进 Verilog开发、Vitis开发、Linux开发三大系列,带你从零开始,稳扎稳打掌握 FPGA 开发全流程!
| 系列 | 内容定位 |
|---|---|
| Verilog开发 | 硬件描述语言基础、逻辑设计、仿真调试 |
| Vitis开发 | Zynq软硬件协同、外设驱动、网络协议栈 |
| Linux开发 | 嵌入式Linux系统移植、驱动编写、应用开发 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)