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;