From 507ea77ad6727d0a251e144c207d43739c919e68 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Mon, 26 Mar 2018 16:14:37 +0200 Subject: [PATCH] ips/dma: add (simple) DMA driver --- fpga/include/villas/fpga/ips/dma.hpp | 119 +++++++++ fpga/lib/CMakeLists.txt | 1 + fpga/lib/ips/dma.cpp | 355 +++++++++++++++++++++++++++ 3 files changed, 475 insertions(+) create mode 100644 fpga/include/villas/fpga/ips/dma.hpp create mode 100644 fpga/lib/ips/dma.cpp diff --git a/fpga/include/villas/fpga/ips/dma.hpp b/fpga/include/villas/fpga/ips/dma.hpp new file mode 100644 index 000000000..b5cd34f51 --- /dev/null +++ b/fpga/include/villas/fpga/ips/dma.hpp @@ -0,0 +1,119 @@ +/** DMA driver + * + * @author Daniel Krebs + * @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 . + ******************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include + +#include + +#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 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 + +/** @} */ diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt index 34901a74e..f16350efe 100644 --- a/fpga/lib/CMakeLists.txt +++ b/fpga/lib/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES ips/fifo.cpp ips/intc.cpp ips/pcie.cpp + ips/dma.cpp kernel/kernel.c kernel/pci.c diff --git a/fpga/lib/ips/dma.cpp b/fpga/lib/ips/dma.cpp new file mode 100644 index 000000000..3a5878441 --- /dev/null +++ b/fpga/lib/ips/dma.cpp @@ -0,0 +1,355 @@ +/** DMA driver + * + * @author Daniel Krebs + * @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 . + ******************************************************************************/ + +#include +#include + +#include + +#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(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(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(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(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(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(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(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(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