root/usr/src/uts/i86pc/io/acpi_drv/acpi_drv.c
/*
 * 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 2015 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
 */

/*
 * Driver for ACPI Battery, Lid, and Hotkey Control
 */
#include <sys/hotkey_drv.h>
#include <sys/sysevent/pwrctl.h>


#define ACPI_DRV_MOD_STRING             "ACPI driver"

#define ACPI_DRV_MAX_BAT_NUM            8
#define ACPI_DRV_MAX_AC_NUM             10

#define BST_FLAG_DISCHARGING            (0x1)
#define BST_FLAG_CHARGING               (0x2)
#define BST_FLAG_CRITICAL               (0x4)

/* Set if the battery is present */
#define STA_FLAG_BATT_PRESENT           (0x10)

#define ACPI_DEVNAME_CBAT               "PNP0C0A"
#define ACPI_DEVNAME_AC                 "ACPI0003"
#define ACPI_DEVNAME_LID                "PNP0C0D"

#define ACPI_DRV_EVENTS                 (POLLIN | POLLRDNORM)

#ifdef DEBUG

#define ACPI_DRV_PRINT_BUFFER_SIZE      512
static char acpi_drv_prt_buf[ACPI_DRV_PRINT_BUFFER_SIZE];
static kmutex_t acpi_drv_prt_mutex;

static int acpi_drv_debug = 0;
#define ACPI_DRV_DBG(lev, devp, ...) \
        do { \
                if (acpi_drv_debug) acpi_drv_printf((devp), \
(lev), __VA_ARGS__); \
_NOTE(CONSTCOND) } while (0)
#define ACPI_DRV_PRT_NOTIFY(hdl, val) \
        do { \
                if (acpi_drv_debug) acpi_drv_prt_notify((hdl), (val)); \
_NOTE(CONSTCOND) } while (0)

#else

#define ACPI_DRV_DBG(lev, devp, ...)
#define ACPI_DRV_PRT_NOTIFY(hdl, val)

#endif /* DEBUG */

/* ACPI notify types */
enum acpi_drv_notify {
        ACPI_DRV_NTF_UNKNOWN = -1,      /* No notifications seen, ever. */
        ACPI_DRV_NTF_CHANGED,
        ACPI_DRV_NTF_OK
};

static int acpi_drv_dev_present(struct acpi_drv_dev *);
#define acpi_drv_ac_present(a)  (((a)->dev.type == ACPI_DRV_TYPE_AC) ? \
                                acpi_drv_dev_present(&(a)->dev) : -1)
#define acpi_drv_cbat_present(a)        (((a)->dev.type == ACPI_DRV_TYPE_CBAT) \
                                        ? acpi_drv_dev_present(&(a)->dev) : -1)

static dev_info_t *acpi_drv_dip = NULL;
static kmutex_t acpi_drv_mutex;
static struct pollhead acpi_drv_pollhead;
static timeout_id_t acpi_drv_cbat_rescan_timeout;

/* Control Method Battery state */
struct acpi_drv_cbat_state {
        struct acpi_drv_dev dev;
        /* Caches of _BST and _BIF */
        enum acpi_drv_notify bat_bifok;
        acpi_bif_t bif_cache;
        enum acpi_drv_notify bat_bstok;
        acpi_bst_t bst_cache;

        uint32_t charge_warn;
        uint32_t charge_low;

        kstat_t *bat_bif_ksp;
        kstat_t *bat_bst_ksp;
} acpi_drv_cbat[ACPI_DRV_MAX_BAT_NUM];
static int nbat = 0;

/*
 * Synthesis battery state
 * When there are multiple batteries present, the battery subsystem
 * is not required to perform any synthesis of a composite battery
 * from the data of the separate batteries. In cases where the
 * battery subsystem does not synthesize a composite battery from
 * the separate battery's data, the OS must provide that synthesis.
 */
static uint32_t acpi_drv_syn_rem_cap;
static uint32_t acpi_drv_syn_last_cap;
static uint32_t acpi_drv_syn_oem_warn_cap;
static uint32_t acpi_drv_syn_oem_low_cap;

static int acpi_drv_warn_enabled;
static uint32_t acpi_drv_syn_warn_per;
static uint32_t acpi_drv_syn_low_per;
static uint32_t acpi_drv_syn_warn_cap;
static uint32_t acpi_drv_syn_low_cap;
/* Tracking boundery passing of _BST charge levels */
static uint32_t acpi_drv_syn_last_level;

/* AC state */
static struct acpi_drv_ac_state {
        struct acpi_drv_dev dev;
} acpi_drv_ac[ACPI_DRV_MAX_AC_NUM];
static int nac = 0;

/*
 * Current power source device
 * Note: assume only one device can be the power source device.
 */
static int acpi_drv_psr_type = ACPI_DRV_TYPE_UNKNOWN;
static struct acpi_drv_dev *acpi_drv_psr_devp = NULL;

struct obj_desc {
        char *name;
        int offset;
        int size;
        int type;
};

/* Object copy definitions */
#define OFFSETOF(s, m)          ((size_t)(&(((s *)0)->m)))
#define SIZEOF(s, m)            (sizeof (((s *)0)->m))
#define FIELD(n, s, m, t) \
        { n, OFFSETOF(s, m), SIZEOF(s, m), t }
#define FIELD_NULL              { NULL, -1, 0, ACPI_TYPE_ANY }

static struct obj_desc bif_desc[] = {
        FIELD("bif_unit",       acpi_bif_t, bif_unit,   ACPI_TYPE_INTEGER),
        FIELD("bif_design_cap", acpi_bif_t, bif_design_cap, ACPI_TYPE_INTEGER),
        FIELD("bif_last_cap",   acpi_bif_t, bif_last_cap,   ACPI_TYPE_INTEGER),
        FIELD("bif_tech",       acpi_bif_t, bif_tech,   ACPI_TYPE_INTEGER),
        FIELD("bif_voltage",    acpi_bif_t, bif_voltage, ACPI_TYPE_INTEGER),
        FIELD("bif_warn_cap",   acpi_bif_t, bif_warn_cap, ACPI_TYPE_INTEGER),
        FIELD("bif_low_cap",    acpi_bif_t, bif_low_cap,  ACPI_TYPE_INTEGER),
        FIELD("bif_gran1_cap",  acpi_bif_t, bif_gran1_cap, ACPI_TYPE_INTEGER),
        FIELD("bif_gran2_cap",  acpi_bif_t, bif_gran2_cap, ACPI_TYPE_INTEGER),
        FIELD("bif_model",      acpi_bif_t, bif_model,  ACPI_TYPE_STRING),
        FIELD("bif_serial",     acpi_bif_t, bif_serial, ACPI_TYPE_STRING),
        FIELD("bif_type",       acpi_bif_t, bif_type,   ACPI_TYPE_STRING),
        FIELD("bif_oem_info",   acpi_bif_t, bif_oem_info, ACPI_TYPE_STRING),
        FIELD_NULL
};

static struct obj_desc bst_desc[] = {
        FIELD("bst_state",   acpi_bst_t, bst_state,     ACPI_TYPE_INTEGER),
        FIELD("bst_rate",    acpi_bst_t, bst_rate,      ACPI_TYPE_INTEGER),
        FIELD("bst_rem_cap", acpi_bst_t, bst_rem_cap,   ACPI_TYPE_INTEGER),
        FIELD("bst_voltage", acpi_bst_t, bst_voltage,   ACPI_TYPE_INTEGER),
        FIELD_NULL
};

/* kstat definitions */
static kstat_t *acpi_drv_power_ksp;
static kstat_t *acpi_drv_warn_ksp;

acpi_drv_power_kstat_t acpi_drv_power_kstat = {
        { SYSTEM_POWER,                 KSTAT_DATA_STRING },
        { SUPPORTED_BATTERY_COUNT,      KSTAT_DATA_UINT32 },
};

acpi_drv_warn_kstat_t acpi_drv_warn_kstat = {
        { BW_ENABLED,                   KSTAT_DATA_UINT32 },
        { BW_POWEROFF_THRESHOLD,        KSTAT_DATA_UINT32 },
        { BW_SHUTDOWN_THRESHOLD,        KSTAT_DATA_UINT32 },
};

/* BIF */
acpi_drv_bif_kstat_t acpi_drv_bif_kstat = {
        { BIF_UNIT,             KSTAT_DATA_UINT32 },
        { BIF_DESIGN_CAP,       KSTAT_DATA_UINT32 },
        { BIF_LAST_CAP,         KSTAT_DATA_UINT32 },
        { BIF_TECH,             KSTAT_DATA_UINT32 },
        { BIF_VOLTAGE,          KSTAT_DATA_UINT32 },
        { BIF_WARN_CAP,         KSTAT_DATA_UINT32 },
        { BIF_LOW_CAP,          KSTAT_DATA_UINT32 },
        { BIF_GRAN1_CAP,        KSTAT_DATA_UINT32 },
        { BIF_GRAN2_CAP,        KSTAT_DATA_UINT32 },
        { BIF_MODEL,            KSTAT_DATA_STRING },
        { BIF_SERIAL,           KSTAT_DATA_STRING },
        { BIF_TYPE,             KSTAT_DATA_STRING },
        { BIF_OEM_INFO,         KSTAT_DATA_STRING },
};

/* BST */
acpi_drv_bst_kstat_t acpi_drv_bst_kstat = {
        { BST_STATE,            KSTAT_DATA_UINT32 },
        { BST_RATE,             KSTAT_DATA_UINT32 },
        { BST_REM_CAP,          KSTAT_DATA_UINT32 },
        { BST_VOLTAGE,          KSTAT_DATA_UINT32 },
};

struct acpi_drv_lid_state {
        struct acpi_drv_dev dev;
        enum acpi_drv_notify state_ok;
        int state;
} lid;
static int nlid = 0;

struct hotkey_drv acpi_hotkey;

static int acpi_drv_attach(dev_info_t *devi, ddi_attach_cmd_t cmd);
static int acpi_drv_detach(dev_info_t *devi, ddi_detach_cmd_t cmd);
static int acpi_drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
    void **resultp);
static int acpi_drv_open(dev_t *devp, int flag, int otyp, cred_t *crp);
static int acpi_drv_close(dev_t dev, int flag, int otyp, cred_t *crp);
static int acpi_drv_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
    cred_t *cr, int *rval);
static int acpi_drv_chpoll(dev_t dev, short events, int anyyet,
    short *reventsp, struct pollhead **phpp);
static int acpi_drv_ac_ioctl(int index, int cmd, intptr_t arg, int mode,
    cred_t *cr, int *rval);
static int acpi_drv_cbat_ioctl(int index, int cmd, intptr_t arg, int mode,
    cred_t *cr, int *rval);
static int acpi_drv_lid_ioctl(int index, int cmd, intptr_t arg, int mode,
    cred_t *cr, int *rval);
#ifdef DEBUG
static void acpi_drv_printf(struct acpi_drv_dev *devp, uint_t lev,
    const char *fmt, ...);
#endif

static int acpi_drv_update_bif(struct acpi_drv_cbat_state *bp);
static int acpi_drv_update_bst(struct acpi_drv_cbat_state *bp);
static int acpi_drv_update_lid(struct acpi_drv_dev *bp);
static int acpi_drv_set_warn(acpi_drv_warn_t *bwp);
static struct acpi_drv_cbat_state *acpi_drv_idx2cbat(int idx);
static struct acpi_drv_ac_state *acpi_drv_idx2ac(int idx);
static int acpi_drv_acpi_init(void);
static void acpi_drv_acpi_fini(void);
static int acpi_drv_kstat_init(void);
static void acpi_drv_kstat_fini(void);

static int acpi_drv_kstat_bif_update(kstat_t *, int);
static int acpi_drv_kstat_bst_update(kstat_t *, int);

static void acpi_drv_cbat_rescan(void *);

static struct cb_ops acpi_drv_cb_ops = {
        acpi_drv_open,          /* open */
        acpi_drv_close,         /* close */
        nodev,                  /* strategy */
        nodev,                  /* print */
        nodev,                  /* dump */
        nodev,                  /* read */
        nodev,                  /* write */
        acpi_drv_ioctl,         /* ioctl */
        nodev,                  /* devmap */
        nodev,                  /* mmap */
        nodev,                  /* segmap */
        acpi_drv_chpoll,                /* chpoll */
        ddi_prop_op,            /* prop_op */
        NULL,                   /* streamtab */
        D_NEW | D_MP,
        CB_REV,
        nodev,
        nodev
};

static struct dev_ops acpi_drv_dev_ops = {
        DEVO_REV,
        0,                      /* refcnt */
        acpi_drv_getinfo,       /* getinfo */
        nulldev,                /* identify */
        nulldev,                /* probe */
        acpi_drv_attach,        /* attach */
        acpi_drv_detach,        /* detach */
        nodev,                  /* reset */
        &acpi_drv_cb_ops,
        NULL,                   /* no bus operations */
        NULL,                   /* power */
        ddi_quiesce_not_needed, /* quiesce */
};

static struct modldrv modldrv1 = {
        &mod_driverops,
        ACPI_DRV_MOD_STRING,
        &acpi_drv_dev_ops
};

static struct modlinkage modlinkage = {
        MODREV_1,
        (void *)&modldrv1,
        NULL,
};

int
_init(void)
{
        int ret;

        mutex_init(&acpi_drv_mutex, NULL, MUTEX_DRIVER, NULL);
#ifdef DEBUG
        mutex_init(&acpi_drv_prt_mutex, NULL, MUTEX_DRIVER, NULL);
#endif

        if ((ret = mod_install(&modlinkage)) != 0) {
                mutex_destroy(&acpi_drv_mutex);
#ifdef DEBUG
                mutex_destroy(&acpi_drv_prt_mutex);
#endif
        }
        return (ret);
}

int
_fini(void)
{
        int ret;

        if ((ret = mod_remove(&modlinkage)) == 0) {
#ifdef DEBUG
                mutex_destroy(&acpi_drv_prt_mutex);
#endif
                mutex_destroy(&acpi_drv_mutex);
        }

        return (ret);
}

int
_info(struct modinfo *mp)
{
        return (mod_info(&modlinkage, mp));
}

static int
acpi_drv_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
        switch (cmd) {
        case DDI_ATTACH:
                /* Limit to one instance of driver */
                if (acpi_drv_dip) {
                        return (DDI_FAILURE);
                }
                break;
        case DDI_RESUME:
        case DDI_PM_RESUME:
                return (DDI_SUCCESS);
        default:
                return (DDI_FAILURE);
        }

        acpi_drv_dip = devi;

        /* Init ACPI related stuff */
        if (acpi_drv_acpi_init() != ACPI_DRV_OK) {
                goto error;
        }

        /* Init kstat related stuff */
        if (acpi_drv_kstat_init() != ACPI_DRV_OK) {
                goto error;
        }

        acpi_drv_cbat_rescan_timeout = timeout(acpi_drv_cbat_rescan, NULL,
            drv_usectohz(MICROSEC));

        return (DDI_SUCCESS);

error:
        ddi_remove_minor_node(devi, NULL);
        acpi_drv_kstat_fini();
        acpi_drv_acpi_fini();
        acpi_drv_dip = NULL;
        return (DDI_FAILURE);
}

static int
acpi_drv_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
        timeout_id_t tmp_rescan_timeout;

        if (cmd != DDI_DETACH) {
                return (DDI_FAILURE);
        }

        /*
         * Clear the timeout id to indicate that the handler should not
         * reschedule itself.
         */
        mutex_enter(&acpi_drv_mutex);
        tmp_rescan_timeout = acpi_drv_cbat_rescan_timeout;
        acpi_drv_cbat_rescan_timeout = 0;
        mutex_exit(&acpi_drv_mutex);

        (void) untimeout(tmp_rescan_timeout);

        mutex_enter(&acpi_drv_mutex);
        ddi_remove_minor_node(devi, NULL);

        acpi_drv_kstat_fini();
        acpi_drv_acpi_fini();
        mutex_exit(&acpi_drv_mutex);
        return (DDI_SUCCESS);
}

/* ARGSUSED */
static int
acpi_drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
        switch (cmd) {
        case DDI_INFO_DEVT2DEVINFO:
                *resultp = acpi_drv_dip;
                return (DDI_SUCCESS);
        case DDI_INFO_DEVT2INSTANCE:
                *resultp = (void*) 0;
                return (DDI_SUCCESS);
        default:
                return (DDI_FAILURE);
        }
}

/*ARGSUSED*/
static int
acpi_drv_open(dev_t *devp, int flag, int otyp, cred_t *crp)
{
        if (acpi_drv_dip == NULL) {
                return (ENXIO);
        }

        return (0);
}

/*ARGSUSED*/
static int
acpi_drv_close(dev_t dev, int flag, int otyp, cred_t *crp)
{
        return (0);
}

/*ARGSUSED*/
static int
acpi_drv_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cr,
    int *rval)
{
        int minor;
        int type, index;
        int res = 0;

        minor = getminor(dev);
        type = MINOR2TYPE(minor);
        index = MINOR2IDX(minor);

        mutex_enter(&acpi_drv_mutex);

        switch (type) {
        case ACPI_DRV_TYPE_CBAT:
                res = acpi_drv_cbat_ioctl(index, cmd, arg, mode, cr, rval);
                break;
        case ACPI_DRV_TYPE_AC:
                res = acpi_drv_ac_ioctl(index, cmd, arg, mode, cr, rval);
                break;
        case ACPI_DRV_TYPE_LID:
                res = acpi_drv_lid_ioctl(index, cmd, arg, mode, cr, rval);
                break;
        case ACPI_DRV_TYPE_HOTKEY:
                res = acpi_drv_hotkey_ioctl(cmd, arg, mode, cr, rval);
                break;
        default:
                res = EINVAL;
                break;
        }

        mutex_exit(&acpi_drv_mutex);
        return (res);
}

/*ARGSUSED*/
static int
acpi_drv_cbat_ioctl(int index, int cmd, intptr_t arg, int mode, cred_t *cr,
    int *rval)
{
        int res = 0;
        acpi_drv_warn_t bwarn;
        struct acpi_drv_cbat_state *bp;

        ASSERT(mutex_owned(&acpi_drv_mutex));

        bp = acpi_drv_idx2cbat(index);
        if (!bp || bp->dev.valid != 1) {
                return (ENXIO);
        }

        switch (cmd) {
        /*
         * Return _BIF(Battery Information) of battery[index],
         * if battery plugged.
         */
        case ACPI_DRV_IOC_INFO:
                if (bp->dev.present == 0) {
                        res = ENXIO;
                        break;
                }

                res = acpi_drv_update_bif(bp);
                if (res != ACPI_DRV_OK) {
                        break;
                }
                if (copyout(&bp->bif_cache, (void *)arg,
                    sizeof (bp->bif_cache))) {
                        res = EFAULT;
                }
                break;

        /*
         * Return _BST(Battery Status) of battery[index],
         * if battery plugged.
         */
        case ACPI_DRV_IOC_STATUS:
                if (bp->dev.present == 0) {
                        res = ENXIO;
                        break;
                }

                res = acpi_drv_update_bst(bp);
                if (res != ACPI_DRV_OK) {
                        break;
                }
                if (copyout(&bp->bst_cache, (void *)arg,
                    sizeof (bp->bst_cache))) {
                        res = EFAULT;
                }
                break;

        /* Return the state of the battery bays in the system */
        case ACPI_DRV_IOC_BAY:
                {
                        batt_bay_t bay;

                        bay.bay_number = nbat;
                        bay.battery_map = 0;
                        for (bp = &acpi_drv_cbat[0];
                            bp < &acpi_drv_cbat[ACPI_DRV_MAX_BAT_NUM]; bp++) {
                                if (bp->dev.valid) {
                                        if (bp->dev.present) {
                                                bay.battery_map |=
                                                    (1 << bp->dev.index);
                                        }
                                }
                        }
                        if (copyout(&bay, (void *)arg, sizeof (bay))) {
                                res = EFAULT;
                                break;
                        }
                }
                break;

        /*
         * Return the current power source device if available:
         * 0 -- battery supplying power
         * 1 -- AC supplying power
         */
        case ACPI_DRV_IOC_POWER_STATUS:
                {
                        int val;

                        /* State not available */
                        if (acpi_drv_psr_type == ACPI_DRV_TYPE_UNKNOWN) {
                                res = ENXIO;
                                break;
                        }
                        val = (acpi_drv_psr_type == ACPI_DRV_TYPE_AC) ? 1 : 0;
                        if (copyout(&val, (void *)arg, sizeof (val))) {
                                res = EFAULT;
                                break;
                        }
                }
                break;

        /* Get charge-warn and charge-low levels for the whole system */
        case ACPI_DRV_IOC_GET_WARNING:
                bwarn.bw_enabled = acpi_drv_warn_enabled;
                bwarn.bw_charge_warn = acpi_drv_syn_warn_per;
                bwarn.bw_charge_low = acpi_drv_syn_low_per;
                if (copyout(&bwarn, (void *)arg, sizeof (bwarn))) {
                        res = EFAULT;
                }
                break;

        /* Set charge-warn and charge-low levels for the whole system */
        case ACPI_DRV_IOC_SET_WARNING:
                if (drv_priv(cr)) {
                        res = EPERM;
                        break;
                }
                if (copyin((void *)arg, &bwarn, sizeof (bwarn))) {
                        res = EFAULT;
                        break;
                }
                res = acpi_drv_set_warn(&bwarn);
                break;

        default:
                res = EINVAL;
                break;
        }

        return (res);
}

/*ARGSUSED*/
static int
acpi_drv_ac_ioctl(int index, int cmd, intptr_t arg, int mode, cred_t *cr,
    int *rval)
{
        int res = 0;
        int ac_state;
        struct acpi_drv_ac_state *acp;

        ASSERT(mutex_owned(&acpi_drv_mutex));

        acp = acpi_drv_idx2ac(index);
        if (!acp || acp->dev.valid != 1) {
                return (ENXIO);
        }

        switch (cmd) {
        /* Return the number of AC adapters in the system */
        case ACPI_DRV_IOC_AC_COUNT:
                if (copyout(&nac, (void *)arg, sizeof (nac))) {
                        res = EFAULT;
                }
                break;

        /*
         * Return the state of AC[index] if available:
         * 0 -- Off-line
         * 1 -- On-line
         */
        case ACPI_DRV_IOC_POWER_STATUS:
                if (!acp || acp->dev.valid != 1) {
                        res = ENXIO;
                        break;
                }
                /* State not available */
                if ((ac_state = acpi_drv_ac_present(acp)) == -1) {
                        res = ENXIO;
                        break;
                }
                if (copyout(&ac_state, (void *)arg, sizeof (ac_state))) {
                        res = EFAULT;
                }
                break;

        default:
                res = EINVAL;
                break;
        }

        return (res);
}

/*ARGSUSED*/
static int
acpi_drv_lid_ioctl(int index, int cmd, intptr_t arg, int mode, cred_t *cr,
    int *rval)
{
        int res = 0;

        /*
         * lid.state 0 means lid is closed.
         * lid.state non-zero means lid is open.
         */
        switch (cmd) {
        case ACPI_DRV_IOC_LID_STATUS:
                if (lid.state_ok == ACPI_DRV_NTF_UNKNOWN) {
                        /* State not available */
                        res = acpi_drv_update_lid(&lid.dev);
                        if (res != ACPI_DRV_OK) {
                                res = ENXIO;
                                break;
                        }
                }
                if (copyout(&lid.state, (void *)arg, sizeof (lid.state))) {
                        res = EFAULT;
                }
                break;
        case ACPI_DRV_IOC_LID_UPDATE:
                res = acpi_drv_update_lid(&lid.dev);
                if (res != ACPI_DRV_OK) {
                        res = ENXIO;
                        break;
                }
                if (copyout(&lid.state, (void *)arg, sizeof (lid.state))) {
                        res = EFAULT;
                }
                break;

        default:
                res = EINVAL;
                break;
        }
        return (res);
}

/*ARGSUSED*/
static int
acpi_drv_chpoll(dev_t dev, short events, int anyyet,  short *reventsp,
    struct pollhead **phpp)
{
        if (!anyyet) {
                *phpp = &acpi_drv_pollhead;
        }
        *reventsp = 0;
        return (0);
}

#ifdef DEBUG
static void
acpi_drv_printf(struct acpi_drv_dev *devp, uint_t lev,
    const char *fmt, ...)
{
        va_list args;

        mutex_enter(&acpi_drv_prt_mutex);

        va_start(args, fmt);
        (void) vsprintf(acpi_drv_prt_buf, fmt, args);
        va_end(args);

        if (devp) {
                cmn_err(lev, "%s.%s: %s", devp->hid, devp->uid,
                    acpi_drv_prt_buf);
        } else {
                cmn_err(lev, "%s", acpi_drv_prt_buf);
        }
        mutex_exit(&acpi_drv_prt_mutex);
}

static void
acpi_drv_prt_notify(ACPI_HANDLE hdl, UINT32 val)
{
        ACPI_BUFFER buf;
        char str[1024];

        buf.Length = sizeof (str);
        buf.Pointer = str;
        (void) AcpiGetName(hdl, ACPI_FULL_PATHNAME, &buf);
        cmn_err(CE_NOTE, "AcpiNotify(%s, 0x%02x)", str, val);
}
#endif /* DEBUG */

void
acpi_drv_gen_sysevent(struct acpi_drv_dev *devp, char *ev, uint32_t val)
{
        nvlist_t *attr_list = NULL;
        int err;
        char pathname[MAXPATHLEN];

        /* Allocate and build sysevent attribute list */
        err = nvlist_alloc(&attr_list, NV_UNIQUE_NAME_TYPE, DDI_NOSLEEP);
        if (err != 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "cannot allocate memory for sysevent attributes\n");
                return;
        }

        /* Add attributes */
        err = nvlist_add_string(attr_list, PWRCTL_DEV_HID, devp->hid);
        if (err != 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "Failed to add attr [%s] for %s/%s event",
                    PWRCTL_DEV_HID, EC_PWRCTL, ev);
                nvlist_free(attr_list);
                return;
        }

        err = nvlist_add_string(attr_list, PWRCTL_DEV_UID, devp->uid);
        if (err != 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "Failed to add attr [%s] for %s/%s event",
                    PWRCTL_DEV_UID, EC_PWRCTL, ev);
                nvlist_free(attr_list);
                return;
        }

        err = nvlist_add_uint32(attr_list, PWRCTL_DEV_INDEX, devp->index);
        if (err != 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "Failed to add attr [%s] for %s/%s event",
                    PWRCTL_DEV_INDEX, EC_PWRCTL, ev);
                nvlist_free(attr_list);
                return;
        }

        (void) ddi_pathname(acpi_drv_dip, pathname);
        err = nvlist_add_string(attr_list, PWRCTL_DEV_PHYS_PATH, pathname);
        if (err != 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "Failed to add attr [%s] for %s/%s event",
                    PWRCTL_DEV_PHYS_PATH, EC_PWRCTL, ev);
                nvlist_free(attr_list);
                return;
        }

        if (strcmp(ev, ESC_PWRCTL_WARN) && strcmp(ev, ESC_PWRCTL_LOW)) {
                goto finish;
        }

        err = nvlist_add_uint32(attr_list, PWRCTL_CHARGE_LEVEL, val);
        if (err != 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "Failed to add attr [%s] for %s/%s event",
                    PWRCTL_CHARGE_LEVEL, EC_PWRCTL, ev);
                nvlist_free(attr_list);
                return;
        }

finish:
        ACPI_DRV_DBG(CE_NOTE, NULL, "SysEv(%s, %s.%s, %d)",
            ev, devp->hid, devp->uid, val);
        /* Generate/log sysevent */
        err = ddi_log_sysevent(acpi_drv_dip, DDI_VENDOR_SUNW, EC_PWRCTL,
            ev, attr_list, NULL, DDI_NOSLEEP);
#ifdef DEBUG
        if (err != DDI_SUCCESS) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "cannot log sysevent, err code %x\n", err);
        }
#endif

        nvlist_free(attr_list);
}

static int
acpi_drv_obj_copy(ACPI_OBJECT *op, char *bp, struct obj_desc *dp)
{
        ACPI_OBJECT *ep;
        char *fp;

        ep = &op->Package.Elements[0];
        for (; dp->offset != -1; dp++) {
                fp = bp + dp->offset;
                if (dp->type == ACPI_TYPE_INTEGER &&
                    ep->Type == dp->type) {
#ifdef DEBUG
                        if (dp->size <= 4) {
                                ACPI_DRV_DBG(CE_NOTE, NULL, "\t%s: %u",
                                    dp->name,
                                    (uint32_t)ep->Integer.Value);
                        } else {
#ifdef _LP64
                                ACPI_DRV_DBG(CE_NOTE, NULL, "\t%s: %lu",
                                    dp->name, (uint64_t)ep->Integer.Value);
                        }
#else
                                ACPI_DRV_DBG(CE_NOTE, NULL, "\t%s: %llu",
                                    dp->name, (uint64_t)ep->Integer.Value);
                        }
#endif /* _LP64 */
#endif /* DEBUG */
                        *(uint32_t *)fp = ep->Integer.Value;
                } else if (dp->type == ACPI_TYPE_STRING &&
                    ep->Type == dp->type) {
                        ACPI_DRV_DBG(CE_NOTE, NULL, "\t%s: \"%s\"",
                            dp->name, ep->String.Pointer);
                        (void) strlcpy(fp, ep->String.Pointer, dp->size);
                } else if (dp->type == ACPI_TYPE_STRING &&
                    ep->Type == ACPI_TYPE_BUFFER) {
#ifdef DEBUG
                        int len;
                        char buf[MAXNAMELEN + 1];

                        len = (MAXNAMELEN < ep->Buffer.Length) ?
                            MAXNAMELEN : ep->Buffer.Length;
                        bcopy(ep->Buffer.Pointer, buf, len);
                        buf[len] = 0;
                        ACPI_DRV_DBG(CE_NOTE, NULL, "\t%s: [%d] \"%s\"",
                            dp->name, len, buf);
#endif

                        ASSERT(MAXNAMELEN >= ep->Buffer.Length);
                        bcopy(ep->Buffer.Pointer, fp, ep->Buffer.Length);
                } else {
                        ACPI_DRV_DBG(CE_WARN, NULL,
                            "Bad field at offset %d: type %d",
                            dp->offset, ep->Type);
                        if (dp->type != ACPI_TYPE_STRING) {
                                return (ACPI_DRV_ERR);
                        }
                }
                ep++;
        }

        return (ACPI_DRV_OK);
}

/*
 * Returns the current power source devices. Used for the AC adapter and is
 * located under the AC adapter object in name space. Used to determine if
 * system is running off the AC adapter. This will report that the system is
 * not running on the AC adapter if any of the batteries in the system is
 * being forced to discharge through _BMC.
 *
 * Return value:
 *       0 -- Off-line, ie. battery supplying system power
 *       1 -- On-line, ie. AC supplying system power
 *      -1 -- Unknown, some error ocurred.
 * Note: It will also update the driver ac state.
 */
static int
acpi_drv_get_psr(struct acpi_drv_ac_state *acp)
{
        struct acpi_drv_dev *devp = &acp->dev;
        int ac;

        if (!devp->valid) {
                ACPI_DRV_DBG(CE_WARN, NULL, "device not valid");
                return (-1);
        }

        if (acpica_eval_int(devp->hdl, "_PSR", &ac) == AE_OK) {
                ACPI_DRV_DBG(CE_NOTE, devp, "_PSR = %d", ac);
                devp->present = ac;
        } else {
                ACPI_DRV_DBG(CE_WARN, NULL, "AcpiEval _PSR failed");
                devp->present = -1;
        }

        return (ac);
}

/*
 * For most systems, the _STA for this device will always
 * return a value with bits 0-3 set and will toggle bit 4
 * to indicate the actual presence of a battery.
 *
 * Return value:
 *       0 -- battery not present
 *       1 -- battery present
 *      -1 -- Unknown, some error ocurred.
 * Note: It will also update the driver cbat state.
 */
static int
acpi_drv_get_sta(struct acpi_drv_cbat_state *bp)
{
        struct acpi_drv_dev *devp = &bp->dev;
        int val;

        if (!devp->valid) {
                ACPI_DRV_DBG(CE_WARN, NULL, "device not valid");
                return (-1);
        }

        if (acpica_eval_int(devp->hdl, "_STA", &val) == AE_OK) {
                ACPI_DRV_DBG(CE_NOTE, devp, "_STA = 0x%x", val);
                devp->present = ((val & STA_FLAG_BATT_PRESENT) != 0);
        } else {
                ACPI_DRV_DBG(CE_WARN, NULL, "AcpiEval _STA failed");
                devp->present = -1;
        }

        return (val);
}

static int
acpi_drv_update_bif(struct acpi_drv_cbat_state *bp)
{
        ACPI_BUFFER buf;
        ACPI_OBJECT *objp;

        /* BIF is only available when battery plugged */
        ASSERT(bp->dev.present != 0);

        /* Update internal BIF cache */
        bp->bat_bifok = ACPI_DRV_NTF_UNKNOWN;

        buf.Length = ACPI_ALLOCATE_BUFFER;
        if (ACPI_FAILURE(AcpiEvaluateObjectTyped(bp->dev.hdl, "_BIF",
            NULL, &buf, ACPI_TYPE_PACKAGE))) {
                ACPI_DRV_DBG(CE_WARN, NULL, "AcpiEval _BIF failed");
                return (ACPI_DRV_ERR);
        }

        objp = buf.Pointer;
        ACPI_DRV_DBG(CE_NOTE, &bp->dev, "get _BIF");
        if (acpi_drv_obj_copy(objp, (char *)&bp->bif_cache, bif_desc) ==
            ACPI_DRV_ERR) {
                AcpiOsFree(objp);
                return (ACPI_DRV_ERR);
        }
        AcpiOsFree(objp);
        bp->bat_bifok = ACPI_DRV_NTF_OK;
        return (ACPI_DRV_OK);
}

static int
acpi_drv_update_bst(struct acpi_drv_cbat_state *bp)
{
        ACPI_BUFFER buf;
        ACPI_OBJECT *objp;

        /* BST is only available when battery plugged */
        ASSERT(bp->dev.present != 0);

        /* Update internal BST cache */
        bp->bat_bstok = ACPI_DRV_NTF_UNKNOWN;

        buf.Length = ACPI_ALLOCATE_BUFFER;
        if (ACPI_FAILURE(AcpiEvaluateObjectTyped(bp->dev.hdl, "_BST",
            NULL, &buf, ACPI_TYPE_PACKAGE))) {
                ACPI_DRV_DBG(CE_WARN, NULL, "AcpiEval _BST failed");
                return (ACPI_DRV_ERR);
        }

        objp = buf.Pointer;
        ACPI_DRV_DBG(CE_NOTE, &bp->dev, "get _BST");
        if (acpi_drv_obj_copy(objp, (char *)&bp->bst_cache, bst_desc) ==
            ACPI_DRV_ERR) {
                AcpiOsFree(objp);
                return (ACPI_DRV_ERR);
        }
        AcpiOsFree(objp);

        if (bp->bst_cache.bst_rate == 0) {
                bp->bst_cache.bst_state &= ~(ACPI_DRV_BST_CHARGING |
                    ACPI_DRV_BST_DISCHARGING);
        }
        bp->bat_bstok = ACPI_DRV_NTF_OK;
        return (ACPI_DRV_OK);
}

/*
 * Return value:
 *       1 -- device On-line
 *       0 -- device Off-line
 *      -1 -- Unknown, some error ocurred.
 */
static int
acpi_drv_dev_present(struct acpi_drv_dev *devp)
{
        if (!devp->valid) {
                ACPI_DRV_DBG(CE_WARN, NULL, "device not valid");
                return (-1);
        }

        ASSERT(devp->type != ACPI_DRV_TYPE_UNKNOWN);

        /* Update the device state */
        if (devp->present == -1) {
                if (devp->type == ACPI_DRV_TYPE_AC) {
                        (void) acpi_drv_get_psr((struct acpi_drv_ac_state *)
                            devp);
                } else if (devp->type == ACPI_DRV_TYPE_CBAT) {
                        (void) acpi_drv_get_sta((struct acpi_drv_cbat_state *)
                            devp);
                }
        }

        return (devp->present);
}

/*
 * Check if the device p existance state has changed.
 * Return value:
 *       1 -- changed
 *       0 -- no change
 *      -1 -- unknown
 */
static int
acpi_drv_update_present(struct acpi_drv_dev *p)
{
        int old_present = p->present;
        int new_present;

        ASSERT(p && p->valid);

        p->present = -1;
        new_present = acpi_drv_dev_present(p);
        if (new_present == -1) {
                return (-1);
        }
        if (new_present != old_present) {
                return (1);
        }
        return (0);
}

static void
acpi_drv_set_psr(struct acpi_drv_dev *p)
{
        acpi_drv_psr_devp = p;
        if (p != NULL) {
                ACPI_DRV_DBG(CE_NOTE, p, "psr = .");
                acpi_drv_psr_type = p->type;
        } else {
                ACPI_DRV_DBG(CE_NOTE, p, "psr = ?");
                acpi_drv_psr_type = ACPI_DRV_TYPE_UNKNOWN;
        }
}

/*
 * OSPM can determine independent warning and low battery
 * capacity values based on the OEM-designed levels, but
 * cannot set these values lower than the OEM-designed values.
 */
static int
acpi_drv_set_warn(acpi_drv_warn_t *bwp)
{
        uint32_t warn, low;

        warn = acpi_drv_syn_last_cap * bwp->bw_charge_warn / 100;
        low = acpi_drv_syn_last_cap * bwp->bw_charge_low / 100;

        /* Update internal state */
        if (bwp->bw_enabled) {
                if (low >= warn || warn < acpi_drv_syn_oem_warn_cap ||
                    low < acpi_drv_syn_oem_low_cap) {
                        ACPI_DRV_DBG(CE_WARN, NULL, "charge level error");
                        return (EINVAL);
                }

                ACPI_DRV_DBG(CE_NOTE, NULL, "set warn: warn=%d low=%d", warn,
                    low);

                acpi_drv_syn_warn_per = bwp->bw_charge_warn;
                acpi_drv_syn_low_per = bwp->bw_charge_low;
                acpi_drv_syn_warn_cap = warn;
                acpi_drv_syn_low_cap = low;
                acpi_drv_warn_enabled = 1;
        } else {
                acpi_drv_warn_enabled = 0;
        }

        return (0);
}

/*
 * Update information for the synthesis battery
 *
 * Note: Sometimes the value to be returned from _BST or _BIF will be
 * temporarily unknown. In this case, the method may return the value
 * 0xFFFFFFFF as a placeholder. When the value becomes known, the
 * appropriate notification (0x80 for _BST or 0x81 for BIF) should be
 * issued, in like manner to any other change in the data returned by
 * these methods. This will cause OSPM to re-evaluate the method obtaining
 * the correct data value.
 */
static void
acpi_drv_update_cap(int bif_changed)
{
        struct acpi_drv_cbat_state *bp;

        if (bif_changed != 0) {
                acpi_drv_syn_oem_warn_cap = 0xffffffff;
                acpi_drv_syn_oem_low_cap = 0xffffffff;
                acpi_drv_syn_last_cap = 0xffffffff;
        }
        acpi_drv_syn_last_level = acpi_drv_syn_rem_cap;
        acpi_drv_syn_rem_cap = 0xffffffff; /* initially unknown */

        for (bp = &acpi_drv_cbat[0]; bp < &acpi_drv_cbat[ACPI_DRV_MAX_BAT_NUM];
            bp++) {
                if (bp->dev.valid) {
                        /* Escape the empty bays */
                        if (acpi_drv_cbat_present(bp) <= 0) {
                                continue;
                        }

                        if (bif_changed != 0 &&
                            bp->bat_bifok == ACPI_DRV_NTF_OK) {
                                acpi_bif_t *bif;

                                bif = &bp->bif_cache;

                                if (acpi_drv_syn_last_cap == 0xffffffff) {
                                        acpi_drv_syn_last_cap = 0;
                                }
                                acpi_drv_syn_last_cap += bif->bif_last_cap;

                                if (bif->bif_warn_cap == 0xffffffff ||
                                    bif->bif_low_cap == 0xffffffff) {
                                        ACPI_DRV_DBG(CE_WARN, &bp->dev,
                                            "BIF value "
                                            "invalid, warn_cap=0x%x "
                                            "low_cap=0x%x", bif->bif_warn_cap,
                                            bif->bif_low_cap);
                                        continue;
                                }
                                if (acpi_drv_syn_oem_warn_cap == 0xffffffff) {
                                        acpi_drv_syn_oem_warn_cap = 0;
                                }
                                if (acpi_drv_syn_oem_low_cap == 0xffffffff) {
                                        acpi_drv_syn_oem_low_cap = 0;
                                }

                                /*
                                 * Use the highest level as the synthesis
                                 * level.
                                 */
                                if (bif->bif_warn_cap >
                                    acpi_drv_syn_oem_warn_cap) {
                                        acpi_drv_syn_oem_low_cap =
                                            bif->bif_low_cap;
                                        acpi_drv_syn_oem_warn_cap =
                                            bif->bif_warn_cap;
                                }
                        }
#ifdef DEBUG
                        else if (bif_changed) {
                                ACPI_DRV_DBG(CE_NOTE, &bp->dev,
                                    "BIF not ready");
                        }
#endif

                        if (bp->bat_bstok == ACPI_DRV_NTF_OK) {
                                acpi_bst_t *bst;

                                bst = &bp->bst_cache;

                                /*
                                 * Batteries that are rechargeable and are in
                                 * the discharging state are required to return
                                 * a valid Battery Present Rate value.
                                 * 0xFFFFFFFF - Unknown rate/capacity
                                 */
                                if (bst->bst_rem_cap == 0xffffffff) {
                                        ACPI_DRV_DBG(CE_WARN, &bp->dev,
                                            "BST value invalid, "
                                            "rate=0x%x cap=0x%x",
                                            bst->bst_rate, bst->bst_rem_cap);
                                        continue;
                                }

                                if (acpi_drv_syn_rem_cap == 0xffffffff) {
                                        acpi_drv_syn_rem_cap = 0;
                                }
                                acpi_drv_syn_rem_cap += bst->bst_rem_cap;
                                /* Check for overflow */
                                ASSERT(acpi_drv_syn_rem_cap >=
                                    bst->bst_rem_cap);
                        }
#ifdef DEBUG
                        else {
                                ACPI_DRV_DBG(CE_NOTE, &bp->dev,
                                    "BST not ready");
                        }
#endif
                }
        }

        ACPI_DRV_DBG(CE_NOTE, NULL, "syn_cap: %d syn_oem_warn: %d "
            "syn_oem_low: %d", acpi_drv_syn_rem_cap, acpi_drv_syn_oem_warn_cap,
            acpi_drv_syn_oem_low_cap);
}

static struct acpi_drv_cbat_state *
acpi_drv_idx2cbat(int idx)
{
        if (idx >= ACPI_DRV_MAX_BAT_NUM) {
                return (NULL);
        }
        return (&acpi_drv_cbat[idx]);
}

static struct acpi_drv_ac_state *
acpi_drv_idx2ac(int idx)
{
        if (idx >= ACPI_DRV_MAX_AC_NUM) {
                return (NULL);
        }
        return (&acpi_drv_ac[idx]);
}

/*ARGSUSED*/
static void
acpi_drv_cbat_notify(ACPI_HANDLE hdl, UINT32 val, void *ctx)
{
        struct acpi_drv_cbat_state *bp = ctx;
        struct acpi_drv_dev *devp = &bp->dev;
        int bif_changed;
        uint32_t eval;
        char *ev;
        acpi_bst_t *bst;

        mutex_enter(&acpi_drv_mutex);
        ACPI_DRV_PRT_NOTIFY(hdl, val);

        switch (val) {
        /*
         * BST has changed
         * Whenever the Battery State value changes, the
         * system will generate an SCI to notify the OS.
         *
         * Note: trip point is not used to implement the
         * warning levels.
         */
        case 0x80:
                /*
                 * We always get 0x80 and 0x81 at battery plug/unplug,
                 * but 0x80 may come first. In case that situation, we have
                 * to update battery present state here too to update bst
                 * correctly.
                 */
                bif_changed = acpi_drv_update_present(devp);

                if (devp->present == 0) {
                        if (acpi_drv_psr_devp == devp) {
                                acpi_drv_set_psr(NULL);
                        }
                        goto done;
                }

                if (acpi_drv_update_bst(bp) != ACPI_DRV_OK) {
                        break;
                }
                acpi_drv_update_cap(bif_changed);

                bst = &bp->bst_cache;
                eval = bst->bst_rem_cap;

                if (bst->bst_state & BST_FLAG_DISCHARGING) {
                        acpi_drv_set_psr(devp);
                }
                /*
                 * The Critical battery state indicates that all
                 * available batteries are discharged and do not
                 * appear to be able to supply power to run the
                 * system any longer. When this occurs, the OS
                 * should attempt to perform an emergency shutdown.
                 * Right now we do not shutdown.  This would
                 * need some discussion first since it could be
                 * controversial.
                 */
#ifdef DEBUG
                if (bst->bst_state & BST_FLAG_CRITICAL) {
                        ACPI_DRV_DBG(CE_WARN, devp, "BST_FLAG_CRITICAL set");

                        /*
                         * BST_FLAG_CRITICAL may set even with AC,
                         * plugged, when plug/unplug battery. Check
                         * to avoid erroneous shutdown.
                         */
                        if (acpi_drv_psr_devp == devp &&
                            bst->bst_rem_cap != 0xffffffff) {
                                ACPI_DRV_DBG(CE_WARN, NULL,
                                    "Battery in critical state");
                        }
                } else
#endif
                if (acpi_drv_warn_enabled &&
                    (bst->bst_state & BST_FLAG_DISCHARGING)) {
                        /*
                         * This value is an estimation of the amount of
                         * energy or battery capacity required by the
                         * system to transition to any supported sleeping
                         * state. When the OS detects that the total
                         * available battery capacity is less than this
                         * value, it will transition the system to a user
                         * defined system state (S1-S5).
                         */
                        if (acpi_drv_syn_last_level > acpi_drv_syn_low_cap &&
                            acpi_drv_syn_rem_cap <= acpi_drv_syn_low_cap) {
                                acpi_drv_gen_sysevent(devp, ESC_PWRCTL_LOW,
                                    eval);
                        /*
                         * When the total available energy (mWh) or capacity
                         * (mAh) in the batteries falls below this level,
                         * the OS will notify the user through the UI.
                         */
                        } else if (acpi_drv_syn_last_level >
                            acpi_drv_syn_warn_cap &&
                            acpi_drv_syn_rem_cap <= acpi_drv_syn_warn_cap) {
                                acpi_drv_gen_sysevent(devp, ESC_PWRCTL_WARN,
                                    eval);
                        }
                }

done:
                acpi_drv_gen_sysevent(devp, ESC_PWRCTL_STATE_CHANGE, 0);
                pollwakeup(&acpi_drv_pollhead, ACPI_DRV_EVENTS);
                break;

        /* battery has been removed completely */
        case 0x03:
        /* BIF has changed */
        case 0x81:
                /*
                 * Note: Do not eliminate multiple ADD/REMOVE here,
                 * because they may corresponding to different batterys.
                 */
                (void) acpi_drv_update_present(devp);
                if (devp->present == 1) {
                        if (acpi_drv_update_bif(bp) != ACPI_DRV_OK) {
                                break;
                        }
                }

                acpi_drv_update_cap(1);

                eval = devp->present;
                ev = eval ? ESC_PWRCTL_ADD : ESC_PWRCTL_REMOVE;
                acpi_drv_gen_sysevent(devp, ev, 0);
                pollwakeup(&acpi_drv_pollhead, ACPI_DRV_EVENTS);
                break;

        case 0x82:
        default:
                break;
        }

        mutex_exit(&acpi_drv_mutex);
}

static int
acpi_drv_update_lid(struct acpi_drv_dev *p)
{
        struct acpi_drv_lid_state *lp = (struct acpi_drv_lid_state *)p;

        if (acpica_eval_int(p->hdl, "_LID", &lp->state) == AE_OK) {
                lp->state_ok = ACPI_DRV_NTF_OK;
                return (ACPI_DRV_OK);
        }
        return (ACPI_DRV_ERR);
}

/*ARGSUSED*/
static void
acpi_drv_ac_notify(ACPI_HANDLE hdl, UINT32 val, void *ctx)
{
        struct acpi_drv_ac_state *acp = ctx;
        struct acpi_drv_dev *devp = &acp->dev;
        int old_present;
        char *ev;
        int eval;

        ACPI_DRV_PRT_NOTIFY(hdl, val);
        if (val != 0x80) {
                return;
        }

        mutex_enter(&acpi_drv_mutex);
        /*
         * Note: if unplug and then quickly plug back, two ADD
         * events will be generated.
         */
        old_present = devp->present;
        eval = acpi_drv_get_psr(acp);

        /* Eliminate redundant events */
        if (eval != -1 && eval != old_present) {
                /* Keep tracking the current power source device */
                if (eval == 1) {
                        ev = ESC_PWRCTL_ADD;
                        acpi_drv_set_psr(devp);
                } else {
                        ev = ESC_PWRCTL_REMOVE;
                        /* If AC was supplying the power, it's not now */
                        if (acpi_drv_psr_devp == devp) {
                                acpi_drv_set_psr(NULL);
                        }
                }

                acpi_drv_gen_sysevent(devp, ev, 0);
                pollwakeup(&acpi_drv_pollhead, ACPI_DRV_EVENTS);
        }

        mutex_exit(&acpi_drv_mutex);
}

static void
acpi_drv_lid_notify(ACPI_HANDLE hdl, UINT32 val, void *ctx)
{
        struct acpi_drv_lid_state *p = ctx;

        ACPI_DRV_PRT_NOTIFY(hdl, val);
        if (val == 0x80) {
                mutex_enter(&acpi_drv_mutex);
                if (acpi_drv_update_lid(&p->dev) == ACPI_DRV_OK) {
                        acpi_drv_gen_sysevent(&p->dev, p->state ?
                            ESC_PWRCTL_ADD : ESC_PWRCTL_REMOVE, 0);
                }
                mutex_exit(&acpi_drv_mutex);
        }
}

static int
acpi_drv_obj_init(struct acpi_drv_dev *p)
{
        ACPI_DEVICE_INFO *info;
        ACPI_NOTIFY_HANDLER ntf_handler = NULL;
        ACPI_STATUS ret;
        char name[KSTAT_STRLEN];

        ASSERT(p != NULL && p->hdl != NULL);

        p->valid = 0;

        /* Info size is variable depending on existance of _CID */
        ret = AcpiGetObjectInfo(p->hdl, &info);
        if (ACPI_FAILURE(ret)) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "AcpiGetObjectInfo() fail: %d", (int32_t)ret);
                return (ACPI_DRV_ERR);
        }

        if ((info->Valid & ACPI_VALID_HID) == 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "AcpiGetObjectInfo(): _HID not available");
                p->hid[0] = 0;
        } else {
                (void) strlcpy(p->hid, info->HardwareId.String, ID_LEN);
        }

        /*
         * This object is optional, but is required when the device
         * has no other way to report a persistent unique device ID.
         */
        if ((info->Valid & ACPI_VALID_UID) == 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "AcpiGetObjectInfo(): _UID not available");
                /* Use 0 as the default _UID */
                p->uid[0] = 0;
        } else {
                (void) strlcpy(p->uid, info->UniqueId.String, ID_LEN);
        }

        AcpiOsFree(info);
        p->valid = 1;

        if (strcmp(p->hid, ACPI_DEVNAME_CBAT) == 0) {
                struct acpi_drv_cbat_state *bp =
                    (struct acpi_drv_cbat_state *)p;
                kstat_t *ksp;

                p->type = ACPI_DRV_TYPE_CBAT;
                p->index = nbat - 1;

                /* Update device present state */
                (void) acpi_drv_update_present(p);
                if (p->present) {
                        (void) acpi_drv_update_bif(bp);
                        (void) acpi_drv_update_bst(bp);

                        /* Init the current power source */
                        if (bp->bst_cache.bst_state & BST_FLAG_DISCHARGING) {
                                acpi_drv_set_psr(p);
                        }
                }
                ntf_handler = acpi_drv_cbat_notify;
                ACPI_DRV_DBG(CE_NOTE, p, "battery %s",
                    (p->present ? "present" : "absent"));

                /* Create minor node for battery */
                (void) snprintf(name, sizeof (name), "battery%d", p->index);
                if (ddi_create_minor_node(acpi_drv_dip, name, S_IFCHR,
                    MINOR_BATT(p->index), DDI_PSEUDO, 0) == DDI_FAILURE)
                        ACPI_DRV_DBG(CE_WARN, NULL,
                            "%s: minor node create failed", name);

                /*
                 * Allocate, initialize and install BIF and BST kstat
                 */
                /* BIF kstat */
                (void) snprintf(name, KSTAT_STRLEN-1, "%s%d",
                    ACPI_DRV_BIF_KSTAT_NAME, bp->dev.index);
                ksp = kstat_create(ACPI_DRV_NAME, 0, name, "misc",
                    KSTAT_TYPE_NAMED,
                    sizeof (acpi_drv_bif_kstat) / sizeof (kstat_named_t),
                    KSTAT_FLAG_VIRTUAL);
                if (ksp != NULL) {
                        ACPI_DRV_DBG(CE_NOTE, NULL, "kstat_create(%s) ok",
                            name);

                        bp->bat_bif_ksp = ksp;
                        ksp->ks_data = &acpi_drv_bif_kstat;
                        ksp->ks_update = acpi_drv_kstat_bif_update;
                        ksp->ks_data_size += MAXNAMELEN * 4;
                        ksp->ks_private = bp;

                        kstat_install(ksp);
                } else {
                        ACPI_DRV_DBG(CE_WARN, NULL,
                            "kstat_create(%s) fail", name);
                }

                /* BST kstat */
                (void) snprintf(name, KSTAT_STRLEN-1, "%s%d",
                    ACPI_DRV_BST_KSTAT_NAME, bp->dev.index);
                ksp = kstat_create(ACPI_DRV_NAME, 0, name, "misc",
                    KSTAT_TYPE_NAMED,
                    sizeof (acpi_drv_bst_kstat) / sizeof (kstat_named_t),
                    KSTAT_FLAG_VIRTUAL);
                if (ksp != NULL) {
                        ACPI_DRV_DBG(CE_NOTE, NULL, "kstat_create(%s) ok",
                            name);

                        bp->bat_bst_ksp = ksp;
                        ksp->ks_data = &acpi_drv_bst_kstat;
                        ksp->ks_update = acpi_drv_kstat_bst_update;
                        ksp->ks_data_size += MAXNAMELEN * 4;
                        ksp->ks_private = bp;

                        kstat_install(ksp);
                } else {
                        ACPI_DRV_DBG(CE_WARN, NULL,
                            "kstat_create(%s) fail", name);
                }
        } else if (strcmp(p->hid, ACPI_DEVNAME_AC) == 0) {
                p->type = ACPI_DRV_TYPE_AC;
                p->index = nac - 1;

                /* Update device present state */
                (void) acpi_drv_update_present(p);
                if (p->present) {
                        /* Init the current power source */
                        acpi_drv_set_psr(p);
                }
                ntf_handler = acpi_drv_ac_notify;
                ACPI_DRV_DBG(CE_NOTE, p, "AC %s",
                    (p->present ? "on-line" : "off-line"));

                /* Create minor node for AC */
                (void) snprintf(name, sizeof (name), "ac%d", p->index);
                if (ddi_create_minor_node(acpi_drv_dip, name, S_IFCHR,
                    MINOR_AC(p->index), DDI_PSEUDO, 0) == DDI_FAILURE)
                        ACPI_DRV_DBG(CE_WARN, NULL,
                            "%s: minor node create failed", name);
        } else if (strcmp(p->hid, ACPI_DEVNAME_LID) == 0) {
                p->type = ACPI_DRV_TYPE_LID;
                p->index = 0;
                lid.state_ok = ACPI_DRV_NTF_UNKNOWN;
                (void) acpi_drv_update_lid(p);
                ntf_handler = acpi_drv_lid_notify;
                ACPI_DRV_DBG(CE_NOTE, p, "added");

                /* Create minor node for lid. */
                if (ddi_create_minor_node(acpi_drv_dip, "lid", S_IFCHR,
                    MINOR_LID(p->index), DDI_PSEUDO, 0) == DDI_FAILURE)
                        ACPI_DRV_DBG(CE_WARN, NULL,
                            "lid: minor node create failed");
        } else {
                ACPI_DRV_DBG(CE_NOTE, p, "unknown device");
                p->valid = 0;
        }

        /* Register ACPI battery related events */
        if (ntf_handler != NULL) {
                if (ACPI_FAILURE(AcpiInstallNotifyHandler(p->hdl,
                    ACPI_ALL_NOTIFY, ntf_handler, p))) {
                        ACPI_DRV_DBG(CE_NOTE, NULL,
                            "Notify handler for %s.%s install failed",
                            p->hid, p->uid);
                        return (ACPI_DRV_ERR);
                }
        }

        return (ACPI_DRV_OK);
}

/*ARGSUSED*/
static ACPI_STATUS
acpi_drv_find_cb(ACPI_HANDLE ObjHandle, UINT32 NestingLevel, void *Context,
    void **ReturnValue)
{
        struct acpi_drv_dev *devp;
        int *type = (int *)Context;

        if (*type == ACPI_DRV_TYPE_CBAT) {
                struct acpi_drv_cbat_state *bp;

                for (bp = acpi_drv_cbat;
                    bp != &acpi_drv_cbat[ACPI_DRV_MAX_BAT_NUM];
                    bp++)
                        if (bp->dev.hdl == ObjHandle)
                                return (AE_OK);

                if (nbat == ACPI_DRV_MAX_BAT_NUM) {
                        ACPI_DRV_DBG(CE_WARN, NULL,
                            "Need to support more batteries: "
                            "BATTERY_MAX = %d", ACPI_DRV_MAX_BAT_NUM);
                        return (AE_LIMIT);
                }
                bp = &acpi_drv_cbat[nbat++];
                devp = (struct acpi_drv_dev *)bp;
        } else if (*type == ACPI_DRV_TYPE_AC) {
                struct acpi_drv_ac_state *ap;

                for (ap = acpi_drv_ac;
                    ap != &acpi_drv_ac[ACPI_DRV_MAX_AC_NUM];
                    ap++)
                        if (ap->dev.hdl == ObjHandle)
                                return (AE_OK);

                if (nac == ACPI_DRV_MAX_AC_NUM) {
                        ACPI_DRV_DBG(CE_WARN, NULL, "Need to support more ACs: "
                            "AC_MAX = %d", ACPI_DRV_MAX_AC_NUM);
                        return (AE_LIMIT);
                }
                ap = &acpi_drv_ac[nac++];
                devp = (struct acpi_drv_dev *)ap;
        } else if (*type == ACPI_DRV_TYPE_LID) {
                struct acpi_drv_lid_state *lp;

                lp = &lid;
                if (lp->dev.hdl == ObjHandle)
                        return (AE_OK);

                nlid++;
                devp = (struct acpi_drv_dev *)lp;
        } else {
                ACPI_DRV_DBG(CE_WARN, NULL, "acpi_drv_find_cb(): "
                    "Unknown device");
                return (AE_ERROR);
        }

        devp->hdl = ObjHandle;

        /* Try to get as many working objs as possible */
        (void) acpi_drv_obj_init(devp);
        return (AE_OK);
}

/*ARGSUSED*/
static void
acpi_drv_cbat_rescan(void *arg)
{
        int *retp, type = ACPI_DRV_TYPE_CBAT;

        mutex_enter(&acpi_drv_mutex);

        /*
         * The detach routine clears the timeout id to tell us not to
         * reschedule ourselves. If thats the case there's also no point
         * in looking for new ACPI battery devices, so just return.
         */
        if (acpi_drv_cbat_rescan_timeout == 0) {
                mutex_exit(&acpi_drv_mutex);
                return;
        }

        (void) AcpiGetDevices(ACPI_DEVNAME_CBAT, acpi_drv_find_cb, &type,
            (void *)&retp);

        acpi_drv_cbat_rescan_timeout = timeout(acpi_drv_cbat_rescan, NULL,
            drv_usectohz(MICROSEC));
        mutex_exit(&acpi_drv_mutex);
}

static int
acpi_drv_acpi_init(void)
{
        int *retp, type;
        int status = ACPI_DRV_ERR;
        hotkey_drv_t *htkp;

        /* Check to see if ACPI CA services are available */
        if (AcpiSubsystemStatus() != AE_OK) {
                ACPI_DRV_DBG(CE_WARN, NULL, "ACPI CA not ready");
                return (status);
        }

        /* Init Control Method Batterys */
        type = ACPI_DRV_TYPE_CBAT;
        if (ACPI_SUCCESS(AcpiGetDevices(ACPI_DEVNAME_CBAT, acpi_drv_find_cb,
            &type, (void *)&retp)) && nbat) {
                status = ACPI_DRV_OK;
        }

        /* Init AC */
        type = ACPI_DRV_TYPE_AC;
        if (ACPI_SUCCESS(AcpiGetDevices(ACPI_DEVNAME_AC, acpi_drv_find_cb,
            &type, (void *)&retp)) && nac) {
                status = ACPI_DRV_OK;
        }

        /* Init LID */
        type = ACPI_DRV_TYPE_LID;
        if (ACPI_SUCCESS(AcpiGetDevices(ACPI_DEVNAME_LID, acpi_drv_find_cb,
            &type, (void *)&retp)) && nlid) {
                status = ACPI_DRV_OK;
        }

        /* Init Hotkey Device */
        type = ACPI_DRV_TYPE_HOTKEY;
        htkp = &acpi_hotkey;
        bzero(htkp, sizeof (hotkey_drv_t));
        htkp->dip = acpi_drv_dip;
        htkp->hotkey_lock = &acpi_drv_mutex;
        if (hotkey_init(htkp) == ACPI_DRV_OK) {
                status = ACPI_DRV_OK;
        }

        acpi_drv_update_cap(1);

        return (status);
}

static void
acpi_drv_acpi_fini(void)
{
        int i;
        struct acpi_drv_cbat_state *bp;

        for (bp = &acpi_drv_cbat[0]; bp < &acpi_drv_cbat[ACPI_DRV_MAX_BAT_NUM];
            bp++) {
                if (bp->dev.valid) {
                        (void) AcpiRemoveNotifyHandler(bp->dev.hdl,
                            ACPI_DEVICE_NOTIFY, acpi_drv_cbat_notify);
                }
        }
        for (i = 0; i < nac; i++) {
                (void) AcpiRemoveNotifyHandler(acpi_drv_ac[i].dev.hdl,
                    ACPI_DEVICE_NOTIFY, acpi_drv_ac_notify);
        }
        (void) AcpiRemoveNotifyHandler(lid.dev.hdl, ACPI_DEVICE_NOTIFY,
            acpi_drv_lid_notify);

        if (acpi_hotkey.hotkey_method != HOTKEY_METHOD_NONE)
                (void) hotkey_fini(&acpi_hotkey);
}

/*ARGSUSED*/
static int
acpi_drv_kstat_power_update(kstat_t *ksp, int flag)
{
        if (flag == KSTAT_WRITE) {
                return (EACCES);
        }

        mutex_enter(&acpi_drv_mutex);
        if (acpi_drv_psr_type == ACPI_DRV_TYPE_UNKNOWN) {
                mutex_exit(&acpi_drv_mutex);
                return (EIO);
        }
        kstat_named_setstr(&acpi_drv_power_kstat.acpi_drv_power,
            acpi_drv_psr_type == ACPI_DRV_TYPE_AC ? AC : BATTERY);
        acpi_drv_power_kstat.acpi_drv_supported_battery_count.value.ui32 =
            (uint32_t)nbat;
        mutex_exit(&acpi_drv_mutex);

        return (0);
}

/*ARGSUSED*/
static int
acpi_drv_kstat_warn_update(kstat_t *ksp, int flag)
{
        if (flag == KSTAT_WRITE) {
                int ret = 0;
                acpi_drv_warn_t bw;
                acpi_drv_warn_kstat_t kbw;

                kbw = *(acpi_drv_warn_kstat_t *)acpi_drv_warn_ksp->ks_data;

                mutex_enter(&acpi_drv_mutex);
                bw.bw_enabled  = kbw.acpi_drv_bw_enabled.value.ui32;
                bw.bw_charge_warn = kbw.acpi_drv_bw_charge_warn.value.ui32;
                bw.bw_charge_low = kbw.acpi_drv_bw_charge_low.value.ui32;
                ret = acpi_drv_set_warn(&bw);
                mutex_exit(&acpi_drv_mutex);

                return (ret);
        } else {
                acpi_drv_warn_kstat_t *wp = &acpi_drv_warn_kstat;

                mutex_enter(&acpi_drv_mutex);
                wp->acpi_drv_bw_enabled.value.ui32 = acpi_drv_warn_enabled;
                wp->acpi_drv_bw_charge_warn.value.ui32 = acpi_drv_syn_warn_per;
                wp->acpi_drv_bw_charge_low.value.ui32 = acpi_drv_syn_low_per;
                mutex_exit(&acpi_drv_mutex);

                return (0);
        }
}

static int
acpi_drv_kstat_bif_update(kstat_t *ksp, int flag)
{
        struct acpi_drv_cbat_state *bp;
        acpi_bif_t *bif;
        acpi_drv_bif_kstat_t *kp;

        if (flag == KSTAT_WRITE) {
                return (EACCES);
        }

        bp = (struct acpi_drv_cbat_state *)ksp->ks_private;
        mutex_enter(&acpi_drv_mutex);

        if (acpi_drv_cbat_present(bp) <= 0) {
                mutex_exit(&acpi_drv_mutex);
                return (ENXIO);
        }

        bzero(&bif, sizeof (bif));
        if (acpi_drv_update_bif(bp) != ACPI_DRV_OK) {
                mutex_exit(&acpi_drv_mutex);
                return (ENXIO);
        }

        bif = &bp->bif_cache;
        kp = &acpi_drv_bif_kstat;

        /* Update BIF */
        kp->acpi_drv_bif_unit.value.ui32 = bif->bif_unit;
        kp->acpi_drv_bif_design_cap.value.ui32 = bif->bif_design_cap;
        kp->acpi_drv_bif_last_cap.value.ui32 = bif->bif_last_cap;
        kp->acpi_drv_bif_tech.value.ui32 = bif->bif_tech;
        kp->acpi_drv_bif_voltage.value.ui32 = bif->bif_voltage;
        kp->acpi_drv_bif_warn_cap.value.ui32 = bif->bif_warn_cap;
        kp->acpi_drv_bif_low_cap.value.ui32 = bif->bif_low_cap;
        kp->acpi_drv_bif_gran1_cap.value.ui32 = bif->bif_gran1_cap;
        kp->acpi_drv_bif_gran2_cap.value.ui32 = bif->bif_gran2_cap;

        kstat_named_setstr(&kp->acpi_drv_bif_model, bif->bif_model);
        kstat_named_setstr(&kp->acpi_drv_bif_serial, bif->bif_serial);
        kstat_named_setstr(&kp->acpi_drv_bif_type, bif->bif_type);
        kstat_named_setstr(&kp->acpi_drv_bif_oem_info, bif->bif_oem_info);

        mutex_exit(&acpi_drv_mutex);
        return (0);
}

static int
acpi_drv_kstat_bst_update(kstat_t *ksp, int flag)
{
        struct acpi_drv_cbat_state *bp;
        acpi_bst_t *bst;
        acpi_drv_bst_kstat_t *kp;

        if (flag == KSTAT_WRITE) {
                return (EACCES);
        }

        bp = (struct acpi_drv_cbat_state *)ksp->ks_private;
        mutex_enter(&acpi_drv_mutex);

        if (acpi_drv_cbat_present(bp) <= 0) {
                mutex_exit(&acpi_drv_mutex);
                return (ENXIO);
        }

        bzero(&bst, sizeof (bst));
        if (acpi_drv_update_bst(bp) != ACPI_DRV_OK) {
                mutex_exit(&acpi_drv_mutex);
                return (ENXIO);
        }

        bst = &bp->bst_cache;
        kp = &acpi_drv_bst_kstat;

        /* Update BST */
        kp->acpi_drv_bst_state.value.ui32 = bst->bst_state;
        kp->acpi_drv_bst_rate.value.ui32 = bst->bst_rate;
        kp->acpi_drv_bst_rem_cap.value.ui32 = bst->bst_rem_cap;
        kp->acpi_drv_bst_voltage.value.ui32 = bst->bst_voltage;

        mutex_exit(&acpi_drv_mutex);
        return (0);
}

static int
acpi_drv_kstat_init(void)
{
        /*
         * Allocate, initialize and install powerstatus and
         * supported_battery_count kstat.
         */
        acpi_drv_power_ksp = kstat_create(ACPI_DRV_NAME, 0,
            ACPI_DRV_POWER_KSTAT_NAME, "misc",
            KSTAT_TYPE_NAMED,
            sizeof (acpi_drv_power_kstat) / sizeof (kstat_named_t),
            KSTAT_FLAG_VIRTUAL);
        if (acpi_drv_power_ksp == NULL) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "kstat_create(%s) fail", ACPI_DRV_POWER_KSTAT_NAME);
                return (ACPI_DRV_ERR);
        }

        acpi_drv_power_ksp->ks_data = &acpi_drv_power_kstat;
        acpi_drv_power_ksp->ks_update = acpi_drv_kstat_power_update;
        acpi_drv_power_ksp->ks_data_size += MAXNAMELEN;
        kstat_install(acpi_drv_power_ksp);

        /*
         * Allocate, initialize and install battery_capacity_warning kstat.
         */
        acpi_drv_warn_ksp = kstat_create(ACPI_DRV_NAME, 0,
            ACPI_DRV_BTWARN_KSTAT_NAME, "misc",
            KSTAT_TYPE_NAMED,
            sizeof (acpi_drv_warn_kstat) / sizeof (kstat_named_t),
            KSTAT_FLAG_VIRTUAL | KSTAT_FLAG_WRITABLE);
        if (acpi_drv_warn_ksp == NULL) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "kstat_create(%s) fail", ACPI_DRV_BTWARN_KSTAT_NAME);
                return (ACPI_DRV_ERR);
        }

        acpi_drv_warn_ksp->ks_data = &acpi_drv_warn_kstat;
        acpi_drv_warn_ksp->ks_update = acpi_drv_kstat_warn_update;
        kstat_install(acpi_drv_warn_ksp);

        return (ACPI_DRV_OK);
}

static void
acpi_drv_kstat_fini()
{
        struct acpi_drv_cbat_state *bp;

        if (acpi_drv_power_ksp != NULL) {
                kstat_delete(acpi_drv_power_ksp);
        }
        if (acpi_drv_warn_ksp != NULL) {
                kstat_delete(acpi_drv_warn_ksp);
        }
        for (bp = &acpi_drv_cbat[0]; bp < &acpi_drv_cbat[ACPI_DRV_MAX_BAT_NUM];
            bp++) {
                if (bp->dev.valid) {
                        if (bp->bat_bif_ksp != NULL) {
                                kstat_delete(bp->bat_bif_ksp);
                        }
                        if (bp->bat_bst_ksp != NULL) {
                                kstat_delete(bp->bat_bst_ksp);
                        }
                }
        }
}

int
acpi_drv_set_int(ACPI_HANDLE dev, char *method, uint32_t aint)
{
        ACPI_OBJECT_LIST al;
        ACPI_OBJECT ao;

        al.Pointer = &ao;
        al.Count = 1;
        ao.Type = ACPI_TYPE_INTEGER;
        ao.Integer.Value = aint;
        return (AcpiEvaluateObject(dev, method, &al, NULL));
}

int
acpi_drv_dev_init(struct acpi_drv_dev *p)
{
        ACPI_DEVICE_INFO *info;
        ACPI_STATUS ret;

        ASSERT(p != NULL && p->hdl != NULL);

        p->valid = 0;

        /* Info size is variable depending on existance of _CID */
        ret = AcpiGetObjectInfo(p->hdl, &info);
        if (ACPI_FAILURE(ret)) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "AcpiGetObjectInfo() fail: %d", (int32_t)ret);
                return (ACPI_DRV_ERR);
        }

        if ((info->Valid & ACPI_VALID_HID) == 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "!AcpiGetObjectInfo(): _HID not available");
                p->hid[0] = 0;
        } else {
                (void) strlcpy(p->hid, info->HardwareId.String, ID_LEN);
        }

        /*
         * This object is optional, but is required when the device
         * has no other way to report a persistent unique device ID.
         */
        if ((info->Valid & ACPI_VALID_UID) == 0) {
                ACPI_DRV_DBG(CE_WARN, NULL,
                    "!AcpiGetObjectInfo(): _UID not available");
                /* Use 0 as the default _UID */
                p->uid[0] = 0;
        } else {
                (void) strlcpy(p->uid, info->UniqueId.String, ID_LEN);
        }

        if (info->Valid & ACPI_VALID_ADR) {
                p->valid = 1;
                p->type = ACPI_DRV_TYPE_HOTKEY;
        }

        AcpiOsFree(info);

        return (ACPI_DRV_OK);
}