[RFC PATCH v4 37/38] misc: pci_endpoint_test: Add remote eDMA transfer test mode

Koichiro Den <[email protected]>
Newsgroups dev.linux.lists.ntb,dev.linux.lists.iommu,org.kernel.vger.dmaengine,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kernel.vger.linux-pci,org.kernel.vger.linux-renesas-soc,org.kernel.vger.netdev
Message-ID <[email protected]>
Add a new test mode controlled by a flag, PCITEST_FLAGS_USE_REMOTE_EDMA.
When requested, the driver:
- Issues COMMAND_REMOTE_EDMA_SETUP to the endpoint and locates the BAR
  containing a pcitest_edma_info header (magic/version).
- Creates a remote dw-edma instance by ioremapping the endpoint's
  exposed eDMA registers and linked-list regions and probing dw-edma on
  top of it.
- Requests one DMA_SLAVE channel per direction and performs the
  transfer.
- Uses COMMAND_REMOTE_EDMA_CHECKSUM to validate the result when the
  transfer direction is host-to-endpoint. For the opposite direction,
  the endpoint provides the expected checksum up front.

One MSI/MSI-X vector is reserved for the remote dw-edma instance by
freeing the last test IRQ vector. This keeps existing MSI/MSI-X tests
unchanged unless the remote-eDMA mode is invoked.

BAR read/write tests skip the BAR reserved for remote-eDMA metadata to
avoid corrupting the eDMA window.

Signed-off-by: Koichiro Den <[email protected]>
---
 drivers/misc/pci_endpoint_test.c | 633 +++++++++++++++++++++++++++++++
 include/uapi/linux/pcitest.h     |   3 +-
 2 files changed, 635 insertions(+), 1 deletion(-)

diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c
index 1c0fd185114f..52d700374ac6 100644
--- a/drivers/misc/pci_endpoint_test.c
+++ b/drivers/misc/pci_endpoint_test.c
@@ -8,7 +8,10 @@
 
 #include <linux/crc32.h>
 #include <linux/cleanup.h>
+#include <linux/completion.h>
 #include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
 #include <linux/fs.h>
 #include <linux/io.h>
 #include <linux/interrupt.h>
@@ -17,6 +20,7 @@
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/random.h>
+#include <linux/scatterlist.h>
 #include <linux/slab.h>
 #include <linux/uaccess.h>
 #include <linux/pci.h>
@@ -39,6 +43,8 @@
 #define COMMAND_COPY				BIT(5)
 #define COMMAND_ENABLE_DOORBELL			BIT(6)
 #define COMMAND_DISABLE_DOORBELL		BIT(7)
+#define COMMAND_REMOTE_EDMA_SETUP		BIT(8)
+#define COMMAND_REMOTE_EDMA_CHECKSUM		BIT(9)
 
 #define PCI_ENDPOINT_TEST_STATUS		0x8
 #define STATUS_READ_SUCCESS			BIT(0)
@@ -55,6 +61,10 @@
 #define STATUS_DOORBELL_ENABLE_FAIL		BIT(11)
 #define STATUS_DOORBELL_DISABLE_SUCCESS		BIT(12)
 #define STATUS_DOORBELL_DISABLE_FAIL		BIT(13)
+#define STATUS_REMOTE_EDMA_SETUP_SUCCESS	BIT(14)
+#define STATUS_REMOTE_EDMA_SETUP_FAIL		BIT(15)
+#define STATUS_REMOTE_EDMA_CHECKSUM_SUCCESS	BIT(16)
+#define STATUS_REMOTE_EDMA_CHECKSUM_FAIL	BIT(17)
 
 #define PCI_ENDPOINT_TEST_LOWER_SRC_ADDR	0x0c
 #define PCI_ENDPOINT_TEST_UPPER_SRC_ADDR	0x10
@@ -130,6 +140,9 @@ struct pci_endpoint_test {
 	size_t alignment;
 	u32 ep_caps;
 	const char *name;
+
+	/* For extended tests that rely on vendor-specific features */
+	void *data;
 };
 
 struct pci_endpoint_test_data {
@@ -149,6 +162,610 @@ static inline void pci_endpoint_test_writel(struct pci_endpoint_test *test,
 	writel(value, test->base + offset);
 }
 
+static irqreturn_t pci_endpoint_test_irqhandler(int irq, void *dev_id);
+
+#if IS_REACHABLE(CONFIG_DW_EDMA)
+#include <linux/dma/edma.h>
+
+#define PCITEST_EDMA_INFO_MAGIC		0x414d4445U /* 'EDMA' */
+#define PCITEST_EDMA_INFO_VERSION	0x00010000U
+
+struct pci_endpoint_test_edma {
+	bool			probed;
+	void __iomem		*bar_base;
+	int			irq;
+
+	/* Remote dw-edma instance */
+	struct dw_edma_chip	chip;
+
+	/* One channel per direction */
+	struct dma_chan		*m2d;
+	struct dma_chan		*d2m;
+};
+
+struct pcitest_edma_info {
+	__le32 magic;
+	__le32 version;
+
+	__le32 reg_off;
+	__le32 reg_size;
+
+	__le64 ll_rd_phys;
+	__le32 ll_rd_off;
+	__le32 ll_rd_size;
+
+	__le64 ll_wr_phys;
+	__le32 ll_wr_off;
+	__le32 ll_wr_size;
+
+	__le64 test_buf_phys;
+	__le32 test_buf_size;
+};
+
+struct pci_endpoint_test_edma_filter {
+	struct device *dma_dev;
+	unsigned long direction;
+};
+
+static bool test_edma_filter_fn(struct dma_chan *chan, void *param)
+{
+	struct pci_endpoint_test_edma_filter *filter = param;
+	u32 dir = filter->direction;
+	struct dma_slave_caps caps;
+	int ret;
+
+	if (chan->device->dev != filter->dma_dev)
+		return false;
+
+	ret = dma_get_slave_caps(chan, &caps);
+	if (ret < 0)
+		return false;
+
+	return !!(caps.directions & dir);
+}
+
+static int pci_endpoint_test_edma_irq_vector(struct device *dev, unsigned int nr)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct pci_endpoint_test *test = pci_get_drvdata(pdev);
+	struct pci_endpoint_test_edma *edma;
+
+	if (!test)
+		return -EINVAL;
+
+	edma = test->data;
+	if (!edma)
+		return -EINVAL;
+
+	/*
+	 * Only one vector is reserved for remote eDMA use, thus 'nr' is
+	 * ignored. See pci_endpoint_test_edma_reserve_irq().
+	 */
+	return pci_irq_vector(pdev, edma->irq);
+}
+
+static enum pci_barno pci_endpoint_test_edma_bar(struct pci_dev *pdev)
+{
+	int bar;
+
+	for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+		void __iomem *base;
+		u32 magic;
+
+		if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM))
+			continue;
+		if (!pci_resource_len(pdev, bar))
+			continue;
+
+		base = pci_iomap_range(pdev, bar, 0, sizeof(u32));
+		if (!base)
+			continue;
+
+		magic = ioread32(base);
+		pci_iounmap(pdev, base);
+
+		if (magic == PCITEST_EDMA_INFO_MAGIC)
+			return bar;
+	}
+	return NO_BAR;
+}
+
+static bool pci_endpoint_test_bar_is_reserved(struct pci_endpoint_test *test,
+					      enum pci_barno barno)
+{
+	struct pci_dev *pdev = test->pdev;
+	enum pci_barno edma_bar = pci_endpoint_test_edma_bar(pdev);
+
+	return barno == NO_BAR || barno == edma_bar;
+}
+
+static void pci_endpoint_test_dw_edma_cleanup(struct pci_endpoint_test *test,
+					      struct pci_endpoint_test_edma *edma)
+{
+	if (!edma)
+		return;
+
+	if (edma->m2d) {
+		dmaengine_terminate_sync(edma->m2d);
+		dma_release_channel(edma->m2d);
+		edma->m2d = NULL;
+	}
+
+	if (edma->d2m) {
+		dmaengine_terminate_sync(edma->d2m);
+		dma_release_channel(edma->d2m);
+		edma->d2m = NULL;
+	}
+
+	if (edma->probed) {
+		dw_edma_remove(&edma->chip);
+		edma->probed = false;
+	}
+
+	if (edma->bar_base) {
+		pci_iounmap(test->pdev, edma->bar_base);
+		edma->bar_base = NULL;
+	}
+}
+
+static void pci_endpoint_test_remote_edma_teardown(struct pci_endpoint_test *test)
+{
+	struct pci_endpoint_test_edma *edma = test->data;
+
+	pci_endpoint_test_dw_edma_cleanup(test, edma);
+	kfree(edma);
+	test->data = NULL;
+}
+
+/*
+ * Reserve exactly one IRQ vector for dw-edma by freeing the last handler.
+ * This avoids changing existing MSI/MSI-X tests unless remote eDMA is used.
+ */
+static int pci_endpoint_test_edma_reserve_irq(struct pci_endpoint_test *test)
+{
+	struct pci_dev *pdev = test->pdev;
+
+	if (test->irq_type != PCITEST_IRQ_TYPE_MSI &&
+	    test->irq_type != PCITEST_IRQ_TYPE_MSIX)
+		return -EOPNOTSUPP;
+
+	if (test->num_irqs < 2)
+		return -ENOSPC;
+
+	/* use the last vector for remote eDMA use */
+	free_irq(pci_irq_vector(pdev, test->num_irqs - 1), test);
+	return test->num_irqs - 1;
+}
+
+static void pci_endpoint_test_edma_restore_irq(struct pci_endpoint_test *test)
+{
+	struct pci_dev *pdev = test->pdev;
+	int ret;
+
+	ret = request_irq(pci_irq_vector(pdev, test->num_irqs - 1),
+			  pci_endpoint_test_irqhandler, IRQF_SHARED, test->name,
+			  test);
+	if (ret)
+		dev_warn(&pdev->dev,
+			 "failed to restore IRQ vector %d after remote eDMA: %d\n",
+			 test->num_irqs - 1, ret);
+}
+
+static const struct dw_edma_plat_ops test_edma_ops = {
+	.irq_vector     = pci_endpoint_test_edma_irq_vector,
+};
+
+static int pci_endpoint_test_dw_edma_setup(struct pci_endpoint_test *test)
+{
+	struct pci_endpoint_test_edma *edma = test->data;
+	struct pci_endpoint_test_edma_filter f;
+	struct pci_endpoint_test_edma *new;
+	struct pci_dev *pdev = test->pdev;
+	struct device *dev = &pdev->dev;
+	struct pcitest_edma_info info;
+	resource_size_t bar_size;
+	u32 ll_rd_off, ll_rd_size;
+	u32 ll_wr_off, ll_wr_size;
+	u32 reg_off, reg_size;
+	dma_cap_mask_t mask;
+	enum pci_barno bar;
+	int ret;
+
+	if (edma && edma->probed)
+		return 0;
+
+	new = kzalloc_obj(*new, GFP_KERNEL);
+	if (!new)
+		return -ENOMEM;
+
+	ret = pci_endpoint_test_edma_reserve_irq(test);
+	if (ret < 0)
+		goto err_free;
+	new->irq = ret;
+
+	bar = pci_endpoint_test_edma_bar(pdev);
+	if (bar == NO_BAR) {
+		ret = -EOPNOTSUPP;
+		goto err_restore_irq;
+	}
+
+	new->bar_base = pci_iomap(pdev, bar, 0);
+	if (!new->bar_base) {
+		ret = -ENOMEM;
+		goto err_restore_irq;
+	}
+	bar_size = pci_resource_len(pdev, bar);
+
+	/* Snapshot the info (avoid repeated __iomem reads). */
+	memcpy_fromio(&info, new->bar_base, sizeof(info));
+	if (le32_to_cpu(info.magic) != PCITEST_EDMA_INFO_MAGIC ||
+	    le32_to_cpu(info.version) != PCITEST_EDMA_INFO_VERSION) {
+		dev_err(&pdev->dev, "Invalid eDMA info\n");
+		ret = -EINVAL;
+		goto err_cleanup;
+	}
+
+	reg_off = le32_to_cpu(info.reg_off);
+	reg_size = le32_to_cpu(info.reg_size);
+	ll_rd_off = le32_to_cpu(info.ll_rd_off);
+	ll_rd_size = le32_to_cpu(info.ll_rd_size);
+	ll_wr_off = le32_to_cpu(info.ll_wr_off);
+	ll_wr_size = le32_to_cpu(info.ll_wr_size);
+
+	if (reg_off > bar_size || reg_size > bar_size - reg_off ||
+	    ll_rd_off > bar_size || ll_rd_size > bar_size - ll_rd_off ||
+	    ll_wr_off > bar_size || ll_wr_size > bar_size - ll_wr_off) {
+		dev_err(&pdev->dev, "eDMA info offsets out of BAR range\n");
+		ret = -EINVAL;
+		goto err_cleanup;
+	}
+
+	memset(&new->chip, 0, sizeof(new->chip));
+	new->chip.dev = &pdev->dev;
+	new->chip.mf = EDMA_MF_EDMA_UNROLL;
+	new->chip.nr_irqs = 1;
+	new->chip.ops = &test_edma_ops;
+	new->chip.reg_base = new->bar_base + reg_off;
+	new->chip.ll_rd_cnt = 1;
+	new->chip.ll_region_rd[0].paddr = le64_to_cpu(info.ll_rd_phys);
+	new->chip.ll_region_rd[0].vaddr.io = new->bar_base + ll_rd_off;
+	new->chip.ll_region_rd[0].sz = ll_rd_size;
+	new->chip.ll_wr_cnt = 1;
+	new->chip.ll_region_wr[0].paddr = le64_to_cpu(info.ll_wr_phys);
+	new->chip.ll_region_wr[0].vaddr.io = new->bar_base + ll_wr_off;
+	new->chip.ll_region_wr[0].sz = ll_wr_size;
+
+	test->data = new;
+	ret = dw_edma_probe(&new->chip);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to probe eDMA: %d\n", ret);
+		goto err_cleanup;
+	}
+	new->probed = true;
+
+	/* Request one channel per direction. */
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+	f.dma_dev = dev;
+	f.direction = BIT(DMA_MEM_TO_DEV);
+	new->m2d = dma_request_channel(mask, test_edma_filter_fn, &f);
+	f.direction = BIT(DMA_DEV_TO_MEM);
+	new->d2m = dma_request_channel(mask, test_edma_filter_fn, &f);
+	if (!new->m2d || !new->d2m) {
+		ret = -ENODEV;
+		goto err_cleanup;
+	}
+
+	/*
+	 * Best-effort attempt, ie. even if it fails for some reason, the
+	 * endpoint will ignore endpoint-local interrupts (edma_int bus).
+	 */
+	dw_edma_chan_irq_config(new->m2d, DW_EDMA_CH_IRQ_REMOTE);
+	dw_edma_chan_irq_config(new->d2m, DW_EDMA_CH_IRQ_REMOTE);
+
+	return 0;
+err_cleanup:
+	pci_endpoint_test_dw_edma_cleanup(test, new);
+err_restore_irq:
+	pci_endpoint_test_edma_restore_irq(test);
+err_free:
+	kfree(new);
+	test->data = NULL;
+	return ret;
+}
+
+static int pci_endpoint_test_remote_edma_setup(struct pci_endpoint_test *test,
+					       size_t size)
+{
+	struct pci_dev *pdev = test->pdev;
+	struct device *dev = &pdev->dev;
+	unsigned long left;
+	u32 status;
+
+	/* Same rule as existing tests: IRQ type must be configured first */
+	if (test->irq_type != PCITEST_IRQ_TYPE_MSI &&
+	    test->irq_type != PCITEST_IRQ_TYPE_MSIX) {
+		dev_err(dev, "Invalid IRQ type for remote eDMA\n");
+		return -EINVAL;
+	}
+
+	/* Need one spare vector for dw-edma */
+	if (test->num_irqs < 2)
+		return -ENOSPC;
+
+	/*
+	 * Ensure EP command handler won't reject us due to stale flags.
+	 * (remote-eDMA setup itself is not "FLAG_USE_DMA")
+	 */
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_FLAGS, 0);
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE,
+				 test->irq_type);
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1);
+
+	reinit_completion(&test->irq_raised);
+	test->last_irq = -ENODATA;
+
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, size);
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
+				 COMMAND_REMOTE_EDMA_SETUP);
+
+	left = wait_for_completion_timeout(&test->irq_raised,
+					   msecs_to_jiffies(1000));
+	if (!left)
+		return -ETIMEDOUT;
+
+	status = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS);
+	if (status & STATUS_REMOTE_EDMA_SETUP_FAIL) {
+		dev_err(dev, "Endpoint failed to setup remote eDMA window\n");
+		return -EIO;
+	}
+	if (!(status & STATUS_REMOTE_EDMA_SETUP_SUCCESS)) {
+		dev_err(dev,
+			"Endpoint did not report remote eDMA setup success\n");
+		return -EIO;
+	}
+
+	return pci_endpoint_test_dw_edma_setup(test);
+}
+
+static int pci_endpoint_test_edma_xfer(struct pci_dev *pdev,
+				       struct pci_endpoint_test_edma *edma,
+				       void *buf, size_t len,
+				       dma_addr_t dev_addr,
+				       enum dma_transfer_direction dir)
+{
+	struct dma_async_tx_descriptor *tx;
+	enum dma_data_direction map_dir;
+	struct device *dev = &pdev->dev;
+	struct dma_slave_config cfg;
+	struct completion done;
+	struct dma_chan *chan;
+	struct scatterlist sg;
+	dma_cookie_t cookie;
+	int ret;
+
+	memset(&cfg, 0, sizeof(cfg));
+	if (dir == DMA_MEM_TO_DEV) {
+		chan = edma->m2d;
+		map_dir = DMA_TO_DEVICE;
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.dst_addr = dev_addr;
+	} else if (dir == DMA_DEV_TO_MEM) {
+		chan = edma->d2m;
+		map_dir = DMA_FROM_DEVICE;
+		cfg.direction = DMA_DEV_TO_MEM;
+		cfg.src_addr = dev_addr;
+	} else {
+		return -EINVAL;
+	}
+
+	ret = dmaengine_slave_config(chan, &cfg);
+	if (ret)
+		return ret;
+
+	sg_init_one(&sg, buf, len);
+	if (!dma_map_sg(dev, &sg, 1, map_dir)) {
+		dev_err(dev, "unable to map local address\n");
+		return -EIO;
+	}
+
+	tx = dmaengine_prep_slave_sg(chan, &sg, 1, dir,
+				     DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (!tx) {
+		dev_err(dev, "failed to prepare slave for sg\n");
+		ret = -EIO;
+		goto unmap;
+	}
+
+	init_completion(&done);
+	tx->callback = (dma_async_tx_callback)complete;
+	tx->callback_param = &done;
+
+	cookie = dmaengine_submit(tx);
+	ret = dma_submit_error(cookie);
+	if (ret) {
+		dev_err(dev, "remote eDMA submission error: %d\n", ret);
+		goto unmap;
+	}
+
+	dma_async_issue_pending(chan);
+
+	if (!wait_for_completion_timeout(&done, msecs_to_jiffies(5000))) {
+		dev_err(dev, "remote eDMA transfer timeout\n");
+		dmaengine_terminate_sync(chan);
+		ret = -ETIMEDOUT;
+		goto unmap;
+	}
+
+	ret = 0;
+unmap:
+	dma_unmap_sg(dev, &sg, 1, map_dir);
+	return ret;
+}
+
+static int pci_endpoint_test_edma_write(struct pci_endpoint_test *test,
+					size_t size)
+{
+	struct pci_endpoint_test_edma *edma;
+	struct pci_dev *pdev = test->pdev;
+	struct device *dev = &pdev->dev;
+	struct pcitest_edma_info info;
+	u32 reg, crc32, peer_crc32;
+	unsigned long left;
+	int ret;
+
+	/*
+	 * Note that test->alignment does not apply here. If some vendor
+	 * dmaengine for remote use may impose some alignment restriction, we
+	 * may as well introduce another field such as
+	 * test->remote_dma_alignment.
+	 */
+	void *orig_addr __free(kfree) = kzalloc(size, GFP_KERNEL);
+	if (!orig_addr)
+		return -ENOMEM;
+
+	ret = pci_endpoint_test_remote_edma_setup(test, size);
+	if (ret)
+		return ret;
+
+	edma = test->data;
+	if (!edma) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	get_random_bytes(orig_addr, size);
+
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0);
+
+	memcpy_fromio(&info, edma->bar_base, sizeof(info));
+	if (le32_to_cpu(info.test_buf_size) < size) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = pci_endpoint_test_edma_xfer(test->pdev, edma, orig_addr, size,
+					  le64_to_cpu(info.test_buf_phys),
+					  DMA_MEM_TO_DEV);
+	if (ret) {
+		dev_err(dev, "pci_endpoint_test_edma_xfer error: %d\n", ret);
+		goto err;
+	}
+
+	reinit_completion(&test->irq_raised);
+
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0);
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, size);
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND,
+				 COMMAND_REMOTE_EDMA_CHECKSUM);
+
+	left = wait_for_completion_timeout(&test->irq_raised,
+					   msecs_to_jiffies(1000));
+
+	reg = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS);
+
+	if (!left || !(reg & STATUS_REMOTE_EDMA_CHECKSUM_SUCCESS)) {
+		dev_err(dev, "Failed to get checksum\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	crc32 = crc32_le(~0, orig_addr, size);
+	peer_crc32 = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_CHECKSUM);
+	if (crc32 != peer_crc32) {
+		dev_err(dev,
+			"Checksum mismatch: %#x vs %#x\n", crc32, peer_crc32);
+		ret = -EINVAL;
+	}
+err:
+	pci_endpoint_test_remote_edma_teardown(test);
+	pci_endpoint_test_edma_restore_irq(test);
+	return ret;
+}
+
+static int pci_endpoint_test_edma_read(struct pci_endpoint_test *test,
+				       size_t size)
+{
+	struct pci_endpoint_test_edma *edma;
+	struct pci_dev *pdev = test->pdev;
+	struct device *dev = &pdev->dev;
+	struct pcitest_edma_info info;
+	u32 crc32, peer_crc32;
+	int ret;
+
+	/*
+	 * Note that test->alignment does not apply here. If some vendor
+	 * dmaengine for remote use may impose some alignment restriction, we
+	 * may as well introduce another field such as
+	 * test->remote_dma_alignment.
+	 */
+	void *orig_addr __free(kfree) = kzalloc(size, GFP_KERNEL);
+	if (!orig_addr)
+		return -ENOMEM;
+
+	ret = pci_endpoint_test_remote_edma_setup(test, size);
+	if (ret)
+		return ret;
+
+	peer_crc32 = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_CHECKSUM);
+
+	edma = test->data;
+	if (!edma) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0);
+
+	memcpy_fromio(&info, edma->bar_base, sizeof(info));
+	if (le32_to_cpu(info.test_buf_size) < size) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = pci_endpoint_test_edma_xfer(test->pdev, edma, orig_addr, size,
+					  le64_to_cpu(info.test_buf_phys),
+					  DMA_DEV_TO_MEM);
+	if (ret) {
+		dev_err(dev, "pci_endpoint_test_edma_xfer error: %d\n", ret);
+		goto err;
+	}
+
+	crc32 = crc32_le(~0, orig_addr, size);
+	if (crc32 != peer_crc32) {
+		dev_err(dev,
+			"Checksum mismatch: %#x vs %#x\n", crc32, peer_crc32);
+		ret = -EINVAL;
+	}
+err:
+	pci_endpoint_test_remote_edma_teardown(test);
+	pci_endpoint_test_edma_restore_irq(test);
+	return ret;
+}
+#else
+static bool pci_endpoint_test_bar_is_reserved(struct pci_endpoint_test *test,
+					      enum pci_barno barno)
+{
+	return 0;
+}
+
+static void pci_endpoint_test_remote_edma_teardown(struct pci_endpoint_test *test)
+{
+}
+
+static int pci_endpoint_test_edma_write(struct pci_endpoint_test *test,
+					size_t size)
+{
+	return -EOPNOTSUPP;
+}
+
+static int pci_endpoint_test_edma_read(struct pci_endpoint_test *test,
+				       size_t size)
+{
+	return -EOPNOTSUPP;
+}
+#endif
+
 static irqreturn_t pci_endpoint_test_irqhandler(int irq, void *dev_id)
 {
 	struct pci_endpoint_test *test = dev_id;
@@ -307,6 +924,9 @@ static int pci_endpoint_test_bar(struct pci_endpoint_test *test,
 	if (barno == test->test_reg_bar)
 		bar_size = 0x4;
 
+	if (pci_endpoint_test_bar_is_reserved(test, barno))
+		return -EOPNOTSUPP;
+
 	/*
 	 * Allocate a buffer of max size 1MB, and reuse that buffer while
 	 * iterating over the whole BAR size (which might be much larger).
@@ -354,6 +974,9 @@ static void pci_endpoint_test_bars_write_bar(struct pci_endpoint_test *test,
 	if (barno == test->test_reg_bar)
 		size = 0x4;
 
+	if (pci_endpoint_test_bar_is_reserved(test, barno))
+		return;
+
 	for (j = 0; j < size; j += 4)
 		writel_relaxed(bar_test_pattern_with_offset(barno, j),
 			       test->bar[barno] + j);
@@ -372,6 +995,9 @@ static int pci_endpoint_test_bars_read_bar(struct pci_endpoint_test *test,
 	if (barno == test->test_reg_bar)
 		size = 0x4;
 
+	if (pci_endpoint_test_bar_is_reserved(test, barno))
+		return 0;
+
 	for (j = 0; j < size; j += 4) {
 		u32 expected = bar_test_pattern_with_offset(barno, j);
 
@@ -645,6 +1271,9 @@ static int pci_endpoint_test_write(struct pci_endpoint_test *test,
 
 	size = param.size;
 
+	if (param.flags & PCITEST_FLAGS_USE_REMOTE_EDMA)
+		return pci_endpoint_test_edma_write(test, size);
+
 	use_dma = !!(param.flags & PCITEST_FLAGS_USE_DMA);
 	if (use_dma)
 		flags |= FLAG_USE_DMA;
@@ -742,6 +1371,9 @@ static int pci_endpoint_test_read(struct pci_endpoint_test *test,
 
 	size = param.size;
 
+	if (param.flags & PCITEST_FLAGS_USE_REMOTE_EDMA)
+		return pci_endpoint_test_edma_read(test, size);
+
 	use_dma = !!(param.flags & PCITEST_FLAGS_USE_DMA);
 	if (use_dma)
 		flags |= FLAG_USE_DMA;
@@ -1139,6 +1771,7 @@ static void pci_endpoint_test_remove(struct pci_dev *pdev)
 	if (id < 0)
 		return;
 
+	pci_endpoint_test_remote_edma_teardown(test);
 	pci_endpoint_test_release_irq(test);
 	pci_endpoint_test_free_irq_vectors(test);
 
diff --git a/include/uapi/linux/pcitest.h b/include/uapi/linux/pcitest.h
index d6023a45a9d0..c72d999cecf7 100644
--- a/include/uapi/linux/pcitest.h
+++ b/include/uapi/linux/pcitest.h
@@ -30,7 +30,8 @@
 #define PCITEST_IRQ_TYPE_MSIX		2
 #define PCITEST_IRQ_TYPE_AUTO		3
 
-#define PCITEST_FLAGS_USE_DMA	0x00000001
+#define PCITEST_FLAGS_USE_DMA		0x00000001
+#define PCITEST_FLAGS_USE_REMOTE_EDMA	0x00000002
 
 struct pci_endpoint_test_xfer_param {
 	unsigned long size;
-- 
2.51.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.