changeset 10552:bf9134a043f1

6472670 Internal pci root-bus addresses should be independent of bios enumeration order (non-x8400 systems)
author Dana Myers <Dana.Myers@Sun.COM>
date Tue, 15 Sep 2009 19:09:53 -0700
parents 62422ea6e65b
children 0aa4533c70f5
files usr/src/cmd/boot/filelist/i386/filelist.ramdisk usr/src/uts/intel/io/pci/pci_boot.c
diffstat 2 files changed, 321 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/boot/filelist/i386/filelist.ramdisk	Tue Sep 15 18:44:23 2009 -0700
+++ b/usr/src/cmd/boot/filelist/i386/filelist.ramdisk	Tue Sep 15 19:09:53 2009 -0700
@@ -7,6 +7,7 @@
 etc/devices/mdi_ib_cache
 etc/devices/mdi_scsi_vhci_cache
 etc/devices/retire_store
+etc/devices/pci_unitaddr_persistent
 etc/driver_aliases
 etc/driver_classes
 etc/mach
--- a/usr/src/uts/intel/io/pci/pci_boot.c	Tue Sep 15 18:44:23 2009 -0700
+++ b/usr/src/uts/intel/io/pci/pci_boot.c	Tue Sep 15 19:09:53 2009 -0700
@@ -44,6 +44,7 @@
 #include <sys/acpica.h>
 #include <sys/intel_iommu.h>
 #include <sys/iommulib.h>
+#include <sys/devcache.h>
 
 #define	pci_getb	(*pci_getb_func)
 #define	pci_getw	(*pci_getw_func)
@@ -134,6 +135,15 @@
 static void memlist_remove_list(struct memlist **list,
     struct memlist *remove_list);
 
+static void pci_scan_bbn(void);
+static int pci_unitaddr_cache_valid(void);
+static int pci_bus_unitaddr(int);
+static void pci_unitaddr_cache_create(void);
+
+static int pci_cache_unpack_nvlist(nvf_handle_t, nvlist_t *, char *);
+static int pci_cache_pack_nvlist(nvf_handle_t, nvlist_t **);
+static void pci_cache_free_list(nvf_handle_t);
+
 extern int pci_slot_names_prop(int, char *, int);
 
 /* set non-zero to force PCI peer-bus renumbering */
@@ -149,10 +159,284 @@
 } isa_res;
 
 /*
+ * PCI unit-address cache management
+ */
+static nvf_ops_t pci_unitaddr_cache_ops = {
+	"/etc/devices/pci_unitaddr_persistent",	/* path to cache */
+	pci_cache_unpack_nvlist,		/* read in nvlist form */
+	pci_cache_pack_nvlist,			/* convert to nvlist form */
+	pci_cache_free_list,			/* free data list */
+	NULL					/* write complete callback */
+};
+
+typedef struct {
+	list_node_t	pua_nodes;
+	int		pua_index;
+	int		pua_addr;
+} pua_node_t;
+
+nvf_handle_t	puafd_handle;
+int		pua_cache_valid = 0;
+
+
+/*ARGSUSED*/
+static ACPI_STATUS
+pci_process_acpi_device(ACPI_HANDLE hdl, UINT32 level, void *ctx, void **rv)
+{
+	ACPI_BUFFER	rb;
+	ACPI_OBJECT	ro;
+	ACPI_DEVICE_INFO *adi;
+
+	/*
+	 * Use AcpiGetObjectInfo() to find the device _HID
+	 * If not a PCI root-bus, ignore this device and continue
+	 * the walk
+	 */
+
+	rb.Length = ACPI_ALLOCATE_BUFFER;
+	if (ACPI_FAILURE(AcpiGetObjectInfo(hdl, &rb)))
+		return (AE_OK);
+
+	adi = rb.Pointer;
+	if (!(adi->Valid & ACPI_VALID_HID)) {
+		AcpiOsFree(adi);
+		return (AE_OK);
+	}
+
+	if (strncmp(adi->HardwareId.Value, PCI_ROOT_HID_STRING,
+	    sizeof (PCI_ROOT_HID_STRING)) &&
+	    strncmp(adi->HardwareId.Value, PCI_EXPRESS_ROOT_HID_STRING,
+	    sizeof (PCI_EXPRESS_ROOT_HID_STRING))) {
+		AcpiOsFree(adi);
+		return (AE_OK);
+	}
+
+	AcpiOsFree(adi);
+
+	/*
+	 * XXX: ancient Big Bear broken _BBN will result in two
+	 * bus 0 _BBNs being found, so we need to handle duplicate
+	 * bus 0 gracefully.  However, broken _BBN does not
+	 * hide a childless root-bridge so no need to work-around it
+	 * here
+	 */
+	rb.Pointer = &ro;
+	rb.Length = sizeof (ro);
+	if (ACPI_SUCCESS(AcpiEvaluateObjectTyped(hdl, "_BBN",
+	    NULL, &rb, ACPI_TYPE_INTEGER))) {
+		/* PCI with _BBN, process it, go no deeper */
+		if (pci_bus_res[ro.Integer.Value].par_bus == (uchar_t)-1 &&
+		    pci_bus_res[ro.Integer.Value].dip == NULL)
+			create_root_bus_dip((uchar_t)ro.Integer.Value);
+		return (AE_CTRL_DEPTH);
+	}
+
+	/* PCI and no _BBN, continue walk */
+	return (AE_OK);
+}
+
+/*
+ * Scan the ACPI namespace for all top-level instances of _BBN
+ * in order to discover childless root-bridges (which enumeration
+ * may not find; root-bridges are inferred by the existence of
+ * children).  This scan should find all root-bridges that have
+ * been enumerated, and any childless root-bridges not enumerated.
+ * Root-bridge for bus 0 may not have a _BBN object.
+ */
+static void
+pci_scan_bbn()
+{
+	void *rv;
+
+	(void) AcpiGetDevices(NULL, pci_process_acpi_device, NULL, &rv);
+}
+
+static void
+pci_unitaddr_cache_init(void)
+{
+
+	puafd_handle = nvf_register_file(&pci_unitaddr_cache_ops);
+	ASSERT(puafd_handle);
+
+	list_create(nvf_list(puafd_handle), sizeof (pua_node_t),
+	    offsetof(pua_node_t, pua_nodes));
+
+	rw_enter(nvf_lock(puafd_handle), RW_WRITER);
+	(void) nvf_read_file(puafd_handle);
+	rw_exit(nvf_lock(puafd_handle));
+}
+
+/*
+ * Format of /etc/devices/pci_unitaddr_persistent:
+ *
+ * The persistent record of unit-address assignments contains
+ * a list of name/value pairs, where name is a string representation
+ * of the "index value" of the PCI root-bus and the value is
+ * the assigned unit-address.
+ *
+ * The "index value" is simply the zero-based index of the PCI
+ * root-buses ordered by physical bus number; first PCI bus is 0,
+ * second is 1, and so on.
+ */
+
+static int
+pci_cache_unpack_nvlist(nvf_handle_t hdl, nvlist_t *nvl, char *name)
+{
+	long		index;
+	int32_t		value;
+	nvpair_t	*np;
+	pua_node_t	*node;
+
+	np = NULL;
+	while ((np = nvlist_next_nvpair(nvl, np)) != NULL) {
+		/* name of nvpair is index value */
+		if (ddi_strtol(nvpair_name(np), NULL, 10, &index) != 0)
+			continue;
+
+		if (nvpair_value_int32(np, &value) != 0)
+			continue;
+
+		node = kmem_zalloc(sizeof (pua_node_t), KM_SLEEP);
+		node->pua_index = index;
+		node->pua_addr = value;
+		list_insert_tail(nvf_list(hdl), node);
+	}
+
+	pua_cache_valid = 1;
+	return (DDI_SUCCESS);
+}
+
+static int
+pci_cache_pack_nvlist(nvf_handle_t hdl, nvlist_t **ret_nvl)
+{
+	int		rval;
+	nvlist_t	*nvl, *sub_nvl;
+	list_t		*listp;
+	pua_node_t	*pua;
+	char		buf[13];
+
+	ASSERT(RW_WRITE_HELD(nvf_lock(hdl)));
+
+	rval = nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP);
+	if (rval != DDI_SUCCESS) {
+		nvf_error("%s: nvlist alloc error %d\n",
+		    nvf_cache_name(hdl), rval);
+		return (DDI_FAILURE);
+	}
+
+	sub_nvl = NULL;
+	rval = nvlist_alloc(&sub_nvl, NV_UNIQUE_NAME, KM_SLEEP);
+	if (rval != DDI_SUCCESS)
+		goto error;
+
+	listp = nvf_list(hdl);
+	for (pua = list_head(listp); pua != NULL;
+	    pua = list_next(listp, pua)) {
+		snprintf(buf, sizeof (buf), "%d", pua->pua_index);
+		rval = nvlist_add_int32(sub_nvl, buf, pua->pua_addr);
+		if (rval != DDI_SUCCESS)
+			goto error;
+	}
+
+	rval = nvlist_add_nvlist(nvl, "table", sub_nvl);
+	if (rval != DDI_SUCCESS)
+		goto error;
+	nvlist_free(sub_nvl);
+
+	*ret_nvl = nvl;
+	return (DDI_SUCCESS);
+
+error:
+	if (sub_nvl)
+		nvlist_free(sub_nvl);
+	ASSERT(nvl);
+	nvlist_free(nvl);
+	*ret_nvl = NULL;
+	return (DDI_FAILURE);
+}
+
+static void
+pci_cache_free_list(nvf_handle_t hdl)
+{
+	list_t		*listp;
+	pua_node_t	*pua;
+
+	ASSERT(RW_WRITE_HELD(nvf_lock(hdl)));
+
+	listp = nvf_list(hdl);
+	for (pua = list_head(listp); pua != NULL;
+	    pua = list_next(listp, pua)) {
+		list_remove(listp, pua);
+		kmem_free(pua, sizeof (pua_node_t));
+	}
+}
+
+
+static int
+pci_unitaddr_cache_valid(void)
+{
+
+	/* read only, no need for rw lock */
+	return (pua_cache_valid);
+}
+
+
+static int
+pci_bus_unitaddr(int index)
+{
+	pua_node_t	*pua;
+	list_t		*listp;
+	int		addr;
+
+	rw_enter(nvf_lock(puafd_handle), RW_READER);
+
+	addr = -1;	/* default return if no match */
+	listp = nvf_list(puafd_handle);
+	for (pua = list_head(listp); pua != NULL;
+	    pua = list_next(listp, pua)) {
+		if (pua->pua_index == index) {
+			addr = pua->pua_addr;
+			break;
+		}
+	}
+
+	rw_exit(nvf_lock(puafd_handle));
+	return (addr);
+}
+
+static void
+pci_unitaddr_cache_create(void)
+{
+	int		i, index;
+	pua_node_t	*node;
+	list_t		*listp;
+
+	rw_enter(nvf_lock(puafd_handle), RW_WRITER);
+
+	index = 0;
+	listp = nvf_list(puafd_handle);
+	for (i = 0; i <= pci_bios_nbus; i++) {
+		/* skip non-root (peer) PCI busses */
+		if ((pci_bus_res[i].par_bus != (uchar_t)-1) ||
+		    (pci_bus_res[i].dip == NULL))
+			continue;
+		node = kmem_zalloc(sizeof (pua_node_t), KM_SLEEP);
+		node->pua_index = index++;
+		node->pua_addr = pci_bus_res[i].root_addr;
+		list_insert_tail(listp, node);
+	}
+
+	(void) nvf_mark_dirty(puafd_handle);
+	rw_exit(nvf_lock(puafd_handle));
+	nvf_wake_daemon();
+}
+
+
+/*
  * Enumerate all PCI devices
  */
 void
-pci_setup_tree()
+pci_setup_tree(void)
 {
 	uint_t i, root_bus_addr = 0;
 
@@ -978,9 +1262,42 @@
 	int bus;
 
 	/*
-	 * Excise phantom roots if possible
+	 * Scan ACPI namespace for _BBN objects, make sure that
+	 * childless root-bridges appear in devinfo tree
+	 */
+	pci_scan_bbn();
+	pci_unitaddr_cache_init();
+
+	/*
+	 * Fix-up unit-address assignments if cache is available
 	 */
-	pci_renumber_root_busses();
+	if (pci_unitaddr_cache_valid()) {
+		int pci_regs[] = {0, 0, 0};
+		int	new_addr;
+		int	index = 0;
+
+		for (bus = 0; bus <= pci_bios_nbus; bus++) {
+			/* skip non-root (peer) PCI busses */
+			if ((pci_bus_res[bus].par_bus != (uchar_t)-1) ||
+			    (pci_bus_res[bus].dip == NULL))
+				continue;
+
+			new_addr = pci_bus_unitaddr(index);
+			if (pci_bus_res[bus].root_addr != new_addr) {
+				/* update reg property for node */
+				pci_regs[0] = pci_bus_res[bus].root_addr =
+				    new_addr;
+				(void) ndi_prop_update_int_array(
+				    DDI_DEV_T_NONE, pci_bus_res[bus].dip,
+				    "reg", (int *)pci_regs, 3);
+			}
+			index++;
+		}
+	} else {
+		/* perform legacy processing */
+		pci_renumber_root_busses();
+		pci_unitaddr_cache_create();
+	}
 
 	/*
 	 * Do root-bus resource discovery