在使用zynq中DMA控制器的时候,会用到XAxiDma_SimpleTransfer这个函数。学习zynq中的例子程序,大概明白了这个函数的用法。
这个函数只能用在DMA控制器配置为简单模式(simple mode)的情况下。先看这个函数的源代码:
/*****************************************************************************/
/**
* This function does one simple transfer submission
*
* It checks in the following sequence:
* - if engine is busy, cannot submit
* - if engine is in SG mode , cannot submit
*
* @param InstancePtr is the pointer to the driver instance
* @param BuffAddr is the address of the source/destination buffer
* @param Length is the length of the transfer
* @param Direction is DMA transfer direction, valid values are
* - XAXIDMA_DMA_TO_DEVICE.
* - XAXIDMA_DEVICE_TO_DMA.
* @return
* - XST_SUCCESS for success of submission
* - XST_FAILURE for submission failure, maybe caused by:
* Another simple transfer is still going
* - XST_INVALID_PARAM if:Length out of valid range [1:8M]
* Or, address not aligned when DRE is not built in
*
* @note This function is used only when system is configured as
* Simple mode.
*
*****************************************************************************/
u32 XAxiDma_SimpleTransfer(XAxiDma *InstancePtr, UINTPTR BuffAddr, u32 Length,
int Direction)
{
u32 WordBits;
int RingIndex = 0;
/* If Scatter Gather is included then, cannot submit
*/
if (XAxiDma_HasSg(InstancePtr)) {
xdbg_printf(XDBG_DEBUG_ERROR, "Simple DMA mode is not"
" supported\r\n");
return XST_FAILURE;
}
if(Direction == XAXIDMA_DMA_TO_DEVICE){
if ((Length < 1) ||
(Length > InstancePtr->TxBdRing.MaxTransferLen)) {
return XST_INVALID_PARAM;
}
if (!InstancePtr->HasMm2S) {
xdbg_printf(XDBG_DEBUG_ERROR, "MM2S channel is not"
"supported\r\n");
return XST_FAILURE;
}
/* If the engine is doing transfer, cannot submit
*/
if(!(XAxiDma_ReadReg(InstancePtr->TxBdRing.ChanBase,
XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK)) {
if (XAxiDma_Busy(InstancePtr,Direction)) {
xdbg_printf(XDBG_DEBUG_ERROR,
"Engine is busy\r\n");
return XST_FAILURE;
}
}
if (!InstancePtr->MicroDmaMode) {
WordBits = (u32)((InstancePtr->TxBdRing.DataWidth) - 1);
}
else {
WordBits = XAXIDMA_MICROMODE_MIN_BUF_ALIGN;
}
if ((BuffAddr & WordBits)) {
if (!InstancePtr->TxBdRing.HasDRE) {
xdbg_printf(XDBG_DEBUG_ERROR,
"Unaligned transfer without"
" DRE %x\r\n",(unsigned int)BuffAddr);
return XST_INVALID_PARAM;
}
}
XAxiDma_WriteReg(InstancePtr->TxBdRing.ChanBase,
XAXIDMA_SRCADDR_OFFSET, LOWER_32_BITS(BuffAddr));
if (InstancePtr->AddrWidth > 32)
XAxiDma_WriteReg(InstancePtr->TxBdRing.ChanBase,
XAXIDMA_SRCADDR_MSB_OFFSET,
UPPER_32_BITS(BuffAddr));
XAxiDma_WriteReg(InstancePtr->TxBdRing.ChanBase,
XAXIDMA_CR_OFFSET,
XAxiDma_ReadReg(
InstancePtr->TxBdRing.ChanBase,
XAXIDMA_CR_OFFSET)| XAXIDMA_CR_RUNSTOP_MASK);
/* Writing to the BTT register starts the transfer
*/
XAxiDma_WriteReg(InstancePtr->TxBdRing.ChanBase,
XAXIDMA_BUFFLEN_OFFSET, Length);
}
else if(Direction == XAXIDMA_DEVICE_TO_DMA){
if ((Length < 1) ||
(Length >
InstancePtr->RxBdRing[RingIndex].MaxTransferLen)) {
return XST_INVALID_PARAM;
}
if (!InstancePtr->HasS2Mm) {
xdbg_printf(XDBG_DEBUG_ERROR, "S2MM channel is not"
" supported\r\n");
return XST_FAILURE;
}
if(!(XAxiDma_ReadReg(InstancePtr->RxBdRing[RingIndex].ChanBase,
XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK)) {
if (XAxiDma_Busy(InstancePtr,Direction)) {
xdbg_printf(XDBG_DEBUG_ERROR,
"Engine is busy\r\n");
return XST_FAILURE;
}
}
if (!InstancePtr->MicroDmaMode) {
WordBits =
(u32)((InstancePtr->RxBdRing[RingIndex].DataWidth) - 1);
}
else {
WordBits = XAXIDMA_MICROMODE_MIN_BUF_ALIGN;
}
if ((BuffAddr & WordBits)) {
if (!InstancePtr->RxBdRing[RingIndex].HasDRE) {
xdbg_printf(XDBG_DEBUG_ERROR,
"Unaligned transfer without"
" DRE %x\r\n", (unsigned int)BuffAddr);
return XST_INVALID_PARAM;
}
}
XAxiDma_WriteReg(InstancePtr->RxBdRing[RingIndex].ChanBase,
XAXIDMA_DESTADDR_OFFSET, LOWER_32_BITS(BuffAddr));
if (InstancePtr->AddrWidth > 32)
XAxiDma_WriteReg(InstancePtr->RxBdRing[RingIndex].ChanBase,
XAXIDMA_DESTADDR_MSB_OFFSET,
UPPER_32_BITS(BuffAddr));
XAxiDma_WriteReg(InstancePtr->RxBdRing[RingIndex].ChanBase,
XAXIDMA_CR_OFFSET,
XAxiDma_ReadReg(InstancePtr->RxBdRing[RingIndex].ChanBase,
XAXIDMA_CR_OFFSET)| XAXIDMA_CR_RUNSTOP_MASK);
/* Writing to the BTT register starts the transfer
*/
XAxiDma_WriteReg(InstancePtr->RxBdRing[RingIndex].ChanBase,
XAXIDMA_BUFFLEN_OFFSET, Length);
}
return XST_SUCCESS;
}
/** @} */
在函数的注释里面其实写得很清楚,每次调用这个函数的时候,就会在系统中提交一次DMA的传输行为的申请。这是需要稍微理解一下的。当系统接收到申请的时候,会去按照如下顺序查询相应的寄存器的状态:
没有上面的两种情况,那就开始按照函数中的参数进行一次DMA传输。
传输的方向有两种:
XAXIDMA_DMA_TO_DEVICE //DDR到外设,通过DMA进行搬运
XAXIDMA_DEVICE_TO_DMA //外设到DDR,通过DMA进行搬运
在DMA传输的时候会产生两个中断:
S2MM(Stream to Memory Mapped,数据流向内存映射)中断
MM2S(Memory Mapped to Stream,内存映射向数据流)中断
对DMA控制器来说,S2MM就是从外设(比如PL端的FIFO,或者高速AD,比如AD9361等)接收数据,并且将数据放到相应的内存空间中的过程。在S2MM中断程序中,可以设置一个接收完成变量,当传输完成时,将此变量置1,可以通过查询此变量来判断传输是否完成。
同理,MM2S就是将内存空间中的数据搬运到外设(比如PL端的FIFO,高速DA的FIFO等)的过程。在MM2S中断程序中,可以设置一个发送完成变量,当传输完成时,将此变量置1,可以通过查询此变量来判断传输是否完成。
在Xilinx提供的例子程序中,两个中断程序如下:
/*****************************************************************************/
/*
*
* This is the DMA RX interrupt handler function
*
* It gets the interrupt status from the hardware, acknowledges it, and if any
* error happens, it resets the hardware. Otherwise, if a completion interrupt
* is present, then it sets the RxDone flag.
*
* @param Callback is a pointer to RX channel of the DMA engine.
*
* @return None.
*
* @note None.
*
******************************************************************************/
static void RxIntrHandler(void *Callback) {
u32 IrqStatus;
int TimeOut;
XAxiDma *AxiDmaInst = (XAxiDma *) Callback;
/* Read pending interrupts */
IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);
/* Acknowledge pending interrupts */
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);
/*
* 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 could fail and hang
* NEED a way to handle this or do not call it??
*/
XAxiDma_Reset(AxiDmaInst);
TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) {
if (XAxiDma_ResetIsDone(AxiDmaInst)) {
break;
}
TimeOut -= 1;
}
return;
}
/*
* If completion interrupt is asserted, then set RxDone flag
*/
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
RxDone = 1;
}
}
其中的RxDone
变量即传输是否完成的变量。
/*****************************************************************************/
/*
*
* This is the DMA TX Interrupt handler function.
*
* It gets the interrupt status from the hardware, acknowledges it, and if any
* error happens, it resets the hardware. Otherwise, if a completion interrupt
* is present, then sets the TxDone.flag
*
* @param Callback is a pointer to TX channel of the DMA engine.
*
* @return None.
*
* @note None.
*
******************************************************************************/
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);
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;
}
}
其中的TxDone
即发送完成的变量。
在使用过程中,按照例子,设置好传输的方向,对相应的变量进行判断,就能够很好的完成DMA的传输。