misc: pci_endpoint_test: Add consecutive BAR test
authorNiklas Cassel <cassel@kernel.org>
Sat, 16 Nov 2024 03:20:45 +0000 (04:20 +0100)
committerBjorn Helgaas <bhelgaas@google.com>
Tue, 21 Jan 2025 15:44:14 +0000 (09:44 -0600)
Add a more advanced BAR test that writes all BARs in one go, and then reads
them back and verifies that the value matches the BAR number bitwise OR'ed
with offset, this allows us to verify:

  - The BAR number was what we intended to read
  - The offset was what we intended to read

This allows us to detect potential address translation issues on the EP.

Reading back the BAR directly after writing will not allow us to detect the
case where inbound address translation on the endpoint incorrectly causes
multiple BARs to be redirected to the same memory region (within the EP).

Link: https://lore.kernel.org/r/20241116032045.2574168-2-cassel@kernel.org
Signed-off-by: Niklas Cassel <cassel@kernel.org>
Signed-off-by: Krzysztof WilczyƄski <kwilczynski@kernel.org>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
drivers/misc/pci_endpoint_test.c
include/uapi/linux/pcitest.h
tools/pci/pcitest.c
tools/pci/pcitest.sh

index 8a31bd4c237dd68e55edc4a0cc0b1247f138a2f8..5c99da952b7ae15c438ba5380e121835af1fd5d5 100644 (file)
@@ -325,6 +325,91 @@ static bool pci_endpoint_test_bar(struct pci_endpoint_test *test,
        return true;
 }
 
+static u32 bar_test_pattern_with_offset(enum pci_barno barno, int offset)
+{
+       u32 val;
+
+       /* Keep the BAR pattern in the top byte. */
+       val = bar_test_pattern[barno] & 0xff000000;
+       /* Store the (partial) offset in the remaining bytes. */
+       val |= offset & 0x00ffffff;
+
+       return val;
+}
+
+static bool pci_endpoint_test_bars_write_bar(struct pci_endpoint_test *test,
+                                            enum pci_barno barno)
+{
+       struct pci_dev *pdev = test->pdev;
+       int j, size;
+
+       size = pci_resource_len(pdev, barno);
+
+       if (barno == test->test_reg_bar)
+               size = 0x4;
+
+       for (j = 0; j < size; j += 4)
+               writel_relaxed(bar_test_pattern_with_offset(barno, j),
+                              test->bar[barno] + j);
+
+       return true;
+}
+
+static bool pci_endpoint_test_bars_read_bar(struct pci_endpoint_test *test,
+                                           enum pci_barno barno)
+{
+       struct pci_dev *pdev = test->pdev;
+       struct device *dev = &pdev->dev;
+       int j, size;
+       u32 val;
+
+       size = pci_resource_len(pdev, barno);
+
+       if (barno == test->test_reg_bar)
+               size = 0x4;
+
+       for (j = 0; j < size; j += 4) {
+               u32 expected = bar_test_pattern_with_offset(barno, j);
+
+               val = readl_relaxed(test->bar[barno] + j);
+               if (val != expected) {
+                       dev_err(dev,
+                               "BAR%d incorrect data at offset: %#x, got: %#x expected: %#x\n",
+                               barno, j, val, expected);
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static bool pci_endpoint_test_bars(struct pci_endpoint_test *test)
+{
+       enum pci_barno bar;
+       bool ret;
+
+       /* Write all BARs in order (without reading). */
+       for (bar = 0; bar < PCI_STD_NUM_BARS; bar++)
+               if (test->bar[bar])
+                       pci_endpoint_test_bars_write_bar(test, bar);
+
+       /*
+        * Read all BARs in order (without writing).
+        * If there is an address translation issue on the EP, writing one BAR
+        * might have overwritten another BAR. Ensure that this is not the case.
+        * (Reading back the BAR directly after writing can not detect this.)
+        */
+       for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+               if (test->bar[bar]) {
+                       ret = pci_endpoint_test_bars_read_bar(test, bar);
+                       if (!ret)
+                               return ret;
+               }
+       }
+
+       return true;
+}
+
 static bool pci_endpoint_test_intx_irq(struct pci_endpoint_test *test)
 {
        u32 val;
@@ -771,6 +856,9 @@ static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd,
                        goto ret;
                ret = pci_endpoint_test_bar(test, bar);
                break;
+       case PCITEST_BARS:
+               ret = pci_endpoint_test_bars(test);
+               break;
        case PCITEST_INTX_IRQ:
                ret = pci_endpoint_test_intx_irq(test);
                break;
index 94b46b043b536288f31c02e5b4577628d8fecaa7..acd261f498666bbb757b0af26708305fb216b66a 100644 (file)
@@ -20,6 +20,7 @@
 #define PCITEST_MSIX           _IOW('P', 0x7, int)
 #define PCITEST_SET_IRQTYPE    _IOW('P', 0x8, int)
 #define PCITEST_GET_IRQTYPE    _IO('P', 0x9)
+#define PCITEST_BARS           _IO('P', 0xa)
 #define PCITEST_CLEAR_IRQ      _IO('P', 0x10)
 
 #define PCITEST_FLAGS_USE_DMA  0x00000001
index 7b530d838d4082323707c9d56c15add9dce448c9..08f355083754ccf66cda13da12a91da315cdb2c4 100644 (file)
@@ -22,6 +22,7 @@ static char *irq[] = { "LEGACY", "MSI", "MSI-X" };
 struct pci_test {
        char            *device;
        char            barnum;
+       bool            consecutive_bar_test;
        bool            legacyirq;
        unsigned int    msinum;
        unsigned int    msixnum;
@@ -57,6 +58,15 @@ static int run_test(struct pci_test *test)
                        fprintf(stdout, "%s\n", result[ret]);
        }
 
+       if (test->consecutive_bar_test) {
+               ret = ioctl(fd, PCITEST_BARS);
+               fprintf(stdout, "Consecutive BAR test:\t\t");
+               if (ret < 0)
+                       fprintf(stdout, "TEST FAILED\n");
+               else
+                       fprintf(stdout, "%s\n", result[ret]);
+       }
+
        if (test->set_irqtype) {
                ret = ioctl(fd, PCITEST_SET_IRQTYPE, test->irqtype);
                fprintf(stdout, "SET IRQ TYPE TO %s:\t\t", irq[test->irqtype]);
@@ -172,7 +182,7 @@ int main(int argc, char **argv)
        /* set default endpoint device */
        test->device = "/dev/pci-endpoint-test.0";
 
-       while ((c = getopt(argc, argv, "D:b:m:x:i:deIlhrwcs:")) != EOF)
+       while ((c = getopt(argc, argv, "D:b:Cm:x:i:deIlhrwcs:")) != EOF)
        switch (c) {
        case 'D':
                test->device = optarg;
@@ -182,6 +192,9 @@ int main(int argc, char **argv)
                if (test->barnum < 0 || test->barnum > 5)
                        goto usage;
                continue;
+       case 'C':
+               test->consecutive_bar_test = true;
+               continue;
        case 'l':
                test->legacyirq = true;
                continue;
@@ -230,6 +243,7 @@ usage:
                        "Options:\n"
                        "\t-D <dev>             PCI endpoint test device {default: /dev/pci-endpoint-test.0}\n"
                        "\t-b <bar num>         BAR test (bar number between 0..5)\n"
+                       "\t-C                   Consecutive BAR test\n"
                        "\t-m <msi num>         MSI test (msi number between 1..32)\n"
                        "\t-x <msix num>        \tMSI-X test (msix number between 1..2048)\n"
                        "\t-i <irq type>        \tSet IRQ type (0 - Legacy, 1 - MSI, 2 - MSI-X)\n"
index 75ed48ff2990053d1e688d314bd6dabd9ff3e957..770f4d6df34b63f49877b36c8b3cc6b9a875cb40 100644 (file)
@@ -11,6 +11,7 @@ do
        pcitest -b $bar
        bar=`expr $bar + 1`
 done
+pcitest -C
 echo
 
 echo "Interrupt tests"