对于一个希望能够达到软件定义,硬件加速的协议栈来说,打通软硬之间的任督二脉是最为重要的一环。本文通过搭建一个 AXI DMA 子系统,将 PS 中网卡驱动软件接收到的数据包发送给逻辑部分,建立起 PS 到 PL 的数据流。
作者:李凡
来源: https://zhuanlan.zhihu.com/p/61356797
搭建逻辑 DMA 子系统
在 Vivado 的 Block Design 功能中通过拖动 IP 的方式建立一个 DMA传输子系统。DMA 子系统包括一个 AXI-DMA 和一个 AXI-FIFO。本文中的实验因为是把数据从 PS 端传输到 PL 端,所以只会用到接收 FIFO(但为了回环测试,所以又将接收 FIFO 的输出端连接到了 DMA 发送端(S2MM 对应于逻辑部分的发送端))。DMA IP 有多条总线通道,分别用于和 FIFO 以及 PS 连接,下图表示了 DMA 系统各部分的连接情况。
在 IP 连接图上会看到很多的总线矩阵( interconnect ),DMA 和 PS 之间通过两个总线矩阵连接,不考虑这些总线矩阵,本质上的总线连接就如上图,蓝色的是逻辑部分,红色的是 PS 部分。红蓝之间共有 3 条总线与一条信号线连接。系统中真正使用的为接收 FIFO,发送 FIFO 暂未使用,用虚线表示。
信号线是中断控制线,DMA 产生的中断会由 PS 进行中断响应。AXI-Lite 是一条配置总线,PS 端对 DMA 的配置控制都会通过这条总线进行。剩下的是一对 AXI 总线,这对总线分别对应着发送接收的数据流通道。图中的发送是指 DMA 到 PS 的数据流向,在本实验中没有用到,使用的是接收是 PS 到 DMA 的数据流向。
DMA 的工作可以理解为将数据从 AXI 总线的形式转为 AXIS 的形式。AXI 是指 Memory Mapped 的数据,即数据有对应的内存地址,数据存储于存储介质(如 DDR,BRAM)或者寄存器(比如网络 MAC 的数据缓冲寄存器)中。在本实验中 MM 指的是从 PS 的 MAC 数据缓冲寄存器中搬用的网络数据包。
AXIS 总线对应的一般是对数据进行处理的逻辑部分,AXIS 总线在本实验中指的是接收 FIFO,此时的数据没有地址的概念,以数据流的形式出现,简单来说当 valid 信号线为 1 时, data 上的数据有效。FIFO 作为从机 (slave)接收数据,所以整个从 PS 到 PL 的传输过程称为 MM2S (Memory Mapped to Slave),PL 到 PS 的数据流则称为 S2MM 。
本实验中的 DMA 配置为简单模式,数据传输的 AXI 总线连接到 PS 的 HP\_AXI 接口,控制用 AXI-Lite 连接到 PS 的 GP\_AXI 接口。我们将在后续的文章中讨论 DMA 以及 PS 上的多种 AXI 接口。
AXI-DMA 外设驱动
AXI-DMA 属于我们在前文中说的,由 Xilinx 官方提供的逻辑 IP 外设。区别于 PS 系统中包括的 ps7\_dma,DMA 在 Vivado 中是一个标准的 IP 核,所以官方有提供标准的外设驱动以及使用示例。
这里我们同样化用 example 中的代码,封装成我们自己的功能函数。
我们使用的是简单模式,使用中断的例程 axi\_dma\_loop\_test\_bsp\_xaxidma\_example\_simple\_intr,首先看下例程的结构。例程中主要的函数分别是
main(void)
主函数中完成了对 DMA 的初始化,准备一份发送数据,放在数组缓冲区中,调用 DMA 的 XAxiDma\_SimpleTransfer 函数发送,同时接收一份数据放到接收缓冲区中。因为硬件结构上为回环连接,发送的数据会被 DMA 接收,主函数会对发送接收的数据进行比较。
void TxIntrHandler(void *Callback)/void RxIntrHandler(void *Callback)
发送接收中断函数在 DMA 发送/接收后被调用,对当前置起的中断进行处理,如果没有错误发生,那么置起发送完成标志。这是一个全局变量,主程序在发送/接收中断完成后,对发送接收的数据进行比较。在正常的使用中,发送接收中断完成标志表示程序可以进行下一次发送/接收操作。
封装应用驱动函数
为了方便使用,我们以 example 的代码为基础,封装成自己的应用驱动
初始化 DMA 函数,在主函数初始化外设时调用。
int axidma_init(XAxiDma * dma_inst,XScuGic * intc_inst)
{
int Status;
XAxiDma_Config *Config;
xil_printf("\r\n--- Init AXI_DMA Begin--- \r\n");
Config = XAxiDma_LookupConfig(DMA_DEV_ID);
if (!Config) {
xil_printf("No config found for %d\r\n", DMA_DEV_ID);
return XST_FAILURE;
}
Status = XAxiDma_CfgInitialize(dma_inst, Config);
if (Status != XST_SUCCESS) {
xil_printf("Initialization failed %d\r\n", Status);
return XST_FAILURE;
}
if(XAxiDma_HasSg(dma_inst)){
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
/* Set up Interrupt system */
Status = SetupIntrSystem(intc_inst, dma_inst, TX_INTR_ID, RX_INTR_ID);
if (Status != XST_SUCCESS) {
xil_printf("Failed intr setup\r\n");
return XST_FAILURE;
}
/* Disable all interrupts before setup */
XAxiDma_IntrDisable(dma_inst, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrDisable(dma_inst, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
/* Enable all interrupts */
XAxiDma_IntrEnable(dma_inst, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrEnable(dma_inst, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
xil_printf("\r\n--- Init AXI_DMA Scuess--- \r\n");
return XST_SUCCESS;
}
DMA 发送函数,封装了 XAxiDma\_SimpleTransfer 函数。
int axidma_send_data(XAxiDma * dma_inst,u32 * send_buff,u32 data_len)
{
int Status;
Xil_DCacheFlushRange((UINTPTR)send_buff, data_len);
Status = XAxiDma_SimpleTransfer(dma_inst,(UINTPTR) send_buff,
data_len, XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
在 LwIP 中使用 DMA 驱动
在本文实验中,将 LwIP 和 DMA 子系统结合起来,将网卡驱动接收到的数据传输给逻辑部分。在 xilinx sdk 的 LwIP 例程的基础上,添加对于 AXI DMA 驱动的支持。
首先在主函数 main 中初始化平台时,初始化 DMA 。
接下来修改网卡接收中断函数 emacps\_recv\_handler ,加入 DMA 发送函数 axidma\_send\_data 。在将数据包 pbuf 交付上层软件之前,先启动 DMA 传输,传输长度为数据包的长度。我们这里只传输数据包的 payload 部分,所以传入指向 payload 的指针。
数据来了
DMA 开始传输后,数据就开始从 axis 接口进入接收 FIFO。可以使用 vivado 的 debug 功能设置触发电,观察到数据开始到来。我们可以把逻辑收到的数据和软件中 payload 指针指向的地址上的数据相比较,可以发现数据显然是一致的。但注意到此时传输给逻辑部分的数据也是按照主机字节序组织的。
对数据做些什么
目前初步的计划是在逻辑中进行计算数据的校验和,解析协议等操作,在 PS 需要这些操作的结果时,不需要在 PS 端再进行计算,只需要从寄存器中读取由逻辑部分计算完成的信息即可,以此实现减少 PS 端软件运算需求的目的。
结语
本文中简单介绍了 DMA 子系统的硬件搭建,DMA example 的介绍以及在 LwIP 网卡接收中断函数中添加 DMA 传输函数。介绍地比较简单,有兴趣的读者可以来进一步讨论。
推荐阅读
- Zynq SDK 驱动探求(二):外设,从初始化到干活
- Zynq SDK 驱动探求(三):论一个外设驱动的全部身家·Xilinx SDK 驱动源码结构
- [Demo愉快行]Xilinx ZCU102 LwIP Echo Demo 硬件平台搭建
关注此系列,请关注专栏FPGA的逻辑