[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 方法提供了最佳的开发效率与功能平衡。
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)