当前位置: 首页 > news >正文

[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. 实战建议

  1. 快速原型开发:首选 UIO 方法

    • 开发速度快
    • 支持基本功能
    • 调试方便
  2. 性能关键应用

    • 测试阶段:使用 /dev/mem 直接测量极限性能
    • 产品阶段:使用自定义驱动优化性能
  3. 产品级应用

    • 使用自定义驱动
    • 添加适当的访问控制
    • 实现完整的错误处理
    • 添加 IOCTL 接口进行高级控制
  4. 多 BRAM 控制器管理

    • 在自定义驱动中实现统一管理接口
    • 使用设备树配置多个实例
    • 为用户空间提供一致的访问接口
  5. 性能优化技巧

    • 使用大块数据传输代替单字操作
    • 内存对齐访问(4字节对齐)
    • 使用 O_SYNC 标志避免缓存影响
    • 考虑使用 DMA 进行大块数据传输

选择合适的方法取决于项目需求、开发时间和性能要求。对于大多数应用场景,UIO 方法提供了最佳的开发效率与功能平衡。


研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)


http://www.xdnf.cn/news/889435.html

相关文章:

  • MYSQL(三)--服务器启动参数与配置
  • 群晖NAS如何在虚拟机创建飞牛NAS
  • ABP VNext 在 Kubernetes 中的零停机蓝绿发布
  • Abaqus载荷与边界条件(Load BC)
  • 将 Jupyter Notebook 的默认存储路径从 C 盘迁移到 D 盘,可以通过以下步骤实现:
  • 【教学类】20250605立体纸盘(3边形-22边形,角度5、10……40,45)
  • TikTok养号指南:从0到1打造防限流账号的实战策略
  • 【西门子杯工业嵌入式-1-基本环境与空白模板】
  • 瞄准企业级智能体,艺赛旗加速业务出海重塑数智生产力
  • 【LeetCode】1061. 按字典序排列最小的等效字符串(并查集)
  • 如何给windos11 扩大C盘容量
  • CICD实战(二)-----gitlab的安装与配置
  • 热门消息中间件汇总
  • 【计算机网络】五种IO模型——非阻塞IO
  • 使用SSH tunnel访问内网的MySQL
  • 工厂模式 + 模板方法模式 + 策略模式的适用场景
  • 消息的幂等性
  • 【笔记】MSYS2 的 MINGW64 环境 全面工具链
  • 3DEXPERIENCE参考属性和实例属性的获取
  • VUE混合开发用哪个PHP框架好?
  • 三级等保框架下质检 LIMS 系统违规操作溯源技术应用实践
  • 极昆仑智慧与数元灵科技达成战略合作
  • redis配置及优化
  • YOLOv8 × VisDrone 全流程实战:训练你的无人机识别模型 AI(第一部分:数据集准备)
  • 25.6.5学习总结
  • Git操作记录
  • 60天python训练计划----day45
  • 如何开发一个成功的产品
  • OptiStruct结构分析与工程应用:无限元分析指南
  • SpringBoot自动化部署实战技术文章大纲