Commit 8cf82bb5 authored by Koichiro Den's avatar Koichiro Den Committed by Bjorn Helgaas
Browse files

misc: pci_endpoint_test: Add BAR subrange mapping test case



Add a new PCITEST_BAR_SUBRANGE ioctl to exercise EPC BAR subrange
mapping end-to-end.

The test programs a simple 2-subrange layout on the endpoint (via
pci-epf-test) and verifies that:
  - the endpoint-provided per-subrange signature bytes are observed at
    the expected PCIe BAR offsets, and
  - writes to each subrange are routed to the correct backing region
    (i.e. the submap order is applied rather than accidentally working
    due to an identity mapping).

Return -EOPNOTSUPP when the endpoint does not advertise subrange
mapping, -ENODATA when the BAR is disabled, and -EBUSY when the BAR is
reserved for the test register space.

Signed-off-by: default avatarKoichiro Den <den@valinux.co.jp>
Signed-off-by: default avatarManivannan Sadhasivam <mani@kernel.org>
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
Link: https://patch.msgid.link/20260124145012.2794108-8-den@valinux.co.jp
parent 6c5e6101
Loading
Loading
Loading
Loading
+202 −1
Original line number Diff line number Diff line
@@ -39,6 +39,8 @@
#define COMMAND_COPY				BIT(5)
#define COMMAND_ENABLE_DOORBELL			BIT(6)
#define COMMAND_DISABLE_DOORBELL		BIT(7)
#define COMMAND_BAR_SUBRANGE_SETUP		BIT(8)
#define COMMAND_BAR_SUBRANGE_CLEAR		BIT(9)

#define PCI_ENDPOINT_TEST_STATUS		0x8
#define STATUS_READ_SUCCESS			BIT(0)
@@ -55,6 +57,10 @@
#define STATUS_DOORBELL_ENABLE_FAIL		BIT(11)
#define STATUS_DOORBELL_DISABLE_SUCCESS		BIT(12)
#define STATUS_DOORBELL_DISABLE_FAIL		BIT(13)
#define STATUS_BAR_SUBRANGE_SETUP_SUCCESS	BIT(14)
#define STATUS_BAR_SUBRANGE_SETUP_FAIL		BIT(15)
#define STATUS_BAR_SUBRANGE_CLEAR_SUCCESS	BIT(16)
#define STATUS_BAR_SUBRANGE_CLEAR_FAIL		BIT(17)

#define PCI_ENDPOINT_TEST_LOWER_SRC_ADDR	0x0c
#define PCI_ENDPOINT_TEST_UPPER_SRC_ADDR	0x10
@@ -77,6 +83,7 @@
#define CAP_MSI					BIT(1)
#define CAP_MSIX				BIT(2)
#define CAP_INTX				BIT(3)
#define CAP_SUBRANGE_MAPPING			BIT(4)

#define PCI_ENDPOINT_TEST_DB_BAR		0x34
#define PCI_ENDPOINT_TEST_DB_OFFSET		0x38
@@ -100,6 +107,8 @@

#define PCI_DEVICE_ID_ROCKCHIP_RK3588		0x3588

#define PCI_ENDPOINT_TEST_BAR_SUBRANGE_NSUB	2

static DEFINE_IDA(pci_endpoint_test_ida);

#define to_endpoint_test(priv) container_of((priv), struct pci_endpoint_test, \
@@ -414,6 +423,193 @@ static int pci_endpoint_test_bars(struct pci_endpoint_test *test)
	return 0;
}

static u8 pci_endpoint_test_subrange_sig_byte(enum pci_barno barno,
					      unsigned int subno)
{
	return 0x50 + (barno * 8) + subno;
}

static u8 pci_endpoint_test_subrange_test_byte(enum pci_barno barno,
					       unsigned int subno)
{
	return 0xa0 + (barno * 8) + subno;
}

static int pci_endpoint_test_bar_subrange_cmd(struct pci_endpoint_test *test,
					      enum pci_barno barno, u32 command,
					      u32 ok_bit, u32 fail_bit)
{
	struct pci_dev *pdev = test->pdev;
	struct device *dev = &pdev->dev;
	int irq_type = test->irq_type;
	u32 status;

	if (irq_type < PCITEST_IRQ_TYPE_INTX ||
	    irq_type > PCITEST_IRQ_TYPE_MSIX) {
		dev_err(dev, "Invalid IRQ type\n");
		return -EINVAL;
	}

	reinit_completion(&test->irq_raised);

	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0);
	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type);
	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1);
	/* Reuse SIZE as a command parameter: bar number. */
	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, barno);
	pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND, command);

	if (!wait_for_completion_timeout(&test->irq_raised,
					 msecs_to_jiffies(1000)))
		return -ETIMEDOUT;

	status = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS);
	if (status & fail_bit)
		return -EIO;

	if (!(status & ok_bit))
		return -EIO;

	return 0;
}

static int pci_endpoint_test_bar_subrange_setup(struct pci_endpoint_test *test,
						enum pci_barno barno)
{
	return pci_endpoint_test_bar_subrange_cmd(test, barno,
			COMMAND_BAR_SUBRANGE_SETUP,
			STATUS_BAR_SUBRANGE_SETUP_SUCCESS,
			STATUS_BAR_SUBRANGE_SETUP_FAIL);
}

static int pci_endpoint_test_bar_subrange_clear(struct pci_endpoint_test *test,
						enum pci_barno barno)
{
	return pci_endpoint_test_bar_subrange_cmd(test, barno,
			COMMAND_BAR_SUBRANGE_CLEAR,
			STATUS_BAR_SUBRANGE_CLEAR_SUCCESS,
			STATUS_BAR_SUBRANGE_CLEAR_FAIL);
}

static int pci_endpoint_test_bar_subrange(struct pci_endpoint_test *test,
					  enum pci_barno barno)
{
	u32 nsub = PCI_ENDPOINT_TEST_BAR_SUBRANGE_NSUB;
	struct device *dev = &test->pdev->dev;
	size_t sub_size, buf_size;
	resource_size_t bar_size;
	void __iomem *bar_addr;
	void *read_buf = NULL;
	int ret, clear_ret;
	size_t off, chunk;
	u32 i, exp, val;
	u8 pattern;

	if (!(test->ep_caps & CAP_SUBRANGE_MAPPING))
		return -EOPNOTSUPP;

	/*
	 * The test register BAR is not safe to reprogram and write/read
	 * over its full size. BAR_TEST already special-cases it to a tiny
	 * range. For subrange mapping tests, let's simply skip it.
	 */
	if (barno == test->test_reg_bar)
		return -EBUSY;

	bar_size = pci_resource_len(test->pdev, barno);
	if (!bar_size)
		return -ENODATA;

	bar_addr = test->bar[barno];
	if (!bar_addr)
		return -ENOMEM;

	ret = pci_endpoint_test_bar_subrange_setup(test, barno);
	if (ret)
		return ret;

	if (bar_size % nsub || bar_size / nsub > SIZE_MAX) {
		ret = -EINVAL;
		goto out_clear;
	}

	sub_size = bar_size / nsub;
	if (sub_size < sizeof(u32)) {
		ret = -ENOSPC;
		goto out_clear;
	}

	/* Limit the temporary buffer size */
	buf_size = min_t(size_t, sub_size, SZ_1M);

	read_buf = kmalloc(buf_size, GFP_KERNEL);
	if (!read_buf) {
		ret = -ENOMEM;
		goto out_clear;
	}

	/*
	 * Step 1: verify EP-provided signature per subrange. This detects
	 * whether the EP actually applied the submap order.
	 */
	for (i = 0; i < nsub; i++) {
		exp = (u32)pci_endpoint_test_subrange_sig_byte(barno, i) *
			0x01010101U;
		val = ioread32(bar_addr + (i * sub_size));
		if (val != exp) {
			dev_err(dev,
				"BAR%d subrange%u signature mismatch @%#zx: exp %#08x got %#08x\n",
				barno, i, (size_t)i * sub_size, exp, val);
			ret = -EIO;
			goto out_clear;
		}
		val = ioread32(bar_addr + (i * sub_size) + sub_size - sizeof(u32));
		if (val != exp) {
			dev_err(dev,
				"BAR%d subrange%u signature mismatch @%#zx: exp %#08x got %#08x\n",
				barno, i,
				((size_t)i * sub_size) + sub_size - sizeof(u32),
				exp, val);
			ret = -EIO;
			goto out_clear;
		}
	}

	/* Step 2: write unique pattern per subrange (write all first). */
	for (i = 0; i < nsub; i++) {
		pattern = pci_endpoint_test_subrange_test_byte(barno, i);
		memset_io(bar_addr + (i * sub_size), pattern, sub_size);
	}

	/* Step 3: read back and verify (read all after all writes). */
	for (i = 0; i < nsub; i++) {
		pattern = pci_endpoint_test_subrange_test_byte(barno, i);
		for (off = 0; off < sub_size; off += chunk) {
			void *bad;

			chunk = min_t(size_t, buf_size, sub_size - off);
			memcpy_fromio(read_buf, bar_addr + (i * sub_size) + off,
				      chunk);
			bad = memchr_inv(read_buf, pattern, chunk);
			if (bad) {
				size_t bad_off = (u8 *)bad - (u8 *)read_buf;

				dev_err(dev,
					"BAR%d subrange%u data mismatch @%#zx (pattern %#02x)\n",
					barno, i, (size_t)i * sub_size + off + bad_off,
					pattern);
				ret = -EIO;
				goto out_clear;
			}
		}
	}

out_clear:
	kfree(read_buf);
	clear_ret = pci_endpoint_test_bar_subrange_clear(test, barno);
	return ret ?: clear_ret;
}

static int pci_endpoint_test_intx_irq(struct pci_endpoint_test *test)
{
	u32 val;
@@ -936,12 +1132,17 @@ static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd,

	switch (cmd) {
	case PCITEST_BAR:
	case PCITEST_BAR_SUBRANGE:
		bar = arg;
		if (bar <= NO_BAR || bar > BAR_5)
			goto ret;
		if (is_am654_pci_dev(pdev) && bar == BAR_0)
			goto ret;

		if (cmd == PCITEST_BAR)
			ret = pci_endpoint_test_bar(test, bar);
		else
			ret = pci_endpoint_test_bar_subrange(test, bar);
		break;
	case PCITEST_BARS:
		ret = pci_endpoint_test_bars(test);
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@
#define PCITEST_GET_IRQTYPE	_IO('P', 0x9)
#define PCITEST_BARS		_IO('P', 0xa)
#define PCITEST_DOORBELL	_IO('P', 0xb)
#define PCITEST_BAR_SUBRANGE	_IO('P', 0xc)
#define PCITEST_CLEAR_IRQ	_IO('P', 0x10)

#define PCITEST_IRQ_TYPE_UNDEFINED	-1