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>; // 8KB
no-map;
};
bram1_region: buffer@42000000 {
reg = <0x42000000 0x2000>; // 8KB
no-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 0x2000
int 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 0x2000
int 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);
}
// 映射BRAM0
bram0 = mmap(NULL, BRAM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, BRAM0_ADDR);
// 映射BRAM1
bram1 = 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 = 1KB
bram0[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 0x2000
static 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;
// 确定访问哪个BRAM
void __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;
// 确定访问哪个BRAM
void __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 方法提供了最佳的开发效率与功能平衡。
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)