acpica.c revision 2df1fe9ca32bb227b9158c67f5c00b54c20b10fd
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Solaris x86 ACPI CA services
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/esunddi.h>
#include <sys/kstat.h>
#include <sys/acpi/acpi.h>
#include <sys/acpica.h>
/*
*
*/
static struct modlmisc modlmisc = {
&mod_miscops,
"ACPI interpreter",
};
static struct modlinkage modlinkage = {
MODREV_1, /* MODREV_1 manual */
(void *)&modlmisc, /* module linkage */
NULL, /* list terminator */
};
/*
* Local prototypes
*/
static void acpica_init_kstats(void);
/*
* Local data
*/
static kmutex_t acpica_module_lock;
static kstat_t *acpica_ksp;
/*
* State of acpica subsystem
* After successful initialization, will be ACPICA_INITIALIZED
*/
int acpica_init_state = ACPICA_NOT_INITIALIZED;
/*
* Following are set by acpica_process_user_options()
*
* acpica_enable = FALSE prevents initialization of ACPI CA
* completely
*
* acpi_init_level determines level of ACPI CA functionality
* enabled in acpica_init()
*/
int acpica_enable;
UINT32 acpi_init_level;
/*
* Non-zero enables lax behavior with respect to some
* common ACPI BIOS issues; see ACPI CA documentation
* Setting this to zero causes ACPI CA to enforce strict
* compliance with ACPI specification
*/
int acpica_enable_interpreter_slack = 1;
/*
* For non-DEBUG builds, set the ACPI CA debug level to 0
* to quiet chatty BIOS output into /var/adm/messages
* Field-patchable for diagnostic use.
*/
#ifdef DEBUG
int acpica_muzzle_debug_output = 0;
#else
int acpica_muzzle_debug_output = 1;
#endif
/*
* ACPI DDI hooks
*/
static int acpica_ddi_setwake(dev_info_t *dip, int level);
int
_init(void)
{
int error = EBUSY;
int status;
extern int (*acpi_fp_setwake)();
mutex_init(&acpica_module_lock, NULL, MUTEX_DRIVER, NULL);
if ((error = mod_install(&modlinkage)) != 0) {
mutex_destroy(&acpica_module_lock);
goto load_error;
}
AcpiGbl_EnableInterpreterSlack = (acpica_enable_interpreter_slack != 0);
if ((status = AcpiInitializeSubsystem()) != AE_OK) {
cmn_err(CE_WARN, "!acpica: error pre-init:1:%d", status);
}
acpi_fp_setwake = acpica_ddi_setwake;
load_error:
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
/*
* acpica module is never unloaded at run-time; there's always
* a PSM depending on it, at the very least
*/
return (EBUSY);
}
/*
* Install acpica-provided address-space handlers
*/
static int
acpica_install_handlers()
{
ACPI_STATUS rv = AE_OK;
/*
* Install ACPI CA default handlers
*/
if (AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
ACPI_ADR_SPACE_SYSTEM_MEMORY,
ACPI_DEFAULT_HANDLER, NULL, NULL) != AE_OK) {
cmn_err(CE_WARN, "!acpica: no default handler for"
" system memory");
rv = AE_ERROR;
}
if (AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
ACPI_ADR_SPACE_SYSTEM_IO,
ACPI_DEFAULT_HANDLER, NULL, NULL) != AE_OK) {
cmn_err(CE_WARN, "!acpica: no default handler for"
" system I/O");
rv = AE_ERROR;
}
if (AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
ACPI_ADR_SPACE_PCI_CONFIG,
ACPI_DEFAULT_HANDLER, NULL, NULL) != AE_OK) {
cmn_err(CE_WARN, "!acpica: no default handler for"
" PCI Config");
rv = AE_ERROR;
}
return (rv);
}
/*
* Find the BIOS date, and return TRUE if supplied
* date is same or later than the BIOS date, or FALSE
* if the BIOS date can't be fetched for any reason
*/
static int
acpica_check_bios_date(int yy, int mm, int dd)
{
char *datep;
int bios_year, bios_month, bios_day;
/* If firmware has no bios, skip the check */
if (ddi_prop_exists(DDI_DEV_T_ANY, ddi_root_node(), 0, "bios-free"))
return (TRUE);
/*
* PC BIOSes contain a string in the form of
* "mm/dd/yy" at absolute address 0xffff5,
* where mm, dd and yy are all ASCII digits.
* We map the string, pluck out the values,
* and accept all BIOSes from 1 Jan 1999 on
* as valid.
*/
if ((int)AcpiOsMapMemory(0xffff5, 8, (void **) &datep) != AE_OK)
return (FALSE);
/* year */
bios_year = ((int)(*(datep + 6) - '0') * 10) + (*(datep + 7) - '0');
/* month */
bios_month = ((int)(*datep - '0') * 10) + (*(datep + 1) - '0');
/* day */
bios_day = ((int)(*(datep + 3) - '0') * 10) + (*(datep + 4) - '0');
AcpiOsUnmapMemory((void *) datep, 8);
if (bios_year < 0 || bios_year > 99 || bios_month < 0 ||
bios_month > 99 || bios_day < 0 || bios_day > 99) {
/* non-digit chars in BIOS date */
return (FALSE);
}
/*
* Adjust for 2-digit year; note to grand-children:
* need a new scheme before 2080 rolls around
*/
bios_year += (bios_year >= 80 && bios_year <= 99) ?
1900 : 2000;
if (bios_year < yy)
return (FALSE);
else if (bios_year > yy)
return (TRUE);
if (bios_month < mm)
return (FALSE);
else if (bios_month > mm)
return (TRUE);
if (bios_day < dd)
return (FALSE);
return (TRUE);
}
/*
* Check for Metropolis systems with BIOSes older than 10/12/04
* return TRUE if BIOS requires legacy mode, FALSE otherwise
*/
static int
acpica_metro_old_bios()
{
ACPI_TABLE_HEADER *fadt;
/* get the FADT */
if (AcpiGetFirmwareTable(FADT_SIG, 1, ACPI_LOGICAL_ADDRESSING,
(ACPI_TABLE_HEADER **)&fadt) != AE_OK)
return (FALSE);
/* compare OEM Table ID to "SUNmetro" - no match, return false */
if (strncmp("SUNmetro", fadt->OemTableId, 8))
return (FALSE);
/* On a Metro - return FALSE if later than 10/12/04 */
return (!acpica_check_bios_date(2004, 10, 12));
}
/*
* Process acpi-user-options property if present
*/
static void
acpica_process_user_options()
{
static int processed = 0;
int acpi_user_options;
char *acpi_prop;
/*
* return if acpi-user-options has already been processed
*/
if (processed)
return;
else
processed = 1;
/* converts acpi-user-options from type string to int, if any */
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, ddi_root_node(),
DDI_PROP_DONTPASS, "acpi-user-options", &acpi_prop) ==
DDI_PROP_SUCCESS) {
long data;
int ret;
ret = ddi_strtol(acpi_prop, NULL, 0, &data);
if (ret == 0) {
e_ddi_prop_remove(DDI_DEV_T_NONE, ddi_root_node(),
"acpi-user-options");
e_ddi_prop_update_int(DDI_DEV_T_NONE, ddi_root_node(),
"acpi-user-options", data);
}
ddi_prop_free(acpi_prop);
}
/*
* fetch the optional options property
*/
acpi_user_options = ddi_prop_get_int(DDI_DEV_T_ANY, ddi_root_node(), 0,
"acpi-user-options", 0);
/*
* Note that 'off' has precedence over 'on'
* Also note - all cases of ACPI_OUSER_MASK
* provided here, no default: case is present
*/
switch (acpi_user_options & ACPI_OUSER_MASK) {
case ACPI_OUSER_DFLT:
acpica_enable = acpica_check_bios_date(1999, 1, 1);
break;
case ACPI_OUSER_ON:
acpica_enable = TRUE;
break;
case ACPI_OUSER_OFF:
case ACPI_OUSER_OFF | ACPI_OUSER_ON:
acpica_enable = FALSE;
break;
}
acpi_init_level = ACPI_FULL_INITIALIZATION;
/*
* special test here; may be generalized in the
* future - test for a machines that are known to
* work only in legacy mode, and set OUSER_LEGACY if
* we're on one
*/
if (acpica_metro_old_bios())
acpi_user_options |= ACPI_OUSER_LEGACY;
/*
* If legacy mode is specified, set initialization
* options to avoid entering ACPI mode and hooking SCI
* - basically try to act like legacy acpi_intp
*/
if ((acpi_user_options & ACPI_OUSER_LEGACY) != 0)
acpi_init_level |= (ACPI_NO_ACPI_ENABLE | ACPI_NO_HANDLER_INIT);
/*
* modify default ACPI CA debug output level for non-DEBUG builds
* (to avoid BIOS debug chatter in /var/adm/messages)
*/
if (acpica_muzzle_debug_output)
AcpiDbgLevel = 0;
}
/*
* Initialize the CA subsystem if it hasn't been done already
*/
int
acpica_init()
{
ACPI_STATUS status;
/*
* Make sure user options are processed,
* then fail to initialize if ACPI CA has been
* disabled
*/
acpica_process_user_options();
if (!acpica_enable)
return (AE_ERROR);
mutex_enter(&acpica_module_lock);
if (acpica_init_state == ACPICA_NOT_INITIALIZED) {
if ((status = AcpiLoadTables()) != AE_OK) {
goto error;
}
if ((status = acpica_install_handlers()) != AE_OK) {
goto error;
}
if ((status = AcpiEnableSubsystem(acpi_init_level)) != AE_OK) {
goto error;
}
if ((status = AcpiInitializeObjects(0)) != AE_OK) {
goto error;
}
/*
* Initialize EC
*/
acpica_ec_init();
acpica_init_state = ACPICA_INITIALIZED;
acpica_init_kstats();
error:
if (acpica_init_state != ACPICA_INITIALIZED) {
cmn_err(CE_NOTE, "!failed to initialize"
" ACPI services");
}
} else
status = AE_OK;
/*
* Set acpi-status to 13 if acpica has been initialized successfully.
* This indicates that acpica is up and running. This variable name
* and value were chosen in order to remain compatible with acpi_intp.
*/
e_ddi_prop_update_int(DDI_DEV_T_NONE, ddi_root_node(), "acpi-status",
(status == AE_OK) ? (ACPI_BOOT_INIT | ACPI_BOOT_ENABLE |
ACPI_BOOT_BOOTCONF) : 0);
mutex_exit(&acpica_module_lock);
return (status);
}
/*
* SCI handling
*/
ACPI_STATUS
acpica_get_sci(int *sci_irq, iflag_t *sci_flags)
{
APIC_HEADER *ap;
MULTIPLE_APIC_TABLE *mat;
MADT_INTERRUPT_OVERRIDE *mio;
FADT_DESCRIPTOR *fadt;
int madt_seen, madt_size;
/*
* Make sure user options are processed,
* then return error if ACPI CA has been
* disabled or system is not running in ACPI
* and won't need/understand SCI
*/
acpica_process_user_options();
if ((!acpica_enable) || (acpi_init_level & ACPI_NO_ACPI_ENABLE))
return (AE_ERROR);
/*
* according to Intel ACPI developers, SCI
* conforms to PCI bus conventions; level/low
* unless otherwise directed by overrides.
*/
sci_flags->intr_el = INTR_EL_LEVEL;
sci_flags->intr_po = INTR_PO_ACTIVE_LOW;
sci_flags->bustype = BUS_PCI; /* we *do* conform to PCI */
/* get the SCI from the FADT */
if (AcpiGetFirmwareTable(FADT_SIG, 1, ACPI_LOGICAL_ADDRESSING,
(ACPI_TABLE_HEADER **)&fadt) != AE_OK)
return (AE_ERROR);
*sci_irq = fadt->SciInt;
/* search for ISOs that modify it */
/* if we don't find a MADT, that's OK; no ISOs then */
if (AcpiGetFirmwareTable(APIC_SIG, 1, ACPI_LOGICAL_ADDRESSING,
(ACPI_TABLE_HEADER **) &mat) != AE_OK) {
return (AE_OK);
}
ap = (APIC_HEADER *) (mat + 1);
madt_size = mat->Length;
madt_seen = sizeof (*mat);
while (madt_seen < madt_size) {
switch (ap->Type) {
case APIC_XRUPT_OVERRIDE:
mio = (MADT_INTERRUPT_OVERRIDE *) ap;
if (mio->Source == *sci_irq) {
*sci_irq = mio->Interrupt;
sci_flags->intr_el = mio->TriggerMode;
sci_flags->intr_po = mio->Polarity;
}
break;
}
/* advance to next entry */
madt_seen += ap->Length;
ap = (APIC_HEADER *)(((char *)ap) + ap->Length);
}
/*
* One more check; if ISO said "conform", revert to default
*/
if (sci_flags->intr_el == INTR_EL_CONFORM)
sci_flags->intr_el = INTR_EL_LEVEL;
if (sci_flags->intr_po == INTR_PO_CONFORM)
sci_flags->intr_po = INTR_PO_ACTIVE_LOW;
return (AE_OK);
}
/*
* Sets ACPI wake state for device referenced by dip.
* If level is S0 (0), disables wake event; otherwise,
* enables wake event which will wake system from level.
*/
static int
acpica_ddi_setwake(dev_info_t *dip, int level)
{
ACPI_STATUS status;
ACPI_HANDLE devobj, gpeobj;
ACPI_OBJECT *prw, *gpe;
ACPI_BUFFER prw_buf;
int gpebit, pwr_res_count, prw_level, rv;
/*
* initialize these early so we can use a common
* exit point below
*/
prw_buf.Pointer = NULL;
prw_buf.Length = ACPI_ALLOCATE_BUFFER;
rv = 0;
/*
* Attempt to get a handle to a corresponding ACPI object.
* If no object is found, return quietly, since not all
* devices have corresponding ACPI objects.
*/
status = acpica_get_handle(dip, &devobj);
if (ACPI_FAILURE(status)) {
char pathbuf[MAXPATHLEN];
ddi_pathname(dip, pathbuf);
#ifdef DEBUG
cmn_err(CE_NOTE, "!acpica_ddi_setwake: could not get"
" handle for %s, %s:%d", pathbuf, ddi_driver_name(dip),
ddi_get_instance(dip));
#endif
goto done;
}
/*
* Attempt to evaluate _PRW object.
* If no valid object is found, return quietly, since not all
* devices have _PRW objects.
*/
status = AcpiEvaluateObject(devobj, "_PRW", NULL, &prw_buf);
prw = prw_buf.Pointer;
if (ACPI_FAILURE(status) || prw == NULL ||
prw->Type != ACPI_TYPE_PACKAGE || prw->Package.Count < 2 ||
prw->Package.Elements[1].Type != ACPI_TYPE_INTEGER) {
cmn_err(CE_NOTE, "acpica_ddi_setwake: could not "
" evaluate _PRW");
goto done;
}
/* fetch the lowest wake level from the _PRW */
prw_level = prw->Package.Elements[1].Integer.Value;
/*
* process the GPE description
*/
switch (prw->Package.Elements[0].Type) {
case ACPI_TYPE_INTEGER:
gpeobj = NULL;
gpebit = prw->Package.Elements[0].Integer.Value;
break;
case ACPI_TYPE_PACKAGE:
gpe = &prw->Package.Elements[0];
if (gpe->Package.Count != 2 ||
gpe->Package.Elements[1].Type != ACPI_TYPE_INTEGER)
goto done;
gpeobj = gpe->Package.Elements[0].Reference.Handle;
gpebit = gpe->Package.Elements[1].Integer.Value;
if (gpeobj == NULL)
goto done;
default:
goto done;
}
rv = -1;
if (level == 0) {
if (ACPI_FAILURE(AcpiDisableGpe(gpeobj, gpebit, ACPI_NOT_ISR)))
goto done;
} else if (prw_level <= level) {
if (ACPI_SUCCESS(
AcpiSetGpeType(gpeobj, gpebit, ACPI_GPE_TYPE_WAKE)))
if (ACPI_FAILURE(
AcpiEnableGpe(gpeobj, gpebit, ACPI_NOT_ISR)))
goto done;
}
rv = 0;
done:
if (prw_buf.Pointer != NULL)
AcpiOsFree(prw_buf.Pointer);
return (rv);
}
/*
* kstat access to a limited set of ACPI propertis
*/
static void
acpica_init_kstats()
{
ACPI_HANDLE s3handle;
ACPI_STATUS status;
FADT_DESCRIPTOR *fadt;
kstat_named_t *knp;
/*
* Create a small set of named kstats; just return in the rare
* case of a failure, * in which case, the kstats won't be present.
*/
if ((acpica_ksp = kstat_create("acpi", 0, "acpi", "misc",
KSTAT_TYPE_NAMED, 2, 0)) == NULL)
return;
/*
* initialize kstat 'S3' to reflect the presence of \_S3 in
* the ACPI namespace (1 = present, 0 = not present)
*/
knp = acpica_ksp->ks_data;
knp->value.l = (AcpiGetHandle(NULL, "\\_S3", &s3handle) == AE_OK);
kstat_named_init(knp, "S3", KSTAT_DATA_LONG);
knp++; /* advance to next named kstat */
/*
* initialize kstat 'preferred_pm_profile' to the value
* contained in the (always present) FADT
*/
status = AcpiGetFirmwareTable(FADT_SIG, 1, ACPI_LOGICAL_ADDRESSING,
(ACPI_TABLE_HEADER **)&fadt);
knp->value.l = (status == AE_OK) ? fadt->Prefer_PM_Profile : -1;
kstat_named_init(knp, "preferred_pm_profile", KSTAT_DATA_LONG);
/*
* install the named kstats
*/
kstat_install(acpica_ksp);
}
/*
* Attempt to save the current ACPI settings (_CRS) for the device
* which corresponds to the supplied devinfo node. The settings are
* saved as a property on the dip. If no ACPI object is found to be
* associated with the devinfo node, no action is taken and no error
* is reported.
*/
void
acpica_ddi_save_resources(dev_info_t *dip)
{
ACPI_HANDLE devobj;
ACPI_BUFFER resbuf;
int ret;
resbuf.Length = ACPI_ALLOCATE_BUFFER;
if (ACPI_FAILURE(acpica_get_handle(dip, &devobj)) ||
ACPI_FAILURE(AcpiGetCurrentResources(devobj, &resbuf)))
return;
ret = ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
"acpi-crs", resbuf.Pointer, resbuf.Length);
ASSERT(ret == DDI_PROP_SUCCESS);
AcpiOsFree(resbuf.Pointer);
}
/*
* If the supplied devinfo node has an ACPI settings property attached,
* restore them to the associated ACPI device using _SRS. The property
* is deleted from the devinfo node afterward.
*/
void
acpica_ddi_restore_resources(dev_info_t *dip)
{
ACPI_HANDLE devobj;
ACPI_BUFFER resbuf;
uchar_t *propdata;
uint_t proplen;
if (ACPI_FAILURE(acpica_get_handle(dip, &devobj)))
return;
if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"acpi-crs", &propdata, &proplen) != DDI_PROP_SUCCESS)
return;
resbuf.Pointer = propdata;
resbuf.Length = proplen;
(void) AcpiSetCurrentResources(devobj, &resbuf);
ddi_prop_free(propdata);
(void) ddi_prop_remove(DDI_DEV_T_ANY, dip, "acpi-crs");
}