* [PATCH v3 1/3] PCI: sandbox: Use a parallel structure for emulation
2026-01-13 1:20 [PATCH v3 0/3] PCI: Add power sequencing driver for PCI slots Sean Anderson
@ 2026-01-13 1:20 ` Sean Anderson
2026-01-13 20:03 ` Simon Glass
2026-01-13 1:20 ` [PATCH v3 2/3] PCI: sandbox: Add PCI bridge emulation Sean Anderson
2026-01-13 1:20 ` [PATCH v3 3/3] PCI: Add power sequencing driver for PCI slots Sean Anderson
2 siblings, 1 reply; 7+ messages in thread
From: Sean Anderson @ 2026-01-13 1:20 UTC (permalink / raw)
To: Tom Rini, u-boot; +Cc: Simon Glass, Peter Robinson, Marek Vasut, Sean Anderson
Currently, sandbox associates PCI emulators with PCI devices on a
per-device level. To create this association, the PCI bus needs an ofnode
for each device to include the appropriate phandle, or sandbox,dev-info can
be used to automatically create PCI devices. In the latter case, the
emulator is bound to the PCI device itself! This prevents testing any
U-Boot PCI driver, and also precludes bridges without device tree nodes (as
non-zero busses cannot be specified with sandbox,dev-info).
Instead of extending sandbox,dev-info, switch to a more-natural approach
where PCI emulators form a parallel tree exactly matching the PCI hierarchy
to emulate. To do the lookup, we walk up the PCI tree until we get to the
root port, look up the emulator, and retrace our steps down the emulator
tree.
As it's no longer necessary to have device tree nodes just to link to the
appropriate emulator, remove the superfluous ones. 01:10.0 and 02:1f.0 are
left in because they are required by dm_test_pci_drvdata and
dm_test_pci_mixed, respectively.
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---
Changes in v3:
- New
arch/sandbox/dts/sandbox.dtsi | 16 +++--
arch/sandbox/dts/test.dts | 85 +++++++++++++++++-------
doc/develop/driver-model/pci-info.rst | 59 +++++------------
drivers/misc/p2sb_emul.c | 7 +-
drivers/misc/swap_case.c | 7 +-
drivers/pci/pci-emul-uclass.c | 82 +++++++++++++++++------
drivers/pci/pci_sandbox.c | 94 +++++----------------------
include/pci.h | 5 +-
8 files changed, 179 insertions(+), 176 deletions(-)
diff --git a/arch/sandbox/dts/sandbox.dtsi b/arch/sandbox/dts/sandbox.dtsi
index 02b03894eaf..7d5921bb80a 100644
--- a/arch/sandbox/dts/sandbox.dtsi
+++ b/arch/sandbox/dts/sandbox.dtsi
@@ -202,10 +202,11 @@
};
pci@0 {
+ sandbox,emul = <&pci0_emul>;
+
pci@1e,0 {
compatible = "sandbox,pmc";
reg = <0xf000 0 0 0 0>;
- sandbox,emul = <&pmc_emul>;
gpe0-dwx-mask = <0xf>;
gpe0-dwx-shift-base = <4>;
gpe0-dw = <6 7 9>;
@@ -216,17 +217,22 @@
pci@1f,0 {
compatible = "pci-generic";
reg = <0xf800 0 0 0 0>;
- sandbox,emul = <&swap_case_emul>;
};
};
- emul {
+ pci0_emul: emul {
+ #address-cells = <1>;
+ #size-cells = <0>;
compatible = "sandbox,pci-emul-parent";
- pmc_emul: emul@1e,0 {
+
+ emul@1e,0 {
compatible = "sandbox,pmc-emul";
+ reg = <0xf0>;
};
- swap_case_emul: emul@1f,0 {
+
+ emul@1f,0 {
compatible = "sandbox,swap-case";
+ reg = <0xf8>;
};
};
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index cd53c170171..e945a47fb01 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1282,32 +1282,28 @@
0x01000000 0 0x20000000 0x20000000 0 0x2000>;
iommu-map = <0x0010 &iommu 0 1>;
iommu-map-mask = <0xfffffff8>;
- pci@0,0 {
- compatible = "pci-generic";
- reg = <0x0000 0 0 0 0>;
- sandbox,emul = <&swap_case_emul0_0>;
- };
+ sandbox,emul = <&pci_emul0>;
+
pci@1,0 {
compatible = "pci-generic";
/* reg 0 is at 0x14, using FDT_PCI_SPACE_MEM32 */
reg = <0x02000814 0 0 0x80 0
0x01000810 0 0 0xc0 0>;
- sandbox,emul = <&swap_case_emul0_1>;
};
+
p2sb-pci@2,0 {
compatible = "sandbox,p2sb";
reg = <0x02001010 0 0 0 0>;
- sandbox,emul = <&p2sb_emul>;
adder {
intel,p2sb-port-id = <3>;
compatible = "sandbox,adder";
};
};
+
pci@1e,0 {
compatible = "sandbox,pmc";
reg = <0xf000 0 0 0 0>;
- sandbox,emul = <&pmc_emul1e>;
acpi-base = <0x400>;
gpe0-dwx-mask = <0xf>;
gpe0-dwx-shift-base = <4>;
@@ -1315,31 +1311,43 @@
gpe0-sts = <0x20>;
gpe0-en = <0x30>;
};
+
pci@1f,0 {
compatible = "pci-generic";
/* reg 0 is at 0x10, using FDT_PCI_SPACE_IO */
reg = <0x0100f810 0 0 0x100 0>;
- sandbox,emul = <&swap_case_emul0_1f>;
};
};
- pci-emul0 {
+ pci_emul0: pci-emul0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
compatible = "sandbox,pci-emul-parent";
- swap_case_emul0_0: emul0@0,0 {
+
+ emul@0,0 {
compatible = "sandbox,swap-case";
+ reg = <0x00>;
};
- swap_case_emul0_1: emul0@1,0 {
+
+ emul@1,0 {
compatible = "sandbox,swap-case";
+ reg = <0x08>;
use-ea;
};
- swap_case_emul0_1f: emul0@1f,0 {
- compatible = "sandbox,swap-case";
- };
- p2sb_emul: emul@2,0 {
+
+ emul@2,0 {
compatible = "sandbox,p2sb-emul";
+ reg = <0x10>;
};
- pmc_emul1e: emul@1e,0 {
+
+ emul@1e,0 {
compatible = "sandbox,pmc-emul";
+ reg = <0xf0>;
+ };
+
+ emul0@1f,0 {
+ compatible = "sandbox,swap-case";
+ reg = <0xf8>;
};
};
@@ -1352,14 +1360,34 @@
ranges = <0x02000000 0 0x30000000 0x30000000 0 0x2000 // MEM0
0x02000000 0 0x31000000 0x3e000000 0 0x2000 // MEM1
0x01000000 0 0x40000000 0x40000000 0 0x2000>;
- sandbox,dev-info = <0x08 0x00 0x1234 0x5678
- 0x0c 0x00 0x1234 0x5678
- 0x10 0x00 0x1234 0x5678>;
+ sandbox,emul = <&pci_emul1>;
+
pci@10,0 {
reg = <0x8000 0 0 0 0>;
};
};
+ pci_emul1: pci-emul1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "sandbox,pci-emul-parent";
+
+ emul@8,0 {
+ compatible = "sandbox,swap-case";
+ reg = <0x40>;
+ };
+
+ emul@c,0 {
+ compatible = "sandbox,swap-case";
+ reg = <0x60>;
+ };
+
+ emul@10,0 {
+ compatible = "sandbox,swap-case";
+ reg = <0x80>;
+ };
+ };
+
pci2: pci@2 {
compatible = "sandbox,pci";
device_type = "pci";
@@ -1368,18 +1396,27 @@
#size-cells = <2>;
ranges = <0x02000000 0 0x50000000 0x50000000 0 0x2000
0x01000000 0 0x60000000 0x60000000 0 0x2000>;
- sandbox,dev-info = <0x08 0x00 0x1234 0x5678>;
+ sandbox,emul = <&pci_emul2>;
+
pci@1f,0 {
compatible = "pci-generic";
reg = <0xf800 0 0 0 0>;
- sandbox,emul = <&swap_case_emul2_1f>;
};
};
- pci-emul2 {
+ pci_emul2: pci-emul2 {
+ #address-cells = <1>;
+ #size-cells = <0>;
compatible = "sandbox,pci-emul-parent";
- swap_case_emul2_1f: emul2@1f,0 {
+
+ emul@8,0 {
compatible = "sandbox,swap-case";
+ reg = <0x40>;
+ };
+
+ emul@1f,0 {
+ compatible = "sandbox,swap-case";
+ reg = <0xf8>;
};
};
diff --git a/doc/develop/driver-model/pci-info.rst b/doc/develop/driver-model/pci-info.rst
index dea595b6cff..1c916b9009d 100644
--- a/doc/develop/driver-model/pci-info.rst
+++ b/doc/develop/driver-model/pci-info.rst
@@ -116,57 +116,28 @@ With sandbox we need a device emulator for each device on the bus since there
is no real PCI bus. This works by looking in the device tree node for an
emulator driver. For example::
- pci@1f,0 {
- compatible = "pci-generic";
- reg = <0xf800 0 0 0 0>;
- sandbox,emul = <&emul_1f>;
- };
- pci-emul {
+ pci {
+ compatible = "sandbox,pci";
+ /* ... snip ... */
+ sandbox,emul = <&pci_emul>;
+ };
+
+ pci_emul: pci-emul {
compatible = "sandbox,pci-emul-parent";
- emul_1f: emul@1f,0 {
+ emul@1f,0 {
compatible = "sandbox,swap-case";
- #emul-cells = <0>;
+ reg = <0xf8>;
};
};
-This means that there is a 'sandbox,swap-case' driver at that bus position.
-Note that the first cell in the 'reg' value is the bus/device/function. See
-PCI_BDF() for the encoding (it is also specified in the IEEE Std 1275-1994
-PCI bus binding document, v2.1)
-
-The pci-emul node should go outside the pci bus node, since otherwise it will
-be scanned as a PCI device, causing confusion.
+This means that there is a 'sandbox,swap-case' driver at device 31 function 0.
+The 'reg' value is the device (upper 5 bits) and function (lower 3 bits).
When this bus is scanned we will end up with something like this::
- `- * pci@0 @ 05c660c8, 0
- `- pci@1f,0 @ 05c661c8, 63488
- `- emul@1f,0 @ 05c662c8
+ pci 0 [ + ] pci_sandbox |-- pci@0
+ pci_generi 0 [ ] pci_generic_drv | `-- pci@1f,0
+ pci_emul_p 0 [ + ] pci_emul_parent_drv |-- emul
+ pci_emul 1 [ + ] sandbox_swap_case_em | `-- emul@1f,0
When accesses go to the pci@1f,0 device they are forwarded to its emulator.
-
-The sandbox PCI drivers also support dynamic driver binding, allowing device
-driver to declare the driver binding information via U_BOOT_PCI_DEVICE(),
-eliminating the need to provide any device tree node under the host controller
-node. It is required a "sandbox,dev-info" property must be provided in the
-host controller node for this functionality to work.
-
-.. code-block:: none
-
- pci1: pci@1 {
- compatible = "sandbox,pci";
- ...
- sandbox,dev-info = <0x08 0x00 0x1234 0x5678
- 0x0c 0x00 0x1234 0x5678>;
- };
-
-The "sandbox,dev-info" property specifies all dynamic PCI devices on this bus.
-Each dynamic PCI device is encoded as 4 cells a group. The first and second
-cells are PCI device number and function number respectively. The third and
-fourth cells are PCI vendor ID and device ID respectively.
-
-When this bus is scanned we will end up with something like this::
-
- pci [ + ] pci_sandbo |-- pci1
- pci_emul [ ] sandbox_sw | |-- sandbox_swap_case_emul
- pci_emul [ ] sandbox_sw | `-- sandbox_swap_case_emul
diff --git a/drivers/misc/p2sb_emul.c b/drivers/misc/p2sb_emul.c
index 3dac6bd82e3..12c2b0b93ae 100644
--- a/drivers/misc/p2sb_emul.c
+++ b/drivers/misc/p2sb_emul.c
@@ -264,9 +264,14 @@ U_BOOT_DRIVER(sandbox_p2sb_emul_emul) = {
.plat_auto = sizeof(struct p2sb_emul_plat),
};
+U_BOOT_DRIVER(sandbox_p2sb_emul) = {
+ .name = "sandbox_p2sb_emul",
+ .id = UCLASS_MISC,
+};
+
static struct pci_device_id sandbox_p2sb_emul_supported[] = {
{ PCI_VDEVICE(SANDBOX, SANDBOX_PCI_PMC_EMUL_ID) },
{},
};
-U_BOOT_PCI_DEVICE(sandbox_p2sb_emul_emul, sandbox_p2sb_emul_supported);
+U_BOOT_PCI_DEVICE(sandbox_p2sb_emul, sandbox_p2sb_emul_supported);
diff --git a/drivers/misc/swap_case.c b/drivers/misc/swap_case.c
index d4a5620c62c..30c8d68d8a7 100644
--- a/drivers/misc/swap_case.c
+++ b/drivers/misc/swap_case.c
@@ -406,10 +406,15 @@ U_BOOT_DRIVER(sandbox_swap_case_emul) = {
.plat_auto = sizeof(struct swap_case_plat),
};
+U_BOOT_DRIVER(sandbox_swap_case) = {
+ .name = "sandbox_swap_case",
+ .id = UCLASS_MISC,
+};
+
static struct pci_device_id sandbox_swap_case_supported[] = {
{ PCI_VDEVICE(SANDBOX, SANDBOX_PCI_SWAP_CASE_EMUL_ID),
SWAP_CASE_DRV_DATA },
{},
};
-U_BOOT_PCI_DEVICE(sandbox_swap_case_emul, sandbox_swap_case_supported);
+U_BOOT_PCI_DEVICE(sandbox_swap_case, sandbox_swap_case_supported);
diff --git a/drivers/pci/pci-emul-uclass.c b/drivers/pci/pci-emul-uclass.c
index 166ee9fcd43..e919cfd3e61 100644
--- a/drivers/pci/pci-emul-uclass.c
+++ b/drivers/pci/pci-emul-uclass.c
@@ -9,40 +9,65 @@
#include <log.h>
#include <linux/libfdt.h>
#include <pci.h>
+#include <dm/device_compat.h>
#include <dm/lists.h>
struct sandbox_pci_emul_priv {
int dev_count;
};
-int sandbox_pci_get_emul(const struct udevice *bus, pci_dev_t find_devfn,
- struct udevice **containerp, struct udevice **emulp)
+/**
+ * struct pci_emul_uc_plat - holds info about an emulator device
+ *
+ * There is always at most one emulator per client
+ *
+ * @devfn: Device/Function
+ */
+struct pci_emul_uc_plat {
+ u8 devfn;
+};
+
+int sandbox_pci_get_emul(struct udevice *bus, pci_dev_t devfn,
+ struct udevice **emulp)
{
- struct pci_emul_uc_priv *upriv;
+ struct pci_emul_uc_priv *cpriv;
struct udevice *dev;
int ret;
- *containerp = NULL;
- ret = pci_bus_find_devfn(bus, PCI_MASK_BUS(find_devfn), &dev);
- if (ret) {
- debug("%s: Could not find emulator for dev %x\n", __func__,
- find_devfn);
- return ret;
+ if (device_is_on_pci_bus(bus)) {
+ ret = sandbox_pci_get_emul(bus->parent,
+ PCI_MASK_BUS(dm_pci_get_bdf(bus)),
+ &dev);
+ if (ret)
+ return ret;
+ } else {
+ ret = uclass_get_device_by_phandle(UCLASS_PCI_EMUL_PARENT, bus,
+ "sandbox,emul", &dev);
+ if (ret) {
+ dev_dbg(bus, "Could not find emulator: %d\n", ret);
+ return ret;
+ }
}
- *containerp = dev;
- ret = uclass_get_device_by_phandle(UCLASS_PCI_EMUL, dev, "sandbox,emul",
- emulp);
- if (!ret) {
- upriv = dev_get_uclass_priv(*emulp);
+ device_foreach_child_probe(*emulp, dev) {
+ struct pci_emul_uc_plat *plat = dev_get_uclass_plat(*emulp);
- upriv->client = dev;
- } else if (device_get_uclass_id(dev) != UCLASS_PCI_GENERIC) {
- /*
- * See commit 4345998ae9df,
- * "pci: sandbox: Support dynamically binding device driver"
- */
- *emulp = dev;
+ if (PCI_TO_BDF(plat->devfn) == devfn)
+ goto found;
+ }
+
+ dev_dbg(dev, "No emulator for %x.%x\n", PCI_DEV(devfn), PCI_FUNC(devfn));
+ *emulp = NULL;
+ return -ENODEV;
+
+found:
+ cpriv = dev_get_uclass_priv(*emulp);
+ if (!cpriv->client) {
+ int err;
+
+ err = pci_bus_find_devfn(bus, devfn, &cpriv->client);
+ if (err)
+ dev_dbg(*emulp, "Could not find client: %d\n", err);
}
return 0;
@@ -79,6 +104,19 @@ uint sandbox_pci_read_bar(u32 barval, int type, uint size)
return result;
}
+static int sandbox_pci_emul_post_bind(struct udevice *dev)
+{
+ struct pci_emul_uc_plat *plat = dev_get_uclass_plat(dev);
+ fdt_addr_t devfn, size;
+
+ devfn = ofnode_get_addr_size_index_notrans(dev_ofnode(dev), 0, &size);
+ if (devfn > 255)
+ return -EINVAL;
+
+ plat->devfn = devfn;
+ return dm_scan_fdt_dev(dev);
+}
+
static int sandbox_pci_emul_post_probe(struct udevice *dev)
{
struct sandbox_pci_emul_priv *priv = uclass_get_priv(dev->uclass);
@@ -102,10 +140,12 @@ static int sandbox_pci_emul_pre_remove(struct udevice *dev)
UCLASS_DRIVER(pci_emul) = {
.id = UCLASS_PCI_EMUL,
.name = "pci_emul",
+ .post_bind = sandbox_pci_emul_post_bind,
.post_probe = sandbox_pci_emul_post_probe,
.pre_remove = sandbox_pci_emul_pre_remove,
.priv_auto = sizeof(struct sandbox_pci_emul_priv),
.per_device_auto = sizeof(struct pci_emul_uc_priv),
+ .per_device_plat_auto = sizeof(struct pci_emul_uc_plat),
};
/*
diff --git a/drivers/pci/pci_sandbox.c b/drivers/pci/pci_sandbox.c
index fed0850458d..a55802b6720 100644
--- a/drivers/pci/pci_sandbox.c
+++ b/drivers/pci/pci_sandbox.c
@@ -9,29 +9,22 @@
#include <log.h>
#include <pci.h>
-#define FDT_DEV_INFO_CELLS 4
-#define FDT_DEV_INFO_SIZE (FDT_DEV_INFO_CELLS * sizeof(u32))
-
-#define SANDBOX_PCI_DEVFN(d, f) ((d << 3) | f)
-
-struct sandbox_pci_priv {
- struct {
- u16 vendor;
- u16 device;
- } vendev[256];
-};
-
-static int sandbox_pci_write_config(struct udevice *bus, pci_dev_t devfn,
+static int sandbox_pci_write_config(struct udevice *bus, pci_dev_t bdf,
uint offset, ulong value,
enum pci_size_t size)
{
struct dm_pci_emul_ops *ops;
- struct udevice *container, *emul;
+ struct udevice *emul;
int ret;
- ret = sandbox_pci_get_emul(bus, devfn, &container, &emul);
+ ret = uclass_get_device_by_seq(UCLASS_PCI, PCI_BUS(bdf), &bus);
+ if (ret)
+ return ret;
+
+ ret = sandbox_pci_get_emul(bus, PCI_MASK_BUS(bdf), &emul);
if (ret)
return ret == -ENODEV ? 0 : ret;
+
ops = pci_get_emul_ops(emul);
if (!ops || !ops->write_config)
return -ENOSYS;
@@ -39,36 +32,24 @@ static int sandbox_pci_write_config(struct udevice *bus, pci_dev_t devfn,
return ops->write_config(emul, offset, value, size);
}
-static int sandbox_pci_read_config(const struct udevice *bus, pci_dev_t devfn,
+static int sandbox_pci_read_config(const struct udevice *bus, pci_dev_t bdf,
uint offset, ulong *valuep,
enum pci_size_t size)
{
struct dm_pci_emul_ops *ops;
- struct udevice *container, *emul;
- struct sandbox_pci_priv *priv = dev_get_priv(bus);
+ struct udevice *emul, *sub_bus;
int ret;
+ ret = uclass_get_device_by_seq(UCLASS_PCI, PCI_BUS(bdf), &sub_bus);
+ if (ret)
+ return ret;
+
/* Prepare the default response */
*valuep = pci_get_ff(size);
- ret = sandbox_pci_get_emul(bus, devfn, &container, &emul);
- if (ret) {
- if (!container) {
- u16 vendor, device;
+ ret = sandbox_pci_get_emul(sub_bus, PCI_MASK_BUS(bdf), &emul);
+ if (ret)
+ return ret == -ENODEV ? 0 : ret;
- devfn = SANDBOX_PCI_DEVFN(PCI_DEV(devfn),
- PCI_FUNC(devfn));
- vendor = priv->vendev[devfn].vendor;
- device = priv->vendev[devfn].device;
- if (offset == PCI_VENDOR_ID && vendor)
- *valuep = vendor;
- else if (offset == PCI_DEVICE_ID && device)
- *valuep = device;
-
- return 0;
- } else {
- return ret == -ENODEV ? 0 : ret;
- }
- }
ops = pci_get_emul_ops(emul);
if (!ops || !ops->read_config)
return -ENOSYS;
@@ -76,41 +57,6 @@ static int sandbox_pci_read_config(const struct udevice *bus, pci_dev_t devfn,
return ops->read_config(emul, offset, valuep, size);
}
-static int sandbox_pci_probe(struct udevice *dev)
-{
- struct sandbox_pci_priv *priv = dev_get_priv(dev);
- const fdt32_t *cell;
- u8 pdev, pfn, devfn;
- int len;
-
- cell = ofnode_get_property(dev_ofnode(dev), "sandbox,dev-info", &len);
- if (!cell)
- return 0;
-
- if ((len % FDT_DEV_INFO_SIZE) == 0) {
- int num = len / FDT_DEV_INFO_SIZE;
- int i;
-
- for (i = 0; i < num; i++) {
- debug("dev info #%d: %02x %02x %04x %04x\n", i,
- fdt32_to_cpu(cell[0]), fdt32_to_cpu(cell[1]),
- fdt32_to_cpu(cell[2]), fdt32_to_cpu(cell[3]));
-
- pdev = fdt32_to_cpu(cell[0]);
- pfn = fdt32_to_cpu(cell[1]);
- if (pdev > 31 || pfn > 7)
- continue;
- devfn = SANDBOX_PCI_DEVFN(pdev, pfn);
- priv->vendev[devfn].vendor = fdt32_to_cpu(cell[2]);
- priv->vendev[devfn].device = fdt32_to_cpu(cell[3]);
-
- cell += FDT_DEV_INFO_CELLS;
- }
- }
-
- return 0;
-}
-
static const struct dm_pci_ops sandbox_pci_ops = {
.read_config = sandbox_pci_read_config,
.write_config = sandbox_pci_write_config,
@@ -126,10 +72,4 @@ U_BOOT_DRIVER(pci_sandbox) = {
.id = UCLASS_PCI,
.of_match = sandbox_pci_ids,
.ops = &sandbox_pci_ops,
- .probe = sandbox_pci_probe,
- .priv_auto = sizeof(struct sandbox_pci_priv),
-
- /* Attach an emulator if we can */
- .child_post_bind = dm_scan_fdt_dev,
- .per_child_plat_auto = sizeof(struct pci_child_plat),
};
diff --git a/include/pci.h b/include/pci.h
index 4b0facd6dcf..a8960140525 100644
--- a/include/pci.h
+++ b/include/pci.h
@@ -1630,12 +1630,11 @@ struct dm_pci_emul_ops {
*
* @bus: PCI bus to search
* @find_devfn: PCI device and function address (PCI_DEVFN())
- * @containerp: Returns container device if found
* @emulp: Returns emulated device if found
* Return: 0 if found, -ENODEV if not found
*/
-int sandbox_pci_get_emul(const struct udevice *bus, pci_dev_t find_devfn,
- struct udevice **containerp, struct udevice **emulp);
+int sandbox_pci_get_emul(struct udevice *bus, pci_dev_t find_devfn,
+ struct udevice **emulp);
/**
* sandbox_pci_get_client() - Find the client for an emulation device
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH v3 1/3] PCI: sandbox: Use a parallel structure for emulation
2026-01-13 1:20 ` [PATCH v3 1/3] PCI: sandbox: Use a parallel structure for emulation Sean Anderson
@ 2026-01-13 20:03 ` Simon Glass
0 siblings, 0 replies; 7+ messages in thread
From: Simon Glass @ 2026-01-13 20:03 UTC (permalink / raw)
To: Sean Anderson; +Cc: Tom Rini, u-boot, Peter Robinson, Marek Vasut
Hi Sean,
On Mon, 12 Jan 2026 at 18:21, Sean Anderson <sean.anderson@linux.dev> wrote:
>
> Currently, sandbox associates PCI emulators with PCI devices on a
> per-device level. To create this association, the PCI bus needs an ofnode
> for each device to include the appropriate phandle, or sandbox,dev-info can
> be used to automatically create PCI devices. In the latter case, the
> emulator is bound to the PCI device itself! This prevents testing any
> U-Boot PCI driver, and also precludes bridges without device tree nodes (as
> non-zero busses cannot be specified with sandbox,dev-info).
>
> Instead of extending sandbox,dev-info, switch to a more-natural approach
> where PCI emulators form a parallel tree exactly matching the PCI hierarchy
> to emulate. To do the lookup, we walk up the PCI tree until we get to the
> root port, look up the emulator, and retrace our steps down the emulator
> tree.
>
> As it's no longer necessary to have device tree nodes just to link to the
> appropriate emulator, remove the superfluous ones. 01:10.0 and 02:1f.0 are
> left in because they are required by dm_test_pci_drvdata and
> dm_test_pci_mixed, respectively.
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> ---
>
> Changes in v3:
> - New
>
> arch/sandbox/dts/sandbox.dtsi | 16 +++--
> arch/sandbox/dts/test.dts | 85 +++++++++++++++++-------
> doc/develop/driver-model/pci-info.rst | 59 +++++------------
> drivers/misc/p2sb_emul.c | 7 +-
> drivers/misc/swap_case.c | 7 +-
> drivers/pci/pci-emul-uclass.c | 82 +++++++++++++++++------
> drivers/pci/pci_sandbox.c | 94 +++++----------------------
> include/pci.h | 5 +-
> 8 files changed, 179 insertions(+), 176 deletions(-)
Thank you for taking this on.
Reviewed-by: Simon Glass <simon.glass@canonical.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v3 2/3] PCI: sandbox: Add PCI bridge emulation
2026-01-13 1:20 [PATCH v3 0/3] PCI: Add power sequencing driver for PCI slots Sean Anderson
2026-01-13 1:20 ` [PATCH v3 1/3] PCI: sandbox: Use a parallel structure for emulation Sean Anderson
@ 2026-01-13 1:20 ` Sean Anderson
2026-01-13 20:03 ` Simon Glass
2026-01-13 1:20 ` [PATCH v3 3/3] PCI: Add power sequencing driver for PCI slots Sean Anderson
2 siblings, 1 reply; 7+ messages in thread
From: Sean Anderson @ 2026-01-13 1:20 UTC (permalink / raw)
To: Tom Rini, u-boot; +Cc: Simon Glass, Peter Robinson, Marek Vasut, Sean Anderson
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
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH v3 2/3] PCI: sandbox: Add PCI bridge emulation
2026-01-13 1:20 ` [PATCH v3 2/3] PCI: sandbox: Add PCI bridge emulation Sean Anderson
@ 2026-01-13 20:03 ` Simon Glass
0 siblings, 0 replies; 7+ messages in thread
From: Simon Glass @ 2026-01-13 20:03 UTC (permalink / raw)
To: Sean Anderson; +Cc: Tom Rini, u-boot, Peter Robinson, Marek Vasut
On Mon, 12 Jan 2026 at 18:21, Sean Anderson <sean.anderson@linux.dev> wrote:
>
> 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
Reviewed-by: Simon Glass <simon.glass@canonical.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v3 3/3] PCI: Add power sequencing driver for PCI slots
2026-01-13 1:20 [PATCH v3 0/3] PCI: Add power sequencing driver for PCI slots Sean Anderson
2026-01-13 1:20 ` [PATCH v3 1/3] PCI: sandbox: Use a parallel structure for emulation Sean Anderson
2026-01-13 1:20 ` [PATCH v3 2/3] PCI: sandbox: Add PCI bridge emulation Sean Anderson
@ 2026-01-13 1:20 ` Sean Anderson
2026-01-13 20:03 ` Simon Glass
2 siblings, 1 reply; 7+ messages in thread
From: Sean Anderson @ 2026-01-13 1:20 UTC (permalink / raw)
To: Tom Rini, u-boot; +Cc: Simon Glass, Peter Robinson, Marek Vasut, Sean Anderson
Extend the PCI bridge driver to enable resources associated with PCI
slots like clocks, power rails, and resets. This is modeled off of the
PCI power control subsystem in Linux. The traditional compatible for PCI
slots in U-Boot is pci-bridge, but Linux uses the more-systematic
pciclass,0604 so add that as an option.
Add a test to make sure the clock/gpio get enabled and that we skip some
of the delays when PERST is already asserted.
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---
Changes in v3:
- Add a test
Changes in v2:
- Return early if there's no PERST GPIO
- Only mdelay if we need to
- Use CONFIG_IS_ENABLED to set .probe directly
arch/sandbox/dts/test.dts | 12 ++++++-
configs/sandbox64_defconfig | 2 ++
configs/sandbox_defconfig | 2 ++
drivers/pci/Kconfig | 8 +++++
drivers/pci/pci-uclass.c | 65 +++++++++++++++++++++++++++++++++++++
test/dm/pci.c | 57 ++++++++++++++++++++++++++++++++
6 files changed, 145 insertions(+), 1 deletion(-)
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 962d364f9b2..913dea30fb0 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -632,6 +632,13 @@
clocks = <&clk_fixed>;
};
+ pci_refclk: clk-gpio {
+ compatible = "gpio-gate-clock";
+ #clock-cells = <0>;
+ clocks = <&clk_fixed>;
+ enable-gpios = <&gpio_a 26>;
+ };
+
osc {
compatible = "fixed-clock";
#clock-cells = <0>;
@@ -822,7 +829,7 @@
gpio-controller;
#gpio-cells = <1>;
gpio-bank-name = "a";
- sandbox,gpio-count = <25>;
+ sandbox,gpio-count = <27>;
gpio-line-names = "", "eth1-reset", "rtc-irq";
hog_input_active_low {
gpio-hog;
@@ -1308,6 +1315,9 @@
compatible = "pciclass,0604";
reg = <0x00002000 0 0 0 0>;
ranges;
+
+ clocks = <&pci_refclk>;
+ reset-gpios = <&gpio_a 25>;
};
pci@1e,0 {
diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig
index 440e5efa340..624a44eef59 100644
--- a/configs/sandbox64_defconfig
+++ b/configs/sandbox64_defconfig
@@ -139,6 +139,7 @@ CONFIG_BUTTON_ADC=y
CONFIG_BUTTON_GPIO=y
CONFIG_CLK=y
CONFIG_CLK_COMPOSITE_CCF=y
+CONFIG_CLK_GPIO=y
CONFIG_CLK_K210=y
CONFIG_CLK_K210_SET_RATE=y
CONFIG_SANDBOX_CLK_CCF=y
@@ -201,6 +202,7 @@ CONFIG_SPI_FLASH_WINBOND=y
CONFIG_NVMXIP_QSPI=y
CONFIG_NVME_PCI=y
CONFIG_PCI_REGION_MULTI_ENTRY=y
+CONFIG_PCI_PWRCTRL_SLOT=y
CONFIG_PCI_SANDBOX=y
CONFIG_PHY=y
CONFIG_PHY_SANDBOX=y
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 92fb5f844b1..1403ca1d9d0 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -191,6 +191,7 @@ CONFIG_BUTTON_ADC=y
CONFIG_BUTTON_GPIO=y
CONFIG_CLK=y
CONFIG_CLK_COMPOSITE_CCF=y
+CONFIG_CLK_GPIO=y
CONFIG_CLK_K210=y
CONFIG_CLK_K210_SET_RATE=y
CONFIG_SANDBOX_CLK_CCF=y
@@ -279,6 +280,7 @@ CONFIG_MULTIPLEXER=y
CONFIG_MUX_MMIO=y
CONFIG_NVME_PCI=y
CONFIG_PCI_REGION_MULTI_ENTRY=y
+CONFIG_PCI_PWRCTRL_SLOT=y
CONFIG_PCI_FTPCI100=y
CONFIG_PCI_SANDBOX=y
CONFIG_PHY=y
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index ea9868425d0..efac18b33f6 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -100,6 +100,14 @@ config PCI_ENHANCED_ALLOCATION
Enable support for Enhanced Allocation which can be used by supported
devices in place of traditional BARS for allocation of resources.
+config PCI_PWRCTRL_SLOT
+ bool "PCI slot power control"
+ help
+ This is a generic driver that controls the power state of different
+ PCI slots. The clocks and resets for the PCI slots are expected to be
+ defined in the devicetree node of the PCI bridge. Say N if your PCI
+ busses don't have software-controlled clocks or power rails.
+
config PCI_ARID
bool "Enable Alternate Routing-ID support for PCI"
help
diff --git a/drivers/pci/pci-uclass.c b/drivers/pci/pci-uclass.c
index c370f8c6400..6f0e6c2f8cc 100644
--- a/drivers/pci/pci-uclass.c
+++ b/drivers/pci/pci-uclass.c
@@ -6,6 +6,7 @@
#define LOG_CATEGORY UCLASS_PCI
+#include <clk.h>
#include <dm.h>
#include <errno.h>
#include <init.h>
@@ -14,6 +15,7 @@
#include <pci.h>
#include <spl.h>
#include <asm/global_data.h>
+#include <asm/gpio.h>
#include <asm/io.h>
#include <dm/device-internal.h>
#include <dm/lists.h>
@@ -1893,13 +1895,76 @@ static const struct dm_pci_ops pci_bridge_ops = {
static const struct udevice_id pci_bridge_ids[] = {
{ .compatible = "pci-bridge" },
+ { .compatible = "pciclass,0604" },
{ }
};
+#ifdef CONFIG_SANDBOX
+#include <asm/state.h>
+#endif
+
+static int __maybe_unused pci_bridge_probe(struct udevice *dev)
+{
+ struct clk clk;
+ struct gpio_desc perst;
+ unsigned long delay = 0;
+ int ret;
+
+ if (!clk_get_by_index(dev, 0, &clk)) {
+ int ret = clk_enable(&clk);
+
+ if (ret)
+ return log_msg_ret("clk", ret);
+
+ /* Delay for T_PERST-CLK (100 us for all slot types) */
+ udelay(100);
+ }
+
+ if (gpio_request_by_name(dev, "reset-gpios", 0, &perst, 0))
+ return 0;
+
+ /*
+ * If PERST is inactive, the following call to dm_gpio_clrset_flags
+ * will be the first time we assert it and we will need to delay for
+ * T_PERST.
+ */
+ if (dm_gpio_get_value(&perst) != 1)
+ delay = 100;
+
+ ret = dm_gpio_clrset_flags(&perst, GPIOD_MASK_DIR,
+ GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
+ if (ret)
+ return log_msg_ret("set", ret);
+
+#ifdef CONFIG_SANDBOX
+ if (!state_get_skip_delays())
+#endif
+ if (delay)
+ mdelay(delay);
+
+ ret = dm_gpio_set_value(&perst, 0);
+ if (ret)
+ return log_msg_ret("clr", ret);
+
+ /*
+ * PCIe section 6.6.1:
+ * > ... software must wait a minimum of 100 ms before sending a
+ * > Configuration Request to the device immediately below that Port.
+ */
+#ifdef CONFIG_SANDBOX
+ if (!state_get_skip_delays())
+#endif
+ mdelay(100);
+
+ return 0;
+}
+
U_BOOT_DRIVER(pci_bridge_drv) = {
.name = "pci_bridge_drv",
.id = UCLASS_PCI,
.of_match = pci_bridge_ids,
+ .probe = CONFIG_IS_ENABLED(PCI_PWRCTRL_SLOT,
+ (pci_bridge_probe), NULL),
.ops = &pci_bridge_ops,
};
diff --git a/test/dm/pci.c b/test/dm/pci.c
index b6fee7b3bb3..f594783444b 100644
--- a/test/dm/pci.c
+++ b/test/dm/pci.c
@@ -5,6 +5,7 @@
#include <dm.h>
#include <dm/device_compat.h>
+#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/test.h>
#include <dm/test.h>
@@ -16,6 +17,7 @@ static int dm_test_pci_base(struct unit_test_state *uts)
{
struct udevice *bus;
+ test_set_skip_delays(true);
ut_assertok(uclass_get_device(UCLASS_PCI, 0, &bus));
return 0;
@@ -30,6 +32,7 @@ static int dm_test_pci_busdev(struct unit_test_state *uts)
u16 vendor, device;
/* Test bus#0 and its devices */
+ test_set_skip_delays(true);
ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 0, &bus));
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x00, 0), &swap));
@@ -65,6 +68,7 @@ static int dm_test_pci_swapcase(struct unit_test_state *uts)
char *ptr;
/* Check that asking for the device 0 automatically fires up PCI */
+ test_set_skip_delays(true);
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x00, 0), &swap));
/* First test I/O */
@@ -116,6 +120,7 @@ static int dm_test_pci_drvdata(struct unit_test_state *uts)
struct udevice *bus, *swap;
/* Check that asking for the device automatically fires up PCI */
+ test_set_skip_delays(true);
ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 1, &bus));
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &swap));
@@ -141,6 +146,7 @@ static int dm_test_pci_mixed(struct unit_test_state *uts)
ulong io_addr, mem_addr;
char *ptr;
+ test_set_skip_delays(true);
ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 2, &bus));
/* Test the dynamic device */
@@ -201,6 +207,7 @@ static int dm_test_pci_cap(struct unit_test_state *uts)
struct udevice *bus, *swap;
int cap;
+ test_set_skip_delays(true);
ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 0, &bus));
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &swap));
@@ -258,6 +265,7 @@ static int dm_test_pci_ea(struct unit_test_state *uts)
* use emulated device mapping function, we're not using real physical
* addresses in this test
*/
+ test_set_skip_delays(true);
sandbox_set_enable_pci_map(true);
ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 0, &bus));
@@ -303,6 +311,7 @@ static int dm_test_pci_addr_flat(struct unit_test_state *uts)
ulong io_addr, mem_addr;
fdt_addr_t size;
+ test_set_skip_delays(true);
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &swap1f));
io_addr = dm_pci_read_bar32(swap1f, 0);
ut_asserteq(io_addr, dev_read_addr_pci(swap1f, &size));
@@ -334,6 +343,7 @@ static int dm_test_pci_addr_live(struct unit_test_state *uts)
struct udevice *swap1f, *swap1;
fdt_size_t size;
+ test_set_skip_delays(true);
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &swap1f));
ut_asserteq_64(FDT_ADDR_T_NONE, dev_read_addr_pci(swap1f, &size));
ut_asserteq(0, size);
@@ -351,6 +361,7 @@ static int dm_test_pci_on_bus(struct unit_test_state *uts)
{
struct udevice *dev;
+ test_set_skip_delays(true);
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &dev));
ut_asserteq(true, device_is_on_pci_bus(dev));
ut_asserteq(false, device_is_on_pci_bus(dev_get_parent(dev)));
@@ -374,6 +385,7 @@ static int dm_test_pci_region_multi(struct unit_test_state *uts)
ulong mem_addr;
/* Test memory BAR1 on bus#1 */
+ test_set_skip_delays(true);
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &dev));
mem_addr = dm_pci_read_bar32(dev, 1);
ut_asserteq(mem_addr, 0x30000000);
@@ -393,6 +405,7 @@ static int dm_test_pci_bus_to_phys(struct unit_test_state *uts)
struct udevice *dev;
phys_addr_t phys_addr;
+ test_set_skip_delays(true);
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &dev));
/* Before any of the ranges. */
@@ -446,6 +459,7 @@ static int dm_test_pci_phys_to_bus(struct unit_test_state *uts)
struct udevice *dev;
pci_addr_t pci_addr;
+ test_set_skip_delays(true);
ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &dev));
/* Before any of the ranges. */
@@ -620,6 +634,7 @@ static int dm_test_pci_bridge_windows(struct unit_test_state *uts)
{
struct udevice *bus;
+ test_set_skip_delays(true);
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;
@@ -628,3 +643,45 @@ static int dm_test_pci_bridge_windows(struct unit_test_state *uts)
return _dm_test_pci_bridge_windows(uts, bus);
}
DM_TEST(dm_test_pci_bridge_windows, UTF_SCAN_PDATA | UTF_SCAN_FDT);
+
+#if IS_ENABLED(CONFIG_PCI_PWRCTRL_SLOT)
+/* GPIO 25 is PERST; GPIO 26 is REFCLK enable */
+static int dm_test_pci_pwrseq_cold(struct unit_test_state *uts)
+{
+ struct udevice *bus, *gpio;
+ unsigned long start, end;
+
+ ut_assertok(uclass_get_device(UCLASS_GPIO, 1, &gpio));
+ ut_assertok(sandbox_gpio_set_value(gpio, 25, 1));
+
+ start = timer_get_us();
+ ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x04, 0), &bus));
+ end = timer_get_us();
+ ut_assert(end - start > 100000);
+ ut_assert(end - start < 200000);
+
+ ut_asserteq(0, sandbox_gpio_get_value(gpio, 25));
+ ut_asserteq(1, sandbox_gpio_get_value(gpio, 26));
+
+ return 0;
+}
+DM_TEST(dm_test_pci_pwrseq_cold, UTF_SCAN_PDATA | UTF_SCAN_FDT);
+
+static int dm_test_pci_pwrseq_warm(struct unit_test_state *uts)
+{
+ struct udevice *bus, *gpio;
+ unsigned long start;
+
+ ut_assertok(uclass_get_device(UCLASS_GPIO, 1, &gpio));
+
+ start = timer_get_us();
+ ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x04, 0), &bus));
+ ut_assert(timer_get_us() - start > 200000);
+
+ ut_asserteq(0, sandbox_gpio_get_value(gpio, 25));
+ ut_asserteq(1, sandbox_gpio_get_value(gpio, 26));
+
+ return 0;
+}
+DM_TEST(dm_test_pci_pwrseq_warm, UTF_SCAN_PDATA | UTF_SCAN_FDT);
+#endif
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH v3 3/3] PCI: Add power sequencing driver for PCI slots
2026-01-13 1:20 ` [PATCH v3 3/3] PCI: Add power sequencing driver for PCI slots Sean Anderson
@ 2026-01-13 20:03 ` Simon Glass
0 siblings, 0 replies; 7+ messages in thread
From: Simon Glass @ 2026-01-13 20:03 UTC (permalink / raw)
To: Sean Anderson; +Cc: Tom Rini, u-boot, Peter Robinson, Marek Vasut
On Mon, 12 Jan 2026 at 18:21, Sean Anderson <sean.anderson@linux.dev> wrote:
>
> Extend the PCI bridge driver to enable resources associated with PCI
> slots like clocks, power rails, and resets. This is modeled off of the
> PCI power control subsystem in Linux. The traditional compatible for PCI
> slots in U-Boot is pci-bridge, but Linux uses the more-systematic
> pciclass,0604 so add that as an option.
>
> Add a test to make sure the clock/gpio get enabled and that we skip some
> of the delays when PERST is already asserted.
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> ---
>
> Changes in v3:
> - Add a test
>
> Changes in v2:
> - Return early if there's no PERST GPIO
> - Only mdelay if we need to
> - Use CONFIG_IS_ENABLED to set .probe directly
>
> arch/sandbox/dts/test.dts | 12 ++++++-
> configs/sandbox64_defconfig | 2 ++
> configs/sandbox_defconfig | 2 ++
> drivers/pci/Kconfig | 8 +++++
> drivers/pci/pci-uclass.c | 65 +++++++++++++++++++++++++++++++++++++
> test/dm/pci.c | 57 ++++++++++++++++++++++++++++++++
> 6 files changed, 145 insertions(+), 1 deletion(-)
Reviewed-by: Simon Glass <simon.glass@canonical.com>
At some point we should have a static inline to skip delays on
sandbox, e.g. arch_skip_delays() like we have arch_reset_for_test() -
that would avoid the #include and the #ifdef
>
> diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
> index 962d364f9b2..913dea30fb0 100644
> --- a/arch/sandbox/dts/test.dts
> +++ b/arch/sandbox/dts/test.dts
> @@ -632,6 +632,13 @@
> clocks = <&clk_fixed>;
> };
>
> + pci_refclk: clk-gpio {
> + compatible = "gpio-gate-clock";
> + #clock-cells = <0>;
> + clocks = <&clk_fixed>;
> + enable-gpios = <&gpio_a 26>;
> + };
> +
> osc {
> compatible = "fixed-clock";
> #clock-cells = <0>;
> @@ -822,7 +829,7 @@
> gpio-controller;
> #gpio-cells = <1>;
> gpio-bank-name = "a";
> - sandbox,gpio-count = <25>;
> + sandbox,gpio-count = <27>;
> gpio-line-names = "", "eth1-reset", "rtc-irq";
> hog_input_active_low {
> gpio-hog;
> @@ -1308,6 +1315,9 @@
> compatible = "pciclass,0604";
> reg = <0x00002000 0 0 0 0>;
> ranges;
> +
> + clocks = <&pci_refclk>;
> + reset-gpios = <&gpio_a 25>;
> };
>
> pci@1e,0 {
> diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig
> index 440e5efa340..624a44eef59 100644
> --- a/configs/sandbox64_defconfig
> +++ b/configs/sandbox64_defconfig
> @@ -139,6 +139,7 @@ CONFIG_BUTTON_ADC=y
> CONFIG_BUTTON_GPIO=y
> CONFIG_CLK=y
> CONFIG_CLK_COMPOSITE_CCF=y
> +CONFIG_CLK_GPIO=y
> CONFIG_CLK_K210=y
> CONFIG_CLK_K210_SET_RATE=y
> CONFIG_SANDBOX_CLK_CCF=y
> @@ -201,6 +202,7 @@ CONFIG_SPI_FLASH_WINBOND=y
> CONFIG_NVMXIP_QSPI=y
> CONFIG_NVME_PCI=y
> CONFIG_PCI_REGION_MULTI_ENTRY=y
> +CONFIG_PCI_PWRCTRL_SLOT=y
> CONFIG_PCI_SANDBOX=y
> CONFIG_PHY=y
> CONFIG_PHY_SANDBOX=y
> diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
> index 92fb5f844b1..1403ca1d9d0 100644
> --- a/configs/sandbox_defconfig
> +++ b/configs/sandbox_defconfig
> @@ -191,6 +191,7 @@ CONFIG_BUTTON_ADC=y
> CONFIG_BUTTON_GPIO=y
> CONFIG_CLK=y
> CONFIG_CLK_COMPOSITE_CCF=y
> +CONFIG_CLK_GPIO=y
> CONFIG_CLK_K210=y
> CONFIG_CLK_K210_SET_RATE=y
> CONFIG_SANDBOX_CLK_CCF=y
> @@ -279,6 +280,7 @@ CONFIG_MULTIPLEXER=y
> CONFIG_MUX_MMIO=y
> CONFIG_NVME_PCI=y
> CONFIG_PCI_REGION_MULTI_ENTRY=y
> +CONFIG_PCI_PWRCTRL_SLOT=y
> CONFIG_PCI_FTPCI100=y
> CONFIG_PCI_SANDBOX=y
> CONFIG_PHY=y
> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
> index ea9868425d0..efac18b33f6 100644
> --- a/drivers/pci/Kconfig
> +++ b/drivers/pci/Kconfig
> @@ -100,6 +100,14 @@ config PCI_ENHANCED_ALLOCATION
> Enable support for Enhanced Allocation which can be used by supported
> devices in place of traditional BARS for allocation of resources.
>
> +config PCI_PWRCTRL_SLOT
> + bool "PCI slot power control"
> + help
> + This is a generic driver that controls the power state of different
> + PCI slots. The clocks and resets for the PCI slots are expected to be
> + defined in the devicetree node of the PCI bridge. Say N if your PCI
> + busses don't have software-controlled clocks or power rails.
> +
> config PCI_ARID
> bool "Enable Alternate Routing-ID support for PCI"
> help
> diff --git a/drivers/pci/pci-uclass.c b/drivers/pci/pci-uclass.c
> index c370f8c6400..6f0e6c2f8cc 100644
> --- a/drivers/pci/pci-uclass.c
> +++ b/drivers/pci/pci-uclass.c
> @@ -6,6 +6,7 @@
>
> #define LOG_CATEGORY UCLASS_PCI
>
> +#include <clk.h>
> #include <dm.h>
> #include <errno.h>
> #include <init.h>
> @@ -14,6 +15,7 @@
> #include <pci.h>
> #include <spl.h>
> #include <asm/global_data.h>
> +#include <asm/gpio.h>
> #include <asm/io.h>
> #include <dm/device-internal.h>
> #include <dm/lists.h>
> @@ -1893,13 +1895,76 @@ static const struct dm_pci_ops pci_bridge_ops = {
>
> static const struct udevice_id pci_bridge_ids[] = {
> { .compatible = "pci-bridge" },
> + { .compatible = "pciclass,0604" },
> { }
> };
>
> +#ifdef CONFIG_SANDBOX
> +#include <asm/state.h>
> +#endif
> +
> +static int __maybe_unused pci_bridge_probe(struct udevice *dev)
> +{
> + struct clk clk;
> + struct gpio_desc perst;
> + unsigned long delay = 0;
> + int ret;
> +
> + if (!clk_get_by_index(dev, 0, &clk)) {
> + int ret = clk_enable(&clk);
> +
> + if (ret)
> + return log_msg_ret("clk", ret);
> +
> + /* Delay for T_PERST-CLK (100 us for all slot types) */
> + udelay(100);
> + }
> +
> + if (gpio_request_by_name(dev, "reset-gpios", 0, &perst, 0))
> + return 0;
> +
> + /*
> + * If PERST is inactive, the following call to dm_gpio_clrset_flags
> + * will be the first time we assert it and we will need to delay for
> + * T_PERST.
> + */
> + if (dm_gpio_get_value(&perst) != 1)
> + delay = 100;
> +
> + ret = dm_gpio_clrset_flags(&perst, GPIOD_MASK_DIR,
> + GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
> + if (ret)
> + return log_msg_ret("set", ret);
> +
> +#ifdef CONFIG_SANDBOX
> + if (!state_get_skip_delays())
> +#endif
> + if (delay)
> + mdelay(delay);
> +
> + ret = dm_gpio_set_value(&perst, 0);
> + if (ret)
> + return log_msg_ret("clr", ret);
> +
> + /*
> + * PCIe section 6.6.1:
> + * > ... software must wait a minimum of 100 ms before sending a
> + * > Configuration Request to the device immediately below that Port.
> + */
> +#ifdef CONFIG_SANDBOX
> + if (!state_get_skip_delays())
> +#endif
> + mdelay(100);
> +
> + return 0;
> +}
> +
> U_BOOT_DRIVER(pci_bridge_drv) = {
> .name = "pci_bridge_drv",
> .id = UCLASS_PCI,
> .of_match = pci_bridge_ids,
> + .probe = CONFIG_IS_ENABLED(PCI_PWRCTRL_SLOT,
> + (pci_bridge_probe), NULL),
> .ops = &pci_bridge_ops,
> };
>
> diff --git a/test/dm/pci.c b/test/dm/pci.c
> index b6fee7b3bb3..f594783444b 100644
> --- a/test/dm/pci.c
> +++ b/test/dm/pci.c
> @@ -5,6 +5,7 @@
>
> #include <dm.h>
> #include <dm/device_compat.h>
> +#include <asm/gpio.h>
> #include <asm/io.h>
> #include <asm/test.h>
> #include <dm/test.h>
> @@ -16,6 +17,7 @@ static int dm_test_pci_base(struct unit_test_state *uts)
> {
> struct udevice *bus;
>
> + test_set_skip_delays(true);
> ut_assertok(uclass_get_device(UCLASS_PCI, 0, &bus));
>
> return 0;
> @@ -30,6 +32,7 @@ static int dm_test_pci_busdev(struct unit_test_state *uts)
> u16 vendor, device;
>
> /* Test bus#0 and its devices */
> + test_set_skip_delays(true);
> ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 0, &bus));
>
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x00, 0), &swap));
> @@ -65,6 +68,7 @@ static int dm_test_pci_swapcase(struct unit_test_state *uts)
> char *ptr;
>
> /* Check that asking for the device 0 automatically fires up PCI */
> + test_set_skip_delays(true);
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x00, 0), &swap));
>
> /* First test I/O */
> @@ -116,6 +120,7 @@ static int dm_test_pci_drvdata(struct unit_test_state *uts)
> struct udevice *bus, *swap;
>
> /* Check that asking for the device automatically fires up PCI */
> + test_set_skip_delays(true);
> ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 1, &bus));
>
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &swap));
> @@ -141,6 +146,7 @@ static int dm_test_pci_mixed(struct unit_test_state *uts)
> ulong io_addr, mem_addr;
> char *ptr;
>
> + test_set_skip_delays(true);
> ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 2, &bus));
>
> /* Test the dynamic device */
> @@ -201,6 +207,7 @@ static int dm_test_pci_cap(struct unit_test_state *uts)
> struct udevice *bus, *swap;
> int cap;
>
> + test_set_skip_delays(true);
> ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 0, &bus));
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &swap));
>
> @@ -258,6 +265,7 @@ static int dm_test_pci_ea(struct unit_test_state *uts)
> * use emulated device mapping function, we're not using real physical
> * addresses in this test
> */
> + test_set_skip_delays(true);
> sandbox_set_enable_pci_map(true);
>
> ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 0, &bus));
> @@ -303,6 +311,7 @@ static int dm_test_pci_addr_flat(struct unit_test_state *uts)
> ulong io_addr, mem_addr;
> fdt_addr_t size;
>
> + test_set_skip_delays(true);
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &swap1f));
> io_addr = dm_pci_read_bar32(swap1f, 0);
> ut_asserteq(io_addr, dev_read_addr_pci(swap1f, &size));
> @@ -334,6 +343,7 @@ static int dm_test_pci_addr_live(struct unit_test_state *uts)
> struct udevice *swap1f, *swap1;
> fdt_size_t size;
>
> + test_set_skip_delays(true);
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &swap1f));
> ut_asserteq_64(FDT_ADDR_T_NONE, dev_read_addr_pci(swap1f, &size));
> ut_asserteq(0, size);
> @@ -351,6 +361,7 @@ static int dm_test_pci_on_bus(struct unit_test_state *uts)
> {
> struct udevice *dev;
>
> + test_set_skip_delays(true);
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &dev));
> ut_asserteq(true, device_is_on_pci_bus(dev));
ut_assert(device_is_on_pci_bus(dev));
> ut_asserteq(false, device_is_on_pci_bus(dev_get_parent(dev)));
ut_assert(!...)
> @@ -374,6 +385,7 @@ static int dm_test_pci_region_multi(struct unit_test_state *uts)
> ulong mem_addr;
>
> /* Test memory BAR1 on bus#1 */
> + test_set_skip_delays(true);
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &dev));
> mem_addr = dm_pci_read_bar32(dev, 1);
> ut_asserteq(mem_addr, 0x30000000);
> @@ -393,6 +405,7 @@ static int dm_test_pci_bus_to_phys(struct unit_test_state *uts)
> struct udevice *dev;
> phys_addr_t phys_addr;
>
> + test_set_skip_delays(true);
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &dev));
>
> /* Before any of the ranges. */
> @@ -446,6 +459,7 @@ static int dm_test_pci_phys_to_bus(struct unit_test_state *uts)
> struct udevice *dev;
> pci_addr_t pci_addr;
>
> + test_set_skip_delays(true);
> ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &dev));
>
> /* Before any of the ranges. */
> @@ -620,6 +634,7 @@ static int dm_test_pci_bridge_windows(struct unit_test_state *uts)
> {
> struct udevice *bus;
>
> + test_set_skip_delays(true);
> 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;
> @@ -628,3 +643,45 @@ static int dm_test_pci_bridge_windows(struct unit_test_state *uts)
> return _dm_test_pci_bridge_windows(uts, bus);
> }
> DM_TEST(dm_test_pci_bridge_windows, UTF_SCAN_PDATA | UTF_SCAN_FDT);
> +
> +#if IS_ENABLED(CONFIG_PCI_PWRCTRL_SLOT)
> +/* GPIO 25 is PERST; GPIO 26 is REFCLK enable */
> +static int dm_test_pci_pwrseq_cold(struct unit_test_state *uts)
> +{
> + struct udevice *bus, *gpio;
> + unsigned long start, end;
> +
> + ut_assertok(uclass_get_device(UCLASS_GPIO, 1, &gpio));
> + ut_assertok(sandbox_gpio_set_value(gpio, 25, 1));
> +
> + start = timer_get_us();
> + ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x04, 0), &bus));
> + end = timer_get_us();
> + ut_assert(end - start > 100000);
> + ut_assert(end - start < 200000);
We do have get_timer_us() so another option is:
start = get_timer_us(0);
ut_assert(get_timer_us(start) > 100000);
> +
> + ut_asserteq(0, sandbox_gpio_get_value(gpio, 25));
> + ut_asserteq(1, sandbox_gpio_get_value(gpio, 26));
> +
> + return 0;
> +}
> +DM_TEST(dm_test_pci_pwrseq_cold, UTF_SCAN_PDATA | UTF_SCAN_FDT);
> +
> +static int dm_test_pci_pwrseq_warm(struct unit_test_state *uts)
> +{
> + struct udevice *bus, *gpio;
> + unsigned long start;
> +
> + ut_assertok(uclass_get_device(UCLASS_GPIO, 1, &gpio));
> +
> + start = timer_get_us();
> + ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x04, 0), &bus));
> + ut_assert(timer_get_us() - start > 200000);
> +
> + ut_asserteq(0, sandbox_gpio_get_value(gpio, 25));
> + ut_asserteq(1, sandbox_gpio_get_value(gpio, 26));
> +
> + return 0;
> +}
> +DM_TEST(dm_test_pci_pwrseq_warm, UTF_SCAN_PDATA | UTF_SCAN_FDT);
> +#endif
> --
> 2.35.1.1320.gc452695387.dirty
>
Regards,
Simon
^ permalink raw reply [flat|nested] 7+ messages in thread