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

Sean Anderson
2026-01-13T01:21:40Z

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@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@4,0 {
+ #address-cells = <3>;
+ #size-cells = <2>;
+ device_type = "pci";
+ compatible = "pciclass,0604";
+ reg = <0x00002000 0 0 0 0>;
+ ranges;
+ };
+
pci@1e,0 {
compatible = "sandbox,pmc";
reg = <0xf000 0 0 0 0>;
@@ -1340,6 +1349,30 @@
reg = <0x10>;
};

+ emul@3,0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "sandbox,pci-bridge";
+ reg = <0x18>;
+
+ emul@0,0 {
+ compatible = "sandbox,swap-case";
+ reg = <0x00>;
+ };
+ };
+
+ emul@4,0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "sandbox,pci-bridge";
+ reg = <0x20>;
+
+ emul@0,0 {
+ compatible = "sandbox,swap-case";
+ reg = <0x00>;
+ };
+ };
+
emul@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@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