Petalinux环境下BRAM通讯指南

前言

创建Petalinux项目

第一步:创建PetaLinux项目

1
2
3
# 创建基于Zynq模板的项目
petalinux-create --type project --template zynq --name zynq_linux_project
cd zynq_linux_project

说明: 选择zynq模板为Zynq 7000系列芯片提供基础配置框架。

第二步:导入硬件配置

1
2
3
4
5
6
# 创建硬件文件目录
mkdir -p hardware

# 将Vivado生成的.xsa文件放入hardware目录
# 导入硬件配置
petalinux-config --get-hw-description=./hardware/

重要事项:

  • 使用相对路径导入,确保路径正确性
  • 如果弹出配置菜单,通常可以直接保存退出
  • 导入后会在 project-spec/hw-description/ 生成硬件信息

第三步:系统配置

1
petalinux-config

关键配置项:

缓存配置

参考PetaLinux-2023-2-离线缓存与加速编译配置

启动配置为SD卡

  • 路径: Subsystem AUTO Hardware Settings -> SD/SDIO Settings
  • 设置: 确认 Primary SD/SDIO (ps7_sd_0) 已选择
  • Image Packaging Configuration -> Root filesystem type,从 INITRD 改为 EXT4 (SD/eMMC/SATA/USB)

串口配置

  • 路径: Subsystem AUTO Hardware Settings -> Serial Settings
  • 配置(此处是因为开发板的底板串口为串口1,此处配置要看开发板):
    • FSBL Serial stdin/stdout (ps7_uart_1)
    • DTG Serial stdin/stdout (ps7_uart_1)
    • System stdin/stdout baudrate for ps7_uart_1 (115200)

以太网配置

  • 路径: Subsystem AUTO Hardware Settings -> Ethernet Settings
  • 配置:
    • Primary Ethernet (ps7_ethernet_0) - 确认已选择
    • [*] Obtain IP address automatically - 启用DHCP
    • MAC地址保持默认即可

启动参数配置

  • 路径: DTG Settings -> Kernel Bootargs设置为手动设置参数,设置如下

    1
    console=ttyPS0,115200 earlycon root=/dev/mmcblk0p2 ro rootwait uio_pdrv_genirq.of_id=generic-uio

第四步:内核配置

1
petalinux-config -c kernel

必需启用的驱动模块:

DMA引擎支持

  • 路径: Device Drivers -> DMA Engine support
  • 启用: <*> Xilinx AXI DMAS Engine
  • 状态: 通常默认已启用

GPIO支持

  • 路径: Device Drivers -> GPIO Support -> Memory mapped GPIO drivers
  • 启用: <*> Xilinx Zynq GPIO support
  • 状态: 通常默认已启用

UIO支持(用户空间IO访问)

  • 路径: Device Drivers -> Userspace I/O drivers

  • 必须启用:

    • <M> Userspace I/O platform driver with generic IRQ handling,该项无法修改为*
    • <*> Userspace platform driver with generic irq and dynamic memory
  • 重要性: 用于用户空间访问BRAM控制器等自定义IP

  • UIO驱动的作用

    • Userspace I/O (UIO) 的优势:

      • 允许用户空间程序直接访问硬件寄存器

      • 避免编写复杂的内核驱动

      • 适用于自定义IP核的快速原型开发

      • 支持中断处理和内存映射

    • 适用场景:

      • BRAM控制器的用户空间访问
      • 自定义AXI IP核的控制
      • 硬件加速器的用户空间接口

以太网驱动

  • 路径: Device Drivers -> Network device support -> Ethernet driver support
  • 启用: <*> Xilinx 10/100/1000 AXI Ethernet support

PHY驱动(关键!)

  • 路径: Device Drivers -> Network device support -> PHY Device support and infrastructure
  • 必须启用: [*] Micrel Phys
  • 重要性: 没有正确的PHY驱动,网卡无法工作

第五步:根文件系统配置

1
petalinux-config -c rootfs

推荐配置:

基础系统

  • 路径: Filesystem Packages -> base -> busybox
  • [*] busybox - 基础系统工具集
  • [*] busybox-udhcpc - DHCP客户端(用于自动获取IP)

网络支持

  • SSH服务默认已配置,无需额外设置

GPIO库支持

  • 路径: Filesystem Packages -> libs -> libgpiod
  • 启用: [*] libgpiod

自动登录配置(重要!)

  • 路径: Image Features
  • 必须启用,在该开发板上使用UART1登录时root密码登录会出现一直失败的情况,可能是BUG:
    • -*- empty-root-password - 设置root用户空密码
    • [*] serial-autologin-root - 串口自动以root身份登录

第六步:设备树文件配置

  • 编辑 project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi

  • 添加如下内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /include/ "system-conf.dtsi"
    / {
    };

    // BRAM控制器配置为UIO模式
    &axi_bram_ctrl_0 {
    compatible = "generic-uio";
    };

    &axi_bram_ctrl_1 {
    compatible = "generic-uio";
    };

    // CDMA配置为UIO模式
    &axi_cdma_0 {
    compatible = "generic-uio";
    };
  • 设备树配置原理

    • compatible = "generic-uio": 告诉Linux使用UIO框架
    • UIO框架自动创建/dev/uioX设备文件
    • 支持用户空间直接访问硬件寄存器和中断

第七步:系统构建

1
2
# 完整构建(首次构建20-60分钟)
petalinux-build

构建说明:

  • 首次构建会下载并编译大量软件包
  • 构建过程包括:内核编译、根文件系统生成、设备树编译等
  • 如果出现网络下载错误,根据URL下载并补充在downloads目录下,详情参考PetaLinux-2023-2-离线缓存与加速编译配置

第八步:生成启动镜像

1
2
3
4
5
# 打包启动镜像
petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --u-boot --force

# 验证生成的文件
ls -la images/linux/BOOT.BIN images/linux/image.ub images/linux/boot.scr
  • 生成的关键文件:
    • BOOT.BIN (~5MB) - 包含FSBL、FPGA比特流、U-Boot、设备树
    • image.ub (~76MB) - FIT格式,包含Linux内核和根文件系统
    • boot.scr (~3.5KB) - U-Boot启动脚本
  • 注意,2023.2版本似乎不会在后续编译的时候生成boot.scr,因此该文件时间在后续构建是旧的

第九步:SD卡制作

  • 先查看自己的sd卡名称,我的设备是sdb1

    1
    sudo fdisk -l
  • 挂载SD卡分区(需要提前分区:FAT32启动分区 + EXT4根文件系统分区)

    1
    2
    sudo mount /dev/sdb1 /mnt/boot 
    sudo mount /dev/sdb2 /mnt/rootfs
  • 复制启动文件到boot分区,解压根文件系统到rootfs分区

    1
    2
    3
    4
    sudo cp images/linux/BOOT.BIN /mnt/boot/
    sudo cp images/linux/image.ub /mnt/boot/
    sudo cp images/linux/boot.scr /mnt/boot/
    sudo tar -xzf images/linux/rootfs.tar.gz -C /mnt/rootfs/
  • 同步并卸载

    1
    2
    sync
    sudo umount /mnt/boot /mnt/rootfs

Linux下硬件正确性检查

登录Linux

通过串口连接,启动开发板后可以进入串口的控制台页面,添加用户,配置网卡

1
2
mv /etc/network/interfaces /etc/network/interfaces.bak
vi /etc/network/interfaces

添加如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# /etc/network/interfaces -- configuration file for ifup(8), ifdown(8)

# The loopback interface
auto lo
iface lo inet loopback

# Ethernet interface eth0 - Static IP Configuration
auto eth0
iface eth0 inet static
address 192.168.1.101
netmask 255.255.255.0
network 192.168.1.0
gateway 192.168.1.1
# dns-nameservers 8.8.8.8 1.1.1.1

# auto eth1
# iface eth1 inet dhcp

# # Wireless interfaces
# # iface wlan0 inet dhcp
# # wireless_mode managed
# # wireless_essid any
# # wpa-driver wext
# # wpa-conf /etc/wpa_supplicant.conf

添加用户

1
2
3
4
adduser kuang
passwd kuang
usermod -aG sudo kuang
usermod -aG root kuang

这样就可以ssh登录linux了

硬件设计

1
2
3
4
5
硬件架构:
BRAM Controller 0 → Block Memory Port A
BRAM Controller 1 → Block Memory Port B
AXI CDMA → AXI Smart Connect
中断连接: CDMA → xconcat In0 → IRQ_F2P[0]

系统硬件检查命令

1
2
3
4
5
6
7
8
9
10
# 检查UIO设备
ls -la /dev/uio*

# 确认设备对应关系
for uio in /dev/uio*; do
num=$(basename $uio | sed 's/uio//')
echo "=== $uio ==="
cat /sys/class/uio/uio$num/name
cat /sys/class/uio/uio$num/maps/map0/addr
done

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
zynq_linux_project:~$ ls -la /dev/uio*
crw------- 1 root root 246, 0 Jan 1 1970 /dev/uio0
crw------- 1 root root 246, 1 Jan 1 1970 /dev/uio1
crw------- 1 root root 246, 2 Jan 1 1970 /dev/uio2
zynq_linux_project:~$ for uio in /dev/uio*; do
> num=$(basename $uio | sed 's/uio//')
> echo "=== $uio ==="
> cat /sys/class/uio/uio$num/name
> cat /sys/class/uio/uio$num/maps/map0/addr
> done
=== /dev/uio0 ===
axi_bram_ctrl
0x40000000
=== /dev/uio1 ===
axi_bram_ctrl
0x40001000
=== /dev/uio2 ===
dma
0x7e200000
zynq_linux_project:~$

输出分析

  • 设备文件路径/dev/uio0, /dev/uio1, /dev/uio2
  • 硬件地址映射
    • BRAM A: 0x40000000
    • BRAM B: 0x40001000
    • CDMA: 0x7e200000
  • 设备类型确认:两个BRAM控制器 + 一个DMA控制器

查看中断信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@zynq_linux_project:/home/kuang# cat /proc/device-tree/amba_pl/dma@*/interrupts | hexdump -C
00000000 00 00 00 00 00 00 00 1f 00 00 00 04 00 00 00 00 |................|
00000010 00 00 00 20 00 00 00 04 00 00 00 00 00 00 00 1d |... ............|
00000020 00 00 00 04 |....|
00000024
root@zynq_linux_project:/home/kuang# cat /proc/interrupts | grep dma
31: 0 0 GIC-0 45 Level f8003000.dma-controller
32: 0 0 GIC-0 46 Level f8003000.dma-controller
33: 0 0 GIC-0 47 Level f8003000.dma-controller
34: 0 0 GIC-0 48 Level f8003000.dma-controller
35: 0 0 GIC-0 49 Level f8003000.dma-controller
36: 0 0 GIC-0 72 Level f8003000.dma-controller
37: 0 0 GIC-0 73 Level f8003000.dma-controller
38: 0 0 GIC-0 74 Level f8003000.dma-controller
39: 0 0 GIC-0 75 Level f8003000.dma-controller
40: 0 0 GIC-0 63 Level xilinx-dma-controller
41: 0 0 GIC-0 64 Level xilinx-dma-controller
49: 2 0 GIC-0 61 Level dma
root@zynq_linux_project:/home/kuang# cat /sys/class/uio/uio2/name
dma
  • 解析:在第一个命令中,除了每行首位的行号,剩下的一共36个字节,每十二个字节为一组,每组的前四个字节是只能高端类型,中间四个是中断号,后四个是触发方式
    • 00 00 00 00: 中断类型 = 0 (SPI中断)
    • 00 00 00 1d: 中断号 = 0x1d = 29
    • 00 00 00 04: 触发方式 = 4 (高电平触发)
  • 中断号计算
    • 硬件中断号 = 29
    • Linux中断号 = 29 + 32 = 61
    • SPI中断: Linux中断号 = 设备树中断号 + 32

代码实现回环功能

代码编译参考配置-Windows-CLion-为-PetaLinux-虚拟机进行远程交叉编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
/*
* Linux FPGA中断DMA实验总结 - 标准实现
*
* 实验目标:
* 1. 掌握Linux UIO框架的使用
* 2. 理解用户空间硬件中断处理
* 3. 实现BRAM间的DMA数据传输
* 4. 验证中断驱动的异步操作
*/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdint.h>

// ============================================================================
// 硬件地址定义(从Address Editor获取)
// ============================================================================
#define BRAM_A_ADDR 0x40000000 // BRAM A物理基址
#define BRAM_B_ADDR 0x40001000 // BRAM B物理基址

// ============================================================================
// CDMA寄存器定义(来自Xilinx产品指南)
// ============================================================================
#define CDMACR 0x00 // 控制寄存器偏移
#define CDMASR 0x04 // 状态寄存器偏移
#define SA 0x18 // 源地址寄存器偏移
#define DA 0x20 // 目标地址寄存器偏移
#define BTT 0x28 // 传输字节数寄存器偏移

// 控制寄存器位定义
#define CDMACR_RESET (1 << 2) // bit 2: 软件复位
#define CDMACR_IOC_IrqEn (1 << 12) // bit 12: 完成中断使能
#define CDMACR_Err_IrqEn (1 << 14) // bit 14: 错误中断使能

// 状态寄存器位定义
#define CDMASR_IDLE (1 << 1) // bit 1: 空闲状态(只读)
#define CDMASR_IOC_Irq (1 << 12) // bit 12: 完成中断标志
#define CDMASR_Err_Irq (1 << 14) // bit 14: 错误中断标志

// ============================================================================
// BRAM设备类 - 用户空间硬件抽象
// ============================================================================
class BRAMDevice {
private:
int fd; // UIO设备文件描述符
volatile uint32_t *memory; // 映射的内存指针

public:
BRAMDevice() : fd(-1), memory(nullptr) {}

~BRAMDevice() {
// RAII模式:自动资源清理
if (memory) munmap((void*)memory, 0x1000);
if (fd >= 0) close(fd);
}

/*
* 初始化BRAM设备
* device_path: UIO设备路径,如"/dev/uio0"
* 返回: 0成功,-1失败
*/
int init(const char *device_path) {
// 打开UIO设备文件
fd = open(device_path, O_RDWR | O_SYNC);
if (fd < 0) return -1;

// 将硬件寄存器映射到用户空间
// mmap(): 建立虚拟地址到物理地址的映射
memory = (volatile uint32_t*)mmap(
nullptr, // 让内核选择虚拟地址
0x1000, // 映射4KB空间
PROT_READ | PROT_WRITE, // 可读写权限
MAP_SHARED, // 与硬件共享,写入立即生效
fd, // UIO设备文件描述符
0 // 从偏移0开始映射
);

return (memory == MAP_FAILED) ? -1 : 0;
}

/*
* 写入数据到BRAM
* offset: word偏移(0,1,2...)
* data: 32位数据
*/
void write(uint32_t offset, uint32_t data) {
memory[offset] = data;
// volatile确保写入操作立即执行,不被编译器优化
}

/*
* 从BRAM读取数据
* offset: word偏移(0,1,2...)
* 返回: 32位数据
*/
uint32_t read(uint32_t offset) {
return memory[offset];
// volatile确保每次都真实读取硬件
}

/*
* 清空BRAM区域
* start: 起始word偏移
* count: 清空的word数量
*/
void clear(uint32_t start, uint32_t count) {
for (uint32_t i = 0; i < count; i++) {
memory[start + i] = 0x00000000;
}
}
};

// ============================================================================
// CDMA控制器类 - 中断驱动DMA控制
// ============================================================================
class CDMAController {
private:
int fd; // UIO设备文件描述符
volatile uint32_t *regs; // CDMA寄存器映射

public:
CDMAController() : fd(-1), regs(nullptr) {}

~CDMAController() {
// RAII模式:自动资源清理
if (regs) munmap((void*)regs, 0x1000);
if (fd >= 0) close(fd);
}

/*
* 初始化CDMA控制器
* device_path: UIO设备路径,如"/dev/uio2"
* 返回: 0成功,-1失败
*/
int init(const char *device_path) {
// 打开CDMA的UIO设备
fd = open(device_path, O_RDWR | O_SYNC);
if (fd < 0) return -1;

// 映射CDMA寄存器到用户空间
regs = (volatile uint32_t*)mmap(nullptr, 0x1000,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (regs == MAP_FAILED) return -1;

// ========================================
// CDMA初始化序列(标准流程)
// ========================================

// 第1步:软件复位CDMA
regs[CDMACR/4] = CDMACR_RESET;
// 等待硬件清除复位位(表示复位完成)
while (regs[CDMACR/4] & CDMACR_RESET) {
usleep(1000); // 等待1ms
}

// 第2步:配置中断使能
regs[CDMACR/4] = CDMACR_IOC_IrqEn | CDMACR_Err_IrqEn;
// IOC_IrqEn: 传输完成时产生中断
// Err_IrqEn: 传输错误时产生中断

return 0;
}

/*
* 启动DMA传输(异步操作)
* src: 源物理地址
* dst: 目标物理地址
* size: 传输字节数
* 返回: 0成功,-1失败
*/
int start_transfer(uint32_t src, uint32_t dst, uint32_t size) {
// 检查CDMA是否处于空闲状态
if (!(regs[CDMASR/4] & CDMASR_IDLE)) {
return -1; // CDMA忙碌中
}

// 清除之前的中断标志(写1清除)
regs[CDMASR/4] = CDMASR_IOC_Irq | CDMASR_Err_Irq;

// ========================================
// CDMA传输参数配置
// ========================================
regs[SA/4] = src; // 设置源地址
regs[DA/4] = dst; // 设置目标地址
regs[BTT/4] = size; // 写入传输字节数(启动传输)

// 注意:写入BTT寄存器会立即启动DMA传输!
// 这是Xilinx CDMA的硬件设计特性

return 0;
}

/*
* 等待传输完成(中断驱动)
* 返回: 0成功,-1失败
*/
int wait_completion() {
// ========================================
// Linux UIO中断处理标准流程
// ========================================

// 第1步:使能UIO中断等待
uint32_t enable = 1;
if (write(fd, &enable, sizeof(enable)) != sizeof(enable)) {
return -1;
}
// 这不是使能硬件中断(已在init中使能)
// 而是告诉UIO内核模块"我准备等待下一个中断"

// 第2步:阻塞等待中断发生
uint32_t interrupt_count;
if (read(fd, &interrupt_count, sizeof(interrupt_count)) != sizeof(interrupt_count)) {
return -1;
}
// read()会阻塞当前进程,直到硬件中断发生
// 内核收到中断后会唤醒进程,read()返回中断计数

// 第3步:检查中断类型并清除标志
uint32_t status = regs[CDMASR/4];

if (status & CDMASR_IOC_Irq) {
// 传输完成中断
regs[CDMASR/4] = CDMASR_IOC_Irq; // 写1清除中断标志
return 0; // 成功
}

if (status & CDMASR_Err_Irq) {
// 传输错误中断
regs[CDMASR/4] = CDMASR_Err_Irq; // 写1清除错误标志
return -1; // 失败
}

return -2; // 未知中断类型
}
};

// ============================================================================
// 主程序 - Linux标准中断DMA流程演示
// ============================================================================
int main() {
printf("=== Linux FPGA中断DMA实验 ===\n\n");

// 设备对象初始化
BRAMDevice bram_a, bram_b;
CDMAController cdma;

// ========================================
// 第1步:设备初始化
// ========================================
printf("1. 设备初始化\n");
if (bram_a.init("/dev/uio0") < 0 ||
bram_b.init("/dev/uio1") < 0 ||
cdma.init("/dev/uio2") < 0) {
printf(" ❌ 设备初始化失败\n");
return 1;
}
printf(" ✅ 所有设备初始化成功\n");

// ========================================
// 第2步:准备发送数据
// ========================================
printf("2. 准备发送数据\n");
const uint32_t test_size = 16; // 传输16个word
for (uint32_t i = 0; i < test_size; i++) {
bram_a.write(i, 0x12345678 + i);
// 写入测试模式:0x12345678, 0x12345679, 0x1234567A, ...
}
printf(" ✅ 发送数据准备完成\n");

// ========================================
// 第3步:清空接收区域
// ========================================
printf("3. 清空接收区域\n");
bram_b.clear(0, test_size);
printf(" ✅ 接收区域已清空\n");

// ========================================
// 第4步:启动DMA传输(异步)
// ========================================
printf("4. 启动DMA传输\n");
if (cdma.start_transfer(BRAM_A_ADDR, BRAM_B_ADDR, test_size * 4) < 0) {
printf(" ❌ 传输启动失败\n");
return 1;
}
printf(" ✅ DMA传输已启动(异步模式)\n");

// ========================================
// 第5步:等待传输完成(中断驱动)
// ========================================
printf("5. 等待传输完成\n");
int result = cdma.wait_completion();
if (result == 0) {
printf(" ✅ 传输完成(收到完成中断)\n");
} else {
printf(" ❌ 传输失败 (错误码: %d)\n", result);
return 1;
}

// ========================================
// 第6步:验证接收数据
// ========================================
printf("6. 验证接收数据\n");
bool success = true;
for (uint32_t i = 0; i < test_size; i++) {
uint32_t sent = bram_a.read(i);
uint32_t received = bram_b.read(i);
if (sent != received) {
printf(" ❌ 数据不匹配[%u]: 发送=0x%08X, 接收=0x%08X\n",
i, sent, received);
success = false;
}
}

if (success) {
printf(" ✅ 数据完整性验证通过\n");
} else {
printf(" ❌ 数据完整性验证失败\n");
}

// ========================================
// 实验结果
// ========================================
printf("\n=== 实验结果: %s ===\n", success ? "✅ 成功" : "❌ 失败");

return success ? 0 : 1;
}

/*
* ============================================================================
* 编译和运行指令
* ============================================================================
*
* 编译:
* arm-xilinx-linux-gnueabi-g++ -o fpga_dma_experiment main.cpp -std=c++11
*
* 运行:
* sudo ./fpga_dma_experiment
*
* ============================================================================
* 实验学习要点总结
* ============================================================================
*
* 1. Linux UIO框架使用:
* - 设备树配置 compatible = "generic-uio"
* - 通过 /dev/uioX 访问硬件
* - mmap() 映射硬件寄存器到用户空间
*
* 2. 用户空间中断处理:
* - write(fd, &enable, 4) 使能中断等待
* - read(fd, &count, 4) 阻塞等待中断
* - 检查硬件状态寄存器确定中断类型
*
* 3. CDMA控制流程:
* - 软件复位 → 配置中断使能 → 设置传输参数 → 等待完成
* - 写入BTT寄存器启动传输(硬件特性)
* - 中断驱动的异步操作模式
*
* 4. 硬件抽象设计:
* - RAII模式自动资源管理
* - volatile 关键字防止编译器优化
* - 错误检查和异常处理
*
* 这是工业级FPGA应用开发的标准模式!
* ============================================================================
*/

核心概念深度解析

文件描述符机制

1
int fd = open("/dev/uio2", O_RDWR);
  • fd是进程级索引:每个进程独立的文件表索引
  • 内核文件对象:fd指向内核中的file结构体
  • 资源隔离:不同进程的相同fd值可能指向不同文件

内存映射原理

1
volatile uint32_t *regs = (volatile uint32_t*)mmap(...);
  • 虚拟地址映射:硬件物理地址 ↔ 用户空间虚拟地址
  • 零拷贝访问:直接操作硬件,无系统调用开销
  • volatile关键字:防止编译器缓存优化,确保每次访问都是真实硬件操作

UIO中断处理机制

1
2
3
4
5
// 使能中断等待
write(fd, &enable, 4); // 告诉内核"我准备等待"

// 阻塞等待中断
read(fd, &count, 4); // 进程睡眠,直到硬件中断唤醒

内核内部流程:

  1. 硬件产生中断 → 内核中断处理函数
  2. 增加中断计数器 → 唤醒等待的用户进程
  3. 用户进程继续执行 → 检查硬件状态寄存器

CDMA寄存器控制详解

关键寄存器映射:

1
2
3
4
5
6
物理地址        虚拟地址    功能
0x7e200000 → regs[0] → CDMACR控制寄存器
0x7e200004 → regs[1] → CDMASR状态寄存器
0x7e200018 → regs[6] → SA源地址寄存器
0x7e200020 → regs[8] → DA目标地址寄存器
0x7e200028 → regs[10] → BTT传输字节数寄存器

控制流程解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 复位CDMA(清除所有内部状态)
regs[CDMACR/4] = CDMACR_RESET;

// 2. 等待复位完成(硬件自动清除复位位)
while (regs[CDMACR/4] & CDMACR_RESET) usleep(1000);

// 3. 使能硬件中断
regs[CDMACR/4] = CDMACR_IOC_IrqEn | CDMACR_Err_IrqEn;

// 4. 配置传输参数
regs[SA/4] = 0x40000000; // 源地址
regs[DA/4] = 0x40001000; // 目标地址
regs[BTT/4] = 64; // 写入启动传输!

// 5. 等待中断并检查状态
uint32_t status = regs[CDMASR/4];
if (status & CDMASR_IOC_Irq) {
// 传输完成,清除中断标志
regs[CDMASR/4] = CDMASR_IOC_Irq;
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
=== BRAM中断DMA传输流程 ===

1. 准备发送数据
✅ 发送数据准备完成
2. 清空接收区域
✅ 接收区域已清空
3. 启动DMA传输
✅ 传输已启动
4. 等待传输完成
✅ 传输完成
5. 验证接收数据
✅ 数据比对成功

=== 传输结果: ✅ 成功 ===

构建优化

1
2
3
4
5
6
7
8
9
10
11
12
13
# 硬件配置变更后的最小重构
petalinux-build -c device-tree -x cleanall
petalinux-build -c device-tree
petalinux-build -c kernel
petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --u-boot --force

# 仅根文件系统变更
petalinux-build -c rootfs
petalinux-build -c kernel # 重新打包image.ub

# 仅内核配置变更
petalinux-build -c kernel -x cleanall
petalinux-build -c kernel