From 507ea77ad6727d0a251e144c207d43739c919e68 Mon Sep 17 00:00:00 2001
From: Daniel Krebs <github@daniel-krebs.net>
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 <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
+
+/** @} */
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 <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