[zynq] Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解(代码示例)
Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解
文章目录
- Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解
- 1. UIO (Userspace I/O) 驱动方法
- 完整示例代码
- 2. /dev/mem 直接内存映射方法
- 完整示例代码
- 3. 自定义字符设备驱动方法
- 完整示例代码
- 4. 方法对比总结
- 5. 实战建议
在 Zynq Linux 环境下,AXI BRAM 控制器主要有三种驱动和使用方法,每种方法都有其优缺点和适用场景:
1. UIO (Userspace I/O) 驱动方法
原理:通过内核提供的 UIO 框架将设备映射到用户空间
优点:
- 开发简单快速,无需编写内核驱动
- 支持中断处理
- 用户空间直接控制硬件
- 系统稳定性高(驱动崩溃不会导致内核崩溃)
缺点:
- 性能略低于内核驱动
- 需要手动管理内存映射
- 安全性较低(用户空间程序有直接硬件访问权)
完整示例代码
设备树配置 (system-user.dtsi
):
/ {reserved-memory {#address-cells = <1>;#size-cells = <1>;ranges;bram0_region: buffer@40000000 {reg = <0x40000000 0x2000>; // 8KBno-map;};bram1_region: buffer@42000000 {reg = <0x42000000 0x2000>; // 8KBno-map;};};uio@40000000 {compatible = "generic-uio";reg = <0x40000000 0x2000>;status = "okay";};uio@42000000 {compatible = "generic-uio";reg = <0x42000000 0x2000>;status = "okay";};
};
用户空间程序 (uio_bram_example.c
):
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>#define UIO_DEV0 "/dev/uio0"
#define UIO_DEV1 "/dev/uio1"
#define BRAM_SIZE 0x2000int main() {int fd0, fd1;volatile uint32_t *bram0, *bram1;// 打开UIO设备if ((fd0 = open(UIO_DEV0, O_RDWR)) < 0) {perror("open uio0 failed");exit(EXIT_FAILURE);}if ((fd1 = open(UIO_DEV1, O_RDWR)) < 0) {perror("open uio1 failed");close(fd0);exit(EXIT_FAILURE);}// 内存映射bram0 = mmap(NULL, BRAM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd0, 0);bram1 = mmap(NULL, BRAM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);if (bram0 == MAP_FAILED || bram1 == MAP_FAILED) {perror("mmap failed");close(fd0);close(fd1);exit(EXIT_FAILURE);}// BRAM读写测试printf("Writing to BRAM0 at address 0...\n");bram0[0] = 0xDEADBEEF;printf("BRAM0[0] = 0x%08X\n", bram0[0]);printf("Writing to BRAM1 at offset 0x100...\n");bram1[0x100/4] = 0xCAFEBABE;printf("BRAM1[0x100] = 0x%08X\n", bram1[0x100/4]);// 数据交换uint32_t temp = bram0[0];bram0[0] = bram1[0x100/4];bram1[0x100/4] = temp;printf("After swap:\n");printf("BRAM0[0] = 0x%08X\n", bram0[0]);printf("BRAM1[0x100] = 0x%08X\n", bram1[0x100/4]);// 清理munmap((void*)bram0, BRAM_SIZE);munmap((void*)bram1, BRAM_SIZE);close(fd0);close(fd1);return 0;
}
编译命令:
arm-linux-gnueabihf-gcc -o uio_bram_example uio_bram_example.c
2. /dev/mem 直接内存映射方法
原理:直接通过 /dev/mem
设备文件映射物理内存
优点:
- 无需设备树特殊配置
- 访问速度最快
- 最接近硬件的访问方式
缺点:
- 需要 root 权限
- 存在安全风险(直接访问物理内存)
- 不支持中断
- 可能与其他驱动冲突
完整示例代码
设备树配置:
只需在 reserved-memory
中保留地址空间(同 UIO 方法)
C 程序 (mem_bram_example.c
):
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>#define MEM_DEV "/dev/mem"
#define BRAM0_ADDR 0x40000000
#define BRAM1_ADDR 0x42000000
#define BRAM_SIZE 0x2000int main() {int fd;volatile uint32_t *bram0, *bram1;// 打开内存设备if ((fd = open(MEM_DEV, O_RDWR | O_SYNC)) < 0) {perror("open /dev/mem failed");exit(EXIT_FAILURE);}// 映射BRAM0bram0 = mmap(NULL, BRAM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, BRAM0_ADDR);// 映射BRAM1bram1 = mmap(NULL, BRAM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, BRAM1_ADDR);if (bram0 == MAP_FAILED || bram1 == MAP_FAILED) {perror("mmap failed");close(fd);exit(EXIT_FAILURE);}// 性能测试(写入1KB数据)printf("Starting performance test...\n");for (int i = 0; i < 256; i++) { // 256 * 4 bytes = 1KBbram0[i] = i;}// 验证数据int errors = 0;for (int i = 0; i < 256; i++) {if (bram0[i] != i) {errors++;printf("Error at %d: expected 0x%08X, got 0x%08X\n", i, i, bram0[i]);}}printf("Performance test completed with %d errors\n", errors);// 清理munmap((void*)bram0, BRAM_SIZE);munmap((void*)bram1, BRAM_SIZE);close(fd);return 0;
}
编译命令:
arm-linux-gnueabihf-gcc -O2 -o mem_bram_example mem_bram_example.c
3. 自定义字符设备驱动方法
原理:编写内核模块创建字符设备供用户空间访问
优点:
- 性能优异
- 可添加高级功能(如IOCTL控制、中断处理)
- 安全性高(可添加访问控制)
- 提供标准设备接口
缺点:
- 开发复杂,需要内核编程知识
- 调试困难
- 驱动错误可能导致系统崩溃
完整示例代码
内核驱动 (bram_driver.c
):
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of.h>#define DRIVER_NAME "axi_bram"
#define BRAM_SIZE 0x2000static void __iomem *bram0_base;
static void __iomem *bram1_base;
static struct class *bram_class;
static struct device *bram_device;
static dev_t dev_num;static int bram_open(struct inode *inode, struct file *file) {return 0;
}static int bram_release(struct inode *inode, struct file *file) {return 0;
}static ssize_t bram_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {if (*ppos >= BRAM_SIZE) return 0;if (*ppos + count > BRAM_SIZE)count = BRAM_SIZE - *ppos;// 确定访问哪个BRAMvoid __iomem *base = (minor(file_inode(file)->i_rdev) ? bram1_base : bram0_base;if (copy_to_user(buf, base + *ppos, count))return -EFAULT;*ppos += count;return count;
}static ssize_t bram_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {if (*ppos >= BRAM_SIZE) return -ENOSPC;if (*ppos + count > BRAM_SIZE)count = BRAM_SIZE - *ppos;// 确定访问哪个BRAMvoid __iomem *base = (minor(file_inode(file)->i_rdev) ? bram1_base : bram0_base;if (copy_from_user(base + *ppos, buf, count))return -EFAULT;*ppos += count;return count;
}static struct file_operations bram_fops = {.owner = THIS_MODULE,.open = bram_open,.release = bram_release,.read = bram_read,.write = bram_write,
};static int bram_probe(struct platform_device *pdev) {struct resource *res;// 获取BRAM0资源res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res) return -ENODEV;bram0_base = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(bram0_base)) return PTR_ERR(bram0_base);// 获取BRAM1资源res = platform_get_resource(pdev, IORESOURCE_MEM, 1);if (!res) return -ENODEV;bram1_base = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(bram1_base)) return PTR_ERR(bram1_base);// 创建设备号if (alloc_chrdev_region(&dev_num, 0, 2, DRIVER_NAME) < 0)return -ENODEV;// 创建两个设备 (bram0 和 bram1)for (int i = 0; i < 2; i++) {struct device *dev;struct cdev *cdev = cdev_alloc();if (!cdev) goto error;cdev_init(cdev, &bram_fops);if (cdev_add(cdev, MKDEV(MAJOR(dev_num), i), 1) < 0) {kobject_put(&cdev->kobj);goto error;}dev = device_create(bram_class, NULL, MKDEV(MAJOR(dev_num), i), NULL, "bram%d", i);if (IS_ERR(dev)) goto error;}return 0;error:unregister_chrdev_region(dev_num, 2);return -ENODEV;
}static int bram_remove(struct platform_device *pdev) {device_destroy(bram_class, MKDEV(MAJOR(dev_num), 0));device_destroy(bram_class, MKDEV(MAJOR(dev_num), 1));unregister_chrdev_region(dev_num, 2);return 0;
}static const struct of_device_id bram_of_ids[] = {{ .compatible = "xlnx,axi-bram-ctrl-4.0" },{ }
};
MODULE_DEVICE_TABLE(of, bram_of_ids);static struct platform_driver bram_driver = {.driver = {.name = DRIVER_NAME,.of_match_table = bram_of_ids,},.probe = bram_probe,.remove = bram_remove,
};static int __init bram_init(void) {// 创建设备类bram_class = class_create(THIS_MODULE, "bram");if (IS_ERR(bram_class)) return PTR_ERR(bram_class);return platform_driver_register(&bram_driver);
}static void __exit bram_exit(void) {platform_driver_unregister(&bram_driver);class_destroy(bram_class);
}module_init(bram_init);
module_exit(bram_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Custom AXI BRAM Controller Driver");
设备树配置:
axi_bram_ctrl_0: axi_bram_ctrl@40000000 {compatible = "xlnx,axi-bram-ctrl-4.0";reg = <0x40000000 0x2000>;
};axi_bram_ctrl_1: axi_bram_ctrl@42000000 {compatible = "xlnx,axi-bram-ctrl-4.0";reg = <0x42000000 0x2000>;
};
用户空间程序 (char_bram_example.c
):
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>int main() {int fd0 = open("/dev/bram0", O_RDWR);int fd1 = open("/dev/bram1", O_RDWR);if (fd0 < 0 || fd1 < 0) {perror("open failed");return 1;}// 通过标准文件接口访问uint32_t data = 0x12345678;write(fd0, &data, sizeof(data));uint32_t read_data;lseek(fd0, 0, SEEK_SET);read(fd0, &read_data, sizeof(read_data));printf("BRAM0[0] = 0x%08X\n", read_data);// 在BRAM1中写入模式数据for (int i = 0; i < 10; i++) {uint32_t pattern = 0xAA000000 | i;lseek(fd1, i * sizeof(uint32_t), SEEK_SET);write(fd1, &pattern, sizeof(pattern));}// 验证BRAM1内容for (int i = 0; i < 10; i++) {lseek(fd1, i * sizeof(uint32_t), SEEK_SET);read(fd1, &read_data, sizeof(read_data));printf("BRAM1[%d] = 0x%08X\n", i, read_data);}close(fd0);close(fd1);return 0;
}
编译命令:
# 内核模块
make -C <KDIR> M=$PWD modules# 用户程序
arm-linux-gnueabihf-gcc -o char_bram_example char_bram_example.c
4. 方法对比总结
特性 | UIO 方法 | /dev/mem 方法 | 自定义驱动方法 |
---|---|---|---|
开发复杂度 | 低 (纯用户空间) | 低 (纯用户空间) | 高 (需要内核开发) |
性能 | 中 | 高 | 高 |
安全性 | 低 | 极低 | 高 (可添加访问控制) |
中断支持 | 是 | 否 | 是 |
系统稳定性影响 | 低 (用户空间崩溃) | 中 (可能破坏系统) | 中 (内核模块崩溃) |
是否需要 root | 是 (设备节点访问) | 是 (/dev/mem 访问) | 是 (设备节点访问) |
适用场景 | 快速原型开发、简单应用 | 性能测试、底层调试 | 产品级应用、复杂控制逻辑 |
设备树配置 | 需要 | 需要 (保留内存) | 需要 |
多设备支持 | 良好 | 良好 | 优秀 |
5. 实战建议
-
快速原型开发:首选 UIO 方法
- 开发速度快
- 支持基本功能
- 调试方便
-
性能关键应用:
- 测试阶段:使用 /dev/mem 直接测量极限性能
- 产品阶段:使用自定义驱动优化性能
-
产品级应用:
- 使用自定义驱动
- 添加适当的访问控制
- 实现完整的错误处理
- 添加 IOCTL 接口进行高级控制
-
多 BRAM 控制器管理:
- 在自定义驱动中实现统一管理接口
- 使用设备树配置多个实例
- 为用户空间提供一致的访问接口
-
性能优化技巧:
- 使用大块数据传输代替单字操作
- 内存对齐访问(4字节对齐)
- 使用
O_SYNC
标志避免缓存影响 - 考虑使用 DMA 进行大块数据传输
选择合适的方法取决于项目需求、开发时间和性能要求。对于大多数应用场景,UIO 方法提供了最佳的开发效率与功能平衡。
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)
相关文章:
[zynq] Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解(代码示例)
Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解 文章目录 Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解1. UIO (Userspace I/O) 驱动方法完整示例代码 2. /dev/mem 直接内存映射方法完整示例代码 3. 自定义字符设备驱动方法完整示例代码 4. 方法对比总结5. 实战建议 在 Zyn…...

【大模型:知识图谱】--5.neo4j数据库管理(cypher语法2)
目录 1.节点语法 1.1.CREATE--创建节点 1.2.MATCH--查询节点 1.3.RETURN--返回节点 1.4.WHERE--过滤节点 2.关系语法 2.1.创建关系 2.2.查询关系 3.删除语法 3.1.DELETE 删除 3.2.REMOVE 删除 4.功能补充 4.1.SET (添加属性) 4.2.NULL 值 …...
六、数据库的安全性
六、数据库的安全性 数据库的安全问题 数据库中的数据是可以共享的数据共享必然带来数据库的安全性问题 数据库系统中的数据共享不能是无条件的共享数据库中数据的共享是在 DBMS 统一的严格控制之下的共享,即:只允许有合法使用权限的用户访其被授权的数…...

贪心算法应用:装箱问题(BFD算法)详解
贪心算法应用:装箱问题(BFD算法)详解 1. 装箱问题与BFD算法概述 1.1 装箱问题定义 装箱问题(Bin Packing Problem)是组合优化中的经典问题,其定义为: 给定n个物品,每个物品有大小wᵢ (0 < wᵢ ≤ C)无限数量的箱子…...
C#学习第27天:时间和日期的处理
时间和日期的核心概念 1. UTC 和 本地时间 UTC(Coordinated Universal Time): 是一种不受时区影响的世界标准时间。在网络通信和全球协作中,用于统一时间度量 本地时间(Local Time): 是根据所…...

编程技能:格式化打印05,格式控制符
专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏,故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 (一)WIn32 专栏导航 上一篇:编程技能:格式化打印04,sprintf 回到目录…...

MPLAB X IDE 软件安装与卸载
1、下载MPLAB X IDE V6.25 MPLAB X IDE | Microchip Technology 正常选Windows,点击Download,等待自动下载完成; MPLAB X IDE 一台电脑上可以安装多个版本; 2、安装MPLAB X IDE V6.25 右键以管理员运行;next; 勾选 I a…...

windows编程实现文件拷贝
项目源码链接: 实现文件拷贝功能(限制5GB大小) 81c57de 周不才/cpp_linux study - Gitee.com 知识准备: 1.句柄 句柄是一个用于标识和引用系统资源(如文件、窗口、进程、线程、位图等)的值。它不是资…...

[6-01-01].第12节:字节码文件内容 - 属性表集合
JVM学习大纲 二、属性表集合: 2.1.属性计数器: 2.2.属性表: 2.3.字节码文件组成5 -> 属性: 1.属性主要指的是类的属性,比如源码的文件名、内部类的列表等 2.4.字节码文件组成3 -> 字段: 1.字段中…...

基于机器学习的水量智能调度研究
摘要:随着城市化进程的加速和水资源供需矛盾的日益突出,传统的水量调度模式因缺乏精准预测和动态调控能力,难以满足现代供水系统对高效性、稳定性和节能性的要求。本文针对供水系统中用水峰谷预测不准确、能耗高、供需失衡等核心问题…...
深度解码:我如何用“结构进化型交互学习方法”与AI共舞,从学习小白到构建复杂认知体系
嗨,亲爱的学习者们,思考者们,以及所有渴望在知识海洋中自由翱行却时常感到迷茫的朋友们: 你是否也曾有过这样的深夜,面对堆积如山的学习资料,眼神迷离,内心却一片荒芜?明明每个字都…...

深入浅出 Scrapy:打造高效、强大的 Python 网络爬虫
在数据为王的时代,高效获取网络信息是开发者必备的技能。今天我将为大家介绍 Python 爬虫领域的王者框架——Scrapy。无论你是数据工程师、分析师还是开发者,掌握 Scrapy 都能让你的数据采集效率提升数倍! 项目地址:https://github.com/scrapy/scrapy 官方文档:https://do…...
ES6 Promise 状态机
状态机:抽象的计算模型,根据特定的条件或者信号切换不同的状态 一、Promise 是什么? 简单来说,Promise 就是一个“承诺对象”。在ES6 里,有些代码执行起来需要点时间,比如加载文件、等待网络请求或者设置…...
Axure 与 Cursor 集成实现方案
Axure 与 Cursor 集成实现方案 以下是一个完整的 Axure 与 Cursor AI 集成的原型实现方案,通过自定义 JavaScript 代码实现无缝对接: 一、整体架构设计 #mermaid-svg-f9hQDSN4hijU3mJY {font-family:"trebuchet ms",verdana,arial,sans-seri…...
汽车加气站操作工证考试重点
汽车加气站操作工证考试重点 一、汽车加气站操作工证考试主要内容 汽车加气站操作工证是从事CNG(压缩天然气)和LNG(液化天然气)加气站作业人员的必备资格证书。随着新能源汽车的快速发展,该证书的市场需求持续增长&a…...

贪心算法应用:带权任务间隔调度问题详解
贪心算法应用:带权任务间隔调度问题详解 贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。带权任务间隔调度问题是贪心算法的一个经典应用场景。 问题定义…...

用电脑控制keysight示波器
KEYSIGHT示波器HD304MSO性能 亮点: 体验 200 MHz 至 1 GHz 的带宽和 4 个模拟通道。与 12 位 ADC 相比,使用 14 位模数转换器 (ADC) 将垂直分辨率提高四倍。使用 10.1 英寸电容式触摸屏轻松查看和分析您的信号。捕获 50 μVRMS …...

LLaMA-Factory - 批量推理(inference)的脚本
scripts/vllm_infer.py 是 LLaMA-Factory 团队用于批量推理(inference)的脚本,基于 vLLM 引擎,支持高效的并行推理。它可以对一个数据集批量生成模型输出,并保存为 JSONL 文件,适合大规模评测和自动化测试。…...
React从基础入门到高级实战:React 高级主题 - 测试进阶:从单元测试到端到端测试的全面指南
React 测试进阶:从单元测试到端到端测试的全面指南 引言 在2025年的React开发环境中,测试不仅是代码质量的保障,更是提升开发效率和用户体验的关键支柱。随着React应用的复杂性不断增加,高级测试技术——如端到端(E2…...
Ansible 剧本精粹 - 编写你的第一个 Playbook
Ansible 剧本精粹 - 编写你的第一个 Playbook 如果说 Ansible Ad-Hoc 命令像是你对厨房里的助手发出的零散口头指令(“切个洋葱”、“烧开水”),那么 Playbook 就是一份完整、详细、写在纸上的菜谱。它列明了所有需要的“食材”(变量),详细的“烹饪步骤”(任务),甚至还…...

【Elasticsearch】Elasticsearch 核心技术(二):映射
Elasticsearch 核心技术(二):映射 1.什么是映射(Mapping)1.1 元字段(Meta-Fields)1.2 数据类型 vs 映射类型1.2.1 数据类型1.2.2 映射类型 2.实际运用案例案例 1:电商产品索引映射案…...

【计算机网络】网络层协议
1. ICMP协议的介绍及应用 IP协议的助手 —— ICMP 协议 ping 是基于 ICMP 协议工作的,所以要明白 ping 的工作,首先我们先来熟悉 ICMP 协议。 ICMP 全称是 Internet Control Message Protocol,也就是互联网控制报文协议。 里面有个关键词 …...
.NET Core接口IServiceProvider
.NET Core 接口 IServiceProvider 深度剖析 在 .NET Core 和 .NET 5 的世界里,依赖注入(Dependency Injection,简称 DI)是构建可维护、可测试应用程序的关键技术。而 IServiceProvider 接口,正是依赖注入机制中的核心…...

结构型设计模式之Proxy(代理)
结构型设计模式之Proxy(代理) 前言: 代理模式,aop环绕通知,动态代理,静态代理 都是代理的一种,这次主要是记录设计模式的代理demo案例,详情请看其他笔记。 1)意图 为其…...

案例分享--汽车制动卡钳DIC测量
制动系统是汽车的主要组成部分,是汽车的主要安全部件之一。随着车辆性能的不断提高,车速不断提升,对车辆的制动系统也随之提出了更高要求,因此了解车辆制动系统中每个部件的动态行为成为了制动系统优化的主要途径,同时…...

Redis Set集合命令、内部编码及应用场景(详细)
文章目录 前言普通命令SADDSMEMBERSSISMEMBERSCARDSPOPSMOVESREM 集合间操作SINTERSINTERSTORESUNIONSUNIONSTORESDIFFSDIFFSTORE 命令小结内部编码使用场景 前言 集合类型也是保存多个字符串类型的元素的,但和列表类型不同的是,集合中 1)元…...

C++算法动态规划1
DP定义: 动态规划是分治思想的延申,通俗一点来说就是大事化小,小事化无的艺术。 在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。 动态规划具…...
【快速预览经典深度学习模型:CNN、RNN、LSTM、Transformer、ViT全解析!】
🚀快速预览经典深度学习模型:CNN、RNN、LSTM、Transformer、ViT全解析! 📌你是否还在被深度学习模型名词搞混?本文带你用最短时间掌握五大经典模型的核心概念和应用场景,助你打通NLP与CV的任督二脉…...

KaiwuDB在边缘计算领域的应用与优势
KaiwuDB 在边缘计算场景中主要应用于 工业物联网(IIoT)、智能电网、车联网 等领域,通过其分布式多模架构和轻量化设计,在边缘侧承担 数据实时处理、本地存储与协同分析 的核心作用。以下是具体案例和功能解析: 1. 典型…...
如何避免二极管过载?
如何避免二极管过载? 二极管作为电路中的基础元件,其过载可能导致性能下降甚至烧毁。以下从选型、安装、保护设计及散热四方面提供实用解决二极管过载方案: 精准选型匹配需求 根据电路特性选择二极管类型:高频电路优先选用肖特基…...