--- /dev/null Fri Jun 26 08:09:31 2009 +++ new/usr/src/uts/i86pc/io/acpi/acpidev/acpidev_drv.c Fri Jun 26 08:09:30 2009 @@ -0,0 +1,1101 @@ +/* + * Copyright (c) 2009, Intel Corporation. + * All rights reserved. + */ + +/* + * Platform specific device enumerator for ACPI specific device. + * By x86 system device, it refers to the suite of hardware components which are + * common to x86 platform and play important roles in the system architecture + * but can't be enumerated/discovered through industry standard bus + * specifications. Examples of these x86 system devices include: + * * Logical processor/CPU + * * Memory device + * * Non-PCI discoveralbe IOMMU or DMA Remapping Engine + * * Non-PCI discoverable IOxAPIC + * * Non-PCI discoverable HPET(High Precision Event Timer) + * * ACPI defined devices, including power button, sleep button, battery etc. + * + * X86 system devices may be discovered through BIOS/Firmware interfaces, such + * as SMBIOS table, MPS table and ACPI table etc, though they couldn't be + * enumerated through industry standard bus specification. + * + * To aid Solaris flexibly manage x86 system devices, we are trying to organize + * x86 system devices into a specific firmware device subtree which is used to + * host all system devices and currently named as '/devices/fw'. + * + * This driver is aimed to populate the firmware device subtree with ACPI + * discoverable system devices if possible. To achieve that, ACPI object + * namespace is abstracted as ACPI virtual buses hosting system devices. + * Another nexus driver is developed for ACPI virtual bus to manage all devices + * connected to it. + * + * For detail information, please refer to PSARC/2009/104. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Patchable through /etc/system */ +int acpidev_options = 0; +#ifdef DEBUG +int acpidev_debug = 1; +#else +int acpidev_debug = 0; +#endif + +acpidev_class_list_t *acpidev_class_list_root = NULL; + +/* ACPI device autoconfig global status */ +typedef enum acpidev_status { + ACPIDEV_STATUS_FAILED = -2, /* ACPI device autoconfig failed */ + ACPIDEV_STATUS_DISABLED = -1, /* ACPI device autoconfig disabled */ + ACPIDEV_STATUS_UNKNOWN = 0, /* initial status */ + ACPIDEV_STATUS_INITIALIZED, /* ACPI deivce autoconfig initialized */ + ACPIDEV_STATUS_FIRST_PASS, /* first probing finished */ + ACPIDEV_STATUS_READY /* second probing finished */ +} acpidev_status_t; + +static acpidev_status_t acpidev_status = ACPIDEV_STATUS_UNKNOWN; +static kmutex_t acpidev_drv_lock; +static krwlock_t acpidev_class_lock; +static dev_info_t *acpidev_root_dip = NULL; +static ulong_t acpidev_object_type_mask[BT_BITOUL(ACPI_TYPE_NS_NODE_MAX + 1)]; + +/* Boot time ACPI device enumerator. */ +static void acpidev_boot_probe(int type); + +/* DDI module auto configuration interface */ +extern struct mod_ops mod_miscops; + +static struct modlmisc modlmisc = { + &mod_miscops, + "ACPI device enumerator" +}; + +static struct modlinkage modlinkage = { + MODREV_1, + (void *)&modlmisc, + NULL +}; + +int +_init(void) +{ + int err; + + if ((err = mod_install(&modlinkage)) == 0) { + mutex_init(&acpidev_drv_lock, NULL, MUTEX_DRIVER, NULL); + rw_init(&acpidev_class_lock, NULL, RW_DEFAULT, NULL); + impl_bus_add_probe(acpidev_boot_probe); + } else { + cmn_err(CE_WARN, "acpidev: failed to install driver."); + } + + return (err); +} + +int +_fini(void) +{ + /* No support for module unload. */ + return (EBUSY); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +/* Check blacklists and load platform specific driver modules. */ +static ACPI_STATUS +acpidev_load_plat_modules(void) +{ + return (AE_OK); +} + +/* Unload platform specific driver modules. */ +static void +acpidev_unload_plat_modules(void) +{ +} + +/* Unregister all device class drivers from device driver lists. */ +static void +acpidev_class_list_fini(void) +{ + acpidev_unload_plat_modules(); + + if ((acpidev_options & ACPIDEV_OUSER_NO_MEM) == 0) { + (void) acpidev_unregister_class(&acpidev_class_list_device, + &acpidev_class_memory); + } + + if (acpidev_options & ACPIDEV_OUSER_NO_CPU) { + (void) acpidev_unregister_class(&acpidev_class_list_device, + &acpidev_class_cpu); + (void) acpidev_unregister_class(&acpidev_class_list_scope, + &acpidev_class_cpu); + (void) acpidev_unregister_class(&acpidev_class_list_root, + &acpidev_class_cpu); + } + + if ((acpidev_options & ACPIDEV_OUSER_NO_CONTAINER) == 0) { + (void) acpidev_unregister_class(&acpidev_class_list_device, + &acpidev_class_container); + } + + (void) acpidev_unregister_class(&acpidev_class_list_device, + &acpidev_class_device); + (void) acpidev_unregister_class(&acpidev_class_list_root, + &acpidev_class_device); + + (void) acpidev_unregister_class(&acpidev_class_list_root, + &acpidev_class_scope); +} + +/* Register all device class drivers onto driver lists. */ +static ACPI_STATUS +acpidev_class_list_init(uint64_t *fp) +{ + ACPI_STATUS rc = AE_OK; + + /* Set bit in mask for supported object types. */ + BT_SET(acpidev_object_type_mask, ACPI_TYPE_LOCAL_SCOPE); + BT_SET(acpidev_object_type_mask, ACPI_TYPE_DEVICE); + + /* + * Register ACPI scope class driver onto class driver lists. + * Currently only ACPI scope objects under ACPI root node, such as _PR, + * _SB, _TZ etc, need to be handled, so only register scope class + * driver onto root list. + */ + if (ACPI_FAILURE(acpidev_register_class(&acpidev_class_list_root, + &acpidev_class_scope, B_FALSE))) { + goto error_out; + } + + /* + * Register ACPI device class driver onto class dirver lists. + * ACPI device class driver should be reigstered at tail to handle all + * device objects which haven't handled by other HID/CID specific + * device class driver. + */ + if (ACPI_FAILURE(acpidev_register_class(&acpidev_class_list_root, + &acpidev_class_device, B_TRUE))) { + goto error_root_device; + } + if (ACPI_FAILURE(acpidev_register_class(&acpidev_class_list_device, + &acpidev_class_device, B_TRUE))) { + goto error_device_device; + } + + /* Check and register support for ACPI container device. */ + if ((acpidev_options & ACPIDEV_OUSER_NO_CONTAINER) == 0) { + if (ACPI_FAILURE(acpidev_register_class( + &acpidev_class_list_device, &acpidev_class_container, + B_FALSE))) { + goto error_device_container; + } + *fp |= ACPI_DEVCFG_CONTAINER; + } + + /* Check and register support for ACPI CPU device. */ + if ((acpidev_options & ACPIDEV_OUSER_NO_CPU) == 0) { + /* Handle ACPI CPU Device */ + if (ACPI_FAILURE(acpidev_register_class( + &acpidev_class_list_device, &acpidev_class_cpu, B_FALSE))) { + goto error_device_cpu; + } + /* Handle ACPI Processor under _PR */ + if (ACPI_FAILURE(acpidev_register_class( + &acpidev_class_list_scope, &acpidev_class_cpu, B_FALSE))) { + goto error_scope_cpu; + } + /* House-keeping for CPU scan */ + if (ACPI_FAILURE(acpidev_register_class( + &acpidev_class_list_root, &acpidev_class_cpu, B_FALSE))) { + goto error_root_cpu; + } + BT_SET(acpidev_object_type_mask, ACPI_TYPE_PROCESSOR); + *fp |= ACPI_DEVCFG_CPU; + } + + /* Check support for ACPI memory device. */ + if ((acpidev_options & ACPIDEV_OUSER_NO_MEM) == 0) { + /* + * Register ACPI memory class driver onto + * acpidev_class_list_device because ACPI module class driver + * uses that list. + */ + if (ACPI_FAILURE(acpidev_register_class( + &acpidev_class_list_device, &acpidev_class_memory, + B_FALSE))) { + goto error_device_memory; + } + *fp |= ACPI_DEVCFG_MEMORY; + } + + /* Check blacklist and load platform specific modules. */ + rc = acpidev_load_plat_modules(); + if (ACPI_FAILURE(rc)) { + ACPIDEV_DEBUG(CE_WARN, "acpidev: failed to check blacklist " + "or load pratform modules."); + goto error_plat; + } + + return (AE_OK); + +error_plat: + if ((acpidev_options & ACPIDEV_OUSER_NO_MEM) == 0) { + (void) acpidev_unregister_class(&acpidev_class_list_device, + &acpidev_class_memory); + } +error_device_memory: + if (acpidev_options & ACPIDEV_OUSER_NO_CPU) { + (void) acpidev_unregister_class(&acpidev_class_list_root, + &acpidev_class_cpu); + } +error_root_cpu: + if (acpidev_options & ACPIDEV_OUSER_NO_CPU) { + (void) acpidev_unregister_class(&acpidev_class_list_scope, + &acpidev_class_cpu); + } +error_scope_cpu: + if (acpidev_options & ACPIDEV_OUSER_NO_CPU) { + (void) acpidev_unregister_class(&acpidev_class_list_device, + &acpidev_class_cpu); + } +error_device_cpu: + if ((acpidev_options & ACPIDEV_OUSER_NO_CONTAINER) == 0) { + (void) acpidev_unregister_class(&acpidev_class_list_device, + &acpidev_class_container); + } +error_device_container: + (void) acpidev_unregister_class(&acpidev_class_list_device, + &acpidev_class_device); +error_device_device: + (void) acpidev_unregister_class(&acpidev_class_list_root, + &acpidev_class_device); +error_root_device: + (void) acpidev_unregister_class(&acpidev_class_list_root, + &acpidev_class_scope); +error_out: + ACPIDEV_DEBUG(CE_WARN, + "acpidev: failed to register built-in class drivers."); + *fp = 0; + + return (AE_ERROR); +} + +/* Called in single thread context during boot, no protection for reentrance. */ +static int +acpidev_create_root_node(void) +{ + int circ, rv = AE_OK; + dev_info_t *dip = NULL; + acpidev_data_handle_t objhdl; + char *compatibles[] = { + ACPIDEV_HID_ROOTNEX, + ACPIDEV_TYPE_ROOTNEX, + ACPIDEV_HID_VIRTNEX, + ACPIDEV_TYPE_VIRTNEX, + }; + + ndi_devi_enter(ddi_root_node(), &circ); + ASSERT(acpidev_root_dip == NULL); + + /* Query whether device node already exists. */ + dip = ddi_find_devinfo(ACPIDEV_NODE_NAME_ROOT, -1, 0); + if (dip != NULL && ddi_get_parent(dip) == ddi_root_node()) { + ndi_devi_exit(ddi_root_node(), circ); + cmn_err(CE_WARN, + "acpidev: node /devices/%s already exists, disable driver.", + ACPIDEV_NODE_NAME_ROOT); + return (AE_ALREADY_EXISTS); + } + + /* Create device node if doesn't exist. */ + rv = ndi_devi_alloc(ddi_root_node(), ACPIDEV_NODE_NAME_ROOT, + (pnode_t)DEVI_SID_NODEID, &dip); + if (rv != NDI_SUCCESS) { + ndi_devi_exit(ddi_root_node(), circ); + ACPIDEV_DEBUG(CE_WARN, + "acpidev: failed to create node for %s with errcode %d.", + ACPIDEV_OBJECT_NAME_SB, rv); + return (AE_ERROR); + } + + /* Build cross reference between dip and ACPI object. */ + if (ACPI_FAILURE(acpica_tag_devinfo(dip, ACPI_ROOT_OBJECT))) { + (void) ddi_remove_child(dip, 0); + ndi_devi_exit(ddi_root_node(), circ); + ACPIDEV_DEBUG(CE_WARN, "acpidev: failed to tag object %s.", + ACPIDEV_OBJECT_NAME_SB); + return (AE_ERROR); + } + + /* Set device properties. */ + rv = ndi_prop_update_string_array(DDI_DEV_T_NONE, dip, + OBP_COMPATIBLE, ACPIDEV_ARRAY_PARAM(compatibles)); + if (rv == NDI_SUCCESS) { + rv = ndi_prop_update_string(DDI_DEV_T_NONE, dip, + OBP_DEVICETYPE, ACPIDEV_TYPE_ROOTNEX); + } + if (rv != DDI_SUCCESS) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: failed to set device property for /devices/%s.", + ACPIDEV_NODE_NAME_ROOT); + goto error_out; + } + + /* Manually create object handle for root node */ + objhdl = acpidev_data_create_handle(ACPI_ROOT_OBJECT); + if (objhdl == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: failed to create object handle for root."); + goto error_out; + } + objhdl->aod_level = 0; + objhdl->aod_hdl = ACPI_ROOT_OBJECT; + objhdl->aod_dip = dip; + objhdl->aod_class = &acpidev_class_scope; + objhdl->aod_status = acpidev_query_device_status(ACPI_ROOT_OBJECT); + objhdl->aod_iflag = ACPIDEV_ODF_STATUS_VALID | + ACPIDEV_ODF_DEVINFO_CREATED | ACPIDEV_ODF_DEVINFO_TAGGED; + + /* Bind device driver. */ + (void) ndi_devi_bind_driver(dip, 0); + + acpidev_root_dip = dip; + ndi_devi_exit(ddi_root_node(), circ); + + return (AE_OK); + +error_out: + (void) acpica_untag_devinfo(dip, ACPI_ROOT_OBJECT); + (void) ddi_remove_child(dip, 0); + ndi_devi_exit(ddi_root_node(), circ); + return (AE_ERROR); +} + +static void +acpidev_initialize(void) +{ + int rc; + char *str = NULL; + uint64_t features = 0; + + /* Check whether it has already been initialized. */ + if (acpidev_status != ACPIDEV_STATUS_UNKNOWN) { + ACPIDEV_DEBUG(CE_NOTE, + "acpidev: initialization called more than once."); + return; + } + + /* Check whether ACPI device autoconfig has been disabled by user. */ + rc = ddi_prop_lookup_string(DDI_DEV_T_ANY, ddi_root_node(), + DDI_PROP_DONTPASS, "acpidev-autoconfig", &str); + if (rc == DDI_SUCCESS) { + if (strcasecmp(str, "off") == 0 || strcasecmp(str, "no") == 0) { + cmn_err(CE_CONT, "?acpidev: ACPI device autoconfig " + "disabled by user.\n"); + ddi_prop_free(str); + acpidev_status = ACPIDEV_STATUS_DISABLED; + return; + } + ddi_prop_free(str); + } + + /* Initialize acpica subsystem. */ + if (ACPI_FAILURE(acpica_init())) { + cmn_err(CE_CONT, + "?acpidev: failed to initialize acpica subsystem.\n"); + acpidev_status = ACPIDEV_STATUS_FAILED; + return; + } + + /* Check ACPICA subsystem status. */ + if (!acpica_get_core_feature(ACPI_FEATURE_FULL_INIT)) { + cmn_err(CE_CONT, + "?acpidev: ACPI device autoconfig has been disabled " + "because ACPICA hasn't been fully initalized.\n"); + acpidev_status = ACPIDEV_STATUS_DISABLED; + return; + } + + /* Converts acpidev-options from type string to int, if any */ + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, ddi_root_node(), + DDI_PROP_DONTPASS, "acpidev-options", &str) == DDI_PROP_SUCCESS) { + long data; + rc = ddi_strtol(str, NULL, 0, &data); + if (rc == 0) { + (void) e_ddi_prop_remove(DDI_DEV_T_NONE, + ddi_root_node(), "acpidev-options"); + (void) e_ddi_prop_update_int(DDI_DEV_T_NONE, + ddi_root_node(), "acpidev-options", data); + } + ddi_prop_free(str); + } + /* Get acpidev_options user options. */ + acpidev_options = ddi_prop_get_int(DDI_DEV_T_ANY, ddi_root_node(), + DDI_PROP_DONTPASS, "acpidev-options", acpidev_options); + + /* Load all embbeded device class drivers. */ + if (ACPI_FAILURE(acpidev_class_list_init(&features))) { + ACPIDEV_DEBUG(CE_NOTE, + "acpidev: failed to initalize class driver lists."); + acpidev_status = ACPIDEV_STATUS_FAILED; + return; + } + + /* Create root node for ACPI/firmware device subtree. */ + if (ACPI_FAILURE(acpidev_create_root_node())) { + cmn_err(CE_CONT, "?acpidev: failed to create root node " + "for acpi device tree.\n"); + acpidev_class_list_fini(); + acpidev_status = ACPIDEV_STATUS_FAILED; + return; + } + + /* Notify acpica to enable ACPI device auto configuration. */ + acpica_set_core_feature(ACPI_FEATURE_DEVCFG); + acpica_set_devcfg_feature(features); + + ACPIDEV_DEBUG(CE_NOTE, "ACPI device autoconfig initialized."); + acpidev_status = ACPIDEV_STATUS_INITIALIZED; +} + +/* + * Probe devices in ACPI namespace which can't be enumerated by other methods + * at boot time. + */ +static ACPI_STATUS +acpidev_boot_probe_device(acpidev_op_type_t op_type) +{ + ACPI_STATUS rc = AE_OK; + acpidev_walk_info_t *infop; + + ASSERT(acpidev_root_dip != NULL); + ASSERT(op_type == ACPIDEV_OP_BOOT_PROBE || + op_type == ACPIDEV_OP_BOOT_REPROBE); + + /* Enumerate ACPI devices. */ + infop = acpidev_alloc_walk_info(op_type, 0, ACPI_ROOT_OBJECT, + &acpidev_class_list_root, NULL); + if (infop == NULL) { + ACPIDEV_DEBUG(CE_NOTE, "acpidev: failed to allocate walk info " + "object in boot_probe_device()."); + return (AE_ERROR); + } + rc = acpidev_probe_child(infop); + if (ACPI_FAILURE(rc)) { + cmn_err(CE_CONT, "?acpidev: failed to probe child object " + "under ACPI root node."); + } + acpidev_free_walk_info(infop); + + return (rc); +} + +/* + * Platform specific device prober for ACPI virtual bus. + * It will be called in single-thread environment to enumerate devices in + * ACPI namespace at boot time. + */ +static void +acpidev_boot_probe(int type) +{ + ACPI_STATUS rc; + + /* Initialize subsystem on first pass. */ + mutex_enter(&acpidev_drv_lock); + if (type == 0) { + acpidev_initialize(); + if (acpidev_status != ACPIDEV_STATUS_INITIALIZED) { + cmn_err(CE_WARN, "acpidev: driver disabled due to " + "initalization failure."); + } + } + + /* Probe ACPI devices */ + if (type == 0 && acpidev_status == ACPIDEV_STATUS_INITIALIZED) { + rc = acpidev_boot_probe_device(ACPIDEV_OP_BOOT_PROBE); + if (ACPI_SUCCESS(rc)) { + acpidev_status = ACPIDEV_STATUS_FIRST_PASS; + } else { + ACPIDEV_DEBUG(CE_WARN, "acpidev: failed to probe ACPI " + "devices during boot."); + acpidev_status = ACPIDEV_STATUS_FAILED; + } + } else if (type != 0 && acpidev_status == ACPIDEV_STATUS_FIRST_PASS) { + rc = acpidev_boot_probe_device(ACPIDEV_OP_BOOT_REPROBE); + if (ACPI_SUCCESS(rc)) { + acpidev_status = ACPIDEV_STATUS_READY; + } else { + ACPIDEV_DEBUG(CE_WARN, "acpidev: failed to reprobe " + "ACPI devices during boot."); + acpidev_status = ACPIDEV_STATUS_FAILED; + } + } else if (acpidev_status != ACPIDEV_STATUS_FAILED && + acpidev_status != ACPIDEV_STATUS_DISABLED && + acpidev_status != ACPIDEV_STATUS_READY) { + cmn_err(CE_WARN, + "acpidev: invalid ACPI device autoconfig global status."); + } + mutex_exit(&acpidev_drv_lock); +} + +ACPI_STATUS +acpidev_probe_child(acpidev_walk_info_t *infop) +{ + int circ; + dev_info_t *pdip; + ACPI_STATUS res, rc = AE_OK; + ACPI_HANDLE child; + ACPI_OBJECT_TYPE type; + acpidev_class_list_t *it; + acpidev_walk_info_t *cinfop; + acpidev_data_handle_t datap; + + /* Validate parameter first. */ + ASSERT(infop != NULL); + if (infop == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: infop is NULL in probe_child()."); + return (AE_BAD_PARAMETER); + } + ASSERT(infop->awi_level < ACPIDEV_MAX_ENUM_LEVELS - 1); + if (infop->awi_level >= ACPIDEV_MAX_ENUM_LEVELS - 1) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: recursive level is too deep in probe_child()."); + return (AE_BAD_PARAMETER); + } + ASSERT(infop->awi_class_list != NULL); + ASSERT(infop->awi_hdl != NULL); + ASSERT(infop->awi_info != NULL); + ASSERT(infop->awi_name != NULL); + ASSERT(infop->awi_data != NULL); + if (infop->awi_class_list == NULL || infop->awi_hdl == NULL || + infop->awi_info == NULL || infop->awi_name == NULL || + infop->awi_data == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: infop has NULL fields in probe_child()."); + return (AE_BAD_PARAMETER); + } + pdip = acpidev_walk_info_get_pdip(infop); + if (pdip == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: pdip is NULL in probe_child()."); + return (AE_BAD_PARAMETER); + } + + ndi_devi_enter(pdip, &circ); + rw_enter(&acpidev_class_lock, RW_READER); + + /* Call pre-probe callback functions. */ + for (it = *(infop->awi_class_list); it != NULL; it = it->acl_next) { + if (it->acl_class->adc_pre_probe == NULL) { + continue; + } + infop->awi_class_curr = it->acl_class; + if (ACPI_FAILURE(it->acl_class->adc_pre_probe(infop))) { + ACPIDEV_DEBUG(CE_NOTE, "acpidev: failed to pre probe " + "device of type %s under %s.", + it->acl_class->adc_class_name, infop->awi_name); + } + } + + /* Walk child objects. */ + child = NULL; + while (ACPI_SUCCESS(AcpiGetNextObject(ACPI_TYPE_ANY, + infop->awi_hdl, child, &child))) { + /* Skip object if not interested at. */ + if (ACPI_FAILURE(AcpiGetType(child, &type)) || + type > ACPI_TYPE_NS_NODE_MAX || + BT_TEST(acpidev_object_type_mask, type) == 0) { + continue; + } + + /* Allocate walk info structure. */ + cinfop = acpidev_alloc_walk_info(infop->awi_op_type, + infop->awi_level + 1, child, NULL, infop); + if (cinfop == NULL) { + ACPIDEV_DEBUG(CE_NOTE, "acpidev: failed to allocate " + "walk info child object of %s.", + infop->awi_name); + /* Mark error and continue to handle next child. */ + rc = AE_ERROR; + continue; + } + + /* + * Remember class list used to handle this object. + * It should be the same list for different pass of scans. + */ + ASSERT(cinfop->awi_data != NULL); + datap = cinfop->awi_data; + if (cinfop->awi_op_type == ACPIDEV_OP_BOOT_PROBE) { + datap->aod_class_list = infop->awi_class_list; + } else if (datap->aod_class_list != infop->awi_class_list) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: class list for %s has been changed", + infop->awi_name); + acpidev_free_walk_info(cinfop); + continue; + } + + /* Call registered process callbacks. */ + for (it = *(infop->awi_class_list); it != NULL; + it = it->acl_next) { + if (it->acl_class->adc_probe == NULL) { + continue; + } + cinfop->awi_class_curr = it->acl_class; + res = it->acl_class->adc_probe(cinfop); + if (ACPI_FAILURE(res)) { + rc = res; + ACPIDEV_DEBUG(CE_NOTE, "acpidev: failed to " + "process object of type %s under %s.", + it->acl_class->adc_class_name, + infop->awi_name); + } + } + + /* Free resources. */ + acpidev_free_walk_info(cinfop); + } + + /* Call post-probe callback functions. */ + for (it = *(infop->awi_class_list); it != NULL; it = it->acl_next) { + if (it->acl_class->adc_post_probe == NULL) { + continue; + } + infop->awi_class_curr = it->acl_class; + if (ACPI_FAILURE(it->acl_class->adc_post_probe(infop))) { + ACPIDEV_DEBUG(CE_NOTE, "acpidev: failed to post probe " + "device of type %s under %s.", + it->acl_class->adc_class_name, infop->awi_name); + } + } + + rw_exit(&acpidev_class_lock); + ndi_devi_exit(pdip, circ); + + return (rc); +} + +ACPI_STATUS +acpidev_process_object(acpidev_walk_info_t *infop, int flags) +{ + ACPI_STATUS rc = AE_OK; + char *devname; + dev_info_t *dip, *pdip; + ACPI_HANDLE hdl; + ACPI_DEVICE_INFO *adip; + acpidev_class_t *clsp; + acpidev_data_handle_t datap; + acpidev_filter_result_t res; + + /* Validate parameters first. */ + ASSERT(infop != NULL); + if (infop == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: infop is NULL in process_object()."); + return (AE_BAD_PARAMETER); + } + ASSERT(infop->awi_hdl != NULL); + ASSERT(infop->awi_info != NULL); + ASSERT(infop->awi_data != NULL); + ASSERT(infop->awi_class_curr != NULL); + ASSERT(infop->awi_class_curr->adc_filter != NULL); + hdl = infop->awi_hdl; + adip = infop->awi_info; + datap = infop->awi_data; + clsp = infop->awi_class_curr; + if (hdl == NULL || datap == NULL || adip == NULL || clsp == NULL || + clsp->adc_filter == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: infop has NULL pointer in process_object()."); + return (AE_BAD_PARAMETER); + } + pdip = acpidev_walk_info_get_pdip(infop); + if (pdip == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: failed to get pdip for %s in process_object().", + infop->awi_name); + return (AE_BAD_PARAMETER); + } + + /* + * Check whether object has already been handled. + * Tag and child dip pointer are used to indicate the object has been + * handled by ACPI auto configure driver. It has following usages: + * 1) Prevent from creating dip for objects which already has + * associating dip when reloading ACPI auto configure driver. + * 2) Prevent from creating multiple dips for ACPI object with ACPI + * alias. Currently ACPICA framework has no way to tell whether + * an object is an alias or not for some types of object. So tag + * is used to indicate that the object has been handled. + * 3) Prevent multiple class drivers to create multiple device for the + * same ACPI object. + */ + if ((flags & ACPIDEV_PROCESS_FLAG_CREATE) && + (flags & ACPIDEV_PROCESS_FLAG_CHECK) && + !(infop->awi_flags & ACPIDEV_WI_DISABLE_CREATE) && + (infop->awi_flags & ACPIDEV_WI_DEVICE_CREATED)) { + ASSERT(infop->awi_dip != NULL); + ACPIDEV_DEBUG(CE_NOTE, + "acpidev: device has already been created for object %s.", + infop->awi_name); + return (AE_ALREADY_EXISTS); + } + + /* + * Determine action according to following rules based on device + * status return by _STA method. Please refer to ACPI3.0b section + * 6.3.1 and 6.5.1. + * present functioning enabled Action + * 0 0 x Do nothing + * 1 x 0 Create node in OFFLINE and scan child + * 1 x 1 Create node and scan child + * x 1 0 Create node in OFFLINE and scan child + * x 1 1 Create node and scan child + */ + if ((datap->aod_iflag & ACPIDEV_ODF_STATUS_VALID) == 0 || + (flags & ACPIDEV_PROCESS_FLAG_SYNCSTATUS)) { + if (adip->Valid & ACPI_VALID_STA) { + datap->aod_status = adip->CurrentStatus; + } else { + datap->aod_status = acpidev_query_device_status(hdl); + } + datap->aod_iflag |= ACPIDEV_ODF_STATUS_VALID; + } + if (!acpidev_check_device_present(datap->aod_status)) { + ACPIDEV_DEBUG(CE_NOTE, "acpidev: object %s doesn't exist.", + infop->awi_name); + return (AE_NOT_EXIST); + } + + ASSERT(infop->awi_data != NULL); + ASSERT(infop->awi_parent != NULL); + ASSERT(infop->awi_parent->awi_data != NULL); + /* Put device into offline state if parent is in offline state. */ + if (infop->awi_parent->awi_data->aod_iflag & + ACPIDEV_ODF_DEVINFO_OFFLINE) { + flags |= ACPIDEV_PROCESS_FLAG_OFFLINE; + /* Put device into offline state if it's disabled. */ + } else if (!acpidev_check_device_enabled(datap->aod_status)) { + flags |= ACPIDEV_PROCESS_FLAG_OFFLINE; + } + /* + * Mark current node status as OFFLINE no matter device node will be + * created or not. This is needed to handle the case that current node + * is is SKIPPED (no device node will be created for it), so that all + * descedants of current nodes could be correctly marked as OFFLINE. + */ + if (flags & ACPIDEV_PROCESS_FLAG_OFFLINE) { + infop->awi_data->aod_iflag |= ACPIDEV_ODF_DEVINFO_OFFLINE; + } + + /* Evaluate filtering rules and generate device name. */ + devname = kmem_zalloc(ACPIDEV_MAX_NAMELEN + 1, KM_SLEEP); + (void) memcpy(devname, (char *)&adip->Name, sizeof (adip->Name)); + if (flags & ACPIDEV_PROCESS_FLAG_CREATE) { + res = clsp->adc_filter(infop, devname, ACPIDEV_MAX_NAMELEN); + } else { + res = clsp->adc_filter(infop, NULL, 0); + } + + /* Create device if requested. */ + if ((flags & ACPIDEV_PROCESS_FLAG_CREATE) && + !(infop->awi_flags & ACPIDEV_WI_DISABLE_CREATE) && + !(infop->awi_flags & ACPIDEV_WI_DEVICE_CREATED) && + (res == ACPIDEV_FILTER_DEFAULT || res == ACPIDEV_FILTER_CREATE)) { + int ret; + + /* + * Allocate dip and set default properties. + * Properties can be overrided in class specific init routine. + */ + ASSERT(infop->awi_dip == NULL); + ndi_devi_alloc_sleep(pdip, devname, (pnode_t)DEVI_SID_NODEID, + &dip); + infop->awi_dip = dip; + ret = ndi_prop_update_string(DDI_DEV_T_NONE, dip, + OBP_DEVICETYPE, clsp->adc_dev_type); + if (ret != NDI_SUCCESS) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: failed to set device property for %s.", + infop->awi_name); + (void) ddi_remove_child(dip, 0); + infop->awi_dip = NULL; + kmem_free(devname, ACPIDEV_MAX_NAMELEN + 1); + return (AE_ERROR); + } + + /* Build cross reference between dip and ACPI object. */ + if ((flags & ACPIDEV_PROCESS_FLAG_NOTAG) == 0 && + ACPI_FAILURE(acpica_tag_devinfo(dip, hdl))) { + cmn_err(CE_CONT, + "?acpidev: failed to tag object %s.\n", + infop->awi_name); + (void) ddi_remove_child(dip, 0); + infop->awi_dip = NULL; + kmem_free(devname, ACPIDEV_MAX_NAMELEN + 1); + return (AE_ERROR); + } + + /* Call class specific initialization callback. */ + if (clsp->adc_init != NULL && + ACPI_FAILURE(clsp->adc_init(infop))) { + ACPIDEV_DEBUG(CE_NOTE, + "acpidev: failed to initialize device %s.", + infop->awi_name); + if ((flags & ACPIDEV_PROCESS_FLAG_NOTAG) == 0) { + (void) acpica_untag_devinfo(dip, hdl); + } + (void) ddi_remove_child(dip, 0); + infop->awi_dip = NULL; + kmem_free(devname, ACPIDEV_MAX_NAMELEN + 1); + return (AE_ERROR); + } + + /* Set device into offline state if requested. */ + if (flags & ACPIDEV_PROCESS_FLAG_OFFLINE) { + mutex_enter(&(DEVI(dip)->devi_lock)); + DEVI_SET_DEVICE_OFFLINE(dip); + mutex_exit(&(DEVI(dip)->devi_lock)); + } + + /* Mark status */ + infop->awi_flags |= ACPIDEV_WI_DEVICE_CREATED; + datap->aod_iflag |= ACPIDEV_ODF_DEVINFO_CREATED; + datap->aod_dip = dip; + datap->aod_class = clsp; + /* Hold reference count on class driver. */ + atomic_inc_32(&clsp->adc_refcnt); + if ((flags & ACPIDEV_PROCESS_FLAG_NOTAG) == 0) { + datap->aod_iflag |= ACPIDEV_ODF_DEVINFO_TAGGED; + } + + /* Bind device driver. */ + if ((flags & ACPIDEV_PROCESS_FLAG_NOBIND) == 0) { + mutex_enter(&(DEVI(dip)->devi_lock)); + DEVI(dip)->devi_state |= DEVI_NO_BIND; + mutex_exit(&(DEVI(dip)->devi_lock)); + } + (void) ndi_devi_bind_driver(dip, 0); + } + + /* Free resources */ + kmem_free(devname, ACPIDEV_MAX_NAMELEN + 1); + rc = AE_OK; + + /* Recursively scan child objects if requested. */ + switch (res) { + case ACPIDEV_FILTER_DEFAULT: + /* FALLTHROUGH */ + case ACPIDEV_FILTER_SCAN: + /* Check whether need to scan child. */ + if ((flags & ACPIDEV_PROCESS_FLAG_SCAN) && + !(infop->awi_flags & ACPIDEV_WI_DISABLE_SCAN) && + !(infop->awi_flags & ACPIDEV_WI_CHILD_SCANNED)) { + /* probe child object. */ + rc = acpidev_probe_child(infop); + if (ACPI_FAILURE(rc)) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: failed to probe subtree of %s.", + infop->awi_name); + rc = AE_ERROR; + } + /* Mark object has been scanned. */ + infop->awi_flags |= ACPIDEV_WI_CHILD_SCANNED; + } + break; + + case ACPIDEV_FILTER_CREATE: + /* FALLTHROUGH */ + case ACPIDEV_FILTER_CONTINUE: + /* FALLTHROUGH */ + case ACPIDEV_FILTER_SKIP: + break; + + case ACPIDEV_FILTER_FAILED: + ACPIDEV_DEBUG(CE_WARN, + "acpidev: failed to probe device for %s.", + infop->awi_name); + rc = AE_ERROR; + break; + + default: + cmn_err(CE_CONT, + "?acpidev: unknown filter result code %d.\n", res); + rc = AE_ERROR; + break; + } + + return (rc); +} + +/*ARGSUSED*/ +acpidev_filter_result_t +acpidev_filter_default(acpidev_walk_info_t *infop, ACPI_HANDLE hdl, + acpidev_filter_rule_t *afrp, char *devname, int len) +{ + ASSERT(afrp != NULL); + ASSERT(devname == NULL || len >= ACPIDEV_MAX_NAMELEN); + if (infop->awi_level < afrp->adf_minlvl || + infop->awi_level > afrp->adf_maxlvl) { + return (ACPIDEV_FILTER_CONTINUE); + } else if (afrp->adf_pattern != NULL && + strncmp(afrp->adf_pattern, + (char *)&infop->awi_info->Name, + sizeof (infop->awi_info->Name))) { + return (ACPIDEV_FILTER_CONTINUE); + } + if (afrp->adf_replace != NULL && devname != NULL) { + (void) strncpy(devname, afrp->adf_replace, len - 1); + devname[len - 1] = 0; + } + + return (afrp->adf_retcode); +} + +acpidev_filter_result_t +acpidev_filter_device(acpidev_walk_info_t *infop, ACPI_HANDLE hdl, + acpidev_filter_rule_t *afrp, int entries, char *devname, int len) +{ + acpidev_filter_result_t res; + + /* Evaluate filtering rules. */ + for (; entries > 0; entries--, afrp++) { + if (afrp->adf_filter_func != NULL) { + res = afrp->adf_filter_func(infop, hdl, afrp, + devname, len); + } else { + res = acpidev_filter_default(infop, hdl, afrp, + devname, len); + } + if (res == ACPIDEV_FILTER_DEFAULT || + res == ACPIDEV_FILTER_SCAN) { + infop->awi_class_list = afrp->adf_class_list; + break; + } + } + + return (res); +} + +dev_info_t * +acpidev_root_node(void) +{ + return (acpidev_root_dip); +} + +ACPI_STATUS +acpidev_register_class(acpidev_class_list_t **listpp, acpidev_class_t *clsp, + boolean_t tail) +{ + ACPI_STATUS rc; + acpidev_class_list_t *item; + acpidev_class_list_t *temp; + + ASSERT(clsp != NULL); + ASSERT(listpp != NULL); + if (listpp == NULL || clsp == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: invalid parameter for register_class()."); + return (AE_BAD_PARAMETER); + } else if (clsp->adc_version != ACPIDEV_CLASS_REV) { + cmn_err(CE_CONT, + "?acpidev: class driver %s version mismatch.\n", + clsp->adc_class_name); + return (AE_BAD_DATA); + } + + rc = AE_OK; + item = kmem_zalloc(sizeof (*item), KM_SLEEP); + item->acl_class = clsp; + rw_enter(&acpidev_class_lock, RW_WRITER); + /* Check for duplicated item. */ + for (temp = *listpp; temp != NULL; temp = temp->acl_next) { + if (temp->acl_class == clsp) { + cmn_err(CE_CONT, + "?acpidev: register duplicate class driver %s.\n", + clsp->adc_class_name); + rc = AE_ALREADY_EXISTS; + break; + } + } + if (ACPI_SUCCESS(rc)) { + if (tail) { + while (*listpp) { + listpp = &(*listpp)->acl_next; + } + } + item->acl_next = *listpp; + *listpp = item; + } + rw_exit(&acpidev_class_lock); + if (ACPI_FAILURE(rc)) { + kmem_free(item, sizeof (*item)); + } + + return (rc); +} + +ACPI_STATUS +acpidev_unregister_class(acpidev_class_list_t **listpp, + acpidev_class_t *clsp) +{ + ACPI_STATUS rc = AE_NOT_FOUND; + acpidev_class_list_t *temp; + + ASSERT(clsp != NULL); + ASSERT(listpp != NULL); + if (listpp == NULL || clsp == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: invalid parameter for unregister_class()."); + return (AE_BAD_PARAMETER); + } + + rw_enter(&acpidev_class_lock, RW_WRITER); + for (temp = NULL; *listpp; listpp = &(*listpp)->acl_next) { + if ((*listpp)->acl_class == clsp) { + temp = *listpp; + *listpp = (*listpp)->acl_next; + break; + } + } + if (temp == NULL) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: class %p(%s) doesn't exist when unregister.", + (void *)clsp, clsp->adc_class_name); + rc = AE_NOT_FOUND; + } else if (temp->acl_class->adc_refcnt != 0) { + ACPIDEV_DEBUG(CE_WARN, + "acpidev: class %p(%s) is still in use when unregister.", + (void *)clsp, clsp->adc_class_name); + rc = AE_ERROR; + } else { + kmem_free(temp, sizeof (*temp)); + rc = AE_OK; + } + rw_exit(&acpidev_class_lock); + + return (rc); +}