pcie: Add API for scanning for available endpoints

This adds a generic API to be used for scanning for available PCI
endpoints. It takes a more detailed approach than the "brute force"
based scanning that's so far been used in Zephyr, buy inspecting the
host controller node and bridge nodes, and only scanning for busses and
devices that are actually expected to exist.

Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
diff --git a/drivers/pcie/host/pcie.c b/drivers/pcie/host/pcie.c
index 9e7d06d..6c1fe6c 100644
--- a/drivers/pcie/host/pcie.c
+++ b/drivers/pcie/host/pcie.c
@@ -10,6 +10,7 @@
 
 #include <zephyr/kernel.h>
 #include <zephyr/device.h>
+#include <zephyr/sys/check.h>
 #include <stdbool.h>
 #include <zephyr/drivers/pcie/pcie.h>
 
@@ -370,6 +371,106 @@
 	return PCIE_BDF_NONE;
 }
 
+static bool scan_flag(const struct pcie_scan_opt *opt, uint32_t flag)
+{
+	return ((opt->flags & flag) != 0U);
+}
+
+/* Forward declaration needed since scanning a device may reveal a bridge */
+static bool scan_bus(uint8_t bus, const struct pcie_scan_opt *opt);
+
+static bool scan_dev(uint8_t bus, uint8_t dev, const struct pcie_scan_opt *opt)
+{
+	for (uint8_t func = 0; func <= PCIE_MAX_FUNC; func++) {
+		pcie_bdf_t bdf = PCIE_BDF(bus, dev, func);
+		uint32_t secondary = 0;
+		uint32_t id, type;
+		bool do_cb;
+
+		id = pcie_conf_read(bdf, PCIE_CONF_ID);
+		if (!PCIE_ID_IS_VALID(id)) {
+			continue;
+		}
+
+		type = pcie_conf_read(bdf, PCIE_CONF_TYPE);
+		switch (PCIE_CONF_TYPE_GET(type)) {
+		case PCIE_CONF_TYPE_STANDARD:
+			do_cb = true;
+			break;
+		case PCIE_CONF_TYPE_PCI_BRIDGE:
+			if (scan_flag(opt, PCIE_SCAN_RECURSIVE)) {
+				uint32_t num = pcie_conf_read(bdf,
+							      PCIE_BUS_NUMBER);
+				secondary = PCIE_BUS_SECONDARY_NUMBER(num);
+			}
+			__fallthrough;
+		default:
+			do_cb = scan_flag(opt, PCIE_SCAN_CB_ALL);
+			break;
+		}
+
+		if (do_cb && !opt->cb(bdf, id, opt->cb_data)) {
+			return false;
+		}
+
+		if (scan_flag(opt, PCIE_SCAN_RECURSIVE) && secondary != 0) {
+			if (!scan_bus(secondary, opt)) {
+				return false;
+			}
+		}
+
+		/* Only function 0 is valid for non-multifunction devices */
+		if (func == 0 && !PCIE_CONF_MULTIFUNCTION(type)) {
+			break;
+		}
+	}
+
+	return true;
+}
+
+static bool scan_bus(uint8_t bus, const struct pcie_scan_opt *opt)
+{
+	for (uint8_t dev = 0; dev <= PCIE_MAX_DEV; dev++) {
+		if (!scan_dev(bus, dev, opt)) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+int pcie_scan(const struct pcie_scan_opt *opt)
+{
+	uint32_t type;
+	bool multi;
+
+	CHECKIF(opt->cb == NULL) {
+		return -EINVAL;
+	}
+
+	type = pcie_conf_read(PCIE_HOST_CONTROLLER(0), PCIE_CONF_TYPE);
+	multi = PCIE_CONF_MULTIFUNCTION(type);
+	if (opt->bus == 0 && scan_flag(opt, PCIE_SCAN_RECURSIVE) && multi) {
+		/* Each function on the host controller represents a portential bus */
+		for (uint8_t bus = 0; bus <= PCIE_MAX_FUNC; bus++) {
+			pcie_bdf_t bdf = PCIE_HOST_CONTROLLER(bus);
+
+			if (pcie_conf_read(bdf, PCIE_CONF_ID) == PCIE_ID_NONE) {
+				continue;
+			}
+
+			if (!scan_bus(bus, opt)) {
+				break;
+			}
+		}
+	} else {
+		/* Single PCI host controller */
+		scan_bus(opt->bus, opt);
+	}
+
+	return 0;
+}
+
 static int pcie_init(const struct device *dev)
 {
 	size_t dev_count, found;
diff --git a/include/zephyr/drivers/pcie/pcie.h b/include/zephyr/drivers/pcie/pcie.h
index 704589a..d85cd9b 100644
--- a/include/zephyr/drivers/pcie/pcie.h
+++ b/include/zephyr/drivers/pcie/pcie.h
@@ -178,6 +178,47 @@
  */
 extern void pcie_conf_write(pcie_bdf_t bdf, unsigned int reg, uint32_t data);
 
+/** Callback type used for scanning for PCI endpoints
+ *
+ * @param bdf      BDF value for a found endpoint.
+ * @param id       Vendor & Device ID for the found endpoint.
+ * @param cb_data  Custom, use case specific data.
+ *
+ * @return true to continue scanning, false to stop scanning.
+ */
+typedef bool (*pcie_scan_cb_t)(pcie_bdf_t bdf, pcie_id_t id, void *cb_data);
+
+enum {
+	/** Scan all available PCI host controllers and sub-busses */
+	PCIE_SCAN_RECURSIVE = BIT(0),
+	/** Do the callback for all endpoint types, including bridges */
+	PCIE_SCAN_CB_ALL = BIT(1),
+};
+
+/** Options for performing a scan for PCI devices */
+struct pcie_scan_opt {
+	/** Initial bus number to scan */
+	uint8_t bus;
+
+	/** Function to call for each found endpoint */
+	pcie_scan_cb_t cb;
+
+	/** Custom data to pass to the scan callback */
+	void *cb_data;
+
+	/** Scan flags */
+	uint32_t flags;
+};
+
+/** Scan for PCIe devices.
+ *
+ * Scan the PCI bus (or busses) for available endpoints.
+ *
+ * @param opt Options determining how to perform the scan.
+ * @return 0 on success, negative POSIX error number on failure.
+ */
+int pcie_scan(const struct pcie_scan_opt *opt);
+
 /**
  * @brief Probe for the presence of a PCI(e) endpoint.
  *
@@ -327,6 +368,18 @@
 				     const void *parameter,
 				     uint32_t flags);
 
+/**
+ * @brief Get the BDF for a given PCI host controller
+ *
+ * This macro is useful when the PCI host controller behind PCIE_BDF(0, 0, 0)
+ * indicates a multifunction device. In such a case each function of this
+ * endpoint is a potential host controller itself.
+ *
+ * @param n Bus number
+ * @return BDF value of the given host controller
+ */
+#define PCIE_HOST_CONTROLLER(n) PCIE_BDF(0, 0, n)
+
 /*
  * Configuration word 13 contains the head of the capabilities list.
  */
@@ -397,6 +450,11 @@
 
 #define PCIE_CONF_MULTIFUNCTION(w)	(((w) & 0x00800000U) != 0U)
 #define PCIE_CONF_TYPE_BRIDGE(w)	(((w) & 0x007F0000U) != 0U)
+#define PCIE_CONF_TYPE_GET(w)		(((w) >> 16) & 0x7F)
+
+#define PCIE_CONF_TYPE_STANDARD         0x0U
+#define PCIE_CONF_TYPE_PCI_BRIDGE       0x1U
+#define PCIE_CONF_TYPE_CARDBUS_BRIDGE   0x2U
 
 /*
  * Words 4-9 are BARs are I/O or memory decoders. Memory decoders may