summaryrefslogtreecommitdiffstats
path: root/sys/dev
diff options
context:
space:
mode:
authorjhb <jhb@FreeBSD.org>2011-07-15 21:08:58 +0000
committerjhb <jhb@FreeBSD.org>2011-07-15 21:08:58 +0000
commitb75d5a0ef9a633d11ad36f4e2e0cc915b287d36c (patch)
treecb42b98ebf988021855125779898a415b8209443 /sys/dev
parentd3eb43cf7d9826d90122c3bc76f9284edf7eae8d (diff)
downloadFreeBSD-src-b75d5a0ef9a633d11ad36f4e2e0cc915b287d36c.zip
FreeBSD-src-b75d5a0ef9a633d11ad36f4e2e0cc915b287d36c.tar.gz
Respect the BIOS/firmware's notion of acceptable address ranges for PCI
resource allocation on x86 platforms: - Add a new helper API that Host-PCI bridge drivers can use to restrict resource allocation requests to a set of address ranges for different resource types. - For the ACPI Host-PCI bridge driver, use Producer address range resources in _CRS to enumerate valid address ranges for a given Host-PCI bridge. This can be disabled by including "hostres" in the debug.acpi.disabled tunable. - For the MPTable Host-PCI bridge driver, use entries in the extended MPTable to determine the valid address ranges for a given Host-PCI bridge. This required adding code to parse extended table entries. Similar to the new PCI-PCI bridge driver, these changes are only enabled if the NEW_PCIB kernel option is enabled (which is enabled by default on amd64 and i386). Approved by: re (kib)
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/acpica/acpi_pcib_acpi.c166
-rw-r--r--sys/dev/pci/pci_pci.c12
-rw-r--r--sys/dev/pci/pci_subr.c157
-rw-r--r--sys/dev/pci/pcib_private.h28
4 files changed, 350 insertions, 13 deletions
diff --git a/sys/dev/acpica/acpi_pcib_acpi.c b/sys/dev/acpica/acpi_pcib_acpi.c
index 73b2f70..d3d499e 100644
--- a/sys/dev/acpica/acpi_pcib_acpi.c
+++ b/sys/dev/acpica/acpi_pcib_acpi.c
@@ -32,6 +32,7 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
+#include <sys/limits.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/rman.h>
@@ -62,6 +63,9 @@ struct acpi_hpcib_softc {
int ap_bus; /* bios-assigned bus number */
ACPI_BUFFER ap_prt; /* interrupt routing table */
+#ifdef NEW_PCIB
+ struct pcib_host_resources ap_host_res;
+#endif
};
static int acpi_pcib_acpi_probe(device_t bus);
@@ -87,6 +91,11 @@ static struct resource *acpi_pcib_acpi_alloc_resource(device_t dev,
device_t child, int type, int *rid,
u_long start, u_long end, u_long count,
u_int flags);
+#ifdef NEW_PCIB
+static int acpi_pcib_acpi_adjust_resource(device_t dev,
+ device_t child, int type, struct resource *r,
+ u_long start, u_long end);
+#endif
static device_method_t acpi_pcib_acpi_methods[] = {
/* Device interface */
@@ -101,7 +110,11 @@ static device_method_t acpi_pcib_acpi_methods[] = {
DEVMETHOD(bus_read_ivar, acpi_pcib_read_ivar),
DEVMETHOD(bus_write_ivar, acpi_pcib_write_ivar),
DEVMETHOD(bus_alloc_resource, acpi_pcib_acpi_alloc_resource),
+#ifdef NEW_PCIB
+ DEVMETHOD(bus_adjust_resource, acpi_pcib_acpi_adjust_resource),
+#else
DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource),
+#endif
DEVMETHOD(bus_release_resource, bus_generic_release_resource),
DEVMETHOD(bus_activate_resource, bus_generic_activate_resource),
DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource),
@@ -149,6 +162,120 @@ acpi_pcib_acpi_probe(device_t dev)
return (0);
}
+#ifdef NEW_PCIB
+static ACPI_STATUS
+acpi_pcib_producer_handler(ACPI_RESOURCE *res, void *context)
+{
+ struct acpi_hpcib_softc *sc;
+ UINT64 length, min, max;
+ u_int flags;
+ int error, type;
+
+ sc = context;
+ switch (res->Type) {
+ case ACPI_RESOURCE_TYPE_START_DEPENDENT:
+ case ACPI_RESOURCE_TYPE_END_DEPENDENT:
+ panic("host bridge has depenedent resources");
+ case ACPI_RESOURCE_TYPE_ADDRESS16:
+ case ACPI_RESOURCE_TYPE_ADDRESS32:
+ case ACPI_RESOURCE_TYPE_ADDRESS64:
+ case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64:
+ if (res->Data.Address.ProducerConsumer != ACPI_PRODUCER)
+ break;
+ switch (res->Type) {
+ case ACPI_RESOURCE_TYPE_ADDRESS16:
+ min = res->Data.Address16.Minimum;
+ max = res->Data.Address16.Maximum;
+ length = res->Data.Address16.AddressLength;
+ break;
+ case ACPI_RESOURCE_TYPE_ADDRESS32:
+ min = res->Data.Address32.Minimum;
+ max = res->Data.Address32.Maximum;
+ length = res->Data.Address32.AddressLength;
+ break;
+ case ACPI_RESOURCE_TYPE_ADDRESS64:
+ min = res->Data.Address64.Minimum;
+ max = res->Data.Address64.Maximum;
+ length = res->Data.Address64.AddressLength;
+ break;
+ default:
+ KASSERT(res->Type ==
+ ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64,
+ ("should never happen"));
+ min = res->Data.ExtAddress64.Minimum;
+ max = res->Data.ExtAddress64.Maximum;
+ length = res->Data.ExtAddress64.AddressLength;
+ break;
+ }
+ if (length == 0 ||
+ res->Data.Address.MinAddressFixed != ACPI_ADDRESS_FIXED ||
+ res->Data.Address.MaxAddressFixed != ACPI_ADDRESS_FIXED)
+ break;
+ flags = 0;
+ switch (res->Data.Address.ResourceType) {
+ case ACPI_MEMORY_RANGE:
+ type = SYS_RES_MEMORY;
+ if (res->Type != ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64) {
+ if (res->Data.Address.Info.Mem.Caching ==
+ ACPI_PREFETCHABLE_MEMORY)
+ flags |= RF_PREFETCHABLE;
+ } else {
+ /*
+ * XXX: Parse prefetch flag out of
+ * TypeSpecific.
+ */
+ }
+ break;
+ case ACPI_IO_RANGE:
+ type = SYS_RES_IOPORT;
+ break;
+#ifdef PCI_RES_BUS
+ case ACPI_BUS_NUMBER_RANGE:
+ type = PCI_RES_BUS;
+ break;
+#endif
+ default:
+ return (AE_OK);
+ }
+
+ /* XXX: Not sure this is correct? */
+ if (res->Data.Address.Decode != ACPI_POS_DECODE) {
+ device_printf(sc->ap_dev,
+ "Ignoring %d range (%#jx-%#jx) due to negative decode\n",
+ type, (uintmax_t)min, (uintmax_t)max);
+ break;
+ }
+ if (min + length - 1 != max)
+ device_printf(sc->ap_dev,
+ "Length mismatch for %d range: %jx vs %jx\n", type,
+ (uintmax_t)max - min + 1, (uintmax_t)length);
+#ifdef __i386__
+ if (min > ULONG_MAX) {
+ device_printf(sc->ap_dev,
+ "Ignoring %d range above 4GB (%#jx-%#jx)\n",
+ type, (uintmax_t)min, (uintmax_t)max);
+ break;
+ }
+ if (max > ULONG_MAX) {
+ device_printf(sc->ap_dev,
+ "Truncating end of %d range above 4GB (%#jx-%#jx)\n",
+ type, (uintmax_t)min, (uintmax_t)max);
+ max = ULONG_MAX;
+ }
+#endif
+ error = pcib_host_res_decodes(&sc->ap_host_res, type, min, max,
+ flags);
+ if (error)
+ panic("Failed to manage %d range (%#jx-%#jx): %d",
+ type, (uintmax_t)min, (uintmax_t)max, error);
+ break;
+ default:
+ break;
+ }
+ return (AE_OK);
+}
+#endif
+
static int
acpi_pcib_acpi_attach(device_t dev)
{
@@ -179,6 +306,22 @@ acpi_pcib_acpi_attach(device_t dev)
sc->ap_segment = 0;
}
+#ifdef NEW_PCIB
+ /*
+ * Determine which address ranges this bridge decodes and setup
+ * resource managers for those ranges.
+ */
+ if (pcib_host_res_init(sc->ap_dev, &sc->ap_host_res) != 0)
+ panic("failed to init hostb resources");
+ if (!acpi_disabled("hostres")) {
+ status = AcpiWalkResources(sc->ap_handle, "_CRS",
+ acpi_pcib_producer_handler, sc);
+ if (ACPI_FAILURE(status) && status != AE_NOT_FOUND)
+ device_printf(sc->ap_dev, "failed to parse resources: %s\n",
+ AcpiFormatException(status));
+ }
+#endif
+
/*
* Get our base bus number by evaluating _BBN.
* If this doesn't work, we assume we're bus number 0.
@@ -361,10 +504,33 @@ struct resource *
acpi_pcib_acpi_alloc_resource(device_t dev, device_t child, int type, int *rid,
u_long start, u_long end, u_long count, u_int flags)
{
+#ifdef NEW_PCIB
+ struct acpi_hpcib_softc *sc;
+#endif
#if defined(__i386__) || defined(__amd64__)
start = hostb_alloc_start(type, start, end, count);
#endif
+
+#ifdef NEW_PCIB
+ sc = device_get_softc(dev);
+ return (pcib_host_res_alloc(&sc->ap_host_res, child, type, rid, start, end,
+ count, flags));
+#else
return (bus_generic_alloc_resource(dev, child, type, rid, start, end,
count, flags));
+#endif
+}
+
+#ifdef NEW_PCIB
+int
+acpi_pcib_acpi_adjust_resource(device_t dev, device_t child, int type,
+ struct resource *r, u_long start, u_long end)
+{
+ struct acpi_hpcib_softc *sc;
+
+ sc = device_get_softc(dev);
+ return (pcib_host_res_adjust(&sc->ap_host_res, child, type, r, start,
+ end));
}
+#endif
diff --git a/sys/dev/pci/pci_pci.c b/sys/dev/pci/pci_pci.c
index 5cbfb4d..fa14816 100644
--- a/sys/dev/pci/pci_pci.c
+++ b/sys/dev/pci/pci_pci.c
@@ -767,18 +767,6 @@ pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value)
}
#ifdef NEW_PCIB
-static const char *
-pcib_child_name(device_t child)
-{
- static char buf[64];
-
- if (device_get_nameunit(child) != NULL)
- return (device_get_nameunit(child));
- snprintf(buf, sizeof(buf), "pci%d:%d:%d:%d", pci_get_domain(child),
- pci_get_bus(child), pci_get_slot(child), pci_get_function(child));
- return (buf);
-}
-
/*
* Attempt to allocate a resource from the existing resources assigned
* to a window.
diff --git a/sys/dev/pci/pci_subr.c b/sys/dev/pci/pci_subr.c
index 7f0c806..2d16896 100644
--- a/sys/dev/pci/pci_subr.c
+++ b/sys/dev/pci/pci_subr.c
@@ -33,9 +33,10 @@ __FBSDID("$FreeBSD$");
* provide PCI domains.
*/
-#include <sys/types.h>
+#include <sys/param.h>
#include <sys/bus.h>
#include <sys/rman.h>
+#include <sys/systm.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
@@ -128,3 +129,157 @@ host_pcib_get_busno(pci_read_config_fn read_config, int bus, int slot, int func,
return 1;
}
+
+#ifdef NEW_PCIB
+/*
+ * Return a pointer to a pretty name for a PCI device. If the device
+ * has a driver attached, the device's name is used, otherwise a name
+ * is generated from the device's PCI address.
+ */
+const char *
+pcib_child_name(device_t child)
+{
+ static char buf[64];
+
+ if (device_get_nameunit(child) != NULL)
+ return (device_get_nameunit(child));
+ snprintf(buf, sizeof(buf), "pci%d:%d:%d:%d", pci_get_domain(child),
+ pci_get_bus(child), pci_get_slot(child), pci_get_function(child));
+ return (buf);
+}
+
+/*
+ * Some Host-PCI bridge drivers know which resource ranges they can
+ * decode and should only allocate subranges to child PCI devices.
+ * This API provides a way to manage this. The bridge drive should
+ * initialize this structure during attach and call
+ * pcib_host_res_decodes() on each resource range it decodes. It can
+ * then use pcib_host_res_alloc() and pcib_host_res_adjust() as helper
+ * routines for BUS_ALLOC_RESOURCE() and BUS_ADJUST_RESOURCE(). This
+ * API assumes that resources for any decoded ranges can be safely
+ * allocated from the parent via bus_generic_alloc_resource().
+ */
+int
+pcib_host_res_init(device_t pcib, struct pcib_host_resources *hr)
+{
+
+ hr->hr_pcib = pcib;
+ resource_list_init(&hr->hr_rl);
+ return (0);
+}
+
+int
+pcib_host_res_free(device_t pcib, struct pcib_host_resources *hr)
+{
+
+ resource_list_free(&hr->hr_rl);
+ return (0);
+}
+
+int
+pcib_host_res_decodes(struct pcib_host_resources *hr, int type, u_long start,
+ u_long end, u_int flags)
+{
+ struct resource_list_entry *rle;
+ int rid;
+
+ if (bootverbose)
+ device_printf(hr->hr_pcib, "decoding %d %srange %#lx-%#lx\n",
+ type, flags & RF_PREFETCHABLE ? "prefetchable ": "", start,
+ end);
+ rid = resource_list_add_next(&hr->hr_rl, type, start, end,
+ end - start + 1);
+ if (flags & RF_PREFETCHABLE) {
+ KASSERT(type == SYS_RES_MEMORY,
+ ("only memory is prefetchable"));
+ rle = resource_list_find(&hr->hr_rl, type, rid);
+ rle->flags = RLE_PREFETCH;
+ }
+ return (0);
+}
+
+struct resource *
+pcib_host_res_alloc(struct pcib_host_resources *hr, device_t dev, int type,
+ int *rid, u_long start, u_long end, u_long count, u_int flags)
+{
+ struct resource_list_entry *rle;
+ struct resource *r;
+ u_long new_start, new_end;
+
+ if (flags & RF_PREFETCHABLE)
+ KASSERT(type == SYS_RES_MEMORY,
+ ("only memory is prefetchable"));
+
+ rle = resource_list_find(&hr->hr_rl, type, 0);
+ if (rle == NULL) {
+ /*
+ * No decoding ranges for this resource type, just pass
+ * the request up to the parent.
+ */
+ return (bus_generic_alloc_resource(hr->hr_pcib, dev, type, rid,
+ start, end, count, flags));
+ }
+
+restart:
+ /* Try to allocate from each decoded range. */
+ for (; rle != NULL; rle = STAILQ_NEXT(rle, link)) {
+ if (rle->type != type)
+ continue;
+ if (((flags & RF_PREFETCHABLE) != 0) !=
+ ((rle->flags & RLE_PREFETCH) != 0))
+ continue;
+ new_start = ulmax(start, rle->start);
+ new_end = ulmin(end, rle->end);
+ if (new_start > new_end ||
+ new_start + count - 1 > new_end ||
+ new_start + count < new_start)
+ continue;
+ r = bus_generic_alloc_resource(hr->hr_pcib, dev, type, rid,
+ new_start, new_end, count, flags);
+ if (r != NULL) {
+ if (bootverbose)
+ device_printf(hr->hr_pcib,
+ "allocated type %d (%#lx-%#lx) for rid %x of %s\n",
+ type, rman_get_start(r), rman_get_end(r),
+ *rid, pcib_child_name(dev));
+ return (r);
+ }
+ }
+
+ /*
+ * If we failed to find a prefetch range for a memory
+ * resource, try again without prefetch.
+ */
+ if (flags & RF_PREFETCHABLE) {
+ flags &= ~RF_PREFETCHABLE;
+ rle = resource_list_find(&hr->hr_rl, type, 0);
+ goto restart;
+ }
+ return (NULL);
+}
+
+int
+pcib_host_res_adjust(struct pcib_host_resources *hr, device_t dev, int type,
+ struct resource *r, u_long start, u_long end)
+{
+ struct resource_list_entry *rle;
+
+ rle = resource_list_find(&hr->hr_rl, type, 0);
+ if (rle == NULL) {
+ /*
+ * No decoding ranges for this resource type, just pass
+ * the request up to the parent.
+ */
+ return (bus_generic_adjust_resource(hr->hr_pcib, dev, type, r,
+ start, end));
+ }
+
+ /* Only allow adjustments that stay within a decoded range. */
+ for (; rle != NULL; rle = STAILQ_NEXT(rle, link)) {
+ if (rle->start <= start && rle->end >= end)
+ return (bus_generic_adjust_resource(hr->hr_pcib, dev,
+ type, r, start, end));
+ }
+ return (ERANGE);
+}
+#endif /* NEW_PCIB */
diff --git a/sys/dev/pci/pcib_private.h b/sys/dev/pci/pcib_private.h
index 1574deb..0561581 100644
--- a/sys/dev/pci/pcib_private.h
+++ b/sys/dev/pci/pcib_private.h
@@ -33,6 +33,31 @@
#ifndef __PCIB_PRIVATE_H__
#define __PCIB_PRIVATE_H__
+#ifdef NEW_PCIB
+/*
+ * Data structure and routines that Host to PCI bridge drivers can use
+ * to restrict allocations for child devices to ranges decoded by the
+ * bridge.
+ */
+struct pcib_host_resources {
+ device_t hr_pcib;
+ struct resource_list hr_rl;
+};
+
+int pcib_host_res_init(device_t pcib,
+ struct pcib_host_resources *hr);
+int pcib_host_res_free(device_t pcib,
+ struct pcib_host_resources *hr);
+int pcib_host_res_decodes(struct pcib_host_resources *hr, int type,
+ u_long start, u_long end, u_int flags);
+struct resource *pcib_host_res_alloc(struct pcib_host_resources *hr,
+ device_t dev, int type, int *rid, u_long start, u_long end,
+ u_long count, u_int flags);
+int pcib_host_res_adjust(struct pcib_host_resources *hr,
+ device_t dev, int type, struct resource *r, u_long start,
+ u_long end);
+#endif
+
/*
* Export portions of generic PCI:PCI bridge support so that it can be
* used by subclasses.
@@ -90,6 +115,9 @@ struct pcib_softc
typedef uint32_t pci_read_config_fn(int b, int s, int f, int reg, int width);
+#ifdef NEW_PCIB
+const char *pcib_child_name(device_t child);
+#endif
int host_pcib_get_busno(pci_read_config_fn read_config, int bus,
int slot, int func, uint8_t *busnum);
int pcib_attach(device_t dev);
OpenPOWER on IntegriCloud