/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
* Copyright 2011 Joyent, Inc. All rights reserved.
* Copyright 2011 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Power Button Driver
*
* This driver handles interrupt generated by the power button on
* platforms with "power" device node which has "button" property.
* Currently, these platforms are:
*
* ACPI-enabled x86/x64 platforms
* Ultra-5_10, Ultra-80, Sun-Blade-100, Sun-Blade-150,
* Sun-Blade-1500, Sun-Blade-2500,
* Sun-Fire-V210, Sun-Fire-V240, Netra-240
*
* Only one instance is allowed to attach. In order to know when
* an application that has opened the device is going away, a new
* minor clone is created for each open(9E) request. There are
* allocations for creating minor clones between 1 and 255. The ioctl
* interface is defined by pbio(7I) and approved as part of
* PSARC/1999/393 case.
*/
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <sys/pbio.h>
#include <sys/sysevent/eventdefs.h>
#include <sys/sysevent/pwrctl.h>
#if defined(__sparc)
#include <sys/machsystm.h>
#endif
#ifdef ACPI_POWER_BUTTON
#include <sys/acpi/acpi.h>
#include <sys/acpica.h>
#else
#include <sys/epic.h>
/*
* Some #defs that must be here as they differ for power.c
* and epic.c
*/
#define EPIC_REGS_OFFSET 0x00
#define EPIC_REGS_LEN 0x82
/*
* This flag, which is set for platforms, that have EPIC processor
* to process power button interrupt, helps in executing platform
* specific code.
*/
static char hasEPIC = B_FALSE;
#endif /* ACPI_POWER_BUTTON */
/*
* Maximum number of clone minors that is allowed. This value
* is defined relatively low to save memory.
*/
#define POWER_MAX_CLONE 256
/*
* Minor number is instance << 8 + clone minor from range 1-255; clone 0
* is reserved for "original" minor.
*/
#define POWER_MINOR_TO_CLONE(minor) ((minor) & (POWER_MAX_CLONE - 1))
/*
* Power Button Abort Delay
*/
#define ABORT_INCREMENT_DELAY 10
/*
* FWARC 2005/687: power device compatible property
*/
#define POWER_DEVICE_TYPE "power-device-type"
/*
* Driver global variables
*/
static void *power_state;
static int power_inst = -1;
static hrtime_t power_button_debounce = MSEC2NSEC(10);
static hrtime_t power_button_abort_interval = 1.5 * NANOSEC;
static int power_button_abort_presses = 3;
static int power_button_abort_enable = 1;
static int power_button_enable = 1;
static int power_button_pressed = 0;
static int power_button_cancel = 0;
static int power_button_timeouts = 0;
static int timeout_cancel = 0;
static int additional_presses = 0;
/*
* Function prototypes
*/
static int power_attach(dev_info_t *, ddi_attach_cmd_t);
static int power_detach(dev_info_t *, ddi_detach_cmd_t);
static int power_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int power_open(dev_t *, int, int, cred_t *);
static int power_close(dev_t, int, int, cred_t *);
static int power_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int power_chpoll(dev_t, short, int, short *, struct pollhead **);
#ifndef ACPI_POWER_BUTTON
static uint_t power_high_intr(caddr_t);
#endif
static uint_t power_soft_intr(caddr_t);
static uint_t power_issue_shutdown(caddr_t);
static void power_timeout(caddr_t);
static void power_log_message(void);
/*
* Structure used in the driver
*/
struct power_soft_state {
dev_info_t *dip; /* device info pointer */
kmutex_t power_mutex; /* mutex lock */
kmutex_t power_intr_mutex; /* interrupt mutex lock */
ddi_iblock_cookie_t soft_iblock_cookie; /* holds interrupt cookie */
ddi_iblock_cookie_t high_iblock_cookie; /* holds interrupt cookie */
ddi_softintr_t softintr_id; /* soft interrupt id */
uchar_t clones[POWER_MAX_CLONE]; /* array of minor clones */
int monitor_on; /* clone monitoring the button event */
/* clone 0 indicates no one is */
/* monitoring the button event */
pollhead_t pollhd; /* poll head struct */
int events; /* bit map of occured events */
int shutdown_pending; /* system shutdown in progress */
#ifdef ACPI_POWER_BUTTON
boolean_t fixed_attached; /* true means fixed is attached */
boolean_t gpe_attached; /* true means GPE is attached */
ACPI_HANDLE button_obj; /* handle to device power button */
#else
ddi_acc_handle_t power_rhandle; /* power button register handle */
uint8_t *power_btn_reg; /* power button register address */
uint8_t power_btn_bit; /* power button register bit */
boolean_t power_regs_mapped; /* flag to tell if regs mapped */
boolean_t power_btn_ioctl; /* flag to specify ioctl request */
#endif
};
static void power_gen_sysevent(struct power_soft_state *);
#ifdef ACPI_POWER_BUTTON
static int power_attach_acpi(struct power_soft_state *softsp);
static void power_detach_acpi(struct power_soft_state *softsp);
static UINT32 power_acpi_fixed_event(void *ctx);
#else
static int power_setup_regs(struct power_soft_state *softsp);
static void power_free_regs(struct power_soft_state *softsp);
#endif /* ACPI_POWER_BUTTON */
/*
* Configuration data structures
*/
static struct cb_ops power_cb_ops = {
power_open, /* open */
power_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
power_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
power_chpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
NULL, /* streamtab */
D_MP | D_NEW, /* Driver compatibility flag */
CB_REV, /* rev */
nodev, /* cb_aread */
nodev /* cb_awrite */
};
static struct dev_ops power_ops = {
DEVO_REV, /* devo_rev, */
0, /* refcnt */
power_getinfo, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
power_attach, /* attach */
power_detach, /* detach */
nodev, /* reset */
&power_cb_ops, /* cb_ops */
(struct bus_ops *)NULL, /* bus_ops */
NULL, /* power */
ddi_quiesce_not_supported, /* devo_quiesce */
};
static struct modldrv modldrv = {
&mod_driverops, /* Type of module. This one is a driver */
"power button driver", /* name of module */
&power_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modldrv,
NULL
};
/*
* These are the module initialization routines.
*/
int
_init(void)
{
int error;
if ((error = ddi_soft_state_init(&power_state,
sizeof (struct power_soft_state), 0)) != 0)
return (error);
if ((error = mod_install(&modlinkage)) != 0)
ddi_soft_state_fini(&power_state);
return (error);
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&modlinkage)) == 0)
ddi_soft_state_fini(&power_state);
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*ARGSUSED*/
static int
power_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result)
{
struct power_soft_state *softsp;
if (power_inst == -1)
return (DDI_FAILURE);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
if ((softsp = ddi_get_soft_state(power_state, power_inst))
== NULL)
return (DDI_FAILURE);
*result = (void *)softsp->dip;
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)power_inst;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
power_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
struct power_soft_state *softsp;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* If the power node doesn't have "button" property, quietly
* fail to attach.
*/
if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"button") == 0)
return (DDI_FAILURE);
if (power_inst != -1)
return (DDI_FAILURE);
power_inst = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(power_state, power_inst) != DDI_SUCCESS)
return (DDI_FAILURE);
if (ddi_create_minor_node(dip, "power_button", S_IFCHR,
(power_inst << 8) + 0, "ddi_power_button", 0) != DDI_SUCCESS)
return (DDI_FAILURE);
softsp = ddi_get_soft_state(power_state, power_inst);
softsp->dip = dip;
#ifdef ACPI_POWER_BUTTON
(void) power_attach_acpi(softsp);
#else
if (power_setup_regs(softsp) != DDI_SUCCESS) {
cmn_err(CE_WARN, "power_attach: failed to setup registers");
goto error;
}
if (ddi_get_iblock_cookie(dip, 0,
&softsp->high_iblock_cookie) != DDI_SUCCESS) {
cmn_err(CE_WARN, "power_attach: ddi_get_soft_iblock_cookie "
"failed.");
goto error;
}
mutex_init(&softsp->power_intr_mutex, NULL, MUTEX_DRIVER,
softsp->high_iblock_cookie);
if (ddi_add_intr(dip, 0, &softsp->high_iblock_cookie, NULL,
power_high_intr, (caddr_t)softsp) != DDI_SUCCESS) {
cmn_err(CE_WARN, "power_attach: failed to add high-level "
" interrupt handler.");
mutex_destroy(&softsp->power_intr_mutex);
goto error;
}
#endif /* ACPI_POWER_BUTTON */
if (ddi_get_soft_iblock_cookie(dip, DDI_SOFTINT_LOW,
&softsp->soft_iblock_cookie) != DDI_SUCCESS) {
cmn_err(CE_WARN, "power_attach: ddi_get_soft_iblock_cookie "
"failed.");
mutex_destroy(&softsp->power_intr_mutex);
ddi_remove_intr(dip, 0, NULL);
goto error;
}
mutex_init(&softsp->power_mutex, NULL, MUTEX_DRIVER,
(void *)softsp->soft_iblock_cookie);
if (ddi_add_softintr(dip, DDI_SOFTINT_LOW, &softsp->softintr_id,
NULL, NULL, power_soft_intr, (caddr_t)softsp) != DDI_SUCCESS) {
cmn_err(CE_WARN, "power_attach: failed to add soft "
"interrupt handler.");
mutex_destroy(&softsp->power_mutex);
mutex_destroy(&softsp->power_intr_mutex);
ddi_remove_intr(dip, 0, NULL);
goto error;
}
ddi_report_dev(dip);
return (DDI_SUCCESS);
error:
#ifdef ACPI_POWER_BUTTON
/*
* detach ACPI power button
*/
power_detach_acpi(softsp);
#else
power_free_regs(softsp);
#endif /* ACPI_POWER_BUTTON */
ddi_remove_minor_node(dip, "power_button");
ddi_soft_state_free(power_state, power_inst);
return (DDI_FAILURE);
}
/*ARGSUSED*/
/*
* This driver doesn't detach.
*/
static int
power_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
/*
* Since the "power" node has "reg" property, as part of
* the suspend operation, detach(9E) entry point is called.
* There is no state to save, since this register is used
* by OBP to power off the system and the state of the
* power off is preserved by hardware.
*/
return ((cmd == DDI_SUSPEND) ? DDI_SUCCESS :
DDI_FAILURE);
}
#ifndef ACPI_POWER_BUTTON
/*
* Handler for the high-level interrupt.
*/
static uint_t
power_high_intr(caddr_t arg)
{
struct power_soft_state *softsp = (struct power_soft_state *)arg;
ddi_acc_handle_t hdl = softsp->power_rhandle;
uint8_t reg;
hrtime_t tstamp;
static hrtime_t o_tstamp = 0;
static hrtime_t power_button_tstamp = 0;
static int power_button_cnt;
if (softsp->power_regs_mapped) {
mutex_enter(&softsp->power_intr_mutex);
/* Check if power button interrupt is delivered by EPIC HW */
if (hasEPIC) {
/* read isr - first issue command */
EPIC_WR(hdl, softsp->power_btn_reg,
EPIC_ATOM_INTR_READ);
/* next, read the reg */
EPIC_RD(hdl, softsp->power_btn_reg, reg);
if (reg & EPIC_FIRE_INTERRUPT) { /* PB pressed */
/* clear the interrupt */
EPIC_WR(hdl, softsp->power_btn_reg,
EPIC_ATOM_INTR_CLEAR);
} else {
if (!softsp->power_btn_ioctl) {
mutex_exit(&softsp->power_intr_mutex);
return (DDI_INTR_CLAIMED);
}
softsp->power_btn_ioctl = B_FALSE;
}
} else {
reg = ddi_get8(hdl, softsp->power_btn_reg);
if (reg & softsp->power_btn_bit) {
reg &= softsp->power_btn_bit;
ddi_put8(hdl, softsp->power_btn_reg, reg);
(void) ddi_get8(hdl, softsp->power_btn_reg);
} else {
if (!softsp->power_btn_ioctl) {
mutex_exit(&softsp->power_intr_mutex);
return (DDI_INTR_CLAIMED);
}
softsp->power_btn_ioctl = B_FALSE;
}
}
mutex_exit(&softsp->power_intr_mutex);
}
tstamp = gethrtime();
/* need to deal with power button debounce */
if (o_tstamp && (tstamp - o_tstamp) < power_button_debounce) {
o_tstamp = tstamp;
return (DDI_INTR_CLAIMED);
}
o_tstamp = tstamp;
power_button_cnt++;
mutex_enter(&softsp->power_intr_mutex);
power_button_pressed++;
mutex_exit(&softsp->power_intr_mutex);
/*
* If power button abort is enabled and power button was pressed
* power_button_abort_presses times within power_button_abort_interval
* then call abort_sequence_enter();
*/
if (power_button_abort_enable) {
if (power_button_abort_presses == 1 ||
tstamp < (power_button_tstamp +
power_button_abort_interval)) {
if (power_button_cnt == power_button_abort_presses) {
mutex_enter(&softsp->power_intr_mutex);
power_button_cancel += power_button_timeouts;
power_button_pressed = 0;
mutex_exit(&softsp->power_intr_mutex);
power_button_cnt = 0;
abort_sequence_enter("Power Button Abort");
return (DDI_INTR_CLAIMED);
}
} else {
power_button_cnt = 1;
power_button_tstamp = tstamp;
}
}
if (!power_button_enable)
return (DDI_INTR_CLAIMED);
/* post softint to issue timeout for power button action */
if (softsp->softintr_id != NULL)
ddi_trigger_softintr(softsp->softintr_id);
return (DDI_INTR_CLAIMED);
}
#endif /* ifndef ACPI_POWER_BUTTON */
/*
* Handle the softints....
*
* If only one softint is posted for several button presses, record
* the number of additional presses just incase this was actually not quite
* an Abort sequence so that we can log this event later.
*
* Issue a timeout with a duration being a fraction larger than
* the specified Abort interval inorder to perform a power down if required.
*/
static uint_t
power_soft_intr(caddr_t arg)
{
struct power_soft_state *softsp = (struct power_soft_state *)arg;
if (!power_button_abort_enable)
return (power_issue_shutdown(arg));
mutex_enter(&softsp->power_intr_mutex);
if (!power_button_pressed) {
mutex_exit(&softsp->power_intr_mutex);
return (DDI_INTR_CLAIMED);
}
/*
* Schedule a timeout to do the necessary
* work for shutdown, only one timeout for
* n presses if power button was pressed
* more than once before softint fired
*/
if (power_button_pressed > 1)
additional_presses += power_button_pressed - 1;
timeout_cancel = 0;
power_button_pressed = 0;
power_button_timeouts++;
mutex_exit(&softsp->power_intr_mutex);
(void) timeout((void(*)(void *))power_timeout,
softsp, NSEC_TO_TICK(power_button_abort_interval) +
ABORT_INCREMENT_DELAY);
return (DDI_INTR_CLAIMED);
}
/*
* Upon receiving a timeout the following is determined:
*
* If an Abort sequence was issued, then we cancel all outstanding timeouts
* and additional presses prior to the Abort sequence.
*
* If we had multiple timeouts issued and the abort sequence was not met,
* then we had more than one button press to power down the machine. We
* were probably trying to issue an abort. So log a message indicating this
* and cancel all outstanding timeouts.
*
* If we had just one timeout and the abort sequence was not met then
* we really did want to power down the machine, so call power_issue_shutdown()
* to do the work and schedule a power down
*/
static void
power_timeout(caddr_t arg)
{
struct power_soft_state *softsp = (struct power_soft_state *)arg;
static int first = 0;
/*
* Abort was generated cancel all outstanding power
* button timeouts
*/
mutex_enter(&softsp->power_intr_mutex);
if (power_button_cancel) {
power_button_cancel--;
power_button_timeouts--;
if (!first) {
first++;
additional_presses = 0;
}
mutex_exit(&softsp->power_intr_mutex);
return;
}
first = 0;
/*
* We get here if the timeout(s) have fired and they were
* not issued prior to an abort.
*
* If we had more than one press in the interval we were
* probably trying to issue an abort, but didnt press the
* required number within the interval. Hence cancel all
* timeouts and do not continue towards shutdown.
*/
if (!timeout_cancel) {
timeout_cancel = power_button_timeouts +
additional_presses;
power_button_timeouts--;
if (!power_button_timeouts)
additional_presses = 0;
if (timeout_cancel > 1) {
mutex_exit(&softsp->power_intr_mutex);
cmn_err(CE_NOTE, "Power Button pressed "
"%d times, cancelling all requests",
timeout_cancel);
return;
}
mutex_exit(&softsp->power_intr_mutex);
/* Go and do the work to request shutdown */
(void) power_issue_shutdown((caddr_t)softsp);
return;
}
power_button_timeouts--;
if (!power_button_timeouts)
additional_presses = 0;
mutex_exit(&softsp->power_intr_mutex);
}
#ifdef ACPI_POWER_BUTTON
static void
do_shutdown(void)
{
proc_t *initpp;
/*
* If we're still booting and init(1) isn't set up yet, simply halt.
*/
mutex_enter(&pidlock);
initpp = prfind(P_INITPID);
mutex_exit(&pidlock);
if (initpp == NULL) {
extern void halt(char *);
halt("Power off the System"); /* just in case */
}
/*
* else, graceful shutdown with inittab and all getting involved
*/
psignal(initpp, SIGPWR);
}
#endif
static uint_t
power_issue_shutdown(caddr_t arg)
{
struct power_soft_state *softsp = (struct power_soft_state *)arg;
mutex_enter(&softsp->power_mutex);
softsp->events |= PB_BUTTON_PRESS;
if (softsp->monitor_on != 0) {
mutex_exit(&softsp->power_mutex);
pollwakeup(&softsp->pollhd, POLLRDNORM);
pollwakeup(&softsp->pollhd, POLLIN);
power_gen_sysevent(softsp);
return (DDI_INTR_CLAIMED);
}
if (!softsp->shutdown_pending) {
cmn_err(CE_WARN, "Power off requested from power button or "
"SC, powering down the system!");
softsp->shutdown_pending = 1;
do_shutdown();
/*
* Wait a while for "do_shutdown()" to shut down the system
* before logging an error message.
*/
(void) timeout((void(*)(void *))power_log_message, NULL,
100 * hz);
}
mutex_exit(&softsp->power_mutex);
return (DDI_INTR_CLAIMED);
}
static void
power_gen_sysevent(struct power_soft_state *softsp)
{
nvlist_t *attr_list = NULL;
int err;
char pathname[MAXPATHLEN];
char hid[9] = "\0";
/* Allocate and build sysevent attribute list */
err = nvlist_alloc(&attr_list, NV_UNIQUE_NAME_TYPE, DDI_NOSLEEP);
if (err != 0) {
cmn_err(CE_WARN,
"cannot allocate memory for sysevent attributes\n");
return;
}
#ifdef ACPI_POWER_BUTTON
/* Only control method power button has HID */
if (softsp->gpe_attached) {
(void) strlcpy(hid, "PNP0C0C", sizeof (hid));
}
#endif
err = nvlist_add_string(attr_list, PWRCTL_DEV_HID, hid);
if (err != 0) {
cmn_err(CE_WARN,
"Failed to add attr [%s] for %s/%s event",
PWRCTL_DEV_HID, EC_PWRCTL, ESC_PWRCTL_POWER_BUTTON);
nvlist_free(attr_list);
return;
}
(void) ddi_pathname(softsp->dip, pathname);
err = nvlist_add_string(attr_list, PWRCTL_DEV_PHYS_PATH, pathname);
if (err != 0) {
cmn_err(CE_WARN,
"Failed to add attr [%s] for %s/%s event",
PWRCTL_DEV_PHYS_PATH, EC_PWRCTL, ESC_PWRCTL_POWER_BUTTON);
nvlist_free(attr_list);
return;
}
/* Generate/log sysevent */
err = ddi_log_sysevent(softsp->dip, DDI_VENDOR_SUNW, EC_PWRCTL,
ESC_PWRCTL_POWER_BUTTON, attr_list, NULL, DDI_NOSLEEP);
if (err != DDI_SUCCESS) {
cmn_err(CE_WARN,
"cannot log sysevent, err code %x\n", err);
}
nvlist_free(attr_list);
}
/*
* Open the device.
*/
/*ARGSUSED*/
static int
power_open(dev_t *devp, int openflags, int otyp, cred_t *credp)
{
struct power_soft_state *softsp;
int clone;
if (otyp != OTYP_CHR)
return (EINVAL);
if ((softsp = ddi_get_soft_state(power_state, power_inst)) ==
NULL)
return (ENXIO);
mutex_enter(&softsp->power_mutex);
for (clone = 1; clone < POWER_MAX_CLONE; clone++)
if (!softsp->clones[clone])
break;
if (clone == POWER_MAX_CLONE) {
cmn_err(CE_WARN, "power_open: No more allocation left "
"to create a clone minor.");
mutex_exit(&softsp->power_mutex);
return (ENXIO);
}
*devp = makedevice(getmajor(*devp), (power_inst << 8) + clone);
softsp->clones[clone] = 1;
mutex_exit(&softsp->power_mutex);
return (0);
}
/*
* Close the device.
*/
/*ARGSUSED*/
static int
power_close(dev_t dev, int openflags, int otyp, cred_t *credp)
{
struct power_soft_state *softsp;
int clone;
if (otyp != OTYP_CHR)
return (EINVAL);
if ((softsp = ddi_get_soft_state(power_state, power_inst)) ==
NULL)
return (ENXIO);
clone = POWER_MINOR_TO_CLONE(getminor(dev));
mutex_enter(&softsp->power_mutex);
if (softsp->monitor_on == clone)
softsp->monitor_on = 0;
softsp->clones[clone] = 0;
mutex_exit(&softsp->power_mutex);
return (0);
}
/*ARGSUSED*/
static int
power_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred_p,
int *rval_p)
{
struct power_soft_state *softsp;
int clone;
if ((softsp = ddi_get_soft_state(power_state, power_inst)) ==
NULL)
return (ENXIO);
clone = POWER_MINOR_TO_CLONE(getminor(dev));
switch (cmd) {
case PB_BEGIN_MONITOR:
mutex_enter(&softsp->power_mutex);
if (softsp->monitor_on) {
mutex_exit(&softsp->power_mutex);
return (EBUSY);
}
softsp->monitor_on = clone;
mutex_exit(&softsp->power_mutex);
return (0);
case PB_END_MONITOR:
mutex_enter(&softsp->power_mutex);
/*
* If PB_END_MONITOR is called without first
* calling PB_BEGIN_MONITOR, an error will be
* returned.
*/
if (!softsp->monitor_on) {
mutex_exit(&softsp->power_mutex);
return (ENXIO);
}
/*
* This clone is not monitoring the button.
*/
if (softsp->monitor_on != clone) {
mutex_exit(&softsp->power_mutex);
return (EINVAL);
}
softsp->monitor_on = 0;
mutex_exit(&softsp->power_mutex);
return (0);
case PB_GET_EVENTS:
mutex_enter(&softsp->power_mutex);
if (ddi_copyout((void *)&softsp->events, (void *)arg,
sizeof (int), mode) != 0) {
mutex_exit(&softsp->power_mutex);
return (EFAULT);
}
/*
* This ioctl returned the events detected since last
* call. Note that any application can get the events
* and clear the event register.
*/
softsp->events = 0;
mutex_exit(&softsp->power_mutex);
return (0);
/*
* This ioctl is used by the test suite.
*/
case PB_CREATE_BUTTON_EVENT:
#ifdef ACPI_POWER_BUTTON
(UINT32)power_acpi_fixed_event((void *)softsp);
#else
if (softsp->power_regs_mapped) {
mutex_enter(&softsp->power_intr_mutex);
softsp->power_btn_ioctl = B_TRUE;
mutex_exit(&softsp->power_intr_mutex);
}
(void) power_high_intr((caddr_t)softsp);
#endif /* ACPI_POWER_BUTTON */
return (0);
default:
return (ENOTTY);
}
}
/*ARGSUSED*/
static int
power_chpoll(dev_t dev, short events, int anyyet,
short *reventsp, struct pollhead **phpp)
{
struct power_soft_state *softsp;
if ((softsp = ddi_get_soft_state(power_state, power_inst)) == NULL)
return (ENXIO);
mutex_enter(&softsp->power_mutex);
*reventsp = 0;
if (softsp->events)
*reventsp = POLLRDNORM|POLLIN;
else {
if (!anyyet)
*phpp = &softsp->pollhd;
}
mutex_exit(&softsp->power_mutex);
return (0);
}
static void
power_log_message(void)
{
struct power_soft_state *softsp;
if ((softsp = ddi_get_soft_state(power_state, power_inst)) == NULL) {
cmn_err(CE_WARN, "Failed to get internal state!");
return;
}
mutex_enter(&softsp->power_mutex);
softsp->shutdown_pending = 0;
cmn_err(CE_WARN, "Failed to shut down the system!");
mutex_exit(&softsp->power_mutex);
}
#ifdef ACPI_POWER_BUTTON
/*
*
*/
/*ARGSUSED*/
static ACPI_STATUS
acpi_device(ACPI_HANDLE obj, UINT32 nesting, void *context, void **rv)
{
*((ACPI_HANDLE *)context) = obj;
return (AE_OK);
}
/*
*
*/
static ACPI_HANDLE
probe_acpi_pwrbutton()
{
ACPI_HANDLE obj = NULL;
(void) AcpiGetDevices("PNP0C0C", acpi_device, (void *)&obj, NULL);
return (obj);
}
static UINT32
power_acpi_fixed_event(void *ctx)
{
mutex_enter(&((struct power_soft_state *)ctx)->power_intr_mutex);
power_button_pressed++;
mutex_exit(&((struct power_soft_state *)ctx)->power_intr_mutex);
/* post softint to issue timeout for power button action */
if (((struct power_soft_state *)ctx)->softintr_id != NULL)
ddi_trigger_softintr(
((struct power_soft_state *)ctx)->softintr_id);
return (AE_OK);
}
/*ARGSUSED*/
static void
power_acpi_notify_event(ACPI_HANDLE obj, UINT32 val, void *ctx)
{
if (val == 0x80)
(void) power_acpi_fixed_event(ctx);
}
/*
*
*/
static int
power_probe_method_button(struct power_soft_state *softsp)
{
ACPI_HANDLE button_obj;
button_obj = probe_acpi_pwrbutton();
softsp->button_obj = button_obj; /* remember obj */
if ((button_obj != NULL) &&
(AcpiInstallNotifyHandler(button_obj, ACPI_DEVICE_NOTIFY,
power_acpi_notify_event, (void*)softsp) == AE_OK))
return (1);
return (0);
}
/*
*
*/
static int
power_probe_fixed_button(struct power_soft_state *softsp)
{
ACPI_TABLE_FADT *fadt;
if (AcpiGetTable(ACPI_SIG_FADT, 1, (ACPI_TABLE_HEADER **) &fadt) !=
AE_OK)
return (0);
if ((fadt->Flags & ACPI_FADT_POWER_BUTTON) == 0) {
if (AcpiInstallFixedEventHandler(ACPI_EVENT_POWER_BUTTON,
power_acpi_fixed_event, (void *)softsp) == AE_OK)
return (1);
}
return (0);
}
/*
*
*/
static int
power_attach_acpi(struct power_soft_state *softsp)
{
/*
* If we've attached anything already, return an error
*/
if ((softsp->gpe_attached) || (softsp->fixed_attached))
return (DDI_FAILURE);
/*
* attempt to attach both a fixed-event handler and a GPE
* handler; remember what we got
*/
softsp->fixed_attached = (power_probe_fixed_button(softsp) != 0);
softsp->gpe_attached = (power_probe_method_button(softsp) != 0);
/*
* If we've attached anything now, return success
*/
if ((softsp->gpe_attached) || (softsp->fixed_attached))
return (DDI_SUCCESS);
return (DDI_FAILURE);
}
/*
*
*/
static void
power_detach_acpi(struct power_soft_state *softsp)
{
if (softsp->gpe_attached) {
if (AcpiRemoveNotifyHandler(softsp->button_obj,
ACPI_DEVICE_NOTIFY, power_acpi_notify_event) != AE_OK)
cmn_err(CE_WARN, "!power: failed to remove Notify"
" handler");
}
if (softsp->fixed_attached) {
if (AcpiRemoveFixedEventHandler(ACPI_EVENT_POWER_BUTTON,
power_acpi_fixed_event) != AE_OK)
cmn_err(CE_WARN, "!power: failed to remove Power"
" Button handler");
}
}
#else
/*
* Code for platforms that have EPIC processor for processing power
* button interrupts.
*/
static int
power_setup_epic_regs(dev_info_t *dip, struct power_soft_state *softsp)
{
ddi_device_acc_attr_t attr;
uint8_t *reg_base;
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&reg_base,
EPIC_REGS_OFFSET, EPIC_REGS_LEN, &attr,
&softsp->power_rhandle) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
softsp->power_btn_reg = reg_base;
softsp->power_regs_mapped = B_TRUE;
/* Clear power button interrupt first */
EPIC_WR(softsp->power_rhandle, softsp->power_btn_reg,
EPIC_ATOM_INTR_CLEAR);
/* Enable EPIC interrupt for power button single press event */
EPIC_WR(softsp->power_rhandle, softsp->power_btn_reg,
EPIC_ATOM_INTR_ENABLE);
/*
* At this point, EPIC interrupt processing is fully initialised.
*/
hasEPIC = B_TRUE;
return (DDI_SUCCESS);
}
/*
*
* power button register definitions for acpi register on m1535d
*/
#define M1535D_PWR_BTN_REG_01 0x1
#define M1535D_PWR_BTN_EVENT_FLAG 0x1
static int
power_setup_m1535_regs(dev_info_t *dip, struct power_soft_state *softsp)
{
ddi_device_acc_attr_t attr;
uint8_t *reg_base;
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&reg_base, 0, 0, &attr,
&softsp->power_rhandle) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
softsp->power_btn_reg = &reg_base[M1535D_PWR_BTN_REG_01];
softsp->power_btn_bit = M1535D_PWR_BTN_EVENT_FLAG;
softsp->power_regs_mapped = B_TRUE;
return (DDI_SUCCESS);
}
/*
* MBC Fire/SSI Interrupt Status Register definitions
*/
#define FIRE_SSI_ISR 0x0
#define FIRE_SSI_INTR_ENA 0x8
#define FIRE_SSI_SHUTDOWN_REQ 0x4
static int
power_setup_mbc_regs(dev_info_t *dip, struct power_soft_state *softsp)
{
ddi_device_acc_attr_t attr;
uint8_t *reg_base;
ddi_acc_handle_t hdl;
uint8_t reg;
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&reg_base, 0, 0, &attr,
&softsp->power_rhandle) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
softsp->power_btn_reg = &reg_base[FIRE_SSI_ISR];
softsp->power_btn_bit = FIRE_SSI_SHUTDOWN_REQ;
hdl = softsp->power_rhandle;
/*
* Clear MBC Fire Power Button interrupt, if set.
*/
reg = ddi_get8(hdl, softsp->power_btn_reg);
if (reg & softsp->power_btn_bit) {
reg &= softsp->power_btn_bit;
ddi_put8(hdl, softsp->power_btn_reg, reg);
(void) ddi_get8(hdl, softsp->power_btn_reg);
}
/*
* Enable MBC Fire Power Button interrupt.
*/
reg = ddi_get8(hdl, &reg_base[FIRE_SSI_INTR_ENA]);
reg |= FIRE_SSI_SHUTDOWN_REQ;
ddi_put8(hdl, &reg_base[FIRE_SSI_INTR_ENA], reg);
softsp->power_regs_mapped = B_TRUE;
return (DDI_SUCCESS);
}
/*
* Setup register map for the power button
* NOTE:- we only map registers for platforms if
* the OBP power device has any of the following
* properties:
*
* a) Boston: power-device-type set to "SUNW,mbc"
* b) Seattle: power-device-type set to "SUNW,pic18lf65j10"
* c) Chalupa: compatible set to "ali1535d+-power"
*
* Cases (a) and (b) are defined in FWARC 2005/687.
* If none of the above conditions are true, then we
* do not need to map in any registers, and this
* function can simply return DDI_SUCCESS.
*/
static int
power_setup_regs(struct power_soft_state *softsp)
{
char *binding_name;
char *power_type = NULL;
int retval = DDI_SUCCESS;
softsp->power_regs_mapped = B_FALSE;
softsp->power_btn_ioctl = B_FALSE;
binding_name = ddi_binding_name(softsp->dip);
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, softsp->dip,
DDI_PROP_DONTPASS, POWER_DEVICE_TYPE,
&power_type) == DDI_PROP_SUCCESS) {
if (strcmp(power_type, "SUNW,mbc") == 0) {
retval = power_setup_mbc_regs(softsp->dip, softsp);
} else if (strcmp(power_type, "SUNW,pic18lf65j10") == 0) {
retval = power_setup_epic_regs(softsp->dip, softsp);
} else {
cmn_err(CE_WARN, "unexpected power-device-type: %s\n",
power_type);
retval = DDI_FAILURE;
}
ddi_prop_free(power_type);
} else if (strcmp(binding_name, "ali1535d+-power") == 0) {
retval = power_setup_m1535_regs(softsp->dip, softsp);
}
/*
* If power-device-type does not exist AND the binding name is not
* "ali1535d+-power", that means there is no additional HW and hence
* no extra processing is necessary. In that case, retval should still
* be set to its initial value of DDI_SUCCESS.
*/
return (retval);
}
static void
power_free_regs(struct power_soft_state *softsp)
{
if (softsp->power_regs_mapped)
ddi_regs_map_free(&softsp->power_rhandle);
}
#endif /* ACPI_POWER_BUTTON */