[zynq] Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解(代码示例)

news2025/6/6 8:36:04

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

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

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

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

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

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

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

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


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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2401448.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【大模型:知识图谱】--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 &#xff08;添加属性&#xff09; 4.2.NULL 值 …

贪心算法应用:装箱问题(BFD算法)详解

贪心算法应用&#xff1a;装箱问题(BFD算法)详解 1. 装箱问题与BFD算法概述 1.1 装箱问题定义 装箱问题(Bin Packing Problem)是组合优化中的经典问题&#xff0c;其定义为&#xff1a; 给定n个物品&#xff0c;每个物品有大小wᵢ (0 < wᵢ ≤ C)无限数量的箱子&#xf…

编程技能:格式化打印05,格式控制符

专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏&#xff0c;故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 &#xff08;一&#xff09;WIn32 专栏导航 上一篇&#xff1a;编程技能&#xff1a;格式化打印04&#xff0c;sprintf 回到目录…

MPLAB X IDE ​软件安装与卸载

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

windows编程实现文件拷贝

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

[6-01-01].第12节:字节码文件内容 - 属性表集合

JVM学习大纲 二、属性表集合&#xff1a; 2.1.属性计数器&#xff1a; 2.2.属性表&#xff1a; 2.3.字节码文件组成5 -> 属性&#xff1a; 1.属性主要指的是类的属性&#xff0c;比如源码的文件名、内部类的列表等 2.4.字节码文件组成3 -> 字段&#xff1a; 1.字段中…

基于机器学习的水量智能调度研究

摘要&#xff1a;随着城市化进程的加速和水资源供需矛盾的日益突出&#xff0c;传统的水量调度模式因缺乏精准预测和动态调控能力&#xff0c;难以满足现代供水系统对高效性、稳定性和节能性的要求。本文针对供水系统中用水峰谷预测不准确、能耗高、供需失衡等核心问题&#xf…

深入浅出 Scrapy:打造高效、强大的 Python 网络爬虫

在数据为王的时代,高效获取网络信息是开发者必备的技能。今天我将为大家介绍 Python 爬虫领域的王者框架——Scrapy。无论你是数据工程师、分析师还是开发者,掌握 Scrapy 都能让你的数据采集效率提升数倍! 项目地址:https://github.com/scrapy/scrapy 官方文档:https://do…

贪心算法应用:带权任务间隔调度问题详解

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

用电脑控制keysight示波器

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

LLaMA-Factory - 批量推理(inference)的脚本

scripts/vllm_infer.py 是 LLaMA-Factory 团队用于批量推理&#xff08;inference&#xff09;的脚本&#xff0c;基于 vLLM 引擎&#xff0c;支持高效的并行推理。它可以对一个数据集批量生成模型输出&#xff0c;并保存为 JSONL 文件&#xff0c;适合大规模评测和自动化测试。…

【Elasticsearch】Elasticsearch 核心技术(二):映射

Elasticsearch 核心技术&#xff08;二&#xff09;&#xff1a;映射 1.什么是映射&#xff08;Mapping&#xff09;1.1 元字段&#xff08;Meta-Fields&#xff09;1.2 数据类型 vs 映射类型1.2.1 数据类型1.2.2 映射类型 2.实际运用案例案例 1&#xff1a;电商产品索引映射案…

【计算机网络】网络层协议

1. ICMP协议的介绍及应用 IP协议的助手 —— ICMP 协议 ping 是基于 ICMP 协议工作的&#xff0c;所以要明白 ping 的工作&#xff0c;首先我们先来熟悉 ICMP 协议。 ICMP 全称是 Internet Control Message Protocol&#xff0c;也就是互联网控制报文协议。 里面有个关键词 …

结构型设计模式之Proxy(代理)

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

案例分享--汽车制动卡钳DIC测量

制动系统是汽车的主要组成部分&#xff0c;是汽车的主要安全部件之一。随着车辆性能的不断提高&#xff0c;车速不断提升&#xff0c;对车辆的制动系统也随之提出了更高要求&#xff0c;因此了解车辆制动系统中每个部件的动态行为成为了制动系统优化的主要途径&#xff0c;同时…

Redis Set集合命令、内部编码及应用场景(详细)

文章目录 前言普通命令SADDSMEMBERSSISMEMBERSCARDSPOPSMOVESREM 集合间操作SINTERSINTERSTORESUNIONSUNIONSTORESDIFFSDIFFSTORE 命令小结内部编码使用场景 前言 集合类型也是保存多个字符串类型的元素的&#xff0c;但和列表类型不同的是&#xff0c;集合中 1&#xff09;元…

C++算法动态规划1

DP定义&#xff1a; 动态规划是分治思想的延申&#xff0c;通俗一点来说就是大事化小&#xff0c;小事化无的艺术。 在将大问题化解为小问题的分治过程中&#xff0c;保存对这些小问题已经处理好的结果&#xff0c;并供后面处理更大规模的问题时直接使用这些结果。 动态规划具…

KaiwuDB在边缘计算领域的应用与优势

KaiwuDB 在边缘计算场景中主要应用于 工业物联网&#xff08;IIoT&#xff09;、智能电网、车联网 等领域&#xff0c;通过其分布式多模架构和轻量化设计&#xff0c;在边缘侧承担 数据实时处理、本地存储与协同分析 的核心作用。以下是具体案例和功能解析&#xff1a; 1. 典型…

鸿蒙开发List滑动每项标题切换悬停

鸿蒙开发List滑动每项标题切换悬停 鸿蒙List滑动每项标题切换悬停&#xff0c;功能也很常见 一、效果图&#xff1a; 二、思路&#xff1a; ListItemGroup({ header: this.itemHead(secondClassify, index) }) 三、关键代码&#xff1a; build() {Column() {List() {ListIt…

ubuntu开机自动挂载windows下的硬盘

我是ubuntu和windows的双系统开发&#xff0c;在ubuntu下如果想要访问windows的硬盘&#xff0c;需要手动点击硬盘进行挂载&#xff0c;这个硬盘我每次编译完都会使用&#xff0c;所以用下面的步骤简化操作&#xff0c;让系统每次开机后自动挂载。 第一步. 确定硬盘的设备标识…