[PATCH v3 2/3] PCI: sandbox: Add PCI bridge emulation

Sean Anderson sean.anderson at linux.dev
Tue Jan 13 02:20:36 CET 2026


Add a PCI bridge emulation device. Although real bridges need to be
configured in order to forward requests, this happens automatically for
now (see arch/sandbox/lib/pci_io.c). So just record the window registers
and check them in a test to make sure pci_auto is sane.

As each bridge consume at least 4k I/O registers, increase the root
port's I/O space so we have some left for other devices.

Signed-off-by: Sean Anderson <sean.anderson at linux.dev>
---

Changes in v3:
- New

 arch/sandbox/dts/test.dts        |  35 +++++-
 arch/sandbox/include/asm/test.h  |   1 +
 drivers/pci/Makefile             |   2 +-
 drivers/pci/pci_sandbox_bridge.c | 192 +++++++++++++++++++++++++++++++
 test/dm/pci.c                    | 142 +++++++++++++++++++++++
 5 files changed, 370 insertions(+), 2 deletions(-)
 create mode 100644 drivers/pci/pci_sandbox_bridge.c

diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index e945a47fb01..962d364f9b2 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1279,7 +1279,7 @@
 		#address-cells = <3>;
 		#size-cells = <2>;
 		ranges = <0x02000000 0 0x10000000 0x10000000 0 0x2000000
-				0x01000000 0 0x20000000 0x20000000 0 0x2000>;
+			  0x01000000 0 0x20000000 0x20000000 0 0x20000>;
 		iommu-map = <0x0010 &iommu 0 1>;
 		iommu-map-mask = <0xfffffff8>;
 		sandbox,emul = <&pci_emul0>;
@@ -1301,6 +1301,15 @@
 			};
 		};
 
+		pci at 4,0 {
+			#address-cells = <3>;
+			#size-cells = <2>;
+			device_type = "pci";
+			compatible = "pciclass,0604";
+			reg = <0x00002000 0 0 0 0>;
+			ranges;
+		};
+
 		pci at 1e,0 {
 			compatible = "sandbox,pmc";
 			reg = <0xf000 0 0 0 0>;
@@ -1340,6 +1349,30 @@
 			reg = <0x10>;
 		};
 
+		emul at 3,0 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			compatible = "sandbox,pci-bridge";
+			reg = <0x18>;
+
+			emul at 0,0 {
+				compatible = "sandbox,swap-case";
+				reg = <0x00>;
+			};
+		};
+
+		emul at 4,0 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			compatible = "sandbox,pci-bridge";
+			reg = <0x20>;
+
+			emul at 0,0 {
+				compatible = "sandbox,swap-case";
+				reg = <0x00>;
+			};
+		};
+
 		emul at 1e,0 {
 			compatible = "sandbox,pmc-emul";
 			reg = <0xf0>;
diff --git a/arch/sandbox/include/asm/test.h b/arch/sandbox/include/asm/test.h
index 0e8d19ce232..86c7c833b6e 100644
--- a/arch/sandbox/include/asm/test.h
+++ b/arch/sandbox/include/asm/test.h
@@ -19,6 +19,7 @@ struct unit_test_state;
 #define SANDBOX_PCI_SWAP_CASE_EMUL_ID	0x5678
 #define SANDBOX_PCI_PMC_EMUL_ID		0x5677
 #define SANDBOX_PCI_P2SB_EMUL_ID	0x5676
+#define SANDBOX_PCI_BRIDGE_EMUL_ID	0x5662
 #define SANDBOX_PCI_CLASS_CODE		(PCI_CLASS_COMMUNICATION_SERIAL >> 8)
 #define SANDBOX_PCI_CLASS_SUB_CODE	(PCI_CLASS_COMMUNICATION_SERIAL & 0xff)
 
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 98f3c226f63..70a0dd3c1e3 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -7,7 +7,7 @@ obj-$(CONFIG_VIDEO) += pci_rom.o
 obj-$(CONFIG_PCI) += pci-uclass.o pci_auto.o
 obj-$(CONFIG_DM_PCI_COMPAT) += pci_compat.o
 obj-$(CONFIG_PCI_SANDBOX) += pci_sandbox.o
-obj-$(CONFIG_SANDBOX) += pci-emul-uclass.o
+obj-$(CONFIG_SANDBOX) += pci-emul-uclass.o pci_sandbox_bridge.o
 obj-$(CONFIG_X86) += pci_x86.o pci_rom.o
 obj-$(CONFIG_PCI) += pci_auto_common.o pci_common.o
 
diff --git a/drivers/pci/pci_sandbox_bridge.c b/drivers/pci/pci_sandbox_bridge.c
new file mode 100644
index 00000000000..81a4e993b47
--- /dev/null
+++ b/drivers/pci/pci_sandbox_bridge.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * PCI emulation device which swaps the case of text
+ *
+ * Copyright (c) 2014 Google, Inc
+ * Written by Simon Glass <sjg at chromium.org>
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <log.h>
+#include <pci.h>
+#include <asm/test.h>
+#include <linux/ctype.h>
+
+/**
+ * struct bridge_priv - Sandbox PCI bridge emulation private data
+ */
+struct bridge_priv {
+	u64 pref_base, pref_limit;
+	u32 io_base, io_limit;
+	u16 command, control;
+	u16 mem_base, mem_limit;
+	u8 primary, secondary, sub;
+};
+
+#define mask(s) GENMASK_ULL((1 << ((s) + 3)) - 1, 0)
+#define extract(v, o, s) (((v) >> ((o) * 8)) & mask(s))
+#define deposit(v, o, s, f) ({ \
+	uint _o = (o); \
+	typeof(v) _v = v & ~((typeof(v))mask(s) << (_o * 8)); \
+	_v | ((typeof(v))(f) << (_o * 8)); \
+})
+
+static ulong _sandbox_bridge_read_config(const struct udevice *dev, uint off,
+					 enum pci_size_t size)
+{
+	struct bridge_priv *priv = dev_get_priv(dev);
+
+	switch (off) {
+	case PCI_VENDOR_ID ... PCI_VENDOR_ID + 1:
+		return extract(SANDBOX_PCI_VENDOR_ID, off & 1, size);
+	case PCI_DEVICE_ID ... PCI_DEVICE_ID + 1:
+		return extract(SANDBOX_PCI_BRIDGE_EMUL_ID, off & 1, size);
+	case PCI_COMMAND ... PCI_COMMAND + 1:
+		return extract(priv->command, off & 1, size);
+	case PCI_REVISION_ID ... PCI_CLASS_CODE:
+		return extract(PCI_CLASS_BRIDGE_PCI_NORMAL << 8, off & 3, size);
+	case PCI_HEADER_TYPE:
+		return PCI_HEADER_TYPE_BRIDGE;
+	case PCI_PRIMARY_BUS:
+		return priv->primary;
+	case PCI_SECONDARY_BUS:
+		return priv->secondary;
+	case PCI_SUBORDINATE_BUS:
+		return priv->sub;
+	case PCI_IO_BASE:
+		return priv->io_base;
+	case PCI_IO_LIMIT:
+		return priv->io_limit;
+	case PCI_MEMORY_BASE ... PCI_MEMORY_BASE + 1:
+		return extract(priv->mem_base, off & 1, size);
+	case PCI_MEMORY_LIMIT ... PCI_MEMORY_LIMIT + 1:
+		return extract(priv->mem_limit, off & 1, size);
+	case PCI_PREF_MEMORY_BASE ... PCI_PREF_MEMORY_BASE + 1:
+		return extract(priv->pref_base, off & 1, size);
+	case PCI_PREF_MEMORY_LIMIT ... PCI_PREF_MEMORY_LIMIT + 1:
+		return extract(priv->pref_limit, off & 1, size);
+	case PCI_PREF_BASE_UPPER32 ... PCI_PREF_BASE_UPPER32 + 3:
+		return extract(priv->pref_base >> 16, off & 3, size);
+	case PCI_PREF_LIMIT_UPPER32 ... PCI_PREF_LIMIT_UPPER32 + 3:
+		return extract(priv->pref_limit >> 16, off & 3, size);
+	case PCI_IO_BASE_UPPER16 ... PCI_IO_BASE_UPPER16 + 1:
+		return extract(priv->io_base >> 8, off & 1, size);
+	case PCI_IO_LIMIT_UPPER16 ... PCI_IO_LIMIT_UPPER16 + 1:
+		return extract(priv->io_limit >> 8, off & 1, size);
+	case PCI_BRIDGE_CONTROL ... PCI_BRIDGE_CONTROL + 1:
+		return extract(priv->control, off & 1, size);
+	}
+
+	return 0;
+}
+
+static int sandbox_bridge_read_config(const struct udevice *dev, uint off,
+				      ulong *valp, enum pci_size_t size)
+{
+	*valp = _sandbox_bridge_read_config(dev, off, size);
+	return 0;
+}
+
+static void sandbox_bridge_fix_regs(struct bridge_priv *priv)
+{
+	priv->io_base &= PCI_IO_RANGE_MASK;
+	priv->io_base |= PCI_IO_RANGE_TYPE_32;
+	priv->io_limit &= PCI_IO_RANGE_MASK;
+	priv->io_limit |= PCI_IO_RANGE_TYPE_32;
+	priv->mem_base &= PCI_MEMORY_RANGE_MASK;
+	priv->mem_limit &= PCI_MEMORY_RANGE_MASK;
+	priv->pref_base &= PCI_PREF_RANGE_MASK;
+	priv->pref_base |= PCI_PREF_RANGE_TYPE_64;
+	priv->pref_limit &= PCI_PREF_RANGE_MASK;
+	priv->pref_limit |= PCI_PREF_RANGE_TYPE_64;
+}
+
+static int sandbox_bridge_write_config(struct udevice *dev, uint off, ulong val,
+				       enum pci_size_t size)
+{
+	struct bridge_priv *priv = dev_get_priv(dev);
+
+	switch (off) {
+	case PCI_COMMAND ... PCI_COMMAND + 1:
+		priv->command = deposit(priv->command, off & 1, size, val);
+		break;
+	case PCI_PRIMARY_BUS:
+		priv->primary = val;
+		break;
+	case PCI_SECONDARY_BUS:
+		priv->secondary = val;
+		break;
+	case PCI_SUBORDINATE_BUS:
+		priv->sub = val;
+		break;
+	case PCI_IO_BASE:
+		priv->io_base = deposit(priv->io_base, 0, 0, val);
+		break;
+	case PCI_IO_LIMIT:
+		priv->io_limit = deposit(priv->io_limit, 0, 0, val);
+		break;
+	case PCI_MEMORY_BASE ... PCI_MEMORY_BASE + 1:
+		priv->mem_base = deposit(priv->mem_base, off & 1, size, val);
+		break;
+	case PCI_MEMORY_LIMIT ... PCI_MEMORY_LIMIT + 1:
+		priv->mem_limit = deposit(priv->mem_limit, off & 1, size, val);
+		break;
+	case PCI_PREF_MEMORY_BASE ... PCI_PREF_MEMORY_BASE + 1:
+		priv->pref_base = deposit(priv->pref_base, off & 1, size, val);
+		break;
+	case PCI_PREF_MEMORY_LIMIT ... PCI_PREF_MEMORY_LIMIT + 1:
+		priv->pref_limit = deposit(priv->pref_limit, off & 1, size,
+					   val);
+		break;
+	case PCI_PREF_BASE_UPPER32 ... PCI_PREF_BASE_UPPER32 + 3:
+		priv->pref_base = deposit(priv->pref_base, (off & 3) + 2, size,
+					  val);
+		break;
+	case PCI_PREF_LIMIT_UPPER32 ... PCI_PREF_LIMIT_UPPER32 + 3:
+		priv->pref_limit = deposit(priv->pref_limit, (off & 3) + 2,
+					   size, val);
+		break;
+	case PCI_IO_BASE_UPPER16 ... PCI_IO_BASE_UPPER16 + 1:
+		priv->io_base = deposit(priv->io_base, (off & 1) + 1, size,
+					val);
+		break;
+	case PCI_IO_LIMIT_UPPER16 ... PCI_IO_LIMIT_UPPER16 + 1:
+		priv->io_limit = deposit(priv->io_limit, (off & 1) + 1, size,
+					 val);
+		break;
+	case PCI_BRIDGE_CONTROL ... PCI_BRIDGE_CONTROL + 1:
+		priv->control = deposit(priv->control, off & 1, size, val);
+		break;
+	}
+
+	sandbox_bridge_fix_regs(priv);
+	return 0;
+}
+
+static struct dm_pci_emul_ops sandbox_bridge_emul_ops = {
+	.read_config = sandbox_bridge_read_config,
+	.write_config = sandbox_bridge_write_config,
+};
+
+static int sandbox_bridge_probe(struct udevice *dev)
+{
+	struct bridge_priv *priv = dev_get_priv(dev);
+
+	sandbox_bridge_fix_regs(priv);
+	return 0;
+}
+
+static const struct udevice_id sandbox_bridge_ids[] = {
+	{ .compatible = "sandbox,pci-bridge" },
+	{ }
+};
+
+U_BOOT_DRIVER(sandbox_bridge_emul) = {
+	.name		= "sandbox_bridge_emul",
+	.id		= UCLASS_PCI_EMUL,
+	.of_match	= sandbox_bridge_ids,
+	.probe		= sandbox_bridge_probe,
+	.ops		= &sandbox_bridge_emul_ops,
+	.priv_auto	= sizeof(struct bridge_priv),
+};
diff --git a/test/dm/pci.c b/test/dm/pci.c
index 6eb19f6fea3..b6fee7b3bb3 100644
--- a/test/dm/pci.c
+++ b/test/dm/pci.c
@@ -4,6 +4,7 @@
  */
 
 #include <dm.h>
+#include <dm/device_compat.h>
 #include <asm/io.h>
 #include <asm/test.h>
 #include <dm/test.h>
@@ -486,3 +487,144 @@ static int dm_test_pci_phys_to_bus(struct unit_test_state *uts)
 	return 0;
 }
 DM_TEST(dm_test_pci_phys_to_bus, UTF_SCAN_PDATA | UTF_SCAN_FDT);
+
+#define PCI_BAR(n) (PCI_BASE_ADDRESS_0 + (n) * 4)
+static int read_bar(struct unit_test_state *uts, struct udevice *dev, int bar,
+		    u32 *base, u32 *mask)
+{
+	int addr = PCI_BASE_ADDRESS_0 + bar * 4;
+
+	ut_assertok(dm_pci_read_config32(dev, addr, base));
+	ut_assertok(dm_pci_write_config32(dev, addr, 0xffffffff));
+	ut_assertok(dm_pci_read_config32(dev, addr, mask));
+	ut_assertok(dm_pci_write_config32(dev, addr, *base));
+	return 0;
+}
+
+/*
+ * Test that all BARs on devices under each bridge lie within the bridge's
+ * forwarding windows.
+ */
+static int _dm_test_pci_bridge_windows(struct unit_test_state *uts,
+				       struct udevice *bus)
+{
+	struct udevice *dev;
+	u64 pref_lo, pref_hi;
+	u32 val32, io_lo, io_hi, mem_lo, mem_hi;
+	u16 val16;
+	u8 val8;
+
+	ut_assertok(dm_pci_read_config8(bus, PCI_IO_BASE, &val8));
+	ut_assertok(dm_pci_read_config16(bus, PCI_IO_BASE_UPPER16, &val16));
+	io_lo = ((u32)(val8 & PCI_IO_RANGE_MASK) << 8) | ((u32)val16 << 16);
+	ut_assertok(dm_pci_read_config8(bus, PCI_IO_LIMIT, &val8));
+	ut_assertok(dm_pci_read_config16(bus, PCI_IO_LIMIT_UPPER16, &val16));
+	io_hi = ((u32)(val8 & PCI_IO_RANGE_MASK) << 8) | ((u32)val16 << 16) |
+		GENMASK(11, 0);
+
+	ut_assertok(dm_pci_read_config16(bus, PCI_MEMORY_BASE, &val16));
+	mem_lo = (u32)val16 << 16;
+	ut_assertok(dm_pci_read_config16(bus, PCI_MEMORY_LIMIT, &val16));
+	mem_hi = ((u32)val16 << 16) | GENMASK(19, 0);
+
+	ut_assertok(dm_pci_read_config16(bus, PCI_PREF_MEMORY_BASE, &val16));
+	ut_assertok(dm_pci_read_config32(bus, PCI_PREF_BASE_UPPER32, &val32));
+	pref_lo = ((u64)(val16 & PCI_PREF_RANGE_MASK) << 16) |
+		  ((u64)val32 << 32) | GENMASK(19, 0);
+	ut_assertok(dm_pci_read_config16(bus, PCI_PREF_MEMORY_LIMIT, &val16));
+	ut_assertok(dm_pci_read_config32(bus, PCI_PREF_LIMIT_UPPER32, &val32));
+	pref_hi = ((u64)(val16 & PCI_PREF_RANGE_MASK) << 16) |
+		  ((u64)val32 << 32) | GENMASK(19, 0);
+
+	dev_dbg(bus, "io   %08x %08x\n", io_lo, io_hi);
+	dev_dbg(bus, "mem  %08x %08x\n", mem_lo, mem_hi);
+	dev_dbg(bus, "pref %016llx %016llx\n", pref_lo, pref_hi);
+	device_foreach_child(dev, bus) {
+		unsigned int bar, max_bar;
+
+		ut_assertok(dm_pci_read_config8(dev, PCI_HEADER_TYPE, &val8));
+		switch (val8) {
+		case PCI_HEADER_TYPE_NORMAL:
+			max_bar = 6;
+			break;
+		case PCI_HEADER_TYPE_BRIDGE:
+			max_bar = 2;
+			break;
+		case PCI_HEADER_TYPE_CARDBUS:
+			max_bar = 0;
+			break;
+		default:
+			ut_reportf("Unknown header type %x!\n", val8);
+		}
+
+		for (bar = 0; bar < max_bar; bar++) {
+			u32 base32, mask32;
+
+			if (read_bar(uts, dev, bar, &base32, &mask32))
+				return CMD_RET_FAILURE;
+
+			if (!mask32)
+				continue;
+
+			if (base32 & PCI_BASE_ADDRESS_SPACE_IO) {
+				mask32 &= PCI_BASE_ADDRESS_IO_MASK;
+				dev_dbg(dev, "io   %08x %08x\n", base32,
+					base32 + ~mask32);
+
+				ut_assert(base32 >= io_lo);
+				ut_assert(base32 + ~mask32 <= io_hi);
+			} else {
+				u64 base64 = base32 & PCI_BASE_ADDRESS_MEM_MASK;
+				u64 mask64 = mask32 & PCI_BASE_ADDRESS_MEM_MASK;
+				u64 mem_type = base32 &
+					       PCI_BASE_ADDRESS_MEM_TYPE_MASK;
+				bool pref = base32 &
+					    PCI_BASE_ADDRESS_MEM_PREFETCH;
+
+				if (mem_type == PCI_BASE_ADDRESS_MEM_TYPE_64) {
+					ut_assert(++bar < max_bar);
+					if (read_bar(uts, dev, bar, &base32,
+						     &mask32))
+						return CMD_RET_FAILURE;
+
+					base64 |= (u64)base32 << 32;
+					mask64 |= (u64)mask32 << 32;
+					mask64 = ~mask64;
+				} else {
+					ut_asserteq(PCI_BASE_ADDRESS_MEM_TYPE_32,
+						    mem_type);
+					mask64 |= GENMASK_ULL(63, 32);
+				}
+
+				if (pref) {
+					dev_dbg(dev, "pref %016llx %016llx\n",
+						base64, base64 + ~mask64);
+
+					ut_assert(base64 >= pref_lo);
+					ut_assert(base64 + ~mask64 <= pref_hi);
+				} else {
+					dev_dbg(dev, "mem  %08llx %08llx\n",
+						base64, base64 + ~mask64);
+
+					ut_assert(base64 >= mem_lo);
+					ut_assert(base64 + ~mask64 <= mem_hi);
+				}
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int dm_test_pci_bridge_windows(struct unit_test_state *uts)
+{
+	struct udevice *bus;
+
+	ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x03, 0), &bus));
+	if (_dm_test_pci_bridge_windows(uts, bus))
+		return CMD_RET_FAILURE;
+
+	ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x04, 0), &bus));
+	return _dm_test_pci_bridge_windows(uts, bus);
+}
+DM_TEST(dm_test_pci_bridge_windows, UTF_SCAN_PDATA | UTF_SCAN_FDT);
-- 
2.35.1.1320.gc452695387.dirty



More information about the U-Boot mailing list