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

Koichiro Den
2026-01-18T13:56:00Z

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 <den@valinux.co.jp>
---
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