power.c revision 6c9bfa0b39999e3f2c9448ede1e4cbd8bfaca728
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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
* 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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* 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:
*
* 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/ddi_impldefs.h>
#include <sys/machsystm.h>
/*
* 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.
*/
/*
* Power Button Abort Delay
*/
#define ABORT_INCREMENT_DELAY 10
/*
* Driver global variables
*/
static void *power_state;
static int power_inst = -1;
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 void power_timeout(caddr_t);
static void power_log_message(void);
/*
* Structure used in the driver
*/
struct power_soft_state {
int monitor_on; /* clone monitoring the button event */
/* clone 0 indicates no one is */
/* monitoring the button event */
int events; /* bit map of occured events */
int shutdown_pending; /* system shutdown in progress */
};
/*
* 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 */
CB_REV, /* rev */
nodev, /* cb_aread */
nodev /* cb_awrite */
};
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 */
NULL /* power */
};
&mod_driverops, /* Type of module. This one is a driver */
"power button driver v%I%", /* name of module */
&power_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
(void *)&modldrv,
};
/*
* These are the module initialization routines.
*/
int
_init(void)
{
int error;
sizeof (struct power_soft_state), 0)) != 0)
return (error);
return (error);
}
int
_fini(void)
{
int error;
return (error);
}
int
{
}
/*ARGSUSED*/
static int
void **result)
{
struct power_soft_state *softsp;
if (power_inst == -1)
return (DDI_FAILURE);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
== NULL)
return (DDI_FAILURE);
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
{
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.
*/
"button") == 0)
return (DDI_FAILURE);
if (power_inst != -1)
return (DDI_FAILURE);
return (DDI_FAILURE);
return (DDI_FAILURE);
goto error;
}
if (ddi_get_iblock_cookie(dip, 0,
"failed.");
goto error;
}
" interrupt handler.");
goto error;
}
"failed.");
goto error;
}
(void *)softsp->soft_iblock_cookie);
"interrupt handler.");
goto error;
}
return (DDI_SUCCESS);
return (DDI_FAILURE);
}
/*ARGSUSED*/
/*
* This driver doesn't detach.
*/
static int
{
/*
* 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.
*/
}
/*
* Handler for the high-level interrupt.
*/
static uint_t
{
static hrtime_t power_button_tstamp = 0;
static int power_button_cnt;
if (softsp->power_regs_mapped) {
} else {
if (!softsp->power_btn_ioctl) {
return (DDI_INTR_CLAIMED);
}
}
}
/* need to deal with power button debounce */
return (DDI_INTR_CLAIMED);
}
/*
* 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 ||
if (power_button_cnt == power_button_abort_presses) {
power_button_pressed = 0;
power_button_cnt = 0;
abort_sequence_enter("Power Button Abort");
return (DDI_INTR_CLAIMED);
}
} else {
power_button_cnt = 1;
}
}
if (!power_button_enable)
return (DDI_INTR_CLAIMED);
/* post softint to issue timeout for power button action */
return (DDI_INTR_CLAIMED);
}
/*
* 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
{
return (power_issue_shutdown(arg));
if (!power_button_pressed) {
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)
timeout_cancel = 0;
power_button_pressed = 0;
(void) timeout((void(*)(void *))power_timeout,
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
{
static int first = 0;
/*
* Abort was generated cancel all outstanding power
* button timeouts
*/
if (power_button_cancel) {
if (!first) {
first++;
additional_presses = 0;
}
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) {
if (!power_button_timeouts)
additional_presses = 0;
if (timeout_cancel > 1) {
"%d times, cancelling all requests",
return;
}
/* Go and do the work to request shutdown */
return;
}
if (!power_button_timeouts)
additional_presses = 0;
}
static uint_t
{
if (softsp->monitor_on != 0) {
return (DDI_INTR_CLAIMED);
}
if (!softsp->shutdown_pending) {
"SC, powering down the system!");
do_shutdown();
/*
* Wait a while for "do_shutdown()" to shut down the system
* before logging an error message.
*/
100 * hz);
}
return (DDI_INTR_CLAIMED);
}
/*
* Open the device.
*/
/*ARGSUSED*/
static int
{
struct power_soft_state *softsp;
int clone;
return (EINVAL);
NULL)
return (ENXIO);
break;
if (clone == POWER_MAX_CLONE) {
"to create a clone minor.");
return (ENXIO);
}
return (0);
}
/*
* Close the device.
*/
/*ARGSUSED*/
static int
{
struct power_soft_state *softsp;
int clone;
return (EINVAL);
NULL)
return (ENXIO);
softsp->monitor_on = 0;
return (0);
}
/*ARGSUSED*/
static int
int *rval_p)
{
struct power_soft_state *softsp;
int clone;
NULL)
return (ENXIO);
switch (cmd) {
case PB_BEGIN_MONITOR:
if (softsp->monitor_on) {
return (EBUSY);
}
return (0);
case PB_END_MONITOR:
/*
* If PB_END_MONITOR is called without first
* calling PB_BEGIN_MONITOR, an error will be
* returned.
*/
if (!softsp->monitor_on) {
return (ENXIO);
}
/*
* This clone is not monitoring the button.
*/
return (EINVAL);
}
softsp->monitor_on = 0;
return (0);
case PB_GET_EVENTS:
sizeof (int), mode) != 0) {
return (EFAULT);
}
/*
* This ioctl returned the events detected since last
* call. Note that any application can get the events
* and clear the event register.
*/
return (0);
/*
* This ioctl is used by the test suite.
*/
case PB_CREATE_BUTTON_EVENT:
if (softsp->power_regs_mapped) {
}
return (0);
default:
return (ENOTTY);
}
}
/*ARGSUSED*/
static int
{
struct power_soft_state *softsp;
return (ENXIO);
*reventsp = 0;
else {
if (!anyyet)
}
return (0);
}
static void
power_log_message(void)
{
struct power_soft_state *softsp;
return;
}
softsp->shutdown_pending = 0;
}
/*
* 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
{
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Setup register map for the power button
* NOTE:- we only map registers for platforms
* binding with the ali1535d+-power compatible
* property.
*/
static int
{
char *binding_name;
return (DDI_SUCCESS);
}
static void
{
if (softsp->power_regs_mapped)
}