mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
ips/dma: add (simple) DMA driver
This commit is contained in:
parent
4f6694420f
commit
507ea77ad6
3 changed files with 475 additions and 0 deletions
119
fpga/include/villas/fpga/ips/dma.hpp
Normal file
119
fpga/include/villas/fpga/ips/dma.hpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/** DMA driver
|
||||
*
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS)
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASfpga
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
/** @addtogroup fpga VILLASfpga
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include <xilinx/xaxidma.h>
|
||||
|
||||
#include "fpga/ip_node.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
namespace villas {
|
||||
namespace fpga {
|
||||
namespace ip {
|
||||
|
||||
class Dma : public IpNode
|
||||
{
|
||||
public:
|
||||
friend class DmaFactory;
|
||||
|
||||
bool init();
|
||||
bool reset();
|
||||
|
||||
size_t write(const MemoryBlock& mem, size_t len);
|
||||
size_t read(const MemoryBlock& mem, size_t len);
|
||||
|
||||
bool writeComplete()
|
||||
{ return hasScatterGather() ? writeCompleteSG() : writeCompleteSimple(); }
|
||||
|
||||
bool readComplete()
|
||||
{ return hasScatterGather() ? readCompleteSG() : readCompleteSimple(); }
|
||||
|
||||
bool pingPong(const MemoryBlock& src, const MemoryBlock& dst, size_t len);
|
||||
|
||||
inline bool
|
||||
hasScatterGather() const
|
||||
{ return hasSG; }
|
||||
|
||||
private:
|
||||
size_t writeSG(const void* buf, size_t len);
|
||||
size_t readSG(void* buf, size_t len);
|
||||
bool writeCompleteSG();
|
||||
bool readCompleteSG();
|
||||
|
||||
size_t writeSimple(const void* buf, size_t len);
|
||||
size_t readSimple(void* buf, size_t len);
|
||||
bool writeCompleteSimple();
|
||||
bool readCompleteSimple();
|
||||
|
||||
private:
|
||||
static constexpr char registerMemory[] = "Reg";
|
||||
|
||||
static constexpr char mm2sInterrupt[] = "mm2s_introut";
|
||||
static constexpr char mm2sInterface[] = "M_AXI_MM2S";
|
||||
|
||||
static constexpr char s2mmInterrupt[] = "s2mm_introut";
|
||||
static constexpr char s2mmInterface[] = "M_AXI_S2MM";
|
||||
|
||||
// optional Scatter-Gather interface to access descriptors
|
||||
static constexpr char sgInterface[] = "M_AXI_SG";
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
{ return { registerMemory }; }
|
||||
|
||||
XAxiDma xDma;
|
||||
bool hasSG;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class DmaFactory : public IpNodeFactory {
|
||||
public:
|
||||
DmaFactory();
|
||||
|
||||
IpCore* create()
|
||||
{ return new Dma; }
|
||||
|
||||
std::string
|
||||
getName() const
|
||||
{ return "Dma"; }
|
||||
|
||||
std::string
|
||||
getDescription() const
|
||||
{ return "Xilinx's AXI4 Direct Memory Access Controller"; }
|
||||
|
||||
Vlnv getCompatibleVlnv() const
|
||||
{ return {"xilinx.com:ip:axi_dma:"}; }
|
||||
};
|
||||
|
||||
} // namespace ip
|
||||
} // namespace fpga
|
||||
} // namespace villas
|
||||
|
||||
/** @} */
|
|
@ -9,6 +9,7 @@ set(SOURCES
|
|||
ips/fifo.cpp
|
||||
ips/intc.cpp
|
||||
ips/pcie.cpp
|
||||
ips/dma.cpp
|
||||
|
||||
kernel/kernel.c
|
||||
kernel/pci.c
|
||||
|
|
355
fpga/lib/ips/dma.cpp
Normal file
355
fpga/lib/ips/dma.cpp
Normal file
|
@ -0,0 +1,355 @@
|
|||
/** DMA driver
|
||||
*
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS)
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASfpga
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <xilinx/xaxidma.h>
|
||||
|
||||
#include "fpga/card.hpp"
|
||||
#include "fpga/ips/dma.hpp"
|
||||
#include "fpga/ips/intc.hpp"
|
||||
|
||||
#include "log.hpp"
|
||||
#include "memory_manager.hpp"
|
||||
|
||||
// max. size of a DMA transfer in simple mode
|
||||
#define FPGA_DMA_BOUNDARY 0x1000
|
||||
|
||||
|
||||
namespace villas {
|
||||
namespace fpga {
|
||||
namespace ip {
|
||||
|
||||
// instantiate factory to make available to plugin infrastructure
|
||||
static DmaFactory factory;
|
||||
|
||||
DmaFactory::DmaFactory() :
|
||||
IpNodeFactory(getName())
|
||||
{
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Dma::init()
|
||||
{
|
||||
// if there is a scatter-gather interface, then this instance has it
|
||||
hasSG = busMasterInterfaces.count(sgInterface) == 1;
|
||||
logger->info("Scatter-Gather support: {}", hasScatterGather());
|
||||
|
||||
XAxiDma_Config xdma_cfg;
|
||||
|
||||
xdma_cfg.BaseAddr = getBaseAddr(registerMemory);
|
||||
xdma_cfg.HasStsCntrlStrm = 0;
|
||||
xdma_cfg.HasMm2S = 1;
|
||||
xdma_cfg.HasMm2SDRE = 1;
|
||||
xdma_cfg.Mm2SDataWidth = 128;
|
||||
xdma_cfg.HasS2Mm = 1;
|
||||
xdma_cfg.HasS2MmDRE = 1; /* Data Realignment Engine */
|
||||
xdma_cfg.HasSg = hasScatterGather();
|
||||
xdma_cfg.S2MmDataWidth = 128;
|
||||
xdma_cfg.Mm2sNumChannels = 1;
|
||||
xdma_cfg.S2MmNumChannels = 1;
|
||||
xdma_cfg.Mm2SBurstSize = 64;
|
||||
xdma_cfg.S2MmBurstSize = 64;
|
||||
xdma_cfg.MicroDmaMode = 0;
|
||||
xdma_cfg.AddrWidth = 32;
|
||||
|
||||
if (XAxiDma_CfgInitialize(&xDma, &xdma_cfg) != XST_SUCCESS) {
|
||||
logger->error("Cannot initialize Xilinx DMA driver");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (XAxiDma_Selftest(&xDma) != XST_SUCCESS) {
|
||||
logger->error("DMA selftest failed");
|
||||
return false;
|
||||
} else {
|
||||
logger->debug("DMA selftest passed");
|
||||
}
|
||||
|
||||
/* Map buffer descriptors */
|
||||
if (hasScatterGather()) {
|
||||
logger->warn("Scatter Gather not yet implemented");
|
||||
return false;
|
||||
|
||||
// ret = dma_alloc(c, &dma->bd, FPGA_DMA_BD_SIZE, 0);
|
||||
// if (ret)
|
||||
// return -3;
|
||||
|
||||
// ret = dma_init_rings(&xDma, &dma->bd);
|
||||
// if (ret)
|
||||
// return -4;
|
||||
}
|
||||
|
||||
/* Enable completion interrupts for both channels */
|
||||
XAxiDma_IntrEnable(&xDma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE);
|
||||
XAxiDma_IntrEnable(&xDma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA);
|
||||
|
||||
irqs[mm2sInterrupt].irqController->enableInterrupt(irqs[mm2sInterrupt], false);
|
||||
irqs[s2mmInterrupt].irqController->enableInterrupt(irqs[s2mmInterrupt], false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Dma::reset()
|
||||
{
|
||||
XAxiDma_Reset(&xDma);
|
||||
|
||||
// value taken from libxil implementation
|
||||
int timeout = 500;
|
||||
|
||||
while(timeout > 0) {
|
||||
if(XAxiDma_ResetIsDone(&xDma))
|
||||
return true;
|
||||
|
||||
timeout--;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Dma::pingPong(const MemoryBlock& src, const MemoryBlock& dst, size_t len)
|
||||
{
|
||||
if(this->read(dst, len) == 0)
|
||||
return false;
|
||||
|
||||
if(this->write(src, len) == 0)
|
||||
return false;
|
||||
|
||||
if(not this->writeComplete())
|
||||
return false;
|
||||
|
||||
if(not this->readComplete())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
Dma::write(const MemoryBlock& mem, size_t len)
|
||||
{
|
||||
// make sure memory is reachable
|
||||
if(not card->mapMemoryBlock(mem)) {
|
||||
logger->error("Memory not accessible by DMA");
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& mm = MemoryManager::get();
|
||||
auto translation = mm.getTranslation(busMasterInterfaces[mm2sInterface],
|
||||
mem.getAddrSpaceId());
|
||||
const void* buf = reinterpret_cast<void*>(translation.getLocalAddr(0));
|
||||
|
||||
return hasScatterGather() ? writeSG(buf, len) : writeSimple(buf, len);
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
Dma::read(const MemoryBlock& mem, size_t len)
|
||||
{
|
||||
// make sure memory is reachable
|
||||
if(not card->mapMemoryBlock(mem)) {
|
||||
logger->error("Memory not accessible by DMA");
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& mm = MemoryManager::get();
|
||||
auto translation = mm.getTranslation(busMasterInterfaces[s2mmInterface],
|
||||
mem.getAddrSpaceId());
|
||||
void* buf = reinterpret_cast<void*>(translation.getLocalAddr(0));
|
||||
|
||||
return hasScatterGather() ? readSG(buf, len) : readSimple(buf, len);
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
Dma::writeSG(const void* buf, size_t len)
|
||||
{
|
||||
(void) buf;
|
||||
(void) len;
|
||||
logger->error("DMA Scatter Gather write not implemented");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
Dma::readSG(void* buf, size_t len)
|
||||
{
|
||||
(void) buf;
|
||||
(void) len;
|
||||
logger->error("DMA Scatter Gather read not implemented");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Dma::writeCompleteSG()
|
||||
{
|
||||
logger->error("DMA Scatter Gather write not implemented");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Dma::readCompleteSG()
|
||||
{
|
||||
logger->error("DMA Scatter Gather read not implemented");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
Dma::writeSimple(const void *buf, size_t len)
|
||||
{
|
||||
XAxiDma_BdRing *ring = XAxiDma_GetTxRing(&xDma);
|
||||
|
||||
if ((len == 0) || (len > FPGA_DMA_BOUNDARY))
|
||||
return 0;
|
||||
|
||||
if (not ring->HasDRE) {
|
||||
const uint32_t mask = xDma.MicroDmaMode
|
||||
? XAXIDMA_MICROMODE_MIN_BUF_ALIGN
|
||||
: ring->DataWidth - 1;
|
||||
|
||||
if (reinterpret_cast<uintptr_t>(buf) & mask) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const bool dmaChannelHalted =
|
||||
XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK;
|
||||
|
||||
const bool dmaToDeviceBusy = XAxiDma_Busy(&xDma, XAXIDMA_DMA_TO_DEVICE);
|
||||
|
||||
/* If the engine is doing a transfer, cannot submit */
|
||||
if (not dmaChannelHalted and dmaToDeviceBusy) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// set lower 32 bit of source address
|
||||
XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_OFFSET,
|
||||
LOWER_32_BITS(reinterpret_cast<uintptr_t>(buf)));
|
||||
|
||||
// if neccessary, set upper 32 bit of source address
|
||||
if (xDma.AddrWidth > 32) {
|
||||
XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_MSB_OFFSET,
|
||||
UPPER_32_BITS(reinterpret_cast<uintptr_t>(buf)));
|
||||
}
|
||||
|
||||
// start DMA channel
|
||||
auto channelControl = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET);
|
||||
channelControl |= XAXIDMA_CR_RUNSTOP_MASK;
|
||||
XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, channelControl);
|
||||
|
||||
// set tail descriptor pointer
|
||||
XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len);
|
||||
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
Dma::readSimple(void *buf, size_t len)
|
||||
{
|
||||
XAxiDma_BdRing *ring = XAxiDma_GetRxRing(&xDma);
|
||||
|
||||
if ((len == 0) || (len > FPGA_DMA_BOUNDARY))
|
||||
return 0;
|
||||
|
||||
if (not ring->HasDRE) {
|
||||
const uint32_t mask = xDma.MicroDmaMode
|
||||
? XAXIDMA_MICROMODE_MIN_BUF_ALIGN
|
||||
: ring->DataWidth - 1;
|
||||
|
||||
if (reinterpret_cast<uintptr_t>(buf) & mask) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const bool dmaChannelHalted =
|
||||
XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK;
|
||||
|
||||
const bool deviceToDmaBusy = XAxiDma_Busy(&xDma, XAXIDMA_DEVICE_TO_DMA);
|
||||
|
||||
/* If the engine is doing a transfer, cannot submit */
|
||||
if (not dmaChannelHalted and deviceToDmaBusy) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// set lower 32 bit of destination address
|
||||
XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_OFFSET,
|
||||
LOWER_32_BITS(reinterpret_cast<uintptr_t>(buf)));
|
||||
|
||||
// if neccessary, set upper 32 bit of destination address
|
||||
if (xDma.AddrWidth > 32)
|
||||
XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_MSB_OFFSET,
|
||||
UPPER_32_BITS(reinterpret_cast<uintptr_t>(buf)));
|
||||
|
||||
// start DMA channel
|
||||
auto channelControl = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET);
|
||||
channelControl |= XAXIDMA_CR_RUNSTOP_MASK;
|
||||
XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, channelControl);
|
||||
|
||||
// set tail descriptor pointer
|
||||
XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Dma::writeCompleteSimple()
|
||||
{
|
||||
while (!(XAxiDma_IntrGetIrq(&xDma, XAXIDMA_DMA_TO_DEVICE) & XAXIDMA_IRQ_IOC_MASK))
|
||||
irqs[mm2sInterrupt].irqController->waitForInterrupt(irqs[mm2sInterrupt]);
|
||||
|
||||
XAxiDma_IntrAckIrq(&xDma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Dma::readCompleteSimple()
|
||||
{
|
||||
while (!(XAxiDma_IntrGetIrq(&xDma, XAXIDMA_DEVICE_TO_DMA) & XAXIDMA_IRQ_IOC_MASK))
|
||||
irqs[s2mmInterrupt].irqController->waitForInterrupt(irqs[s2mmInterrupt]);
|
||||
|
||||
XAxiDma_IntrAckIrq(&xDma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace ip
|
||||
} // namespace fpga
|
||||
} // namespace villas
|
Loading…
Add table
Reference in a new issue