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


Reply via email to