device/pci_device: Add notion of "hidden" PCI devices

On some SoCs, there are PCI devices that may get hidden from PCI
enumeration by platform firmware. Because the Vendor ID reads back as
0xffffffff, it appears that there is no PCI device located at that BDF.
However, because the device does exist, designers may wish to hang its
PCI resources off of a real __pci_driver, as well as have it participate
in ACPI table generation.

This patch extends the semantics of the 'hidden' keyword in
devicetree.cb. If a device now uses 'hidden' instead of 'on', then it
will be assumed during PCI enumeration that the device indeed does
exist, and it will not be removed as a "leftover device." This allows
child devices to be enumerated correctly and also PCI resources can be
designated from the {read,set}_resources callbacks.

It should be noted that as of this commit, there are precisely 0 devices
using 'hidden' in their devicetree.cb files, so this should be a safe
thing to do.

Later patches will begin moving PCI resources from random places (typically
hung off of fixed SA and LPC) into the PMC device (procedure will vary per-
platform).

Change-Id: I16c2d3e1d1433343e63dfc16856cff69cd815e2a
Signed-off-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/41384
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Furquan Shaikh <furquan@google.com>
diff --git a/src/device/pci_device.c b/src/device/pci_device.c
index 689325d..5f50a31 100644
--- a/src/device/pci_device.c
+++ b/src/device/pci_device.c
@@ -778,6 +778,13 @@
 	.reset_bus        = pci_bus_reset,
 };
 
+/** Default device operations for PCI devices marked 'hidden' */
+static struct device_operations default_hidden_pci_ops_dev = {
+	.read_resources   = noop_read_resources,
+	.set_resources    = noop_set_resources,
+	.scan_bus         = scan_static_bus,
+};
+
 /**
  * Check for compatibility to route legacy VGA cycles through a bridge.
  *
@@ -1147,6 +1154,46 @@
 }
 
 /**
+ * PCI devices that are marked as "hidden" do not get probed. However, the same
+ * initialization logic is still performed as if it were. This is useful when
+ * devices would like to be described in the devicetree.cb file, and/or present
+ * static PCI resources to the allocator, but the platform firmware hides the
+ * device (makes the device invisible to PCI enumeration) before PCI enumeration
+ * takes place.
+ *
+ * The expected semantics of PCI devices marked as 'hidden':
+ * 1) The device is actually present under the specified BDF
+ * 2) The device config space can still be accessed somehow, but the Vendor ID
+ *     indicates there is no device there (it reads as 0xffffffff).
+ * 3) The device may still consume PCI resources. Typically, these would have
+ *     been hardcoded elsewhere.
+ *
+ * @param dev Pointer to the device structure.
+ */
+static void pci_scan_hidden_device(struct device *dev)
+{
+	if (dev->chip_ops && dev->chip_ops->enable_dev)
+		dev->chip_ops->enable_dev(dev);
+
+	/*
+	 * If chip_ops->enable_dev did not set dev->ops, then set to a default
+	 * .ops, because PCI enumeration is effectively being skipped, therefore
+	 * no PCI driver will bind to this device. However, children may want to
+	 * be enumerated, so this provides scan_static_bus for the .scan_bus
+	 * callback.
+	 */
+	if (dev->ops == NULL)
+		dev->ops = &default_hidden_pci_ops_dev;
+
+	if (dev->ops->enable)
+		dev->ops->enable(dev);
+
+	/* Display the device almost as if it were probed normally */
+	printk(BIOS_DEBUG, "%s [0000/%04x] hidden%s\n", dev_path(dev),
+	       dev->device, dev->ops ? "" : " No operations");
+}
+
+/**
  * Scan a PCI bus.
  *
  * Determine the existence of devices and bridges on a PCI bus. If there are
@@ -1190,6 +1237,14 @@
 		/* First thing setup the device structure. */
 		dev = pci_scan_get_dev(bus, devfn);
 
+		/* Devices marked 'hidden' do not get probed */
+		if (dev && dev->hidden) {
+			pci_scan_hidden_device(dev);
+
+			/* Skip pci_probe_dev, go to next devfn */
+			continue;
+		}
+
 		/* See if a device is present and setup the device structure. */
 		dev = pci_probe_dev(dev, bus, devfn);
 
@@ -1213,8 +1268,12 @@
 
 	prev = &bus->children;
 	for (dev = bus->children; dev; dev = dev->sibling) {
-		/* If we read valid vendor id, it is not leftover device. */
-		if (dev->vendor != 0) {
+		/*
+		 * The device is only considered leftover if it is not hidden
+		 * and it has a Vendor ID of 0 (the default for a device that
+		 * could not be probed).
+		 */
+		if (dev->vendor != 0 || dev->hidden) {
 			prev = &dev->sibling;
 			continue;
 		}