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;