Module Name: src
Committed By: jruoho
Date: Thu Apr 22 14:50:31 UTC 2010
Modified Files:
src/sys/dev/acpi: acpi.c acpi_pci.c acpi_pci.h acpivar.h
Log Message:
>From Gregoire Sutre: rework the ACPI PCI support. This makes ACPI to
correctly pick PCI segment groups, PCI bus numbers, PCI root bridges,
PCI-to-PCI bridges, and PCI devices, among other things. In short: it is
more robust than the old code or anything in sys/arch/x86/x86/mpacpi.c.
ok cegger@, jmcneill@
To generate a diff of this commit:
cvs rdiff -u -r1.179 -r1.180 src/sys/dev/acpi/acpi.c
cvs rdiff -u -r1.5 -r1.6 src/sys/dev/acpi/acpi_pci.c
cvs rdiff -u -r1.3 -r1.4 src/sys/dev/acpi/acpi_pci.h
cvs rdiff -u -r1.49 -r1.50 src/sys/dev/acpi/acpivar.h
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/acpi/acpi.c
diff -u src/sys/dev/acpi/acpi.c:1.179 src/sys/dev/acpi/acpi.c:1.180
--- src/sys/dev/acpi/acpi.c:1.179 Tue Apr 20 04:57:04 2010
+++ src/sys/dev/acpi/acpi.c Thu Apr 22 14:50:30 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: acpi.c,v 1.179 2010/04/20 04:57:04 jruoho Exp $ */
+/* $NetBSD: acpi.c,v 1.180 2010/04/22 14:50:30 jruoho Exp $ */
/*-
* Copyright (c) 2003, 2007 The NetBSD Foundation, Inc.
@@ -65,7 +65,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: acpi.c,v 1.179 2010/04/20 04:57:04 jruoho Exp $");
+__KERNEL_RCSID(0, "$NetBSD: acpi.c,v 1.180 2010/04/22 14:50:30 jruoho Exp $");
#include "opt_acpi.h"
#include "opt_pcifixup.h"
@@ -662,6 +662,7 @@
ad->ad_device = NULL;
ad->ad_notify = NULL;
+ ad->ad_pciinfo = NULL;
ad->ad_type = type;
ad->ad_handle = handle;
Index: src/sys/dev/acpi/acpi_pci.c
diff -u src/sys/dev/acpi/acpi_pci.c:1.5 src/sys/dev/acpi/acpi_pci.c:1.6
--- src/sys/dev/acpi/acpi_pci.c:1.5 Sun Apr 18 14:05:26 2010
+++ src/sys/dev/acpi/acpi_pci.c Thu Apr 22 14:50:31 2010
@@ -1,11 +1,11 @@
-/* $NetBSD: acpi_pci.c,v 1.5 2010/04/18 14:05:26 jruoho Exp $ */
+/* $NetBSD: acpi_pci.c,v 1.6 2010/04/22 14:50:31 jruoho Exp $ */
/*
- * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * Copyright (c) 2009, 2010 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
- * by Christoph Egger.
+ * by Christoph Egger and Gregoire Sutre.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -33,147 +33,287 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: acpi_pci.c,v 1.5 2010/04/18 14:05:26 jruoho Exp $");
+__KERNEL_RCSID(0, "$NetBSD: acpi_pci.c,v 1.6 2010/04/22 14:50:31 jruoho Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/kmem.h>
-#include <sys/queue.h>
#include <sys/systm.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcidevs.h>
+#include <dev/pci/ppbreg.h>
+
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpi_pci.h>
-struct acpi_pcidev;
+#define _COMPONENT ACPI_BUS_COMPONENT
+ACPI_MODULE_NAME ("acpi_pci")
+
+static ACPI_STATUS acpi_pcidev_pciroot_bus(ACPI_HANDLE, uint16_t *);
+static ACPI_STATUS acpi_pcidev_pciroot_bus_callback(ACPI_RESOURCE *,
+ void *);
+static ACPI_STATUS acpi_pcidev_scan_rec(struct acpi_devnode *);
-static TAILQ_HEAD(, acpi_pcidev) acpi_pcidevlist =
- TAILQ_HEAD_INITIALIZER(acpi_pcidevlist);
-struct acpi_pcidev {
- struct acpi_devnode *ap_node;
- uint32_t ap_pciseg;
- uint32_t ap_pcibus;
- uint32_t ap_pcidev;
- uint32_t ap_pcifunc;
- bool ap_pcihost;
- TAILQ_ENTRY(acpi_pcidev) ap_list;
-};
-
-
-static const char * const acpi_pcidev_ids[] = {
- "PNP0A??", /* ACPI PCI host controllers */
- NULL
-};
+/*
+ * Regarding PCI Segment Groups, the ACPI spec says (cf. ACPI 4.0, p. 277):
+ *
+ * "The optional _SEG object is located under a PCI host bridge and
+ * evaluates to an integer that describes the PCI Segment Group (see PCI
+ * Firmware Specification v3.0)."
+ *
+ * "PCI Segment Group supports more than 256 buses in a system by allowing
+ * the reuse of the PCI bus numbers. Within each PCI Segment Group, the bus
+ * numbers for the PCI buses must be unique. PCI buses in different PCI
+ * Segment Group are permitted to have the same bus number."
+ *
+ * "If _SEG does not exist, OSPM assumes that all PCI bus segments are in
+ * PCI Segment Group 0."
+ *
+ * "The lower 16 bits of _SEG returned integer is the PCI Segment Group
+ * number. Other bits are reserved."
+ */
-static bool
-acpi_pcidev_add(struct acpi_softc *sc, struct acpi_devnode *ad)
+/*
+ * Regarding PCI Base Bus Numbers, the ACPI spec says (cf. ACPI 4.0, p. 277):
+ *
+ * "For multi-root PCI platforms, the _BBN object evaluates to the PCI bus
+ * number that the BIOS assigns. This is needed to access a PCI_Config
+ * operation region for the specified bus. The _BBN object is located under
+ * a PCI host bridge and must be unique for every host bridge within a
+ * segment since it is the PCI bus number."
+ *
+ * Moreover, the ACPI FAQ (http://www.acpi.info/acpi_faq.htm) says:
+ *
+ * "For a multiple root bus machine, _BBN is required for each bus. _BBN
+ * should provide the bus number assigned to this bus by the BIOS at boot
+ * time."
+ */
+
+
+/*
+ * acpi_pcidev_pciroot_bus:
+ *
+ * Derive the PCI bus number of a PCI root bridge from its resources.
+ * If successful, return AE_OK and fill *busp. Otherwise, return an
+ * exception code and leave *busp unchanged.
+ *
+ * XXX Use ACPI resource parsing functions (acpi_resource.c) once bus number
+ * ranges are implemented there.
+ */
+static ACPI_STATUS
+acpi_pcidev_pciroot_bus(ACPI_HANDLE handle, uint16_t *busp)
{
- struct acpi_pcidev *ap;
ACPI_STATUS rv;
- ACPI_INTEGER seg, bus, addr;
+ int32_t bus;
+
+ bus = -1;
+ rv = AcpiWalkResources(handle, METHOD_NAME__CRS,
+ acpi_pcidev_pciroot_bus_callback, &bus);
- /*
- * ACPI spec: "The _BBN object is located under a
- * PCI host bridge and must be unique for every
- * host bridge within a segment since it is the PCI bus number."
- */
- rv = acpi_eval_integer(ad->ad_handle, "_BBN", &bus);
- if (ACPI_FAILURE(rv))
- return false;
- /*
- * The ACPI address (_ADR) is equal to: (device << 16) | function.
- */
- rv = acpi_eval_integer(ad->ad_handle, "_ADR", &addr);
if (ACPI_FAILURE(rv))
- return false;
+ return rv;
- /*
- * ACPI spec: "The optional _SEG object is located under a PCI host
- * bridge and evaluates to an integer that describes the
- * PCI Segment Group (see PCI Firmware Specification v3.0)."
- *
- * "PCI Segment Group supports more than 256 buses
- * in a system by allowing the reuse of the PCI bus numbers.
- * Within each PCI Segment Group, the bus numbers for the PCI
- * buses must be unique. PCI buses in different PCI Segment
- * Group are permitted to have the same bus number."
- */
- rv = acpi_eval_integer(ad->ad_handle, "_SEG", &seg);
- if (ACPI_FAILURE(rv)) {
- /*
- * ACPI spec: "If _SEG does not exist, OSPM assumes that all
- * PCI bus segments are in PCI Segment Group 0."
- */
- seg = 0;
- }
+ if (bus < 0 || bus > 0xFFFF)
+ return AE_NOT_EXIST;
- ap = kmem_alloc(sizeof(*ap), KM_SLEEP);
- if (ap == NULL) {
- aprint_error("%s: kmem_alloc failed\n", __func__);
- return false;
- }
+ *busp = (uint16_t)bus;
+ return rv;
+}
- if (acpi_match_hid(ad->ad_devinfo, acpi_pcidev_ids))
- ap->ap_pcihost = true;
- else
- ap->ap_pcihost = false;
+static ACPI_STATUS
+acpi_pcidev_pciroot_bus_callback(ACPI_RESOURCE *res, void *context)
+{
+ int32_t *bus = context;
+ ACPI_RESOURCE_ADDRESS64 addr64;
- ap->ap_node = ad;
- ap->ap_pciseg = seg;
- ap->ap_pcibus = bus;
- ap->ap_pcidev = addr >> 16;
- ap->ap_pcifunc = addr & 0xffff;
+ if ((res->Type != ACPI_RESOURCE_TYPE_ADDRESS16) &&
+ (res->Type != ACPI_RESOURCE_TYPE_ADDRESS32) &&
+ (res->Type != ACPI_RESOURCE_TYPE_ADDRESS64))
+ return AE_OK; /* continue the walk */
- TAILQ_INSERT_TAIL(&acpi_pcidevlist, ap, ap_list);
+ if (ACPI_FAILURE(AcpiResourceToAddress64(res, &addr64)))
+ return AE_OK; /* continue the walk */
- return true;
-}
+ if (addr64.ResourceType != ACPI_BUS_NUMBER_RANGE)
+ return AE_OK; /* continue the walk */
-static void
-acpi_pcidev_print(struct acpi_pcidev *ap)
-{
- aprint_debug(" %s", ap->ap_node->ad_name);
+ if (*bus != -1)
+ return AE_ALREADY_EXISTS;
+
+ *bus = addr64.Minimum;
+ return AE_OK; /* continue the walk */
}
-int
-acpi_pcidev_scan(struct acpi_softc *sc)
+/*
+ * acpi_pcidev_scan_rec:
+ *
+ * Scan the ACPI device tree for PCI devices. A node is detected as a
+ * PCI device if it has an ancestor that is a PCI root bridge and such
+ * that all intermediate nodes are PCI-to-PCI bridges. Depth-first
+ * recursive implementation.
+ */
+static ACPI_STATUS
+acpi_pcidev_scan_rec(struct acpi_devnode *ad)
{
- struct acpi_devnode *ad;
- struct acpi_pcidev *ap;
- ACPI_DEVICE_INFO *di;
- int count = 0;
+ struct acpi_devnode *child;
+ struct acpi_pci_info *ap;
+ ACPI_INTEGER val;
+ ACPI_STATUS rv;
-#define ACPI_STA_DEV_VALID \
- (ACPI_STA_DEV_PRESENT|ACPI_STA_DEV_ENABLED|ACPI_STA_DEV_OK)
+ if (ad->ad_devinfo->Type != ACPI_TYPE_DEVICE ||
+ !(ad->ad_devinfo->Valid & ACPI_VALID_ADR)) {
+ ad->ad_pciinfo = NULL;
+ goto rec;
+ }
- SIMPLEQ_FOREACH(ad, &sc->ad_head, ad_list) {
+ if (ad->ad_devinfo->Flags & ACPI_PCI_ROOT_BRIDGE) {
+ ap = kmem_zalloc(sizeof(*ap), KM_SLEEP);
+ if (ap == NULL)
+ return AE_NO_MEMORY;
+
+ rv = acpi_eval_integer(ad->ad_handle, METHOD_NAME__SEG, &val);
+ if (ACPI_SUCCESS(rv))
+ ap->ap_segment = ACPI_LOWORD(val);
+ else
+ ap->ap_segment = 0;
+
+ /* try to get bus number using _CRS first */
+ rv = acpi_pcidev_pciroot_bus(ad->ad_handle, &ap->ap_bus);
+ if (ACPI_FAILURE(rv)) {
+ rv = acpi_eval_integer(ad->ad_handle, METHOD_NAME__BBN, &val);
+ if (ACPI_SUCCESS(rv))
+ ap->ap_bus = ACPI_LOWORD(val);
+ else
+ ap->ap_bus = 0;
+ }
+
+ ap->ap_device = ACPI_HIWORD(ACPI_LODWORD(ad->ad_devinfo->Address));
+ ap->ap_function = ACPI_LOWORD(ACPI_LODWORD(ad->ad_devinfo->Address));
- di = ad->ad_devinfo;
+ ap->ap_bridge = true;
+ ap->ap_downbus = ap->ap_bus;
+
+ ad->ad_pciinfo = ap;
+ goto rec;
+ }
- if (di->Type != ACPI_TYPE_DEVICE)
- continue;
+ if ((ad->ad_parent != NULL) &&
+ (ad->ad_parent->ad_pciinfo != NULL) &&
+ (ad->ad_parent->ad_pciinfo->ap_bridge)) {
+ /*
+ * Our parent is a PCI root bridge or a PCI-to-PCI bridge. We
+ * have the same PCI segment#, and our bus# is its downstream
+ * bus number.
+ */
+ ap = kmem_zalloc(sizeof(*ap), KM_SLEEP);
+ if (ap == NULL)
+ return AE_NO_MEMORY;
+
+ ap->ap_segment = ad->ad_parent->ad_pciinfo->ap_segment;
+ ap->ap_bus = ad->ad_parent->ad_pciinfo->ap_downbus;
+ ap->ap_device = ACPI_HIWORD(ACPI_LODWORD(ad->ad_devinfo->Address));
+ ap->ap_function = ACPI_LOWORD(ACPI_LODWORD(ad->ad_devinfo->Address));
- if ((di->Valid & ACPI_VALID_STA) != 0 &&
- (di->CurrentStatus & ACPI_STA_DEV_VALID) !=
- ACPI_STA_DEV_VALID)
- continue;
+ /*
+ * Check whether this device is a PCI-to-PCI bridge and get its
+ * secondary bus#.
+ */
+ rv = acpi_pcidev_ppb_downbus(ap->ap_segment, ap->ap_bus,
+ ap->ap_device, ap->ap_function, &ap->ap_downbus);
+ if (ACPI_SUCCESS(rv))
+ ap->ap_bridge = true;
+ else
+ ap->ap_bridge = false;
- if (acpi_pcidev_add(sc, ad) == true)
- ++count;
+ ad->ad_pciinfo = ap;
+ goto rec;
+ }
+ rec:
+ SIMPLEQ_FOREACH(child, &ad->ad_child_head, ad_child_list) {
+ rv = acpi_pcidev_scan_rec(child);
+ if (ACPI_FAILURE(rv))
+ return rv;
}
-#undef ACPI_STA_DEV_VALID
+ return AE_OK;
+}
- if (count == 0)
- return 0;
+/*
+ * acpi_pcidev_ppb_downbus:
+ *
+ * Retrieve the secondary bus number of the PCI-to-PCI bridge having the
+ * given PCI id. If successful, return AE_OK and fill *busp. Otherwise,
+ * return an exception code and leave *busp unchanged.
+ *
+ * XXX Need to deal with PCI segment groups (see also acpica/OsdHardware.c).
+ */
+ACPI_STATUS
+acpi_pcidev_ppb_downbus(uint16_t segment, uint16_t bus, uint16_t device,
+ uint16_t function, uint16_t *downbus)
+{
+ struct acpi_softc *sc = acpi_softc;
+ pci_chipset_tag_t pc;
+ pcitag_t tag;
+ pcireg_t val;
+ if (bus > 255 || device > 31 || function > 7)
+ return AE_BAD_PARAMETER;
+
+ if (sc == NULL)
+ pc = NULL;
+ else
+ pc = sc->sc_pc;
+
+ tag = pci_make_tag(pc, bus, device, function);
+
+ /* Check that this device exists. */
+ val = pci_conf_read(pc, tag, PCI_ID_REG);
+ if (PCI_VENDOR(val) == PCI_VENDOR_INVALID ||
+ PCI_VENDOR(val) == 0)
+ return AE_NOT_EXIST;
+
+ /* Check that this device is a PCI-to-PCI bridge. */
+ val = pci_conf_read(pc, tag, PCI_BHLC_REG);
+ if (PCI_HDRTYPE_TYPE(val) != PCI_HDRTYPE_PPB)
+ return AE_TYPE;
+
+ /* This is a PCI-to-PCI bridge. Get its secondary bus#. */
+ val = pci_conf_read(pc, tag, PPB_REG_BUSINFO);
+ *downbus = PPB_BUSINFO_SECONDARY(val);
+ return AE_OK;
+}
+
+static void
+acpi_pcidev_print(struct acpi_devnode *ad)
+{
+ aprint_debug(" ");
+ if (ad->ad_devinfo->Flags & ACPI_PCI_ROOT_BRIDGE)
+ aprint_debug("*");
+ aprint_debug("%...@%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16,
+ ad->ad_name,
+ ad->ad_pciinfo->ap_segment,
+ ad->ad_pciinfo->ap_bus,
+ ad->ad_pciinfo->ap_device,
+ ad->ad_pciinfo->ap_function);
+ if (ad->ad_pciinfo->ap_bridge)
+ aprint_debug(">%"PRIx16, ad->ad_pciinfo->ap_downbus);
+}
+
+void
+acpi_pcidev_scan(struct acpi_softc *sc)
+{
+ struct acpi_devnode *ad;
+
+ acpi_pcidev_scan_rec(sc->sc_root);
aprint_debug_dev(sc->sc_dev, "pci devices:");
- TAILQ_FOREACH(ap, &acpi_pcidevlist, ap_list)
- acpi_pcidev_print(ap);
+ SIMPLEQ_FOREACH(ad, &sc->ad_head, ad_list) {
+ if (ad->ad_pciinfo != NULL)
+ acpi_pcidev_print(ad);
+ }
aprint_debug("\n");
-
- return count;
}
/*
@@ -185,27 +325,24 @@
* - AE_OK if one and only one such device was found.
*/
ACPI_STATUS
-acpi_pcidev_find(u_int segment, u_int bus, u_int device, u_int function,
- ACPI_HANDLE *handlep)
+acpi_pcidev_find(uint16_t segment, uint16_t bus, uint16_t device,
+ uint16_t function, struct acpi_devnode **devnodep)
{
- struct acpi_pcidev *ap;
- ACPI_HANDLE hdl;
+ struct acpi_softc *sc = acpi_softc;
+ struct acpi_devnode *ad;
- hdl = NULL;
- TAILQ_FOREACH(ap, &acpi_pcidevlist, ap_list) {
- if (ap->ap_pciseg != segment)
- continue;
- if (ap->ap_pcibus != bus)
- continue;
- if (ap->ap_pcidev != device)
- continue;
- if (ap->ap_pcifunc != function)
- continue;
+ if (sc == NULL)
+ return AE_NOT_FOUND;
- hdl = ap->ap_node->ad_handle;
- break;
+ SIMPLEQ_FOREACH(ad, &sc->ad_head, ad_list) {
+ if ((ad->ad_pciinfo != NULL) &&
+ (ad->ad_pciinfo->ap_segment == segment) &&
+ (ad->ad_pciinfo->ap_bus == bus) &&
+ (ad->ad_pciinfo->ap_device == device) &&
+ (ad->ad_pciinfo->ap_function == function)) {
+ *devnodep = ad;
+ return AE_OK;
+ }
}
-
- *handlep = hdl;
- return (hdl != NULL) ? AE_OK : AE_NOT_FOUND;
+ return AE_NOT_FOUND;
}
Index: src/sys/dev/acpi/acpi_pci.h
diff -u src/sys/dev/acpi/acpi_pci.h:1.3 src/sys/dev/acpi/acpi_pci.h:1.4
--- src/sys/dev/acpi/acpi_pci.h:1.3 Fri Mar 5 08:30:48 2010
+++ src/sys/dev/acpi/acpi_pci.h Thu Apr 22 14:50:31 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: acpi_pci.h,v 1.3 2010/03/05 08:30:48 jruoho Exp $ */
+/* $NetBSD: acpi_pci.h,v 1.4 2010/04/22 14:50:31 jruoho Exp $ */
/*
* Copyright (c) 2009 The NetBSD Foundation, Inc.
@@ -31,7 +31,10 @@
#ifndef _SYS_DEV_ACPI_ACPI_PCI_H
#define _SYS_DEV_ACPI_ACPI_PCI_H
-int acpi_pcidev_scan(struct acpi_softc *);
-ACPI_STATUS acpi_pcidev_find(u_int, u_int, u_int, u_int, ACPI_HANDLE *);
+void acpi_pcidev_scan(struct acpi_softc *);
+ACPI_STATUS acpi_pcidev_find(uint16_t, uint16_t, uint16_t, uint16_t,
+ struct acpi_devnode **);
+ACPI_STATUS acpi_pcidev_ppb_downbus(uint16_t, uint16_t, uint16_t, uint16_t,
+ uint16_t *);
#endif /* !_SYS_DEV_ACPI_ACPI_PCI_H */
Index: src/sys/dev/acpi/acpivar.h
diff -u src/sys/dev/acpi/acpivar.h:1.49 src/sys/dev/acpi/acpivar.h:1.50
--- src/sys/dev/acpi/acpivar.h:1.49 Sun Apr 18 14:05:26 2010
+++ src/sys/dev/acpi/acpivar.h Thu Apr 22 14:50:31 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: acpivar.h,v 1.49 2010/04/18 14:05:26 jruoho Exp $ */
+/* $NetBSD: acpivar.h,v 1.50 2010/04/22 14:50:31 jruoho Exp $ */
/*
* Copyright 2001 Wasabi Systems, Inc.
@@ -71,12 +71,33 @@
#define ACPI_DEVICE_WAKEUP __BIT(1)
/*
+ * PCI information for ACPI device nodes that correspond to PCI devices.
+ */
+struct acpi_pci_info {
+ uint16_t ap_segment; /* PCI segment group */
+ uint16_t ap_bus; /* PCI bus */
+ uint16_t ap_device; /* PCI device */
+ uint16_t ap_function; /* PCI function */
+ bool ap_bridge; /* PCI bridge (PHB or PPB) */
+ uint16_t ap_downbus; /* PCI bridge downstream bus */
+};
+
+/*
* An ACPI device node.
+ *
+ * Remarks:
+ *
+ * ad_root never NULL
+ * ad_parent only NULL if the root of the tree ("\").
+ * ad_device NULL if no device has attached to the node
+ * ad_pciinfo NULL if not a PCI device
+ * ad_notify NULL if there is no notify handler
*/
struct acpi_devnode {
device_t ad_device; /* Device */
device_t ad_root; /* Backpointer to acpi_softc */
struct acpi_devnode *ad_parent; /* Backpointer to parent */
+ struct acpi_pci_info *ad_pciinfo; /* PCI info */
ACPI_NOTIFY_HANDLER ad_notify; /* Device notify */
ACPI_DEVICE_INFO *ad_devinfo; /* Device info */
ACPI_HANDLE ad_handle; /* Device handle */
@@ -85,7 +106,6 @@
uint32_t ad_type; /* Device type */
int ad_wake; /* Device wakeup */
-
SIMPLEQ_ENTRY(acpi_devnode) ad_list;
SIMPLEQ_ENTRY(acpi_devnode) ad_child_list;
SIMPLEQ_HEAD(, acpi_devnode) ad_child_head;