DMA IP PS
实现DMA传输的三要素:
- TX_BUFFE&RX_BUFFER
- TX&RX channel
- TX&RX interrupts
一: Vivado SDK 上的c语言
对DMA ip核的调用其main()主要可以分为如下结构
main()
DMA_Intr_Init() //初始化DMA
XAXIDma_Lookup Config() //查找DMA设备
XAXIDma_CfgIntialize() //初始化DMA设备
Init_Intr_System() //初始化中断
XScuGic_Lookup Config()
XScuGic_CfgConfig()
Setup_Intr_Exception()
Xil_ExceptionInit() //使能硬件中断
Xil_ExceptionRegister Handler() //中断注册函数
DMA_setup_IntrSystem() //设置DMA中断
XScuGic_SetPriority Trigger Type()
XScuGic_Connect() //连接中断源
XScuGic_Enable()
DMA_Intr_Enable() //axi_dma使能中断
XAxiDma_IntrDisable()
XAxiDma_IntrEnable() //根据Xilinx的sample给出的先禁用再使能
Xilinx dma_test_bsp_xaxidma_example_simple_intr:
xilinx给出的官方用例(simple Intr模式)主要包含以下部分:
- main()函数
- CheckData()函数
- TxIntrHandler & RxIntrHandler 函数
- SetupIntrSystem及DisableIntrSystem函数
具体如下:
1.Include files及变量定义(分完全代码)
#ifndef
DDR_BASE_ADDR
#warning CHECK
FOR THE VALID DDR ADDRESS IN XPARAMETERS.H, \
DEFAULT SET TO 0x01000000
#define
MEM_BASE_ADDR 0x01000000
#else
#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1000000)
#endif
#ifdef XPAR_INTC_0_DEVICE_ID
#define RX_INTR_ID XPAR_INTC_0_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define TX_INTR_ID XPAR_INTC_0_AXIDMA_0_MM2S_INTROUT_VEC_ID
#else
#define
RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define
TX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
#endif
#define
TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000)
#define
RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000)/*TX buffer与RX buffer的基地址分别定义为MEM_BASE_ADDR加上0x00100000和0x00300000的偏移量*/
#define RX_BUFFER_HIGH (MEM_BASE_ADDR + 0x004FFFFF)
在这里我们通过查看xparameters.h文件可以得到DDR的内存地址范围,我们定义的MEM_BASE_ADDR必须处在这一范围之内,笔者查看到的范围如下:
/*
Definitions for peripheral PS7_DDR_0 */
#define
XPAR_PS7_DDR_0_S_AXI_BASEADDR 0x00100000
#define
XPAR_PS7_DDR_0_S_AXI_HIGHADDR 0x3FFFFFFF
2.main()
int main(void)
{
int Status;
XAxiDma_Config *Config;
int Tries = NUMBER_OF_TRANSFERS;
int Index;
u8 *TxBufferPtr;
u8 *RxBufferPtr;
u8 Value;
TxBufferPtr = (u8 *)TX_BUFFER_BASE ;
RxBufferPtr = (u8 *)RX_BUFFER_BASE;
xil_printf("\r\n--- Entering main() --- \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;
}
/* Initialize DMA engine */
Status = XAxiDma_CfgInitialize(&AxiDma,Config);
if (Status != XST_SUCCESS) {
xil_printf("Initialization failed %d\r\n", Status);
return XST_FAILURE;
}
if(XAxiDma_HasSg(&AxiDma)){
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
/* Set up Interrupt system */
Status = SetupIntrSystem(&Intc,&AxiDma, 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(&AxiDma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrDisable(&AxiDma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
/* Enable all interrupts */
XAxiDma_IntrEnable(&AxiDma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrEnable(&AxiDma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
/* Initialize flags before start transfer test */
TxDone = 0;
RxDone = 0;
Error = 0;
Value = TEST_START_VALUE;
for(Index = 0; Index < MAX_PKT_LEN; Index ++) {
TxBufferPtr[Index] = Value;
Value = (Value + 1) & 0xFF;
}
/* Flush the SrcBuffer before the DMA transfer, in case
the Data Cache is enabled*/
Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN);
#ifdef __aarch64__
Xil_DCacheFlushRange((UINTPTR)RxBufferPtr,MAX_PKT_LEN);
#endif
/* Send a packet */
for(Index = 0; Index < Tries; Index ++) {
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR)RxBufferPtr,MAX_PKT_LEN,XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,MAX_PKT_LEN,XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*Wait TX done and RX done*/
while (!TxDone && !RxDone &&!Error) {
/* NOP */
}
if (Error) {
xil_printf("Failed test transmit%s done, "
"receive%s done\r\n", TxDone? "":" not",
RxDone? "":" not");
goto Done;
}
/*Test finished, check data*/
Status = CheckData(MAX_PKT_LEN, 0xC);
if (Status != XST_SUCCESS) {
xil_printf("Data check failed\r\n");
goto Done;
}
}
xil_printf("Successfully ran AXI DMA interrupt Example\r\n");
/* Disable TX and RX Ring interrupts and return success */
DisableIntrSystem(&Intc, TX_INTR_ID,RX_INTR_ID);
Done:
xil_printf("--- Exiting main() --- \r\n");
return XST_SUCCESS;
}
3.TxIntrHandler & RxIntrHandler 函数(二者结构功能类似,这里以TX为例)
static void TxIntrHandler(void *Callback)
{
u32 IrqStatus;
int TimeOut;
XAxiDma *AxiDmaInst = (XAxiDma *)Callback;
/* Read pending interrupts */
IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst,XAXIDMA_DMA_TO_DEVICE); //取出当前中断
/* Acknowledge pending interrupts */
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus,XAXIDMA_DMA_TO_DEVICE); //确认当前中断被接收
/* If no interrupt is asserted, we do not do anything*/
if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
return;
}
/** If error interrupt is asserted, raise error flag, reset the hardware to recover from the error, and return with no further processing.*/
if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
Error = 1; //设置错误标记
/*Reset should never fail for transmit channel*/
XAxiDma_Reset(AxiDmaInst); //复位DMA
TimeOut = RESET_TIMEOUT_COUNTER;
//看门狗控制复位完成等待//
while (TimeOut) {
if (XAxiDma_ResetIsDone(AxiDmaInst)) {
break;
}
TimeOut -= 1;
}
return;
}
/*If Completion interrupt is asserted, then set the TxDone flag*/
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
TxDone = 1; //中断类型为传输完成,表示tx传输结束
}
}
二:PYNQ里的python.lib.dma
from pynq import DefaultIP
import warnings
MAX_C_SG_LENGTH_WIDTH = 26 #应大于vivado pl侧定义的width of buffer length register
class _DMAChannel:
"""Drives a single channel of the Xilinx AXI DMA
This driver is designed to be used in conjunction with the
`pynq.allocate()` method of memory allocation. The channel has
main functions `transfer` and `wait` which start and wait for
the transfer to finish respectively. If interrupts are enabled
there is also a `wait_async` coroutine.
This class should not be constructed directly, instead used
through the AxiDMA class.
"""
def __init__(self, mmio, offset, size, flush_before, interrupt=None):
self._mmio = mmio
self._offset = offset
self._interrupt = interrupt
self._flush_before = flush_before
self._size = size
self._active_buffer = None
self._first_transfer = True
self.start()
@property
def running(self):
"""True if the DMA engine is currently running
"""
return self._mmio.read(self._offset + 4) & 0x01 == 0x00
#通过查阅pg021 IP AXI DMA v7.0手册得知,self._offset+4对应寄存器为MM2S_DMASR,其bit0在为0时,DMA cahnnel 处于running状态
@property
def idle(self):
"""True if the DMA engine is idle
`transfer` can only be called when the DMA is idle
"""
return self._mmio.read(self._offset + 4) & 0x02 == 0x02
#通过查阅pg021 IP AXI DMA v7.0手册得知,self._offset+4对应寄存器为MM2S_DMASR,其bit1在为1时,DMA cahnnel 处于Idle状态
def start(self):
"""Start the DMA engine if stopped
"""
if self._interrupt:
self._mmio.write(self._offset, 0x1001) #向寄存器MM2S_DMACR赋值,0x1001,bit0为1时DMA = RUN,bits31-24赋值为1使能Interrupt Delay Time Out
else:
self._mmio.write(self._offset, 0x0001) #为启用interrupt则正常开启DMA
while not self.running:
pass
self._first_transfer = True
def stop(self):
"""Stops the DMA channel and aborts the current transfer
"""
self._mmio.write(self._offset, 0x0000) #DMA Stop
while self.running:
pass
def _clear_interrupt(self):
self._mmio.write(self._offset + 4, 0x1000)
def transfer(self, array):
"""Transfer memory with the DMA
Transfer must only be called when the channel is idle.
Parameters
----------
array : ContiguousArray
An xlnk allocated array to be transferred
"""
if array.nbytes > self._size:
raise ValueError('Transferred array is {} bytes, which exceeds '
'the maximum DMA buffer size {}.'.format(
array.nbytes, self._size))
if not self.running:
raise RuntimeError('DMA channel not started')
if not self.idle and not self._first_transfer:
raise RuntimeError('DMA channel not idle')
if self._flush_before:
array.flush()
self._mmio.write(self._offset + 0x18,array.physical_address) #Direct Register Mode MM2S_SA,Source Address
self._mmio.write(self._offset + 0x28, array.nbytes) #MM2S transfer length(Bytes)
self._active_buffer = array
self._first_transfer = False
def wait(self):
"""Wait for the transfer to complete
"""
if not self.running:
raise RuntimeError('DMA channel not started')
while not self.idle:
pass
if not self._flush_before:
self._active_buffer.invalidate()
async def wait_async(self):
"""Wait for the transfer to complete
"""
if not self.running:
raise RuntimeError('DMA channel not started')
while not self.idle:
await self._interrupt.wait()
self._clear_interrupt()
if not self._flush_before:
self._active_buffer.invalidate()
class DMA(DefaultIP):
def __init__(self, description, *args, **kwargs):
"""Create an instance of the DMA Driver
Parameters
----------
description : dict
The entry in the IP dict describing the DMA engine
"""
if type(description) is not dict or args or kwargs:
raise RuntimeError('You appear to want the old DMA driver which '
'has been deprecated and moved to '
'pynq.lib.deprecated')
super().__init__(description=description)
if 'parameters' in description and \
'c_sg_length_width' in description['parameters']:
self.buffer_max_size = \
1 << int(description['parameters']['c_sg_length_width'])
else:
self.buffer_max_size = 1 << MAX_C_SG_LENGTH_WIDTH
message = 'Failed to find parameter c_sg_length_width; ' \
'users should really use *.hwh files for overlays.'
warnings.warn(message, UserWarning)
if 'mm2s_introut' in description['interrupts']:
self.sendchannel = _DMAChannel(self.mmio, 0x0,
self.buffer_max_size,
True, self.mm2s_introut) #基地址00h配置MM2S
else:
self.sendchannel = _DMAChannel(self.mmio, 0x0,
self.buffer_max_size,
True)
if 's2mm_introut' in description['interrupts']:
self.recvchannel = _DMAChannel(self.mmio, 0x30,
self.buffer_max_size,
False, self.s2mm_introut) #基地址30h配置S2MM
else:
self.recvchannel = _DMAChannel(self.mmio, 0x30,
self.buffer_max_size,
False)
bindto = ['xilinx.com:ip:axi_dma:7.1']
Fin