All of lore.kernel.org
 help / color / mirror / Atom feed
From: Alexander Gordeev <a.gordeev.box@gmail.com>
To: linux-kernel@vger.kernel.org
Cc: Alexander Gordeev <a.gordeev.box@gmail.com>, dmaengine@vger.kernel.org
Subject: [PATCH v3 0/1] dmaengine: avalon: Intel Avalon-MM DMA Interface for PCIe
Date: Wed, 16 Oct 2019 13:21:52 +0200	[thread overview]
Message-ID: <cover.1571224166.git.a.gordeev.box@gmail.com> (raw)

This series is against v5.4-rc3

I am posting "avalon-dma" update alone and going to post "avalon-test"
update as a follow-up or in the next round.

Changes since v2:
- avalon_dma_register() return value bug fixed;
- device_prep_slave_sg() does not crash dmaengine_prep_slave_single() now;
- kernel configuration options removed in favour of module parameters;
- BUG_ONs, WARN_ONs and dev_dbgs removed;
- goto labels renamed, other style issues addressed;
- polling loop in interrupt handler commented;

Changes since v1:
- "avalon-dma" converted to "dmaengine" model;
- "avalon-drv" renamed to "avalon-test";

The Avalon-MM DMA Interface for PCIe is a design used in hard IPs for
Intel Arria, Cyclone or Stratix FPGAs. It transfers data between on-chip
memory and system memory.

Testing was done using a custom FPGA build with Arria 10 FPGA streaming
data to target device RAM:

  +----------+    +----------+    +----------+        +----------+
  | Nios CPU |<-->|   RAM    |<-->|  Avalon  |<-PCIe->| Host CPU |
  +----------+    +----------+    +----------+        +----------+

The RAM was examined for data integrity by examining RAM contents
from host CPU (indirectly - checking data DMAed to the system) and
from Nios CPU that has direct access to the device RAM. A companion
tool using "avalon-test" driver was used to DMA files to the device:
https://github.com/a-gordeev/avalon-tool.git

CC: dmaengine@vger.kernel.org

Alexander Gordeev (1):
  dmaengine: avalon: Intel Avalon-MM DMA Interface for PCIe

 drivers/dma/Kconfig              |   2 +
 drivers/dma/Makefile             |   1 +
 drivers/dma/avalon/Kconfig       |  14 +
 drivers/dma/avalon/Makefile      |   6 +
 drivers/dma/avalon/avalon-core.c | 476 +++++++++++++++++++++++++++++++
 drivers/dma/avalon/avalon-core.h |  92 ++++++
 drivers/dma/avalon/avalon-hw.c   | 186 ++++++++++++
 drivers/dma/avalon/avalon-hw.h   |  85 ++++++
 drivers/dma/avalon/avalon-pci.c  | 144 ++++++++++
 9 files changed, 1006 insertions(+)
 create mode 100644 drivers/dma/avalon/Kconfig
 create mode 100644 drivers/dma/avalon/Makefile
 create mode 100644 drivers/dma/avalon/avalon-core.c
 create mode 100644 drivers/dma/avalon/avalon-core.h
 create mode 100644 drivers/dma/avalon/avalon-hw.c
 create mode 100644 drivers/dma/avalon/avalon-hw.h
 create mode 100644 drivers/dma/avalon/avalon-pci.c

Because the amount of changes since the previous version is quite big,
I am also posting the interdiff.

diff -u b/drivers/dma/avalon/Kconfig b/drivers/dma/avalon/Kconfig
--- b/drivers/dma/avalon/Kconfig
+++ b/drivers/dma/avalon/Kconfig
@@ -15,74 +14,0 @@
-
-if AVALON_DMA
-
-config AVALON_DMA_MASK_WIDTH
-	int "Avalon DMA streaming and coherent bitmask width"
-	range 0 64
-	default 64
-	help
-	  Width of bitmask for streaming and coherent DMA operations
-
-config AVALON_DMA_CTRL_BASE
-	hex "Avalon DMA controllers base"
-	default "0x00000000"
-
-config AVALON_DMA_RD_EP_DST_LO
-	hex "Avalon DMA read controller base low"
-	default "0x80000000"
-	help
-	  Specifies the lower 32-bits of the base address of the read
-	  status and descriptor table in the Root Complex memory.
-
-config AVALON_DMA_RD_EP_DST_HI
-	hex "Avalon DMA read controller base high"
-	default "0x00000000"
-	help
-	  Specifies the upper 32-bits of the base address of the read
-	  status and descriptor table in the Root Complex memory.
-
-config AVALON_DMA_WR_EP_DST_LO
-	hex "Avalon DMA write controller base low"
-	default "0x80002000"
-	help
-	  Specifies the lower 32-bits of the base address of the write
-	  status and descriptor table in the Root Complex memory.
-
-config AVALON_DMA_WR_EP_DST_HI
-	hex "Avalon DMA write controller base high"
-	default "0x00000000"
-	help
-	  Specifies the upper 32-bits of the base address of the write
-	  status and descriptor table in the Root Complex memory.
-
-config AVALON_DMA_PCI_VENDOR_ID
-	hex "PCI vendor ID"
-	default "0x1172"
-
-config AVALON_DMA_PCI_DEVICE_ID
-	hex "PCI device ID"
-	default "0xe003"
-
-config AVALON_DMA_PCI_BAR
-	int "PCI device BAR the Avalon DMA controller is mapped to"
-	range 0 5
-	default 0
-	help
-	  Number of PCI BAR the DMA controller is mapped to
-
-config AVALON_DMA_PCI_MSI_COUNT_ORDER
-	int "Count of MSIs the PCI device provides (order)"
-	range 0 5
-	default 5
-	help
-	  Number of vectors the PCI device uses in multiple MSI mode.
-	  This number is provided as the power of two.
-
-config AVALON_DMA_PCI_MSI_VECTOR
-	int "Vector number the DMA controller is mapped to"
-	range 0 31
-	default 0
-	help
-	  Number of MSI vector the DMA controller is mapped to in
-	  multiple MSI mode.
-
-endif
diff -u b/drivers/dma/avalon/avalon-core.c b/drivers/dma/avalon/avalon-core.c
--- b/drivers/dma/avalon/avalon-core.c
+++ b/drivers/dma/avalon/avalon-core.c
@@ -13,29 +13,56 @@
 #include "avalon-core.h"
 
 #define INTERRUPT_NAME		"avalon_dma"
-#define DMA_MASK_WIDTH		CONFIG_AVALON_DMA_MASK_WIDTH
+
+static unsigned int dma_mask_width = 64;
+module_param(dma_mask_width, uint, 0644);
+MODULE_PARM_DESC(dma_mask_width, "Avalon DMA bitmask width (default: 64)");
+
+unsigned long ctrl_base;
+module_param(ctrl_base, ulong, 0644);
+MODULE_PARM_DESC(ctrl_base, "Avalon DMA controller base (default: 0)");
+
+static unsigned int rd_ep_dst_lo = 0x80000000;
+module_param(rd_ep_dst_lo, uint, 0644);
+MODULE_PARM_DESC(rd_ep_dst_lo,
+		 "Read status and desc table low (default: 0x80000000)");
+
+static unsigned int rd_ep_dst_hi = 0;
+module_param(rd_ep_dst_hi, uint, 0644);
+MODULE_PARM_DESC(rd_ep_dst_hi,
+		 "Read status and desc table hi (default: 0)");
+
+static unsigned int wr_ep_dst_lo = 0x80002000;
+module_param(wr_ep_dst_lo, uint, 0644);
+MODULE_PARM_DESC(wr_ep_dst_lo,
+		 "Write status and desc table low (default: 0x80002000)");
+
+static unsigned int wr_ep_dst_hi = 0;
+module_param(wr_ep_dst_hi, uint, 0644);
+MODULE_PARM_DESC(wr_ep_dst_hi,
+		 "Write status and desc table hi (default: 0)");
 
 static int setup_dma_descs(struct dma_desc *dma_descs,
 			   struct avalon_dma_desc *desc)
 {
-	struct scatterlist *sg_stop;
-	unsigned int sg_set;
+	unsigned int seg_stop;
+	unsigned int seg_set;
 	int ret;
 
 	ret = setup_descs_sg(dma_descs, 0,
 			     desc->direction,
 			     desc->dev_addr,
-			     desc->sg, desc->sg_len,
-			     desc->sg_curr, desc->sg_offset,
-			     &sg_stop, &sg_set);
-	BUG_ON(!ret);
-	if (ret > 0) {
-		if (sg_stop == desc->sg_curr) {
-			desc->sg_offset += sg_set;
-		} else {
-			desc->sg_curr = sg_stop;
-			desc->sg_offset = sg_set;
-		}
+			     desc->seg, desc->nr_segs,
+			     desc->seg_curr, desc->seg_off,
+			     &seg_stop, &seg_set);
+	if (ret < 0)
+		return ret;
+
+	if (seg_stop == desc->seg_curr) {
+		desc->seg_off += seg_set;
+	} else {
+		desc->seg_curr = seg_stop;
+		desc->seg_off = seg_set;
 	}
 
 	return ret;
@@ -57,8 +84,8 @@
 
 		ctrl_off = AVALON_DMA_RD_CTRL_OFFSET;
 
-		ep_dst_hi = AVALON_DMA_RD_EP_DST_HI;
-		ep_dst_lo = AVALON_DMA_RD_EP_DST_LO;
+		ep_dst_hi = rd_ep_dst_hi;
+		ep_dst_lo = rd_ep_dst_lo;
 
 		__last_id = &hw->h2d_last_id;
 	} else if (desc->direction == DMA_FROM_DEVICE) {
@@ -66,19 +93,19 @@
 
 		ctrl_off = AVALON_DMA_WR_CTRL_OFFSET;
 
-		ep_dst_hi = AVALON_DMA_WR_EP_DST_HI;
-		ep_dst_lo = AVALON_DMA_WR_EP_DST_LO;
+		ep_dst_hi = wr_ep_dst_hi;
+		ep_dst_lo = wr_ep_dst_lo;
 
 		__last_id = &hw->d2h_last_id;
 	} else {
-		BUG();
+		return -EINVAL;
 	}
 
 	table = __table->cpu_addr;
 	memset(&table->flags, 0, sizeof(table->flags));
 
 	nr_descs = setup_dma_descs(table->descs, desc);
-	if (WARN_ON(nr_descs < 1))
+	if (nr_descs < 0)
 		return nr_descs;
 
 	last_id = nr_descs - 1;
@@ -97,14 +124,10 @@
 
 static bool is_desc_complete(struct avalon_dma_desc *desc)
 {
-	struct scatterlist *sg_curr = desc->sg_curr;
-	unsigned int sg_len = sg_dma_len(sg_curr);
-
-	if (!sg_is_last(sg_curr))
+	if (desc->seg_curr < (desc->nr_segs - 1))
 		return false;
 
-	BUG_ON(desc->sg_offset > sg_len);
-	if (desc->sg_offset < sg_len)
+	if (desc->seg_off < desc->seg[desc->seg_curr].dma_len)
 		return false;
 
 	return true;
@@ -115,7 +138,6 @@
 	struct avalon_dma *adma = (struct avalon_dma *)dev_id;
 	struct avalon_dma_chan *chan = &adma->chan;
 	struct avalon_dma_hw *hw = &chan->hw;
-	spinlock_t *lock = &chan->vchan.lock;
 	u32 *rd_flags = hw->dma_desc_table_rd.cpu_addr->flags;
 	u32 *wr_flags = hw->dma_desc_table_wr.cpu_addr->flags;
 	struct avalon_dma_desc *desc;
@@ -123,28 +145,40 @@
 	bool rd_done;
 	bool wr_done;
 
-	spin_lock(lock);
+	spin_lock(&chan->vchan.lock);
 
 	rd_done = (hw->h2d_last_id < 0);
 	wr_done = (hw->d2h_last_id < 0);
 
 	if (rd_done && wr_done) {
-		spin_unlock(lock);
+		spin_unlock(&chan->vchan.lock);
 		return IRQ_NONE;
 	}
 
+	/*
+	 * The Intel documentation claims "The Descriptor Controller
+	 * writes a 1 to the done bit of the status DWORD to indicate
+	 * successful completion. The Descriptor Controller also sends
+	 * an MSI interrupt for the final descriptor. After receiving
+	 * this MSI, host software can poll the done bit to determine
+	 * status."
+	 *
+	 * The above could be read like MSI interrupt might be delivered
+	 * before the corresponding done bit is set. But in reality it
+	 * does not happen at all (or happens really rare). So put here
+	 * the done bit polling, just in case.
+	 */
 	do {
 		if (!rd_done && rd_flags[hw->h2d_last_id])
 			rd_done = true;
-
 		if (!wr_done && wr_flags[hw->d2h_last_id])
 			wr_done = true;
+		cpu_relax();
 	} while (!rd_done || !wr_done);
 
 	hw->h2d_last_id = -1;
 	hw->d2h_last_id = -1;
 
-	BUG_ON(!chan->active_desc);
 	desc = chan->active_desc;
 
 	if (is_desc_complete(desc)) {
@@ -162,12 +196,10 @@
 		}
 	}
 
-	if (chan->active_desc) {
-		BUG_ON(desc != chan->active_desc);
+	if (chan->active_desc)
 		start_dma_xfer(hw, desc);
-	}
 
-	spin_unlock(lock);
+	spin_unlock(&chan->vchan.lock);
 
 	return IRQ_HANDLED;
 }
@@ -206,19 +238,17 @@
 	hw->h2d_last_id		= -1;
 	hw->d2h_last_id		= -1;
 
-	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(DMA_MASK_WIDTH));
+	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_mask_width));
 	if (ret)
-		goto dma_set_mask_err;
+		return ret;
 
 	hw->dma_desc_table_rd.cpu_addr = dma_alloc_coherent(
 		dev,
 		sizeof(struct dma_desc_table),
 		&hw->dma_desc_table_rd.dma_addr,
 		GFP_KERNEL);
-	if (!hw->dma_desc_table_rd.cpu_addr) {
-		ret = -ENOMEM;
-		goto alloc_rd_dma_table_err;
-	}
+	if (!hw->dma_desc_table_rd.cpu_addr)
+		return -ENOMEM;
 
 	hw->dma_desc_table_wr.cpu_addr = dma_alloc_coherent(
 		dev,
@@ -227,32 +257,30 @@
 		GFP_KERNEL);
 	if (!hw->dma_desc_table_wr.cpu_addr) {
 		ret = -ENOMEM;
-		goto alloc_wr_dma_table_err;
+		goto free_table_rd;
 	}
 
 	ret = request_irq(irq, avalon_dma_interrupt, IRQF_SHARED,
 			  INTERRUPT_NAME, adma);
 	if (ret)
-		goto req_irq_err;
+		goto free_table_wr;
 
 	return 0;
 
-req_irq_err:
+free_table_wr:
 	dma_free_coherent(
 		dev,
 		sizeof(struct dma_desc_table),
 		hw->dma_desc_table_wr.cpu_addr,
 		hw->dma_desc_table_wr.dma_addr);
 
-alloc_wr_dma_table_err:
+free_table_rd:
 	dma_free_coherent(
 		dev,
 		sizeof(struct dma_desc_table),
 		hw->dma_desc_table_rd.cpu_addr,
 		hw->dma_desc_table_rd.dma_addr);
 
-alloc_rd_dma_table_err:
-dma_set_mask_err:
 	return ret;
 }
 
@@ -262,7 +290,7 @@
 	struct avalon_dma_hw *hw = &chan->hw;
 	struct device *dev = adma->dev;
 
-	free_irq(adma->irq, (void *)adma);
+	free_irq(adma->irq, adma);
 
 	dma_free_coherent(
 		dev,
@@ -282,6 +310,10 @@
 {
 	struct avalon_dma_chan *chan = to_avalon_dma_chan(dma_chan);
 
+	if (!IS_ALIGNED(config->src_addr, sizeof(u32)) ||
+	    !IS_ALIGNED(config->dst_addr, sizeof(u32)))
+		return -EINVAL;
+
 	chan->src_addr = config->src_addr;
 	chan->dst_addr = config->dst_addr;
 
@@ -296,8 +328,8 @@
 {
 	struct avalon_dma_chan *chan = to_avalon_dma_chan(dma_chan);
 	struct avalon_dma_desc *desc;
-	gfp_t gfp_flags = in_interrupt() ? GFP_NOWAIT : GFP_KERNEL;
 	dma_addr_t dev_addr;
+	int i;
 
 	if (direction == DMA_MEM_TO_DEV)
 		dev_addr = chan->dst_addr;
@@ -306,16 +338,30 @@
 	else
 		return NULL;
 
-	desc = kzalloc(sizeof(*desc), gfp_flags);
+	desc = kzalloc(struct_size(desc, seg, sg_len), GFP_NOWAIT);
 	if (!desc)
 		return NULL;
 
 	desc->direction = direction;
 	desc->dev_addr	= dev_addr;
-	desc->sg	= sg;
-	desc->sg_len	= sg_len;
-	desc->sg_curr	= sg;
-	desc->sg_offset	= 0;
+	desc->seg_curr	= 0;
+	desc->seg_off	= 0;
+	desc->nr_segs	= sg_len;
+
+	for (i = 0; i < sg_len; i++) {
+		struct dma_segment *seg = &desc->seg[i];
+		dma_addr_t dma_addr = sg_dma_address(sg);
+		unsigned int dma_len = sg_dma_len(sg);
+
+		if (!IS_ALIGNED(dma_addr, sizeof(u32)) ||
+		    !IS_ALIGNED(dma_len, sizeof(u32)))
+			return NULL;
+
+		seg->dma_addr = dma_addr;
+		seg->dma_len = dma_len;
+
+		sg = sg_next(sg);
+	}
 
 	return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
 }
@@ -324,12 +370,11 @@
 {
 	struct avalon_dma_chan *chan = to_avalon_dma_chan(dma_chan);
 	struct avalon_dma_hw *hw = &chan->hw;
-	spinlock_t *lock = &chan->vchan.lock;
 	struct avalon_dma_desc *desc;
 	struct virt_dma_desc *vdesc;
 	unsigned long flags;
 
-	spin_lock_irqsave(lock, flags);
+	spin_lock_irqsave(&chan->vchan.lock, flags);
 
 	if (!vchan_issue_pending(&chan->vchan))
 		goto out;
@@ -339,22 +384,19 @@
 	 * BOTH read and write status must be checked here!
 	 */
 	if (hw->d2h_last_id < 0 && hw->h2d_last_id < 0) {
-		BUG_ON(chan->active_desc);
+		if (chan->active_desc)
+			goto out;
 
 		vdesc = vchan_next_desc(&chan->vchan);
-		BUG_ON(!vdesc);
 		desc = to_avalon_dma_desc(vdesc);
+		chan->active_desc = desc;
 
 		if (start_dma_xfer(hw, desc))
 			goto out;
-
-		chan->active_desc = desc;
-	} else {
-		BUG_ON(!chan->active_desc);
 	}
 
 out:
-	spin_unlock_irqrestore(lock, flags);
+	spin_unlock_irqrestore(&chan->vchan.lock, flags);
 }
 
 static void avalon_dma_desc_free(struct virt_dma_desc *vdesc)
@@ -379,7 +421,7 @@
 
 	ret = avalon_dma_init(adma, dev, regs, irq);
 	if (ret)
-		goto avalon_init_err;
+		goto err;
 
 	dev->dma_parms = &adma->dma_parms;
 	dma_set_max_seg_size(dev, UINT_MAX);
@@ -405,20 +447,22 @@
 	INIT_LIST_HEAD(&dma_dev->channels);
 
 	chan = &adma->chan;
+	chan->src_addr = -1;
+	chan->dst_addr = -1;
 	chan->vchan.desc_free = avalon_dma_desc_free;
+
 	vchan_init(&chan->vchan, dma_dev);
 
 	ret = dma_async_device_register(dma_dev);
 	if (ret)
-		goto dma_dev_reg;
+		goto err;
 
 	return adma;
 
-dma_dev_reg:
-avalon_init_err:
+err:
 	kfree(adma);
 
-	return NULL;
+	return ERR_PTR(ret);
 }
 
 void avalon_dma_unregister(struct avalon_dma *adma)
diff -u b/drivers/dma/avalon/avalon-core.h b/drivers/dma/avalon/avalon-core.h
--- b/drivers/dma/avalon/avalon-core.h
+++ b/drivers/dma/avalon/avalon-core.h
@@ -12,6 +12,8 @@
 
 #include "../virt-dma.h"
 
+#include "avalon-hw.h"
+
 struct avalon_dma_desc {
 	struct virt_dma_desc	vdesc;
 
@@ -19,11 +21,11 @@
 
 	dma_addr_t		dev_addr;
 
-	struct scatterlist	*sg;
-	unsigned int		sg_len;
+	unsigned int		seg_curr;
+	unsigned int		seg_off;
 
-	struct scatterlist	*sg_curr;
-	unsigned int		sg_offset;
+	unsigned int		nr_segs;
+	struct dma_segment	seg[];
 };
 
 struct avalon_dma_hw {
diff -u b/drivers/dma/avalon/avalon-hw.c b/drivers/dma/avalon/avalon-hw.c
--- b/drivers/dma/avalon/avalon-hw.c
+++ b/drivers/dma/avalon/avalon-hw.c
@@ -5,19 +5,14 @@
  * Author: Alexander Gordeev <a.gordeev.box@gmail.com>
  */
 #include <linux/kernel.h>
-#include <linux/delay.h>
 
 #include "avalon-hw.h"
 
-#define DMA_DESC_MAX	AVALON_DMA_DESC_NUM
+#define DMA_DESC_MAX		AVALON_DMA_DESC_NUM
 
 static void setup_desc(struct dma_desc *desc, u32 desc_id,
 		       u64 dest, u64 src, u32 size)
 {
-	BUG_ON(!size);
-	WARN_ON(!IS_ALIGNED(size, sizeof(u32)));
-	BUG_ON(desc_id > (DMA_DESC_MAX - 1));
-
 	desc->src_lo = cpu_to_le32(src & 0xfffffffful);
 	desc->src_hi = cpu_to_le32((src >> 32));
 	desc->dst_lo = cpu_to_le32(dest & 0xfffffffful);
@@ -39,25 +34,17 @@
 	dma_addr_t src;
 	dma_addr_t dest;
 
+	if (desc_id >= DMA_DESC_MAX)
+		return -EINVAL;
+
 	if (direction == DMA_TO_DEVICE) {
 		src = host_addr;
 		dest = dev_addr;
-	} else if (direction == DMA_FROM_DEVICE) {
+	} else {
 		src = dev_addr;
 		dest = host_addr;
-	} else {
-		BUG();
-		return -EINVAL;
-	}
-
-	if (unlikely(desc_id > DMA_DESC_MAX - 1)) {
-		BUG();
-		return -EINVAL;
 	}
 
-	if (WARN_ON(!len))
-		return -EINVAL;
-
 	while (len) {
 		unsigned int xfer_len = min_t(unsigned int, len, AVALON_DMA_MAX_TANSFER_SIZE);
 
@@ -89,111 +76,98 @@
 int setup_descs_sg(struct dma_desc *descs, unsigned int desc_id,
 		   enum dma_data_direction direction,
 		   dma_addr_t dev_addr,
-		   struct scatterlist *__sg, unsigned int __sg_len,
-		   struct scatterlist *sg_start, unsigned int sg_offset,
-		   struct scatterlist **_sg_stop, unsigned int *_sg_set)
+		   struct dma_segment *seg, unsigned int nr_segs,
+		   unsigned int seg_start, unsigned int seg_off,
+		   unsigned int *seg_stop, unsigned int *seg_set)
 {
-	struct scatterlist *sg;
-	dma_addr_t sg_addr;
-	unsigned int sg_len;
-	unsigned int sg_set;
+	unsigned int set = -1;
 	int nr_descs = 0;
 	int ret;
 	int i;
 
-	/*
-	 * Find the SGE that the previous xfer has stopped on -
-	 * it should exist.
-	 */
-	for_each_sg(__sg, sg, __sg_len, i) {
-		if (sg == sg_start)
-			break;
-
-		dev_addr += sg_dma_len(sg);
-	}
-
-	if (WARN_ON(i >= __sg_len))
+	if (seg_start >= nr_segs)
+		return -EINVAL;
+	if ((direction != DMA_TO_DEVICE) && (direction != DMA_FROM_DEVICE))
 		return -EINVAL;
 
 	/*
-	 * The offset can not be longer than the SGE length.
+	 * Skip all SGEs that have been fully transmitted.
 	 */
-	sg_len = sg_dma_len(sg);
-	if (WARN_ON(sg_len < sg_offset))
-		return -EINVAL;
+	for (i = 0; i < seg_start; i++)
+		dev_addr += seg[i].dma_len;
 
 	/*
-	 * Skip the starting SGE if it has been fully transmitted.
+	 * Skip the current SGE if it has been fully transmitted.
 	 */
-	if (sg_offset == sg_len) {
-		if (WARN_ON(sg_is_last(sg)))
-			return -EINVAL;
-
-		dev_addr += sg_len;
-		sg_offset = 0;
-
+	if (seg[i].dma_len == seg_off) {
+		dev_addr += seg_off;
+		seg_off = 0;
 		i++;
-		sg = sg_next(sg);
 	}
 
 	/*
 	 * Setup as many SGEs as the controller is able to transmit.
 	 */
-	BUG_ON(i >= __sg_len);
-	for (; i < __sg_len; i++) {
-		sg_addr = sg_dma_address(sg);
-		sg_len = sg_dma_len(sg);
-
-		if (sg_offset) {
-			if (unlikely(sg_len <= sg_offset)) {
-				BUG();
-				return -EINVAL;
-			}
-
-			dev_addr += sg_offset;
-			sg_addr += sg_offset;
-			sg_len -= sg_offset;
+	for (; i < nr_segs; i++) {
+		dma_addr_t dma_addr = seg[i].dma_addr;
+		unsigned int dma_len = seg[i].dma_len;
+
+		/*
+		 * The offset can not be longer than the SGE length.
+		 */
+		if (dma_len < seg_off)
+			return -EINVAL;
+
+		if (seg_off) {
+			dev_addr += seg_off;
+			dma_addr += seg_off;
+			dma_len -= seg_off;
 
-			sg_offset = 0;
+			seg_off = 0;
 		}
 
 		ret = setup_descs(descs, desc_id, direction,
-				  dev_addr, sg_addr, sg_len, &sg_set);
+				  dev_addr, dma_addr, dma_len, &set);
 		if (ret < 0)
 			return ret;
 
-		if (unlikely((desc_id + ret > DMA_DESC_MAX) ||
-			     (nr_descs + ret > DMA_DESC_MAX))) {
-			BUG();
-			return -ENOMEM;
-		}
+		if ((desc_id + ret > DMA_DESC_MAX) ||
+		    (nr_descs + ret > DMA_DESC_MAX))
+			return -EINVAL;
 
 		nr_descs += ret;
 		desc_id += ret;
 
-		if (desc_id >= DMA_DESC_MAX)
+		/*
+		 * Stop when descriptor table entries are exhausted.
+		 */
+		if (desc_id == DMA_DESC_MAX)
 			break;
 
-		if (unlikely(sg_len != sg_set)) {
-			BUG();
+		/*
+		 * The descriptor table still has free entries, thus
+		 * the current SGE should have fit.
+		 */
+		if (dma_len != set)
 			return -EINVAL;
-		}
 
-		if (sg_is_last(sg))
+		if (i >= nr_segs - 1)
 			break;
 
 		descs += ret;
-		dev_addr += sg_len;
-
-		sg = sg_next(sg);
+		dev_addr += dma_len;
 	}
 
 	/*
-	 * Remember the SGE that next transmission should be started from
+	 * Remember the SGE that next transmission should be started from.
 	 */
-	BUG_ON(!sg);
-	*_sg_stop = sg;
-	*_sg_set = sg_set;
+	if (nr_descs) {
+		*seg_stop = i;
+		*seg_set = set;
+	} else {
+		*seg_stop = seg_start;
+		*seg_set = seg_off;
+	}
 
 	return nr_descs;
 }
diff -u b/drivers/dma/avalon/avalon-hw.h b/drivers/dma/avalon/avalon-hw.h
--- b/drivers/dma/avalon/avalon-hw.h
+++ b/drivers/dma/avalon/avalon-hw.h
@@ -8,21 +8,39 @@
 #define __AVALON_HW_H__
 
 #include <linux/dma-direction.h>
-#include <linux/scatterlist.h>
+#include <linux/io.h>
 
 #define AVALON_DMA_DESC_NUM		128
 
 #define AVALON_DMA_FIXUP_SIZE		0x100
 #define AVALON_DMA_MAX_TANSFER_SIZE	(0x100000 - AVALON_DMA_FIXUP_SIZE)
 
-#define AVALON_DMA_CTRL_BASE		CONFIG_AVALON_DMA_CTRL_BASE
 #define AVALON_DMA_RD_CTRL_OFFSET	0x0
 #define AVALON_DMA_WR_CTRL_OFFSET	0x100
 
-#define AVALON_DMA_RD_EP_DST_LO		CONFIG_AVALON_DMA_RD_EP_DST_LO
-#define AVALON_DMA_RD_EP_DST_HI		CONFIG_AVALON_DMA_RD_EP_DST_HI
-#define AVALON_DMA_WR_EP_DST_LO		CONFIG_AVALON_DMA_WR_EP_DST_LO
-#define AVALON_DMA_WR_EP_DST_HI		CONFIG_AVALON_DMA_WR_EP_DST_HI
+extern unsigned long ctrl_base;
+
+static inline
+u32 __av_read32(void __iomem *base, size_t ctrl_off, size_t reg_off)
+{
+	size_t offset = ctrl_base + ctrl_off + reg_off;
+
+	return ioread32(base + offset);
+}
+
+static inline
+void __av_write32(u32 val,
+		  void __iomem *base, size_t ctrl_off, size_t reg_off)
+{
+	size_t offset = ctrl_base + ctrl_off + reg_off;
+
+	iowrite32(val, base + offset);
+}
+
+#define av_read32(b, o, r) \
+	__av_read32(b, o, offsetof(struct dma_ctrl, r))
+#define av_write32(v, b, o, r) \
+	__av_write32(v, b, o, offsetof(struct dma_ctrl, r))
 
 struct dma_ctrl {
 	u32 rc_src_lo;
@@ -48,36 +66,17 @@
 	struct dma_desc descs[AVALON_DMA_DESC_NUM];
 } __packed;
 
-static inline u32 __av_read32(void __iomem *base,
-			      size_t ctrl_off,
-			      size_t reg_off)
-{
-	size_t offset = AVALON_DMA_CTRL_BASE + ctrl_off + reg_off;
-
-	return ioread32(base + offset);
-}
-
-static inline void __av_write32(u32 value,
-				void __iomem *base,
-				size_t ctrl_off,
-				size_t reg_off)
-{
-	size_t offset = AVALON_DMA_CTRL_BASE + ctrl_off + reg_off;
-
-	iowrite32(value, base + offset);
-}
-
-#define av_read32(b, o, r) \
-	__av_read32(b, o, offsetof(struct dma_ctrl, r))
-#define av_write32(v, b, o, r) \
-	__av_write32(v, b, o, offsetof(struct dma_ctrl, r))
+struct dma_segment {
+	dma_addr_t	dma_addr;
+	unsigned int	dma_len;
+};
 
 int setup_descs_sg(struct dma_desc *descs, unsigned int desc_id,
 		   enum dma_data_direction direction,
 		   dma_addr_t dev_addr,
-		   struct scatterlist *__sg, unsigned int __sg_len,
-		   struct scatterlist *sg_start, unsigned int sg_offset,
-		   struct scatterlist **_sg_stop, unsigned int *_sg_set);
+		   struct dma_segment *seg, unsigned int nr_segs,
+		   unsigned int seg_start, unsigned int sg_off,
+		   unsigned int *seg_stop, unsigned int *seg_set);
 
 void start_xfer(void __iomem *base, size_t ctrl_off,
 		u32 rc_src_hi, u32 rc_src_lo,
diff -u b/drivers/dma/avalon/avalon-pci.c b/drivers/dma/avalon/avalon-pci.c
--- b/drivers/dma/avalon/avalon-pci.c
+++ b/drivers/dma/avalon/avalon-pci.c
@@ -10,40 +10,46 @@
 
 #include "avalon-core.h"
 
-#define DRIVER_NAME		"avalon-dma"
+#define DRIVER_NAME "avalon-dma"
 
-#define PCI_BAR			CONFIG_AVALON_DMA_PCI_BAR
-#define PCI_MSI_VECTOR		CONFIG_AVALON_DMA_PCI_MSI_VECTOR
-#define PCI_MSI_COUNT		BIT(CONFIG_AVALON_DMA_PCI_MSI_COUNT_ORDER)
-
-#define AVALON_DMA_PCI_VENDOR_ID	CONFIG_AVALON_DMA_PCI_VENDOR_ID
-#define AVALON_DMA_PCI_DEVICE_ID	CONFIG_AVALON_DMA_PCI_DEVICE_ID
+static unsigned int pci_bar = 0;
+module_param(pci_bar, uint, 0644);
+MODULE_PARM_DESC(pci_bar,
+		 "PCI BAR number the controller is mapped to (default: 0)");
+
+static unsigned int pci_msi_vector = 0;
+module_param(pci_msi_vector, uint, 0644);
+MODULE_PARM_DESC(pci_msi_vector,
+		 "MSI vector number used for the controller (default: 0)");
+
+static unsigned int pci_msi_count_order = 5;
+module_param(pci_msi_count_order, uint, 0644);
+MODULE_PARM_DESC(pci_msi_count_order,
+		 "Number of MSI vectors (order) device uses (default: 5)");
 
 static int init_interrupts(struct pci_dev *pci_dev)
 {
+	unsigned int nr_vecs = BIT(pci_msi_count_order);
 	int ret;
 
-	ret = pci_alloc_irq_vectors(pci_dev,
-				    PCI_MSI_COUNT, PCI_MSI_COUNT,
-				    PCI_IRQ_MSI);
+	ret = pci_alloc_irq_vectors(pci_dev, nr_vecs, nr_vecs, PCI_IRQ_MSI);
 	if (ret < 0) {
-		goto msi_err;
-	} else if (ret != PCI_MSI_COUNT) {
+		return ret;
+
+	} else if (ret != nr_vecs) {
 		ret = -ENOSPC;
-		goto nr_msi_err;
+		goto disable_msi;
 	}
 
-	ret = pci_irq_vector(pci_dev, PCI_MSI_VECTOR);
+	ret = pci_irq_vector(pci_dev, pci_msi_vector);
 	if (ret < 0)
-		goto vec_err;
+		goto disable_msi;
 
 	return ret;
 
-vec_err:
-nr_msi_err:
+disable_msi:
 	pci_disable_msi(pci_dev);
 
-msi_err:
 	return ret;
 }
 
@@ -52,8 +58,8 @@
 	pci_disable_msi(pci_dev);
 }
 
-static int __init avalon_pci_probe(struct pci_dev *pci_dev,
-				   const struct pci_device_id *id)
+static int avalon_pci_probe(struct pci_dev *pci_dev,
+			    const struct pci_device_id *id)
 {
 	void *adma;
 	void __iomem *regs;
@@ -61,26 +67,26 @@
 
 	ret = pci_enable_device(pci_dev);
 	if (ret)
-		goto enable_err;
+		return ret;
 
 	ret = pci_request_regions(pci_dev, DRIVER_NAME);
 	if (ret)
-		goto reg_err;
+		goto disable_device;
 
-	regs = pci_ioremap_bar(pci_dev, PCI_BAR);
+	regs = pci_ioremap_bar(pci_dev, pci_bar);
 	if (!regs) {
 		ret = -ENOMEM;
-		goto ioremap_err;
+		goto release_regions;
 	}
 
 	ret = init_interrupts(pci_dev);
 	if (ret < 0)
-		goto int_err;
+		goto unmap_bars;
 
 	adma = avalon_dma_register(&pci_dev->dev, regs, ret);
 	if (IS_ERR(adma)) {
 		ret = PTR_ERR(adma);
-		goto dma_err;
+		goto terminate_interrupts;
 	}
 
 	pci_set_master(pci_dev);
@@ -88,23 +94,22 @@
 
 	return 0;
 
-dma_err:
+terminate_interrupts:
 	term_interrupts(pci_dev);
 
-int_err:
+unmap_bars:
 	pci_iounmap(pci_dev, regs);
 
-ioremap_err:
+release_regions:
 	pci_release_regions(pci_dev);
 
-reg_err:
+disable_device:
 	pci_disable_device(pci_dev);
 
-enable_err:
 	return ret;
 }
 
-static void __exit avalon_pci_remove(struct pci_dev *pci_dev)
+static void avalon_pci_remove(struct pci_dev *pci_dev)
 {
 	void *adma = pci_get_drvdata(pci_dev);
 	void __iomem *regs = avalon_dma_mmio(adma);
@@ -122,29 +127,18 @@
-static struct pci_device_id pci_ids[] = {
-	{ PCI_DEVICE(AVALON_DMA_PCI_VENDOR_ID, AVALON_DMA_PCI_DEVICE_ID) },
+static struct pci_device_id avalon_pci_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_ALTERA, 0xe003) },
 	{ 0 }
 };
 
-static struct pci_driver dma_driver_ops = {
+static struct pci_driver avalon_pci_driver = {
 	.name		= DRIVER_NAME,
-	.id_table	= pci_ids,
+	.id_table	= avalon_pci_ids,
 	.probe		= avalon_pci_probe,
 	.remove		= avalon_pci_remove,
 };
 
-static int __init avalon_drv_init(void)
-{
-	return pci_register_driver(&dma_driver_ops);
-}
-
-static void __exit avalon_drv_exit(void)
-{
-	pci_unregister_driver(&dma_driver_ops);
-}
-
-module_init(avalon_drv_init);
-module_exit(avalon_drv_exit);
+module_pci_driver(avalon_pci_driver);
 
 MODULE_AUTHOR("Alexander Gordeev <a.gordeev.box@gmail.com>");
 MODULE_DESCRIPTION("Avalon-MM DMA Interface for PCIe");
 MODULE_LICENSE("GPL v2");
-MODULE_DEVICE_TABLE(pci, pci_ids);
+MODULE_DEVICE_TABLE(pci, avalon_pci_ids);


Signed-off-by: Alexander Gordeev <a.gordeev.box@gmail.com>

-- 
2.23.0


             reply	other threads:[~2019-10-16 11:22 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-10-16 11:21 Alexander Gordeev [this message]
2019-10-16 11:21 ` [PATCH v3 1/1] dmaengine: avalon: Intel Avalon-MM DMA Interface for PCIe Alexander Gordeev

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cover.1571224166.git.a.gordeev.box@gmail.com \
    --to=a.gordeev.box@gmail.com \
    --cc=dmaengine@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.