Module Name:    src
Committed By:   thorpej
Date:           Sat Jan 18 22:17:34 UTC 2020

Modified Files:
        src/sys/dev/pci: pciconf.c

Log Message:
Fix several problems with pci_configure_bus():
- Track the 64-bit range capability of prefetchable and non-prefetchable
  memory separately.  Probe the extent maps provided by the caller to
  initialize these values.  Without this, we never get 64-bit range
  capablity on the root bus, and thus are never able to forward it along
  to downstream busses.
- Always prefer allocating space for a 64-bit memory BAR > 4GB.  We will
  fall back on a 32-bit range if no space above 4GB is available.
- Constrain allocation of 32-bit memory BARs (including expansion ROM BARs)
  to be below 4GB, even if the window has a larger range available.
- When allocating non-prefetchable memory space for a PCI-PCI bridge, ensure
  it falls below 4GB, since a bridge cannot forward a 64-bit non-prefetchable
  range.
- Account for expansion ROMs as non-prefetchable memory rather than
  prefetchable memory; expansion ROMs have 32-bit BARs, and if a device
  with an expansion ROM is downstream of a brige, a 32-bit prefetchable
  range might not be available.

Tested by jmcneill@ on an Arm Neoverse N1 SDP, where the previous
code failed to configure all devices correctly.


To generate a diff of this commit:
cvs rdiff -u -r1.43 -r1.44 src/sys/dev/pci/pciconf.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/pci/pciconf.c
diff -u src/sys/dev/pci/pciconf.c:1.43 src/sys/dev/pci/pciconf.c:1.44
--- src/sys/dev/pci/pciconf.c:1.43	Thu Dec  5 07:03:01 2019
+++ src/sys/dev/pci/pciconf.c	Sat Jan 18 22:17:34 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: pciconf.c,v 1.43 2019/12/05 07:03:01 msaitoh Exp $	*/
+/*	$NetBSD: pciconf.c,v 1.44 2020/01/18 22:17:34 thorpej Exp $	*/
 
 /*
  * Copyright 2001 Wasabi Systems, Inc.
@@ -65,7 +65,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: pciconf.c,v 1.43 2019/12/05 07:03:01 msaitoh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: pciconf.c,v 1.44 2020/01/18 22:17:34 thorpej Exp $");
 
 #include "opt_pci.h"
 
@@ -131,6 +131,7 @@ typedef struct _s_pciconf_bus_t {
 	int		swiz;
 	int		io_32bit;
 	int		pmem_64bit;
+	int		mem_64bit;
 	int		io_align;
 	int		mem_align;
 	int		pmem_align;
@@ -164,7 +165,7 @@ static int	setup_iowins(pciconf_bus_t *)
 static int	setup_memwins(pciconf_bus_t *);
 static int	configure_bridge(pciconf_dev_t *);
 static int	configure_bus(pciconf_bus_t *);
-static uint64_t	pci_allocate_range(struct extent *, uint64_t, int);
+static uint64_t	pci_allocate_range(struct extent *, uint64_t, int, bool);
 static pciconf_win_t	*get_io_desc(pciconf_bus_t *, bus_size_t);
 static pciconf_win_t	*get_mem_desc(pciconf_bus_t *, bus_size_t);
 static pciconf_bus_t	*query_bus(pciconf_bus_t *, pciconf_dev_t *, int);
@@ -180,6 +181,12 @@ print_tag(pci_chipset_tag_t pc, pcitag_t
 	printf("PCI: bus %d, device %d, function %d: ", bus, dev, func);
 }
 
+#ifdef _LP64
+#define	__used_only_lp64	__unused
+#else
+#define	__used_only_lp64	/* nothing */
+#endif /* _LP64 */
+
 /************************************************************************/
 /************************************************************************/
 /***********************   Bus probing routines   ***********************/
@@ -353,6 +360,9 @@ query_bus(pciconf_bus_t *parent, pciconf
 			pb->pmem_64bit = 1;
 	}
 
+	/* Bridges only forward a 32-bit range of non-prefetcable memory. */
+	pb->mem_64bit = 0;
+
 	if (probe_bus(pb)) {
 		printf("Failed to probe bus %d\n", pb->busno);
 		goto err;
@@ -660,14 +670,22 @@ pci_do_device_query(pciconf_bus_t *pb, p
 			pm->reg = PCI_MAPREG_ROM;
 			pm->size = size;
 			pm->align = 4;
-			pm->prefetch = 1;
+			pm->prefetch = 0;
 			if (pci_conf_debug) {
 				print_tag(pb->pc, tag);
 				printf("Expansion ROM memory size %"
 				    PRIu64 "\n", pm->size);
 			}
 			pb->nmemwin++;
-			pb->pmem_total += size;
+			if (pm->prefetch) {
+				pb->pmem_total += size;
+				if (pb->pmem_align < pm->size)
+					pb->pmem_align = pm->size;
+			} else {
+				pb->mem_total += size;
+				if (pb->mem_align < pm->size)
+					pb->mem_align = pm->size;
+			}
 		}
 	} else {
 		/* Don't enable ROMs if we aren't going to map them. */
@@ -691,15 +709,52 @@ pci_do_device_query(pciconf_bus_t *pb, p
 /************************************************************************/
 /************************************************************************/
 static uint64_t
-pci_allocate_range(struct extent *ex, uint64_t amt, int align)
+pci_allocate_range(struct extent * const ex, const uint64_t amt,
+		   const int align, const bool ok64 __used_only_lp64)
 {
 	int	r;
 	u_long	addr;
 
-	r = extent_alloc(ex, amt, align, 0, EX_NOWAIT, &addr);
+	u_long end = ex->ex_end;
+
+#ifdef _LP64
+	/*
+	 * If a 64-bit range is not OK:
+	 * ==> If the start of the range is > 4GB, allocation not possible.
+	 * ==> If the end of the range is > (4GB-1), constrain the end.
+	 *
+	 * If a 64-bit range IS OK, then we prefer allocating above 4GB.
+	 *
+	 * XXX We guard this with _LP64 because extent maps use u_long
+	 * internally.
+	 */
+	if (!ok64) {
+		if (ex->ex_start >= (1UL << 32)) {
+			printf("PCI: 32-BIT RESTRICTION, RANGE BEGINS AT %#lx\n",
+			    ex->ex_start);
+			return ~0ULL;
+		}
+		if (end > 0xffffffffUL) {
+			end = 0xffffffffUL;
+		}
+	} else if (end > (1UL << 32)) {
+		u_long start4g = ex->ex_start;
+		if (start4g < (1UL << 32)) {
+			start4g = (1UL << 32);
+		}
+		r = extent_alloc_subregion(ex, start4g, end, amt, align, 0,
+					   EX_NOWAIT, &addr);
+		if (r == 0) {
+			return addr;
+		}
+	}
+#endif /* _L64 */
+
+	r = extent_alloc_subregion(ex, ex->ex_start, end, amt, align, 0,
+				   EX_NOWAIT, &addr);
 	if (r) {
-		printf("extent_alloc(%p, %#" PRIx64 ", %#x) returned %d\n",
-		    ex, amt, align, r);
+		printf("extent_alloc_subregion(%p, %#lx, %#lx, %#" PRIx64 ", %#x) returned %d\n",
+		    ex, ex->ex_start, end, amt, align, r);
 		extent_print(ex);
 		return ~0ULL;
 	}
@@ -718,7 +773,7 @@ setup_iowins(pciconf_bus_t *pb)
 
 		pd = pi->dev;
 		pi->address = pci_allocate_range(pb->ioext, pi->size,
-		    pi->align);
+		    pi->align, false);
 		if (~pi->address == 0) {
 			print_tag(pd->pc, pd->tag);
 			printf("Failed to allocate PCI I/O space (%"
@@ -761,19 +816,43 @@ setup_memwins(pciconf_bus_t *pb)
 	pciconf_dev_t	*pd;
 	pcireg_t	base;
 	struct extent	*ex;
+	bool		ok64;
 
 	for (pm = pb->pcimemwin; pm < &pb->pcimemwin[pb->nmemwin]; pm++) {
 		if (pm->size == 0)
 			continue;
 
+		ok64 = false;
 		pd = pm->dev;
-		ex = (pm->prefetch) ? pb->pmemext : pb->memext;
-		pm->address = pci_allocate_range(ex, pm->size, pm->align);
+		if (pm->prefetch) {
+			ex = pb->pmemext;
+			ok64 = pb->pmem_64bit;
+		} else {
+			ex = pb->memext;
+			ok64 = pb->mem_64bit && pd->ppb == NULL;
+		}
+
+		/*
+		 * We need to figure out if the memory BAR is 64-bit
+		 * capable or not.  If it's not, then we need to constrain
+		 * the address allocation.
+		 */
+		if (pm->reg == PCI_MAPREG_ROM) {
+			ok64 = false;
+		} else if (ok64) {
+			base = pci_conf_read(pd->pc, pd->tag, pm->reg);
+			ok64 = PCI_MAPREG_MEM_TYPE(base) ==
+			    PCI_MAPREG_MEM_TYPE_64BIT;
+		}
+
+		pm->address = pci_allocate_range(ex, pm->size, pm->align,
+						 ok64);
 		if (~pm->address == 0) {
 			print_tag(pd->pc, pd->tag);
 			printf(
 			   "Failed to allocate PCI memory space (%" PRIu64
-			   " req)\n", pm->size);
+			   " req, prefetch=%d ok64=%d)\n", pm->size,
+			   pm->prefetch, (int)ok64);
 			return -1;
 		}
 		if (pd->ppb && pm->reg == 0) {
@@ -792,8 +871,7 @@ setup_memwins(pciconf_bus_t *pb)
 
 			continue;
 		}
-		if (pm->prefetch && !pb->pmem_64bit &&
-		    pm->address > 0xFFFFFFFFULL) {
+		if (!ok64 && pm->address > 0xFFFFFFFFULL) {
 			pm->address = 0;
 			pd->enable &= ~PCI_CONF_ENABLE_MEM;
 		} else
@@ -842,6 +920,30 @@ setup_memwins(pciconf_bus_t *pb)
 	return 0;
 }
 
+static bool
+constrain_bridge_mem_range(struct extent * const ex,
+			   u_long * const base,
+			   u_long * const limit,
+			   const bool ok64 __used_only_lp64)
+{
+
+	*base = ex->ex_start;
+	*limit = ex->ex_end;
+
+#ifdef _LP64
+	if (!ok64) {
+		if (ex->ex_start >= (1UL << 32)) {
+			return true;
+		}
+		if (ex->ex_end > 0xffffffffUL) {
+			*limit = 0xffffffffUL;
+		}
+	}
+#endif /* _LP64 */
+
+	return false;
+}
+
 /*
  * Configure I/O, memory, and prefetcable memory spaces, then make
  * a call to configure_bus().
@@ -854,6 +956,7 @@ configure_bridge(pciconf_dev_t *pd)
 	pcireg_t	io, iohigh, mem, cmd;
 	int		rv;
 	bool		isprefetchmem64;
+	bool		bad_range;
 
 	pb = pd->ppb;
 	/* Configure I/O base & limit*/
@@ -887,21 +990,22 @@ configure_bridge(pciconf_dev_t *pd)
 	pci_conf_write(pb->pc, pd->tag, PCI_BRIDGE_IOHIGH_REG, iohigh);
 
 	/* Configure mem base & limit */
+	bad_range = false;
 	if (pb->memext) {
-		mem_base = pb->memext->ex_start;
-		mem_limit = pb->memext->ex_end;
+		bad_range = constrain_bridge_mem_range(pb->memext,
+						       &mem_base,
+						       &mem_limit,
+						       false);
 	} else {
 		mem_base  = 0x100000;	/* 1M */
 		mem_limit = 0x000000;
 	}
-#if ULONG_MAX > 0xffffffff
-	if (mem_limit > 0xFFFFFFFFULL) {
+	if (bad_range) {
 		printf("Bus %d bridge MEM range out of range.  ", pb->busno);
 		printf("Disabling MEM accesses\n");
 		mem_base  = 0x100000;	/* 1M */
 		mem_limit = 0x000000;
 	}
-#endif
 	mem = __SHIFTIN((mem_base >> 16) & PCI_BRIDGE_MEMORY_ADDR,
 	    PCI_BRIDGE_MEMORY_BASE);
 	mem |= __SHIFTIN((mem_limit >> 16) & PCI_BRIDGE_MEMORY_ADDR,
@@ -909,24 +1013,25 @@ configure_bridge(pciconf_dev_t *pd)
 	pci_conf_write(pb->pc, pd->tag, PCI_BRIDGE_MEMORY_REG, mem);
 
 	/* Configure prefetchable mem base & limit */
+	mem = pci_conf_read(pb->pc, pd->tag, PCI_BRIDGE_PREFETCHMEM_REG);
+	isprefetchmem64 = PCI_BRIDGE_PREFETCHMEM_64BITS(mem);
+	bad_range = false;
 	if (pb->pmemext) {
-		mem_base = pb->pmemext->ex_start;
-		mem_limit = pb->pmemext->ex_end;
+		bad_range = constrain_bridge_mem_range(pb->pmemext,
+						       &mem_base,
+						       &mem_limit,
+						       isprefetchmem64);
 	} else {
 		mem_base  = 0x100000;	/* 1M */
 		mem_limit = 0x000000;
 	}
-	mem = pci_conf_read(pb->pc, pd->tag, PCI_BRIDGE_PREFETCHMEM_REG);
-	isprefetchmem64 = PCI_BRIDGE_PREFETCHMEM_64BITS(mem);
-#if ULONG_MAX > 0xffffffff
-	if (!isprefetchmem64 && mem_limit > 0xFFFFFFFFULL) {
+	if (bad_range) {
 		printf("Bus %d bridge does not support 64-bit PMEM.  ",
 		    pb->busno);
 		printf("Disabling prefetchable-MEM accesses\n");
 		mem_base  = 0x100000;	/* 1M */
 		mem_limit = 0x000000;
 	}
-#endif
 	mem = __SHIFTIN((mem_base >> 16) & PCI_BRIDGE_PREFETCHMEM_ADDR,
 	    PCI_BRIDGE_PREFETCHMEM_BASE);
 	mem |= __SHIFTIN((mem_limit >> 16) & PCI_BRIDGE_PREFETCHMEM_ADDR,
@@ -1078,6 +1183,35 @@ configure_bus(pciconf_bus_t *pb)
 	return 0;
 }
 
+static bool
+mem_region_ok64(struct extent * const ex __used_only_lp64)
+{
+	bool rv = false;
+
+#ifdef _LP64
+	/*
+	 * XXX We need to guard this with _LP64 because
+	 * extent maps use u_long internally.
+	 */
+	u_long addr64;
+	if (ex->ex_end > (1UL << 32) &&
+	    extent_alloc_subregion(ex, (1UL << 32),
+				   ex->ex_end,
+				   1 /* size */,
+				   1 /* alignment */,
+				   0 /* boundary */,
+				   EX_NOWAIT,
+				   &addr64) == 0) {
+		(void) extent_free(ex, addr64,
+				   1 /* size */,
+				   EX_NOWAIT);
+		rv = true;
+	}
+#endif /* _LP64 */
+
+	return rv;
+}
+
 /*
  * Let's configure the PCI bus.
  * This consists of basically scanning for all existing devices,
@@ -1121,7 +1255,6 @@ pci_configure_bus(pci_chipset_tag_t pc, 
 	pb->parent_bus = NULL;
 	pb->swiz = 0;
 	pb->io_32bit = 1;
-	pb->pmem_64bit = 0;
 	pb->ioext = ioext;
 	pb->memext = memext;
 	if (pmemext == NULL)
@@ -1129,6 +1262,13 @@ pci_configure_bus(pci_chipset_tag_t pc, 
 	else
 		pb->pmemext = pmemext;
 
+	/*
+	 * Probe the memory region extent maps to see
+	 * if allocation of 64-bit addresses is possible.
+	 */
+	pb->mem_64bit = mem_region_ok64(pb->memext);
+	pb->pmem_64bit = mem_region_ok64(pb->pmemext);
+
 	pb->pc = pc;
 	pb->io_total = pb->mem_total = pb->pmem_total = 0;
 

Reply via email to