acpidev_cpu.c revision 571909175b4f9a1ef15ec4afead6d6d463dbe760
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2009, Intel Corporation.
* All rights reserved.
*/
/*
* [Support of X2APIC]
* According to the ACPI Spec, when using the X2APIC interrupt model, logical
* processors with APIC ID values of 255 and greater are required to have a
* Processor Device object and must convey the Processor's APIC information to
* OSPM using the Processor Local X2APIC structure. Logical Processors with APIC
* ID values less than 255 must use the Processor Local XAPIC structure to
* convey their APIC information to OSPM.
*/
#include <sys/types.h>
#include <sys/atomic.h>
#include <sys/bootconf.h>
#include <sys/cpuvar.h>
#include <sys/machsystm.h>
#include <sys/psm_types.h>
#include <sys/x86_archext.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/acpi/acpi.h>
#include <sys/acpica.h>
#include <sys/acpidev.h>
#include <sys/acpidev_impl.h>
struct acpidev_cpu_map_item {
uint32_t proc_id;
uint32_t apic_id;
};
struct acpidev_cpu_MAT_arg {
boolean_t found;
boolean_t enabled;
uint32_t proc_id;
uint32_t apic_id;
};
static ACPI_STATUS acpidev_cpu_pre_probe(acpidev_walk_info_t *infop);
static ACPI_STATUS acpidev_cpu_post_probe(acpidev_walk_info_t *infop);
static ACPI_STATUS acpidev_cpu_probe(acpidev_walk_info_t *infop);
static acpidev_filter_result_t acpidev_cpu_filter(acpidev_walk_info_t *infop,
char *devname, int maxlen);
static ACPI_STATUS acpidev_cpu_init(acpidev_walk_info_t *infop);
static acpidev_filter_result_t acpidev_cpu_filter_func(
acpidev_walk_info_t *infop, ACPI_HANDLE hdl, acpidev_filter_rule_t *afrp,
char *devname, int len);
static int acpidev_cpu_query_dip(cpu_t *, dev_info_t **);
/*
* Default class driver for ACPI processor/CPU objects.
*/
acpidev_class_t acpidev_class_cpu = {
0, /* adc_refcnt */
ACPIDEV_CLASS_REV1, /* adc_version */
ACPIDEV_CLASS_ID_CPU, /* adc_class_id */
"ACPI CPU", /* adc_class_name */
ACPIDEV_TYPE_CPU, /* adc_dev_type */
NULL, /* adc_private */
acpidev_cpu_pre_probe, /* adc_pre_probe */
acpidev_cpu_post_probe, /* adc_post_probe */
acpidev_cpu_probe, /* adc_probe */
acpidev_cpu_filter, /* adc_filter */
acpidev_cpu_init, /* adc_init */
NULL, /* adc_fini */
};
/*
* List of class drivers which will be called in order when handling
* children of ACPI cpu/processor objects.
*/
acpidev_class_list_t *acpidev_class_list_cpu = NULL;
/* Filter rule table for the first probe at boot time. */
static acpidev_filter_rule_t acpidev_cpu_filters[] = {
{ /* Skip all processors under root node, should be there. */
NULL,
0,
ACPIDEV_FILTER_SKIP,
NULL,
1,
1,
NULL,
NULL,
},
{ /* Create and scan other processor objects */
acpidev_cpu_filter_func,
0,
ACPIDEV_FILTER_DEFAULT,
&acpidev_class_list_cpu,
2,
INT_MAX,
NULL,
ACPIDEV_NODE_NAME_CPU,
}
};
/* ACPI/PNP hardware id for processor. */
static char *acpidev_processor_device_ids[] = {
ACPIDEV_HID_CPU,
};
static char *acpidev_cpu_uid_formats[] = {
"SCK%x-CPU%x",
};
static ACPI_HANDLE acpidev_cpu_map_hdl = NULL;
static uint32_t acpidev_cpu_map_count = 0;
static struct acpidev_cpu_map_item *acpidev_cpu_map = NULL;
extern int (*psm_cpu_create_devinfo)(cpu_t *, dev_info_t **);
static int (*psm_cpu_create_devinfo_old)(cpu_t *, dev_info_t **) = NULL;
/* Count how many enabled CPUs are in the MADT table. */
static ACPI_STATUS
acpidev_cpu_count_MADT(ACPI_SUBTABLE_HEADER *ap, void *context)
{
uint32_t *cntp;
ACPI_MADT_LOCAL_APIC *mpa;
ACPI_MADT_LOCAL_X2APIC *mpx2a;
cntp = (uint32_t *)context;
switch (ap->Type) {
case ACPI_MADT_TYPE_LOCAL_APIC:
mpa = (ACPI_MADT_LOCAL_APIC *)ap;
if (mpa->LapicFlags & ACPI_MADT_ENABLED) {
ASSERT(mpa->Id != 255);
(*cntp)++;
}
break;
case ACPI_MADT_TYPE_LOCAL_X2APIC:
mpx2a = (ACPI_MADT_LOCAL_X2APIC *)ap;
/* See comment at beginning about 255 limitation. */
if ((mpx2a->LapicFlags & ACPI_MADT_ENABLED) &&
(mpx2a->LocalApicId >= 255)) {
(*cntp)++;
}
break;
default:
break;
}
return (AE_OK);
}
/* Extract information from the enabled CPUs using the MADT table. */
static ACPI_STATUS
acpidev_cpu_parse_MADT(ACPI_SUBTABLE_HEADER *ap, void *context)
{
uint32_t *cntp;
ACPI_MADT_LOCAL_APIC *mpa;
ACPI_MADT_LOCAL_X2APIC *mpx2a;
cntp = (uint32_t *)context;
switch (ap->Type) {
case ACPI_MADT_TYPE_LOCAL_APIC:
mpa = (ACPI_MADT_LOCAL_APIC *)ap;
if (mpa->LapicFlags & ACPI_MADT_ENABLED) {
ASSERT(mpa->Id != 255);
ASSERT(*cntp < acpidev_cpu_map_count);
acpidev_cpu_map[*cntp].proc_id = mpa->ProcessorId;
acpidev_cpu_map[*cntp].apic_id = mpa->Id;
(*cntp)++;
}
break;
case ACPI_MADT_TYPE_LOCAL_X2APIC:
mpx2a = (ACPI_MADT_LOCAL_X2APIC *)ap;
/* See comment at beginning about 255 limitation. */
if (mpx2a->LocalApicId < 255) {
ACPIDEV_DEBUG(CE_WARN,
"acpidev: encountered CPU with X2APIC Id < 255.");
} else if (mpx2a->LapicFlags & ACPI_MADT_ENABLED) {
ASSERT(*cntp < acpidev_cpu_map_count);
acpidev_cpu_map[*cntp].proc_id = mpx2a->Uid;
acpidev_cpu_map[*cntp].apic_id = mpx2a->LocalApicId;
(*cntp)++;
}
break;
default:
break;
}
return (AE_OK);
}
static ACPI_STATUS
acpidev_cpu_get_apicid(uint32_t procid, uint32_t *apicidp)
{
uint32_t i;
for (i = 0; i < acpidev_cpu_map_count; i++) {
if (acpidev_cpu_map[i].proc_id == procid) {
*apicidp = acpidev_cpu_map[i].apic_id;
return (AE_OK);
}
}
return (AE_NOT_FOUND);
}
/*
* Extract information for enabled CPUs from the buffer returned
* by the _MAT method.
*/
static ACPI_STATUS
acpidev_cpu_query_MAT(ACPI_SUBTABLE_HEADER *ap, void *context)
{
ACPI_MADT_LOCAL_APIC *mpa;
ACPI_MADT_LOCAL_X2APIC *mpx2a;
struct acpidev_cpu_MAT_arg *rp;
rp = (struct acpidev_cpu_MAT_arg *)context;
switch (ap->Type) {
case ACPI_MADT_TYPE_LOCAL_APIC:
mpa = (ACPI_MADT_LOCAL_APIC *)ap;
ASSERT(mpa->Id != 255);
rp->found = B_TRUE;
rp->proc_id = mpa->ProcessorId;
rp->apic_id = mpa->Id;
if (mpa->LapicFlags & ACPI_MADT_ENABLED) {
rp->enabled = B_TRUE;
} else {
rp->enabled = B_FALSE;
}
return (AE_CTRL_TERMINATE);
case ACPI_MADT_TYPE_LOCAL_X2APIC:
mpx2a = (ACPI_MADT_LOCAL_X2APIC *)ap;
if (mpx2a->LocalApicId >= 255) {
rp->found = B_TRUE;
rp->proc_id = mpx2a->Uid;
rp->apic_id = mpx2a->LocalApicId;
if (mpx2a->LapicFlags & ACPI_MADT_ENABLED) {
rp->enabled = B_TRUE;
} else {
rp->enabled = B_FALSE;
}
return (AE_CTRL_TERMINATE);
} else {
ACPIDEV_DEBUG(CE_WARN, "acpidev: encountered CPU "
"with X2APIC Id < 255 in _MAT.");
}
break;
case ACPI_MADT_TYPE_LOCAL_APIC_NMI:
/* UNIMPLEMENTED */
break;
case ACPI_MADT_TYPE_LOCAL_X2APIC_NMI:
/* UNIMPLEMENTED */
break;
default:
/*
* According to the ACPI Spec, the buffer returned by _MAT
* for a processor object should only contain Local APIC,
* Local SAPIC, and local APIC NMI entries.
* x2APIC Specification extends it to support Processor
* x2APIC and x2APIC NMI Structure.
*/
ACPIDEV_DEBUG(CE_NOTE,
"acpidev: unknown APIC entry type %u in _MAT.", ap->Type);
break;
}
return (AE_OK);
}
/*
* Query ACPI processor ID by evaluating ACPI _MAT, _UID, and PROCESSOR
* objects.
*/
static ACPI_STATUS
acpidev_cpu_get_procid(acpidev_walk_info_t *infop, uint32_t *idp)
{
int id;
ACPI_HANDLE hdl;
struct acpidev_cpu_MAT_arg mat;
if (infop->awi_info->Type != ACPI_TYPE_PROCESSOR &&
infop->awi_info->Type != ACPI_TYPE_DEVICE) {
ACPIDEV_DEBUG(CE_WARN,
"acpidev: object %s is not PROCESSOR or DEVICE.",
infop->awi_name);
return (AE_BAD_PARAMETER);
}
hdl = infop->awi_hdl;
/*
* First try to evaluate _MAT.
* According to the ACPI Spec3.0b, it's legal for ACPI PROCESSOR objects
* to have ACPI method objects.
*/
bzero(&mat, sizeof (mat));
(void) acpidev_walk_apic(NULL, hdl, ACPIDEV_METHOD_NAME_MAT,
acpidev_cpu_query_MAT, &mat);
if (mat.found) {
*idp = mat.proc_id;
return (AE_OK);
}
/* Then evalute PROCESSOR object. */
if (infop->awi_info->Type == ACPI_TYPE_PROCESSOR) {
ACPI_BUFFER rb;
rb.Pointer = NULL;
rb.Length = ACPI_ALLOCATE_BUFFER;
if (ACPI_SUCCESS(AcpiEvaluateObjectTyped(hdl, NULL, NULL, &rb,
ACPI_TYPE_PROCESSOR))) {
*idp = ((ACPI_OBJECT *)rb.Pointer)->Processor.ProcId;
AcpiOsFree(rb.Pointer);
return (AE_OK);
} else {
ACPIDEV_DEBUG(CE_WARN,
"acpidev: failed to evaluate ACPI object %s.",
infop->awi_name);
}
}
/*
* Finally, try to evalute the _UID method.
* According to the ACPI Spec3.0b, it's legal for ACPI PROCESSOR objects
* to have ACPI method objects.
* The CPU _UID method should return Processor Id as an integer on x86.
*/
if (ACPI_SUCCESS(acpica_eval_int(hdl, METHOD_NAME__UID, &id))) {
*idp = id;
return (AE_OK);
}
return (AE_NOT_FOUND);
}
static ACPI_STATUS
acpidev_cpu_pre_probe(acpidev_walk_info_t *infop)
{
uint32_t count = 0;
/* Parse and cache APIC info in MADT on the first probe at boot time. */
ASSERT(infop != NULL);
if (infop->awi_op_type == ACPIDEV_OP_BOOT_PROBE &&
acpidev_cpu_map_hdl == NULL) {
(void) acpidev_walk_apic(NULL, NULL, NULL,
acpidev_cpu_count_MADT, &acpidev_cpu_map_count);
acpidev_cpu_map = kmem_zalloc(sizeof (acpidev_cpu_map[0])
* acpidev_cpu_map_count, KM_SLEEP);
(void) acpidev_walk_apic(NULL, NULL, NULL,
acpidev_cpu_parse_MADT, &count);
ASSERT(count == acpidev_cpu_map_count);
acpidev_cpu_map_hdl = infop->awi_hdl;
}
return (AE_OK);
}
static ACPI_STATUS
acpidev_cpu_post_probe(acpidev_walk_info_t *infop)
{
/* Free cached APIC info on the second probe at boot time. */
ASSERT(infop != NULL);
if (infop->awi_op_type == ACPIDEV_OP_BOOT_REPROBE &&
acpidev_cpu_map_hdl != NULL &&
infop->awi_hdl == acpidev_cpu_map_hdl) {
if (acpidev_cpu_map != NULL && acpidev_cpu_map_count != 0) {
kmem_free(acpidev_cpu_map, sizeof (acpidev_cpu_map[0])
* acpidev_cpu_map_count);
}
acpidev_cpu_map = NULL;
acpidev_cpu_map_count = 0;
acpidev_cpu_map_hdl = NULL;
/* replace psm_cpu_create_devinfo with local implementation. */
psm_cpu_create_devinfo_old = psm_cpu_create_devinfo;
psm_cpu_create_devinfo = acpidev_cpu_query_dip;
}
return (AE_OK);
}
static ACPI_STATUS
acpidev_cpu_probe(acpidev_walk_info_t *infop)
{
ACPI_STATUS rc = AE_OK;
int flags;
ASSERT(infop != NULL);
ASSERT(infop->awi_hdl != NULL);
ASSERT(infop->awi_info != NULL);
ASSERT(infop->awi_class_curr == &acpidev_class_cpu);
if (infop->awi_info->Type != ACPI_TYPE_PROCESSOR &&
(infop->awi_info->Type != ACPI_TYPE_DEVICE ||
acpidev_match_device_id(infop->awi_info,
ACPIDEV_ARRAY_PARAM(acpidev_processor_device_ids)) == 0)) {
return (AE_OK);
}
/*
* Mark device as offline. It will be changed to online state
* when the corresponding CPU starts up.
*/
if (infop->awi_op_type == ACPIDEV_OP_BOOT_PROBE) {
flags = ACPIDEV_PROCESS_FLAG_SCAN |
ACPIDEV_PROCESS_FLAG_CREATE |
ACPIDEV_PROCESS_FLAG_OFFLINE;
rc = acpidev_process_object(infop, flags);
} else if (infop->awi_op_type == ACPIDEV_OP_BOOT_REPROBE) {
flags = ACPIDEV_PROCESS_FLAG_SCAN;
rc = acpidev_process_object(infop, flags);
} else if (infop->awi_op_type == ACPIDEV_OP_HOTPLUG_PROBE) {
flags = ACPIDEV_PROCESS_FLAG_SCAN |
ACPIDEV_PROCESS_FLAG_CREATE |
ACPIDEV_PROCESS_FLAG_OFFLINE;
rc = acpidev_process_object(infop, flags);
} else {
ACPIDEV_DEBUG(CE_WARN, "acpidev: unknown operation type %u in "
"acpidev_cpu_probe().", infop->awi_op_type);
rc = AE_BAD_PARAMETER;
}
if (ACPI_FAILURE(rc) && rc != AE_NOT_EXIST && rc != AE_ALREADY_EXISTS) {
cmn_err(CE_WARN,
"!acpidev: failed to process processor object %s.",
infop->awi_name);
} else {
rc = AE_OK;
}
return (rc);
}
static acpidev_filter_result_t
acpidev_cpu_filter_func(acpidev_walk_info_t *infop, ACPI_HANDLE hdl,
acpidev_filter_rule_t *afrp, char *devname, int len)
{
acpidev_filter_result_t res;
ASSERT(afrp != NULL);
if (infop->awi_op_type == ACPIDEV_OP_BOOT_PROBE ||
infop->awi_op_type == ACPIDEV_OP_BOOT_REPROBE) {
uint32_t procid;
uint32_t apicid;
if (acpidev_cpu_get_procid(infop, &procid) != 0) {
ACPIDEV_DEBUG(CE_WARN,
"acpidev: failed to query processor id for %s.",
infop->awi_name);
return (ACPIDEV_FILTER_SKIP);
} else if (acpidev_cpu_get_apicid(procid, &apicid) != 0) {
ACPIDEV_DEBUG(CE_WARN,
"acpidev: failed to query apic id for %s.",
infop->awi_name);
return (ACPIDEV_FILTER_SKIP);
}
infop->awi_scratchpad[0] = procid;
infop->awi_scratchpad[1] = apicid;
} else if (infop->awi_op_type == ACPIDEV_OP_HOTPLUG_PROBE) {
struct acpidev_cpu_MAT_arg mat;
bzero(&mat, sizeof (mat));
(void) acpidev_walk_apic(NULL, hdl, ACPIDEV_METHOD_NAME_MAT,
acpidev_cpu_query_MAT, &mat);
if (!mat.found) {
cmn_err(CE_WARN,
"!acpidev: failed to walk apic resource for %s.",
infop->awi_name);
return (ACPIDEV_FILTER_SKIP);
} else if (!mat.enabled) {
ACPIDEV_DEBUG(CE_NOTE,
"acpidev: CPU %s has been disabled.",
infop->awi_name);
return (ACPIDEV_FILTER_SKIP);
}
/* Save processor id and APIC id in scratchpad memory. */
infop->awi_scratchpad[0] = mat.proc_id;
infop->awi_scratchpad[1] = mat.apic_id;
}
res = acpidev_filter_default(infop, hdl, afrp, devname, len);
return (res);
}
static acpidev_filter_result_t
acpidev_cpu_filter(acpidev_walk_info_t *infop, char *devname, int maxlen)
{
acpidev_filter_result_t res;
ASSERT(infop != NULL);
ASSERT(devname == NULL || maxlen >= ACPIDEV_MAX_NAMELEN);
if (infop->awi_op_type == ACPIDEV_OP_BOOT_PROBE ||
infop->awi_op_type == ACPIDEV_OP_BOOT_REPROBE ||
infop->awi_op_type == ACPIDEV_OP_HOTPLUG_PROBE) {
res = acpidev_filter_device(infop, infop->awi_hdl,
ACPIDEV_ARRAY_PARAM(acpidev_cpu_filters), devname, maxlen);
} else {
res = ACPIDEV_FILTER_FAILED;
}
return (res);
}
static ACPI_STATUS
acpidev_cpu_init(acpidev_walk_info_t *infop)
{
int count;
dev_info_t *dip;
ACPI_HANDLE hdl;
char unitaddr[64];
char **compatpp;
static char *compatible[] = {
ACPIDEV_HID_PROCESSOR,
ACPIDEV_TYPE_CPU,
"cpu"
};
ASSERT(infop != NULL);
dip = infop->awi_dip;
hdl = infop->awi_hdl;
/* Create "apic_id" and "processor_id" properties. */
if (ndi_prop_update_int(DDI_DEV_T_NONE, dip,
ACPIDEV_PROP_NAME_PROCESSOR_ID, infop->awi_scratchpad[0]) !=
NDI_SUCCESS) {
cmn_err(CE_WARN,
"!acpidev: failed to set processor_id property for %s.",
infop->awi_name);
return (AE_ERROR);
}
if (ndi_prop_update_int(DDI_DEV_T_NONE, dip,
ACPIDEV_PROP_NAME_LOCALAPIC_ID, infop->awi_scratchpad[1]) !=
NDI_SUCCESS) {
cmn_err(CE_WARN,
"!acpidev: failed to set apic_id property for %s.",
infop->awi_name);
return (AE_ERROR);
}
/* Set "compatible" property for CPU dip */
count = sizeof (compatible) / sizeof (compatible[0]);
if (infop->awi_info->Type == ACPI_TYPE_PROCESSOR) {
compatpp = compatible;
} else if (infop->awi_info->Type == ACPI_TYPE_DEVICE) {
/*
* skip first item for pseudo processor HID.
* acpidev_set_compatible() will handle HID/CID for CPU device.
*/
compatpp = &compatible[1];
count--;
} else {
return (AE_BAD_PARAMETER);
}
if (ACPI_FAILURE(acpidev_set_compatible(infop, compatpp, count))) {
return (AE_ERROR);
}
/*
* Set device unit-address property.
* First try to generate meaningful unit address from _UID,
* then use Processor Id if that fails.
*/
if ((infop->awi_info->Valid & ACPI_VALID_UID) == 0 ||
acpidev_generate_unitaddr(infop->awi_info->UniqueId.String,
ACPIDEV_ARRAY_PARAM(acpidev_cpu_uid_formats),
unitaddr, sizeof (unitaddr)) == NULL) {
(void) snprintf(unitaddr, sizeof (unitaddr), "%u",
(uint32_t)infop->awi_scratchpad[0]);
}
if (ACPI_FAILURE(acpidev_set_unitaddr(infop, NULL, 0, unitaddr))) {
return (AE_ERROR);
}
/*
* Build binding information for CPUs.
*/
if (infop->awi_op_type == ACPIDEV_OP_BOOT_PROBE ||
infop->awi_op_type == ACPIDEV_OP_BOOT_REPROBE ||
infop->awi_op_type == ACPIDEV_OP_HOTPLUG_PROBE) {
if (ACPI_FAILURE(acpica_add_processor_to_map(
infop->awi_scratchpad[0], hdl, infop->awi_scratchpad[1]))) {
cmn_err(CE_WARN, "!acpidev: failed to bind processor "
"id/object handle for %s.", infop->awi_name);
return (AE_ERROR);
}
} else {
ACPIDEV_DEBUG(CE_WARN,
"acpidev: unknown operation type %u in acpidev_cpu_init.",
infop->awi_op_type);
return (AE_BAD_PARAMETER);
}
return (AE_OK);
}
static int
acpidev_cpu_query_dip(cpu_t *cp, dev_info_t **dipp)
{
uint32_t apicid;
ACPI_HANDLE hdl;
dev_info_t *dip = NULL;
*dipp = NULL;
/*
* Try to get the dip associated with the CPU if ACPI_DEVCFG_CPU is
* enabled.
*/
if (acpica_get_devcfg_feature(ACPI_DEVCFG_CPU)) {
apicid = cpuid_get_apicid(cp);
if (acpica_get_cpu_object_by_cpuid(cp->cpu_id, &hdl) == 0 ||
(apicid != UINT32_MAX &&
acpica_get_cpu_object_by_apicid(apicid, &hdl) == 0)) {
ASSERT(hdl != NULL);
if (ACPI_SUCCESS(acpica_get_devinfo(hdl, &dip))) {
ASSERT(dip != NULL);
ndi_hold_devi(dip);
*dipp = dip;
return (PSM_SUCCESS);
}
}
}
ACPIDEV_DEBUG(CE_WARN, "acpidev: failed to get dip for cpu %d(%p).",
cp->cpu_id, (void *)cp);
if (psm_cpu_create_devinfo_old != NULL) {
return (psm_cpu_create_devinfo_old(cp, dipp));
} else {
return (PSM_FAILURE);
}
}