--- /dev/null Fri Jun 26 08:09:29 2009 +++ new/usr/src/uts/i86pc/io/acpi/acpidev/acpidev_cpu.c Fri Jun 26 08:09:29 2009 @@ -0,0 +1,654 @@ +/* + * 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 object. + */ +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 head of class drivers which will be called in order when handling + * children of ACPI cpu/processor object. + */ +acpidev_class_list_t *acpidev_class_list_cpu = NULL; + +/* Filter rule table for first probe at boot time. */ +static acpidev_filter_rule_t acpidev_cpu_filters[] = { + { /* Skip all processor 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 in 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 of enabled CPUs from 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: encounter 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 of enabled CPUs from buffer returned by _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: encounter 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 ACPI Spec, buffer return by _MAT under + * processor object should only contain Local APIC, + * Local SAPIC, 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, PROCESSOR object. */ +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 ACPI Spec3.0b, it's legal for ACPI PROCESSOR object + * 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); + } + } + + /* + * Last try to evalute _UID method. + * According to ACPI Spec3.0b, it's legal for ACPI PROCESSOR object + * to have ACPI method objects. + * CPU _UID method should return Processor Id as 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 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; + + /* 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_post_probe(acpidev_walk_info_t *infop) +{ + /* Free cached APIC info on 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; + } + + 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 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 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_CONT, + "?acpidev: failed to process processor object %s.\n", + 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 *compatibles[] = { + 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_CONT, + "?acpidev: failed to set processor id property for %s.\n", + 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_CONT, + "?acpidev: failed to set apic id property for %s.\n", + infop->awi_name); + return (AE_ERROR); + } + + /* Set "compatibles" property for CPU dip */ + count = sizeof (compatibles) / sizeof (compatibles[0]); + if (infop->awi_info->Type == ACPI_TYPE_PROCESSOR) { + compatpp = compatibles; + } else if (infop->awi_info->Type == ACPI_TYPE_DEVICE) { + /* + * skip first item for pseudo processor HID. + * acpidev_set_compatibles() will handle HID/CID for CPU device. + */ + compatpp = &compatibles[1]; + count--; + } else { + return (AE_BAD_PARAMETER); + } + if (ACPI_FAILURE(acpidev_set_compatibles(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 fails. + */ + if ((infop->awi_info->Valid & ACPI_VALID_UID) == 0 || + acpidev_generate_unitaddr(infop->awi_info->UniqueId.Value, + 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_CONT, "?acpidev: binding proc id/object " + "handle for %s failed.\n", + infop->awi_name); + return (AE_ERROR); + } + } else { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: unknown operation type %u in 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 dip for 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); + } +}