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

【ZYNQ Linux开发】BRAM的几种驱动方式

1 Vivado配置

​       BRAM 的使用方法为使用 AXI BRAM 控制器来控制 BRAM 生成器,Block Design 连接如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

       我这里配置的是真双端口 RAM,通过 PL 的逻辑对 BRAM 生成器的端口 B 进行写操作,在 PS 端对端口 A 进行读。

       BRAM 控制器和生成器的 IP 核配置如下:

​ 此外,我们也需要关注一下 BRAM的地址分配:

       这里的基地址和范围都可以根据自己需求修改,我这里对 BRAM 的需求不大,只用到了10个单元(10*32bit),用 4K 字节就完全足够了。

       Vivado 的相关配置到这就结束了,后续就只要按照流程生成比特流,导出 .xsa 文件即可。

2 Linux访问方式

2.1 通过 /dev/mem 访问

​       在Linux系统中,/dev/mem 是一个特殊的虚拟字符设备,它提供了用户态对内核态物理地址空间的直接访问。这个设备通常与内存映射(mmap)函数结合使用,允许用户空间程序直接读写内核物理内存,而无需经过内核空间

       我们可以采用命令行命令与用户空间程序两种方式来实现对 BRAM 的访问。

2.1.1 命令行命令

​       首先是命令行命令,devmem 是一个在 Linux 系统中用于直接访问物理内存的工具,它可以读取和写入指定物理地址处的内容,命令格式如下:

devmem <address> [64|32|16|8] [value]

       其中,address即要访问的物理地址,[64|32|16|8]是一个可选参数,为访问的位宽,如果不设置,则默认按32位访问,value也是个可选参数,如果设置了,则表示向物理内存对应地址写入相应的值;不设置,则表示向物理内存对应位置读出数据

       对于我的程序逻辑而言,直接在 PL 侧逻辑完成写后,读取即可:

devmem 0x82000008 32

       读取结果符合预期:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1.2 用户空间程序

#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>#define XPAR_BRAM_BASEADDR 0x82000000
#define DATA_LEN    1024int main()
{unsigned int *map_base;int fd = open("/dev/mem", O_RDWR | O_SYNC);if (fd < 0) {printf("can not open /dev/mem \n");return (-1);}map_base = mmap(NULL, DATA_LEN * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, XPAR_BRAM_BASEADDR);if (map_base == 0) {printf("NULL pointer\n");}else {printf("mmap successful\n");}uint32_t read_data;// 验证BRAM内容for (int i = 0; i < 5; i++) {lseek(fd, i * sizeof(uint32_t), SEEK_SET);read(fd, &read_data, sizeof(read_data));printf("BRAM1[%d] = 0x%08X\n", i, read_data);}close(fd);munmap(map_base, DATA_LEN);return 0;
}

​       前面提到,我们可以在Block Design 中的地址编辑中修改地址,它的最小单位就是4K,其它选项都是4K的整数倍,其实是有说法的,就像用户空间程序这里地址空间映射为4K字节,这是因为内存映射通常需要按页对齐(如 4KB,即 0x1000),就算映射的长度实际小于4K,也会向上对齐。

​       验证 BRAM 内容由于我涉及的只有 5 个单元,所以只查看五个单元的值。

1,特别注意 mmap 之后,一定要对应有 munmap,否则会导致内存泄露

2,munmapclose(fd) 在功能上没有严格的依赖顺序。munmap 负责解除内存映射,但不会关闭文件描述符。close(fd) 关闭文件描述符,但不会自动解除映射(已映射的内存仍可访问,但可能导致未定义行为)。

​       测试结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.2 编写字符驱动

2.2.1 设备树

       设备树只需要使用 Vitis 用 Xilinx 提供的模版生成或者使用 Petalinux 生成的设备树 pl.dtsi 即可,如果不知道怎么获取,可以参考我的另一篇博客:【ZYNQ Linux移植】2-获取设备树。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​       这里需要关注的记录的是 axi_bram_ctrl_0 节点的 compatible 属性,因为我们后面使用 platform 设备驱动框架,需要用这个属性进行匹配,大家如果 Vivado 版本不一样,这里的版本会有差别,根据自己的情况进行修改。

2.2.2 驱动程序

       按照 platform 设备驱动框架编写驱动:

#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 0x1000static void __iomem *bram0_base;
static struct class *bram_class;
static struct device *dev_bram;
static struct cdev *cdev;
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;if (copy_to_user(buf, bram0_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;if (copy_from_user(bram0_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);    // 创建设备号if (alloc_chrdev_region(&dev_num, 0, 1, DRIVER_NAME) < 0)return -ENODEV;// 创建设备cdev = cdev_alloc();if (!cdev) goto error;cdev_init(cdev, &bram_fops);if (cdev_add(cdev, MKDEV(MAJOR(dev_num), 0), 1) < 0) {kobject_put(&cdev->kobj);goto error;}dev_bram = device_create(bram_class, NULL, MKDEV(MAJOR(dev_num), 0), NULL, "bram0");if (IS_ERR(dev_bram)) goto error;return 0;error:unregister_chrdev_region(dev_num, 1);return -ENODEV;
}static int bram_remove(struct platform_device *pdev) {device_destroy(bram_class, MKDEV(MAJOR(dev_num), 0));unregister_chrdev_region(dev_num, 1);return 0;
}static const struct of_device_id bram_of_ids[] = {{ .compatible = "xlnx,axi-bram-ctrl-4.1" },{ }
};
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("Huaijun Chen");
MODULE_DESCRIPTION("Custom AXI BRAM Controller Driver");

​       驱动实际上也对内存进行了映射:devm_ioremap_resource(&pdev->dev, res)。但总体来说,使用驱动是在内核态进行操作,会比 2.1 中的映射方式安全一些,但调试难度上也更大。

2.2.3 测试应用程序

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>int main() {int fd = open("/dev/bram0", O_RDWR);if (fd < 0) {perror("open failed");return 1;}    uint32_t read_data;// 验证BRAM内容for (int i = 0; i < 5; i++) {lseek(fd, i * sizeof(uint32_t), SEEK_SET);read(fd, &read_data, sizeof(read_data));printf("BRAM[%d] = 0x%08X\n", i, read_data);}close(fd);return 0;
}

​       实际测试结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​       与 2.1 中测试结果完全相同。

2.3 使用 UIO

​       UIO(Userspace I/O,用户空间 I/O),泛指 UIO 设备和 UIO 驱动。UIO 内核驱动指负责将中断和设备内存暴露给用户空间,再由 UIO 用户态驱动来实现具体的业务

2.3.1 设备树

       与 2.2 中不同,由于需要使用 UIO(Userspace I/O,用户空间 I/O),所以需要在 Xilinx 生成的设备树的基础上进行一定的添加:

/ {uio@40000000 {compatible = "generic-uio";reg = <0x82000000 0x1000>;status = "okay";};
};

       这里需要注意 compatible 不可随便修改,因为这需要与 uio 驱动进行匹配,reg 中放我们的 BRAM 起始地址与大小(按32bit算)

       这里补充说明,修改了设备树启动后,Linux 系统会自动加载相关的驱动,并在 /dev 目录下可以看到 uio 设备的设备节点信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​       而在 /sys/class/uio 目录下可以查看具体 uio 的信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.3.2 测试应用程序

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>#define UIO_DEV "/dev/uio"
#define BRAM_SIZE 0x1000int main() {int fd;volatile uint32_t *bram;// 打开UIO设备if ((fd = open(UIO_DEV, O_RDWR)) < 0) {perror("open uio0 failed");exit(EXIT_FAILURE);}// 内存映射bram = mmap(NULL, BRAM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (bram == MAP_FAILED) {perror("mmap failed");close(fd);exit(EXIT_FAILURE);}uint32_t read_data;// 验证BRAM内容for (int i = 0; i < 5; i++) {lseek(fd, i * sizeof(uint32_t), SEEK_SET);read(fd, &read_data, sizeof(read_data));printf("BRAM[%d] = 0x%08X\n", i, read_data);}munmap((void*)bram, BRAM_SIZE);close(fd);return 0;
}

​       实际测试结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​       与前面的测试结果完全相同。

3 总结

       本文介绍了 ZYNQ Linux 下 BRAM 的三种驱动方式,分别是 /dev/mem/ 访问,编写字符驱动以及 UIO 三种方式。

       就安全性而言,字符驱动的安全性最高(当然也不能自己乱搞),/dev/mem/ 访问和UIO处于用户空间,安全性较差。

       就中断的支持方面来讲,通过 /dev/mem/ 访问是不支持中断的,其它两种均可支持中断。

       就开发难易程度而言,通过 /dev/mem/ 访问与 UIO 都非常简单,如果追求开发速度,可以先用这两种方法实现,避免拖慢整体开发速度,后续再使用字符设备驱动。

http://www.dtcms.com/a/270121.html

相关文章:

  • 微服务集成snail-job分布式定时任务系统实践
  • Mac安装Docker(使用orbstack代替)
  • 单机分布式一体化数据库的架构设计与优化
  • 一个猜想不等式的推广
  • 业务分析技术实践篇
  • kafka集群安装
  • 让事情变得更好
  • Shader面试题100道之(21-40)
  • 光流 | RAFT光流算法如何改进提升
  • 【适合 Java 工程师的 AI 转型方向】
  • 基于PHP/MySQL的企业培训考试系统源码,高并发、稳定运行,源码开源可二开
  • Java中的生产消费模型解析
  • Distance Information Improves Heterogeneous Graph Neural Networks
  • 质量小议56 - 说教
  • [ESP32]VSCODE+ESP-IDF环境搭建及blink例程尝试(win10 win11均配置成功)
  • vscode打开stm32CubeIDE的项目的注释问题
  • 从分层训练到一步生成:Kaiming He 的生成模型进化之路—CVPR2025演讲小结
  • 网络--初级
  • springboot单体项目的发布生产优化
  • DMA(直接内存访问)是什么?
  • 第2章,[标签 Win32] :匈牙利标记法
  • 13届蓝桥杯省赛程序设计试题
  • 字符串大小比较的方式|函数的多返回值
  • 作业03-SparkSQL开发
  • 数字化校园升级:传统网络架构与SD-WAN智能方案对比详解
  • 汽车功能安全-软件单元验证 (Software Unit Verification)【定义、目的、要求建议】6
  • 【数据分析】基于 HRS 数据的多变量相关性分析与可视化
  • uniapp b树
  • C++笔记之使用bitset对uint32_t类型变量对位状态判断
  • 2025年深圳杉川机器人性格测评和Verify测评SHL题库高分攻略