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

Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解

文章目录

    • [Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解](#Zynq Linux 环境下 AXI BRAM 控制器驱动方法详解)
      • [1. UIO (Userspace I/O) 驱动方法](#1. UIO (Userspace I/O) 驱动方法)
      • [2. /dev/mem 直接内存映射方法](#2. /dev/mem 直接内存映射方法)
      • [3. 自定义字符设备驱动方法](#3. 自定义字符设备驱动方法)
      • [4. 方法对比总结](#4. 方法对比总结)
      • [5. 实战建议](#5. 实战建议)

在 Zynq Linux 环境下,AXI BRAM 控制器主要有三种驱动和使用方法,每种方法都有其优缺点和适用场景:


1. UIO (Userspace I/O) 驱动方法

原理:通过内核提供的 UIO 框架将设备映射到用户空间

优点

  • 开发简单快速,无需编写内核驱动
  • 支持中断处理
  • 用户空间直接控制硬件
  • 系统稳定性高(驱动崩溃不会导致内核崩溃)

缺点

  • 性能略低于内核驱动
  • 需要手动管理内存映射
  • 安全性较低(用户空间程序有直接硬件访问权)
完整示例代码

设备树配置 (system-user.dtsi):

dts 复制代码
/ {
    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):

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;
}

编译命令:

bash 复制代码
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):

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;
}

编译命令:

bash 复制代码
arm-linux-gnueabihf-gcc -O2 -o mem_bram_example mem_bram_example.c

3. 自定义字符设备驱动方法

原理:编写内核模块创建字符设备供用户空间访问

优点

  • 性能优异
  • 可添加高级功能(如IOCTL控制、中断处理)
  • 安全性高(可添加访问控制)
  • 提供标准设备接口

缺点

  • 开发复杂,需要内核编程知识
  • 调试困难
  • 驱动错误可能导致系统崩溃
完整示例代码

内核驱动 (bram_driver.c):

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");

设备树配置:

dts 复制代码
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):

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;
}

编译命令:

bash 复制代码
# 内核模块
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 方法提供了最佳的开发效率与功能平衡。


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


相关推荐
资讯第一线6 分钟前
Windows系统工具:WinToolsPlus 之 SQL Server Suspect/质疑/置疑/可疑/单用户等 修复
运维
惊起白鸽45040 分钟前
LVS负载均衡
运维·负载均衡·lvs
Sapphire~2 小时前
Linux-07 ubuntu 的 chrome 启动不了
linux·chrome·ubuntu
伤不起bb2 小时前
NoSQL 之 Redis 配置与优化
linux·运维·数据库·redis·nosql
GXSC2 小时前
国芯思辰|SCS5501/5502芯片组打破技术壁垒,重构车载视频传输链路,兼容MAX9295A/MAX96717
嵌入式硬件
广东数字化转型2 小时前
nginx怎么使用nginx-rtmp-module模块实现直播间功能
linux·运维·nginx
love530love3 小时前
【笔记】在 MSYS2(MINGW64)中正确安装 Rust
运维·开发语言·人工智能·windows·笔记·python·rust
啵啵学习3 小时前
Linux 里 su 和 sudo 命令这两个有什么不一样?
linux·运维·服务器·单片机·ubuntu·centos·嵌入式
半桔3 小时前
【Linux手册】冯诺依曼体系结构
linux·缓存·职场和发展·系统架构