Date: Sun, 17 Jun 2007 10:13:34 -0700 From: Nate Lawson <nate@root.org> To: current <current@freebsd.org> Cc: acpi@freebsd.org, arch@freebsd.org Subject: patch: acpi usermode suspend API change Message-ID: <46756BBE.5030603@root.org>
next in thread | raw e-mail | index | archive | help
This is a multi-part message in MIME format.
--------------040100070106020900000700
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
This patch updates the user API for acpi suspend in a number of ways.
* /etc/rc.suspend,rc.resume are always run, no matter the source of the
suspend event
* suspend now requires positive user acknowledgement. If a user program
wants to cancel the suspend, they can. If one of the user programs
hangs or doesn't respond within 10 seconds, the system suspends anyway.
* /dev/apm is clonable, allowing multiple listeners for suspend events.
In the future, xorg-server can use this to be informed about suspend
even if there are other listeners (i.e. apmd).
The API changes are intended to be MFCd and hence are minimal.
* Two new ACPI ioctls: REQSLPSTATE and ACKSLPSTATE. Request begins the
process of suspending by notifying all listeners. acpi is monitored by
devd(8) and /dev/apm listener(s) are also counted. Users register their
approval or disapproval via Ack. If anyone disapproves, suspend is vetoed.
* Binary compat is preserved in that old user programs or kernel modules
(yeah, right) that called the API can still do so. A message is printed
once that this interface is deprecated.
* acpiconf gains the -k flag to ack the suspend request. This flag is
undocumented on purpose since it's only used by /etc/rc.suspend. It is
not intended to be a permanent change and will be removed once a better
power API is implemented.
These patches have been successfully tested for about a week through
suspend/resumes and various usermode programs, including patches to
xorg-server.
The eventual goal is to improve subr_power to be a more generic central
power management interface to user and kernel providers. powerd(8)
might start managing profiles similar to apmd(8). I don't know yet and
there isn't time to do that. This patch provides a reasonable interface
for now and eliminates some serious weaknesses, especially in that
pressing a sleep button did not guarantee rc.suspend would run before
sleeping.
Comments welcome, patches are for 6.x and 7.x.
--
Nate
--------------040100070106020900000700
Content-Type: text/x-patch;
name="acpi6_wakeapi.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="acpi6_wakeapi.diff"
Index: etc/devd.conf
===================================================================
RCS file: /home/ncvs/src/etc/devd.conf,v
retrieving revision 1.26.2.5
diff -u -r1.26.2.5 devd.conf
--- etc/devd.conf 24 Jul 2006 20:42:36 -0000 1.26.2.5
+++ etc/devd.conf 17 Jun 2007 16:58:35 -0000
@@ -148,6 +148,19 @@
action "logger -p kern.emerg 'WARNING: system temperature too high, shutting down soon!'";
};
+# User requested suspend, so perform preparation steps and then execute
+# the actual suspend process.
+notify 10 {
+ match "system" "ACPI";
+ match "subsystem" "Suspend";
+ action "/etc/rc.suspend acpi $notify";
+};
+notify 10 {
+ match "system" "ACPI";
+ match "subsystem" "Resume";
+ action "/etc/rc.resume acpi $notify";
+};
+
/* EXAMPLES TO END OF FILE
# The following might be an example of something that a vendor might
@@ -186,6 +199,7 @@
# Button: Button pressed (0 for power, 1 for sleep)
# CMBAT: ACPI battery events
# Lid: Lid state (0 is closed, 1 is open)
+# Suspend, Resume: Suspend and resume notification
# Thermal: ACPI thermal zone events
#
# This example calls a script when the AC state changes, passing the
Index: etc/rc.resume
===================================================================
RCS file: /home/ncvs/src/etc/rc.resume,v
retrieving revision 1.7
diff -u -r1.7 rc.resume
--- etc/rc.resume 30 Dec 2003 17:30:39 -0000 1.7
+++ etc/rc.resume 17 Jun 2007 16:58:35 -0000
@@ -30,7 +30,7 @@
# sample run command file for APM Resume Event
if [ $# -ne 2 ]; then
- echo "Usage: $0 [apm|acpi] [standby,suspend|1-5]"
+ echo "Usage: $0 [apm|acpi] [standby,suspend|1-4]"
exit 1
fi
@@ -48,9 +48,13 @@
# pccardq | awk -F '~' '$5 == "inactive" \
# { printf("pccardc power %d 1", $1); }' | sh
-# UHCI has trouble resuming so we just load/unload it. You
-# should add any other kernel modules you want reloaded here.
-# kldload usb
+# If a device driver has problems resuming, try unloading it before
+# suspend and reloading it on resume. Example:
+# kldunload usb
+
+# wpa_supplicant(8) doesn't seem to reassociate during resume. Uncomment
+# the following to signal it to reassociate.
+# /usr/sbin/wpa_cli reassociate
logger -t $subsystem resumed at `date +'%Y%m%d %H:%M:%S'`
sync && sync && sync
Index: etc/rc.suspend
===================================================================
RCS file: /home/ncvs/src/etc/rc.suspend,v
retrieving revision 1.6
diff -u -r1.6 rc.suspend
--- etc/rc.suspend 21 Jan 2004 03:03:40 -0000 1.6
+++ etc/rc.suspend 17 Jun 2007 16:58:35 -0000
@@ -30,7 +30,7 @@
# sample run command file for APM Suspend Event
if [ $# -ne 2 ]; then
- echo "Usage: $0 [apm|acpi] [standby,suspend|1-5]"
+ echo "Usage: $0 [apm|acpi] [standby,suspend|1-4]"
exit 1
fi
@@ -48,15 +48,20 @@
# pccardq | awk -F '~' '$5 == "filled" && $4 ~ /sio/ \
# { printf("pccardc power %d 0", $1); }' | sh
-# UHCI has trouble resuming so we just load/unload it. You
-# should add any other kernel modules you want unloaded here.
+# If a device driver has problems suspending, try unloading it before
+# suspend and reloading it on resume. Example:
# kldunload usb
logger -t $subsystem suspend at `date +'%Y%m%d %H:%M:%S'`
sync && sync && sync
-[ $subsystem = "apm" ] && sleep 3
+sleep 3
rm -f /var/run/rc.suspend.pid
-[ $subsystem = "apm" ] && zzz
+if [ $subsystem = "apm" ]; then
+ /usr/sbin/zzz
+else
+ # Notify the kernel to continue the suspend process
+ /usr/sbin/acpiconf -k 0
+fi
exit 0
Index: sys/dev/acpica/acpi.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi.c,v
retrieving revision 1.214.2.10
diff -u -r1.214.2.10 acpi.c
--- sys/dev/acpica/acpi.c 26 May 2007 00:48:55 -0000 1.214.2.10
+++ sys/dev/acpica/acpi.c 17 Jun 2007 16:58:35 -0000
@@ -136,6 +136,7 @@
static ACPI_STATUS acpi_probe_child(ACPI_HANDLE handle, UINT32 level,
void *context, void **status);
static BOOLEAN acpi_MatchHid(ACPI_HANDLE h, const char *hid);
+static ACPI_STATUS acpi_EnterSleepState(struct acpi_softc *sc, int state);
static void acpi_shutdown_final(void *arg, int howto);
static void acpi_enable_fixed_events(struct acpi_softc *sc);
static int acpi_wake_sleep_prep(ACPI_HANDLE handle, int sstate);
@@ -414,6 +415,7 @@
sc = device_get_softc(dev);
sc->acpi_dev = dev;
+ callout_init(&sc->susp_force_to, TRUE);
/* Initialize resource manager. */
acpi_rman_io.rm_type = RMAN_ARRAY;
@@ -561,7 +563,7 @@
/* Pick the first valid sleep state for the sleep button default. */
sc->acpi_sleep_button_sx = ACPI_S_STATES_MAX + 1;
- for (state = ACPI_STATE_S1; state < ACPI_STATE_S5; state++)
+ for (state = ACPI_STATE_S1; state <= ACPI_STATE_S4; state++)
if (ACPI_SUCCESS(AcpiGetSleepTypeData(state, &TypeA, &TypeB))) {
sc->acpi_sleep_button_sx = state;
break;
@@ -2100,6 +2102,143 @@
return (acpi_SetInteger(ACPI_ROOT_OBJECT, "_PIC", model));
}
+/*
+ * DEPRECATED. This interface has serious deficiencies and will be
+ * removed.
+ *
+ * Immediately enter the sleep state. In the old model, acpiconf(8) ran
+ * rc.suspend and rc.resume so we don't have to notify devd(8) to do this.
+ */
+ACPI_STATUS
+acpi_SetSleepState(struct acpi_softc *sc, int state)
+{
+ static int once;
+
+ if (!once) {
+ printf(
+"warning: acpi_SetSleepState deprecated, need to update your software\n");
+ once = 1;
+ }
+ return (acpi_EnterSleepState(sc, state));
+}
+
+static void
+acpi_sleep_force(void *arg)
+{
+ struct acpi_softc *sc;
+
+ printf("acpi: suspend request timed out, forcing sleep now\n");
+ sc = arg;
+ acpi_EnterSleepState(sc, sc->acpi_next_sstate);
+}
+
+/*
+ * Request that the system enter the given suspend state. All /dev/apm
+ * devices and devd(8) will be notified. Userland then has a chance to
+ * save state and acknowledge the request. The system sleeps once all
+ * acks are in.
+ */
+int
+acpi_ReqSleepState(struct acpi_softc *sc, int state)
+{
+ struct apm_clone_data *clone;
+
+ /* S5 (soft-off) should be entered via shutdown_nice(). */
+ if (state < ACPI_STATE_S1 || state > ACPI_STATE_S4)
+ return (EINVAL);
+
+ /* If a suspend request is already in progress, just return. */
+ ACPI_LOCK(acpi);
+ if (sc->acpi_next_sstate != 0) {
+ ACPI_UNLOCK(acpi);
+ return (0);
+ }
+
+ /* Record the pending state and notify all apm devices. */
+ sc->acpi_next_sstate = state;
+ STAILQ_FOREACH(clone, &sc->apm_cdevs, entries) {
+printf("considering clone of %s\n", devtoname(clone->cdev));
+ clone->notify_status = APM_EV_NONE;
+ if ((clone->flags & ACPI_EVF_DEVD) == 0) {
+printf("acpi ev got %d, waking listener %p\n", state, clone);
+ selwakeuppri(&clone->sel_read, PZERO);
+ KNOTE_UNLOCKED(&clone->sel_read.si_note, 0);
+ }
+ }
+
+ /* Now notify devd(8) also. */
+ acpi_UserNotify("Suspend", ACPI_ROOT_OBJECT, state);
+
+ /*
+ * Set a timeout to fire if userland doesn't ack the suspend request
+ * in time. This way we still eventually go to sleep if we were
+ * overheating or running low on battery, even if userland is hung.
+ * We cancel this timeout once all userland acks are in or the
+ * suspend request is aborted.
+ */
+ callout_reset(&sc->susp_force_to, 10 * hz, acpi_sleep_force, sc);
+ ACPI_UNLOCK(acpi);
+ return (0);
+}
+
+/*
+ * Acknowledge (or reject) a pending sleep state. The caller has
+ * prepared for suspend and is now ready for it to proceed. If the
+ * error argument is non-zero, it indicates suspend should be cancelled
+ * and gives an errno value describing why. Once all votes are in,
+ * we suspend the system.
+ */
+int
+acpi_AckSleepState(struct apm_clone_data *clone, int error)
+{
+ struct acpi_softc *sc;
+ int sleeping;
+
+ /* If no pending sleep state, return an error. */
+ ACPI_LOCK(acpi);
+ sc = clone->acpi_sc;
+ if (sc->acpi_next_sstate == 0) {
+ ACPI_UNLOCK(acpi);
+ return (ENXIO);
+ }
+
+ /* Caller wants to abort suspend process. */
+ if (error) {
+ sc->acpi_next_sstate = 0;
+ callout_stop(&sc->susp_force_to);
+ printf("acpi: listener on %s cancelled the pending suspend\n",
+ devtoname(clone->cdev));
+ ACPI_UNLOCK(acpi);
+ return (0);
+ }
+
+ /*
+ * Mark this device as acking the suspend request. Then, walk through
+ * all devices, seeing if they agree yet. We only count devices that
+ * are writable since read-only devices couldn't ack the request.
+ */
+ clone->notify_status = APM_EV_ACKED;
+ sleeping = TRUE;
+ STAILQ_FOREACH(clone, &sc->apm_cdevs, entries) {
+ if ((clone->flags & ACPI_EVF_WRITE) != 0 &&
+ clone->notify_status != APM_EV_ACKED) {
+ sleeping = FALSE;
+ break;
+ }
+ }
+
+ /* If all devices have voted "yes", we will suspend now. */
+ if (sleeping) {
+printf("ack: all devs said yes, sleeping now\n");
+ callout_stop(&sc->susp_force_to);
+ }
+ ACPI_UNLOCK(acpi);
+ if (sleeping)
+ acpi_EnterSleepState(sc, sc->acpi_next_sstate);
+
+ return (0);
+}
+
static void
acpi_sleep_enable(void *arg)
{
@@ -2116,12 +2255,12 @@
};
/*
- * Set the system sleep state
+ * Enter the desired system sleep state.
*
* Currently we support S1-S5 but S4 is only S4BIOS
*/
-ACPI_STATUS
-acpi_SetSleepState(struct acpi_softc *sc, int state)
+static ACPI_STATUS
+acpi_EnterSleepState(struct acpi_softc *sc, int state)
{
ACPI_STATUS status;
UINT8 TypeA;
@@ -2233,6 +2372,7 @@
* Back out state according to how far along we got in the suspend
* process. This handles both the error and success cases.
*/
+ sc->acpi_next_sstate = 0;
if (slp_state >= ACPI_SS_GPE_SET) {
acpi_wake_prep_walk(state);
sc->acpi_sstate = ACPI_STATE_S0;
@@ -2246,7 +2386,10 @@
/* Allow another sleep request after a while. */
if (state != ACPI_STATE_S5)
- timeout(acpi_sleep_enable, (caddr_t)sc, hz * ACPI_MINIMUM_AWAKETIME);
+ timeout(acpi_sleep_enable, sc, hz * ACPI_MINIMUM_AWAKETIME);
+
+ /* Run /etc/rc.resume after we are back. */
+ acpi_UserNotify("Resume", ACPI_ROOT_OBJECT, state);
mtx_unlock(&Giant);
return_ACPI_STATUS (status);
@@ -2558,11 +2701,14 @@
static void
acpi_system_eventhandler_sleep(void *arg, int state)
{
+ int ret;
ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state);
- if (state >= ACPI_STATE_S0 && state <= ACPI_S_STATES_MAX)
- acpi_SetSleepState((struct acpi_softc *)arg, state);
+ /* Request that the system prepare to enter the given suspend state. */
+ ret = acpi_ReqSleepState((struct acpi_softc *)arg, state);
+ if (ret != 0)
+ printf("acpi: request to enter state S%d failed\n", state);
return_VOID;
}
@@ -2824,7 +2970,15 @@
/* Core system ioctls. */
switch (cmd) {
- case ACPIIO_SETSLPSTATE:
+ case ACPIIO_REQSLPSTATE:
+ state = *(int *)addr;
+ error = acpi_ReqSleepState(sc, state);
+ break;
+ case ACPIIO_ACKSLPSTATE:
+ error = *(int *)addr;
+ error = acpi_AckSleepState(sc->acpi_clone, error);
+ break;
+ case ACPIIO_SETSLPSTATE: /* DEPRECATED */
error = EINVAL;
state = *(int *)addr;
if (state >= ACPI_STATE_S0 && state <= ACPI_S_STATES_MAX)
Index: sys/dev/acpica/acpiio.h
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpiio.h,v
retrieving revision 1.14.2.2
diff -u -r1.14.2.2 acpiio.h
--- sys/dev/acpica/acpiio.h 5 Nov 2005 23:55:56 -0000 1.14.2.2
+++ sys/dev/acpica/acpiio.h 17 Jun 2007 16:58:35 -0000
@@ -33,7 +33,13 @@
/*
* Core ACPI subsystem ioctls
*/
-#define ACPIIO_SETSLPSTATE _IOW('P', 3, int)
+#define ACPIIO_SETSLPSTATE _IOW('P', 3, int) /* DEPRECATED */
+
+/* Request S1-5 sleep state. User is notified and then sleep proceeds. */
+#define ACPIIO_REQSLPSTATE _IOW('P', 4, int)
+
+/* Allow suspend to continue (0) or abort it (errno). */
+#define ACPIIO_ACKSLPSTATE _IOW('P', 5, int)
struct acpi_battinfo {
int cap; /* percent */
Index: sys/dev/acpica/acpivar.h
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpivar.h,v
retrieving revision 1.95.2.5
diff -u -r1.95.2.5 acpivar.h
--- sys/dev/acpica/acpivar.h 23 Jan 2007 07:21:23 -0000 1.95.2.5
+++ sys/dev/acpica/acpivar.h 17 Jun 2007 16:58:35 -0000
@@ -39,12 +39,14 @@
#include <sys/ktr.h>
#include <sys/lock.h>
#include <sys/mutex.h>
+#include <sys/selinfo.h>
#include <sys/sx.h>
#include <sys/sysctl.h>
#include <machine/bus.h>
#include <machine/resource.h>
+struct apm_clone_data;
struct acpi_softc {
device_t acpi_dev;
struct cdev *acpi_dev_t;
@@ -76,6 +78,11 @@
bus_dmamap_t acpi_wakemap;
vm_offset_t acpi_wakeaddr;
vm_paddr_t acpi_wakephys;
+
+ int acpi_next_sstate; /* Next suspend Sx state. */
+ struct apm_clone_data *acpi_clone; /* Pseudo-dev for devd(8). */
+ STAILQ_HEAD(,apm_clone_data) apm_cdevs; /* All apm/apmctl/acpi cdevs. */
+ struct callout susp_force_to; /* Force suspend if no acks. */
};
struct acpi_device {
@@ -89,6 +96,22 @@
struct resource_list ad_rl;
};
+/* Track device (/dev/{apm,apmctl} and /dev/acpi) notification status. */
+struct apm_clone_data {
+ STAILQ_ENTRY(apm_clone_data) entries;
+ struct cdev *cdev;
+ int flags;
+#define ACPI_EVF_NONE 0 /* /dev/apm semantics */
+#define ACPI_EVF_DEVD 1 /* /dev/acpi is handled via devd(8) */
+#define ACPI_EVF_WRITE 2 /* Device instance is opened writable. */
+ int notify_status;
+#define APM_EV_NONE 0 /* Device not yet aware of pending sleep. */
+#define APM_EV_NOTIFIED 1 /* Device saw next sleep state. */
+#define APM_EV_ACKED 2 /* Device agreed sleep can occur. */
+ struct acpi_softc *acpi_sc;
+ struct selinfo sel_read;
+};
+
#define ACPI_PRW_MAX_POWERRES 8
struct acpi_prw_data {
@@ -304,6 +327,8 @@
ACPI_RESOURCE *res);
ACPI_STATUS acpi_OverrideInterruptLevel(UINT32 InterruptNumber);
ACPI_STATUS acpi_SetIntrModel(int model);
+int acpi_ReqSleepState(struct acpi_softc *sc, int state);
+int acpi_AckSleepState(struct apm_clone_data *clone, int error);
ACPI_STATUS acpi_SetSleepState(struct acpi_softc *sc, int state);
int acpi_wake_init(device_t dev, int type);
int acpi_wake_set_enable(device_t dev, int enable);
Index: sys/i386/acpica/acpi_machdep.c
===================================================================
RCS file: /home/ncvs/src/sys/i386/acpica/acpi_machdep.c,v
retrieving revision 1.28.2.4
diff -u -r1.28.2.4 acpi_machdep.c
--- sys/i386/acpica/acpi_machdep.c 2 May 2007 18:42:46 -0000 1.28.2.4
+++ sys/i386/acpica/acpi_machdep.c 17 Jun 2007 16:58:35 -0000
@@ -32,6 +32,8 @@
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/poll.h>
#include <sys/sysctl.h>
#include <sys/uio.h>
#include <vm/vm.h>
@@ -45,8 +47,6 @@
* APM driver emulation
*/
-#include <sys/selinfo.h>
-
#include <machine/apm_bios.h>
#include <machine/pc/bios.h>
@@ -63,21 +63,31 @@
static int intr_model = ACPI_INTR_PIC;
static int apm_active;
+static struct clonedevs *apm_clones;
+
+MALLOC_DEFINE(M_APMDEV, "apmdev", "APM device emulation");
-static d_open_t apmopen;
-static d_close_t apmclose;
-static d_write_t apmwrite;
-static d_ioctl_t apmioctl;
-static d_poll_t apmpoll;
+static d_open_t apmopen;
+static d_close_t apmclose;
+static d_write_t apmwrite;
+static d_ioctl_t apmioctl;
+static d_poll_t apmpoll;
+static d_kqfilter_t apmkqfilter;
+static void apmreadfiltdetach(struct knote *kn);
+static int apmreadfilt(struct knote *kn, long hint);
+static struct filterops apm_readfiltops =
+ { 1, NULL, apmreadfiltdetach, apmreadfilt };
static struct cdevsw apm_cdevsw = {
.d_version = D_VERSION,
+ .d_flags = D_TRACKCLOSE,
.d_open = apmopen,
.d_close = apmclose,
.d_write = apmwrite,
.d_ioctl = apmioctl,
.d_poll = apmpoll,
.d_name = "apm",
+ .d_kqfilter = apmkqfilter
};
static int
@@ -201,44 +211,159 @@
return (0);
}
+/* Create single-use devices for /dev/apm and /dev/apmctl. */
+static void
+apm_clone(void *arg, struct ucred *cred, char *name, int namelen,
+ struct cdev **dev)
+{
+ int ctl_dev, unit;
+
+ if (*dev != NULL)
+ return;
+ if (strcmp(name, "apmctl") == 0)
+ ctl_dev = TRUE;
+ else if (strcmp(name, "apm") == 0)
+ ctl_dev = FALSE;
+ else
+ return;
+
+ /* Always create a new device and unit number. */
+ unit = -1;
+ if (clone_create(&apm_clones, &apm_cdevsw, &unit, dev, 0)) {
+ if (ctl_dev) {
+ *dev = make_dev(&apm_cdevsw, unit2minor(unit),
+ UID_ROOT, GID_OPERATOR, 0660, "apmctl%d", unit);
+ } else {
+ *dev = make_dev(&apm_cdevsw, unit2minor(unit),
+ UID_ROOT, GID_OPERATOR, 0664, "apm%d", unit);
+ }
+ if (*dev != NULL) {
+ dev_ref(*dev);
+ (*dev)->si_flags |= SI_CHEAPCLONE;
+ }
+ }
+}
+
+/* Create a struct for tracking per-device suspend notification. */
+static struct apm_clone_data *
+apm_create_clone(struct cdev *dev, struct acpi_softc *acpi_sc)
+{
+ struct apm_clone_data *clone;
+
+ clone = malloc(sizeof(*clone), M_APMDEV, M_WAITOK);
+ clone->cdev = dev;
+ clone->acpi_sc = acpi_sc;
+ clone->notify_status = APM_EV_NONE;
+ bzero(&clone->sel_read, sizeof(clone->sel_read));
+ knlist_init(&clone->sel_read.si_note, &acpi_mutex, NULL, NULL, NULL);
+
+ /*
+ * The acpi device is always managed by devd(8) and is considered
+ * writable (i.e., ack is required to allow suspend to proceed.)
+ */
+ if (strcmp("acpi", devtoname(dev)) == 0)
+ clone->flags = ACPI_EVF_DEVD | ACPI_EVF_WRITE;
+ else
+ clone->flags = ACPI_EVF_NONE;
+
+ ACPI_LOCK(acpi);
+ STAILQ_INSERT_TAIL(&acpi_sc->apm_cdevs, clone, entries);
+ ACPI_UNLOCK(acpi);
+ return (clone);
+}
+
static int
apmopen(struct cdev *dev, int flag, int fmt, d_thread_t *td)
{
+ struct acpi_softc *acpi_sc;
+ struct apm_clone_data *clone;
+
+ acpi_sc = devclass_get_softc(devclass_find("acpi"), 0);
+ clone = apm_create_clone(dev, acpi_sc);
+ dev->si_drv1 = clone;
+
+ /* If the device is opened for write, record that. */
+ if ((flag & FWRITE) != 0)
+ clone->flags |= ACPI_EVF_WRITE;
+
+printf("%s opened clone %p\n", devtoname(dev), (struct apm_clone_data *)dev->si_drv1);
return (0);
}
static int
apmclose(struct cdev *dev, int flag, int fmt, d_thread_t *td)
{
+ struct apm_clone_data *clone;
+ struct acpi_softc *acpi_sc;
+
+ clone = dev->si_drv1;
+ acpi_sc = clone->acpi_sc;
+
+ /* We are about to lose a reference so check if suspend should occur */
+ if (acpi_sc->acpi_next_sstate != 0 &&
+ clone->notify_status != APM_EV_ACKED)
+ acpi_AckSleepState(clone, 0);
+
+ /* Remove this clone's data from the list and free it. */
+ ACPI_LOCK(acpi);
+ STAILQ_REMOVE(&acpi_sc->apm_cdevs, clone, apm_clone_data, entries);
+ knlist_destroy(&clone->sel_read.si_note);
+printf("%s closed clone %p\n", devtoname(dev), clone);
+ ACPI_UNLOCK(acpi);
+ free(clone, M_APMDEV);
+ /* XXX destroy_dev() needed */
return (0);
}
static int
apmioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, d_thread_t *td)
{
- int error = 0;
+ int error;
+ struct apm_clone_data *clone;
struct acpi_softc *acpi_sc;
- struct apm_info info;
+ struct apm_info info;
+ struct apm_event_info *ev_info;
apm_info_old_t aiop;
- acpi_sc = devclass_get_softc(devclass_find("acpi"), 0);
+ error = 0;
+ clone = dev->si_drv1;
+ acpi_sc = clone->acpi_sc;
switch (cmd) {
case APMIO_SUSPEND:
if ((flag & FWRITE) == 0)
return (EPERM);
- if (apm_active)
- acpi_SetSleepState(acpi_sc, acpi_sc->acpi_suspend_sx);
+printf("apm ioctl suspend\n");
+ if (acpi_sc->acpi_next_sstate == 0)
+ acpi_ReqSleepState(acpi_sc, acpi_sc->acpi_suspend_sx);
else
- error = EINVAL;
+ acpi_AckSleepState(clone, 0);
break;
case APMIO_STANDBY:
if ((flag & FWRITE) == 0)
return (EPERM);
- if (apm_active)
- acpi_SetSleepState(acpi_sc, acpi_sc->acpi_standby_sx);
+printf("apm ioctl standby\n");
+ if (acpi_sc->acpi_next_sstate == 0)
+ acpi_ReqSleepState(acpi_sc, acpi_sc->acpi_standby_sx);
else
- error = EINVAL;
+ acpi_AckSleepState(clone, 0);
+ break;
+ case APMIO_NEXTEVENT:
+ printf("apm nextevent start\n");
+ ACPI_LOCK(acpi);
+ if (acpi_sc->acpi_next_sstate != 0 && clone->notify_status ==
+ APM_EV_NONE) {
+ ev_info = (struct apm_event_info *)addr;
+ if (acpi_sc->acpi_next_sstate <= ACPI_STATE_S3)
+ ev_info->type = PMEV_STANDBYREQ;
+ else
+ ev_info->type = PMEV_SUSPENDREQ;
+ ev_info->index = 0;
+ clone->notify_status = APM_EV_NOTIFIED;
+ printf("apm event returning %d\n", ev_info->type);
+ } else
+ error = EAGAIN;
+ ACPI_UNLOCK(acpi);
break;
case APMIO_GETINFO_OLD:
if (acpi_capm_get_info(&info))
@@ -299,24 +424,72 @@
static int
apmpoll(struct cdev *dev, int events, d_thread_t *td)
{
+ struct apm_clone_data *clone;
+ int revents;
+
+ revents = 0;
+ ACPI_LOCK(acpi);
+ clone = dev->si_drv1;
+ if (clone->acpi_sc->acpi_next_sstate)
+ revents |= events & (POLLIN | POLLRDNORM);
+ else
+ selrecord(td, &clone->sel_read);
+ ACPI_UNLOCK(acpi);
+printf("apm poll returning %x\n", revents);
+ return (revents);
+}
+
+static int
+apmkqfilter(struct cdev *dev, struct knote *kn)
+{
+ struct apm_clone_data *clone;
+
+ ACPI_LOCK(acpi);
+ clone = dev->si_drv1;
+ kn->kn_hook = clone;
+ kn->kn_fop = &apm_readfiltops;
+ knlist_add(&clone->sel_read.si_note, kn, 0);
+ ACPI_UNLOCK(acpi);
return (0);
}
static void
-acpi_capm_init(struct acpi_softc *sc)
+apmreadfiltdetach(struct knote *kn)
+{
+ struct apm_clone_data *clone;
+
+ ACPI_LOCK(acpi);
+ clone = kn->kn_hook;
+ knlist_remove(&clone->sel_read.si_note, kn, 0);
+ ACPI_UNLOCK(acpi);
+}
+
+static int
+apmreadfilt(struct knote *kn, long hint)
{
- make_dev(&apm_cdevsw, 0, 0, 5, 0664, "apm");
+ struct apm_clone_data *clone;
+ int sleeping;
+
+ ACPI_LOCK(acpi);
+ clone = kn->kn_hook;
+ sleeping = clone->acpi_sc->acpi_next_sstate ? 1 : 0;
+ ACPI_UNLOCK(acpi);
+ return (sleeping);
}
int
acpi_machdep_init(device_t dev)
{
- struct acpi_softc *sc;
+ struct acpi_softc *acpi_sc;
- sc = devclass_get_softc(devclass_find("acpi"), 0);
- acpi_capm_init(sc);
+ acpi_sc = devclass_get_softc(devclass_find("acpi"), 0);
- acpi_install_wakeup_handler(sc);
+ /* Create a clone for /dev/acpi also. */
+ STAILQ_INIT(&acpi_sc->apm_cdevs);
+ acpi_sc->acpi_clone = apm_create_clone(acpi_sc->acpi_dev_t, acpi_sc);
+ clone_setup(&apm_clones);
+ EVENTHANDLER_REGISTER(dev_clone, apm_clone, 0, 1000);
+ acpi_install_wakeup_handler(acpi_sc);
if (intr_model == ACPI_INTR_PIC)
BUS_CONFIG_INTR(dev, AcpiGbl_FADT->SciInt, INTR_TRIGGER_LEVEL,
@@ -324,8 +497,8 @@
else
acpi_SetIntrModel(intr_model);
- SYSCTL_ADD_UINT(&sc->acpi_sysctl_ctx,
- SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO,
+ SYSCTL_ADD_UINT(&acpi_sc->acpi_sysctl_ctx,
+ SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO,
"reset_video", CTLFLAG_RW, &acpi_reset_video, 0,
"Call the VESA reset BIOS vector on the resume path");
Index: usr.sbin/acpi/acpiconf/acpiconf.c
===================================================================
RCS file: /home/ncvs/src/usr.sbin/acpi/acpiconf/acpiconf.c,v
retrieving revision 1.22.2.1
diff -u -r1.22.2.1 acpiconf.c
--- usr.sbin/acpi/acpiconf/acpiconf.c 29 Jul 2005 16:31:58 -0000 1.22.2.1
+++ usr.sbin/acpi/acpiconf/acpiconf.c 17 Jun 2007 16:58:35 -0000
@@ -40,8 +40,6 @@
#include <contrib/dev/acpica/acpi.h>
#define ACPIDEV "/dev/acpi"
-#define RC_SUSPEND_PATH "/etc/rc.suspend"
-#define RC_RESUME_PATH "/etc/rc.resume"
static int acpifd;
@@ -55,32 +53,27 @@
err(EX_OSFILE, ACPIDEV);
}
-static int
+/* Prepare to sleep and then wait for the signal that sleeping can occur. */
+static void
acpi_sleep(int sleep_type)
{
- char cmd[64];
int ret;
+
+ /* Notify OS that we want to sleep. devd(8) gets this notify. */
+ ret = ioctl(acpifd, ACPIIO_REQSLPSTATE, &sleep_type);
+ if (ret != 0)
+ err(EX_IOERR, "request sleep type (%d) failed", sleep_type);
+}
- /* Run the suspend rc script, if available. */
- if (access(RC_SUSPEND_PATH, X_OK) == 0) {
- snprintf(cmd, sizeof(cmd), "%s acpi %d", RC_SUSPEND_PATH,
- sleep_type);
- system(cmd);
- }
-
- ret = ioctl(acpifd, ACPIIO_SETSLPSTATE, &sleep_type);
-
- /* Run the resume rc script, if available. */
- if (access(RC_RESUME_PATH, X_OK) == 0) {
- snprintf(cmd, sizeof(cmd), "%s acpi %d", RC_RESUME_PATH,
- sleep_type);
- system(cmd);
- }
+/* Ack or abort a pending suspend request. */
+static void
+acpi_sleep_ack(int err_val)
+{
+ int ret;
+ ret = ioctl(acpifd, ACPIIO_ACKSLPSTATE, &err_val);
if (ret != 0)
- err(EX_IOERR, "sleep type (%d) failed", sleep_type);
-
- return (0);
+ err(EX_IOERR, "ack sleep type failed");
}
/* should be a acpi define, but doesn't appear to be */
@@ -183,7 +176,7 @@
static void
usage(const char* prog)
{
- printf("usage: %s [-h] [-i batt] [-s 1-5]\n", prog);
+ printf("usage: %s [-h] [-i batt] [-k ack] [-s 1-4]\n", prog);
exit(0);
}
@@ -200,17 +193,20 @@
sleep_type = -1;
acpi_init();
- while ((c = getopt(argc, argv, "hi:s:")) != -1) {
+ while ((c = getopt(argc, argv, "hi:k:s:")) != -1) {
switch (c) {
case 'i':
acpi_battinfo(atoi(optarg));
break;
+ case 'k':
+ acpi_sleep_ack(atoi(optarg));
+ break;
case 's':
if (optarg[0] == 'S')
sleep_type = optarg[1] - '0';
else
sleep_type = optarg[0] - '0';
- if (sleep_type < 0 || sleep_type > 5)
+ if (sleep_type < 1 || sleep_type > 4)
errx(EX_USAGE, "invalid sleep type (%d)",
sleep_type);
break;
@@ -223,10 +219,8 @@
argc -= optind;
argv += optind;
- if (sleep_type != -1) {
- sleep(1); /* wait 1 sec. for key-release event */
+ if (sleep_type != -1)
acpi_sleep(sleep_type);
- }
close(acpifd);
exit (0);
--------------040100070106020900000700
Content-Type: text/x-patch;
name="acpi7_wakeapi.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="acpi7_wakeapi.diff"
Index: etc/devd.conf
===================================================================
RCS file: /home/ncvs/src/etc/devd.conf,v
retrieving revision 1.37
diff -u -r1.37 devd.conf
--- etc/devd.conf 8 Apr 2007 16:05:23 -0000 1.37
+++ etc/devd.conf 17 Jun 2007 16:52:50 -0000
@@ -239,6 +239,19 @@
action "logger -p kern.warn 'ZFS: checksum mismatch, zpool=$pool path=$vdev_path offset=$zio_offset size=$zio_size'";
};
+# User requested suspend, so perform preparation steps and then execute
+# the actual suspend process.
+notify 10 {
+ match "system" "ACPI";
+ match "subsystem" "Suspend";
+ action "/etc/rc.suspend acpi $notify";
+};
+notify 10 {
+ match "system" "ACPI";
+ match "subsystem" "Resume";
+ action "/etc/rc.resume acpi $notify";
+};
+
/* EXAMPLES TO END OF FILE
# The following might be an example of something that a vendor might
@@ -277,6 +290,7 @@
# Button: Button pressed (0 for power, 1 for sleep)
# CMBAT: ACPI battery events
# Lid: Lid state (0 is closed, 1 is open)
+# Suspend, Resume: Suspend and resume notification
# Thermal: ACPI thermal zone events
#
# This example calls a script when the AC state changes, passing the
Index: etc/rc.resume
===================================================================
RCS file: /home/ncvs/src/etc/rc.resume,v
retrieving revision 1.7
diff -u -r1.7 rc.resume
--- etc/rc.resume 30 Dec 2003 17:30:39 -0000 1.7
+++ etc/rc.resume 17 Jun 2007 16:52:50 -0000
@@ -30,7 +30,7 @@
# sample run command file for APM Resume Event
if [ $# -ne 2 ]; then
- echo "Usage: $0 [apm|acpi] [standby,suspend|1-5]"
+ echo "Usage: $0 [apm|acpi] [standby,suspend|1-4]"
exit 1
fi
@@ -48,9 +48,13 @@
# pccardq | awk -F '~' '$5 == "inactive" \
# { printf("pccardc power %d 1", $1); }' | sh
-# UHCI has trouble resuming so we just load/unload it. You
-# should add any other kernel modules you want reloaded here.
-# kldload usb
+# If a device driver has problems resuming, try unloading it before
+# suspend and reloading it on resume. Example:
+# kldunload usb
+
+# wpa_supplicant(8) doesn't seem to reassociate during resume. Uncomment
+# the following to signal it to reassociate.
+# /usr/sbin/wpa_cli reassociate
logger -t $subsystem resumed at `date +'%Y%m%d %H:%M:%S'`
sync && sync && sync
Index: etc/rc.suspend
===================================================================
RCS file: /home/ncvs/src/etc/rc.suspend,v
retrieving revision 1.6
diff -u -r1.6 rc.suspend
--- etc/rc.suspend 21 Jan 2004 03:03:40 -0000 1.6
+++ etc/rc.suspend 17 Jun 2007 16:54:56 -0000
@@ -30,7 +30,7 @@
# sample run command file for APM Suspend Event
if [ $# -ne 2 ]; then
- echo "Usage: $0 [apm|acpi] [standby,suspend|1-5]"
+ echo "Usage: $0 [apm|acpi] [standby,suspend|1-4]"
exit 1
fi
@@ -48,15 +48,20 @@
# pccardq | awk -F '~' '$5 == "filled" && $4 ~ /sio/ \
# { printf("pccardc power %d 0", $1); }' | sh
-# UHCI has trouble resuming so we just load/unload it. You
-# should add any other kernel modules you want unloaded here.
+# If a device driver has problems suspending, try unloading it before
+# suspend and reloading it on resume. Example:
# kldunload usb
logger -t $subsystem suspend at `date +'%Y%m%d %H:%M:%S'`
sync && sync && sync
-[ $subsystem = "apm" ] && sleep 3
+sleep 3
rm -f /var/run/rc.suspend.pid
-[ $subsystem = "apm" ] && zzz
+if [ $subsystem = "apm" ]; then
+ /usr/sbin/zzz
+else
+ # Notify the kernel to continue the suspend process
+ /usr/sbin/acpiconf -k 0
+fi
exit 0
Index: sys/dev/acpica/acpi.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi.c,v
retrieving revision 1.239
diff -u -r1.239 acpi.c
--- sys/dev/acpica/acpi.c 15 Jun 2007 18:02:33 -0000 1.239
+++ sys/dev/acpica/acpi.c 17 Jun 2007 16:52:50 -0000
@@ -136,6 +136,7 @@
static ACPI_STATUS acpi_probe_child(ACPI_HANDLE handle, UINT32 level,
void *context, void **status);
static BOOLEAN acpi_MatchHid(ACPI_HANDLE h, const char *hid);
+static ACPI_STATUS acpi_EnterSleepState(struct acpi_softc *sc, int state);
static void acpi_shutdown_final(void *arg, int howto);
static void acpi_enable_fixed_events(struct acpi_softc *sc);
static int acpi_wake_sleep_prep(ACPI_HANDLE handle, int sstate);
@@ -410,6 +411,7 @@
sc = device_get_softc(dev);
sc->acpi_dev = dev;
+ callout_init(&sc->susp_force_to, TRUE);
error = ENXIO;
@@ -592,7 +594,7 @@
/* Pick the first valid sleep state for the sleep button default. */
sc->acpi_sleep_button_sx = ACPI_S_STATES_MAX + 1;
- for (state = ACPI_STATE_S1; state < ACPI_STATE_S5; state++)
+ for (state = ACPI_STATE_S1; state <= ACPI_STATE_S4; state++)
if (ACPI_SUCCESS(AcpiGetSleepTypeData(state, &TypeA, &TypeB))) {
sc->acpi_sleep_button_sx = state;
break;
@@ -2118,6 +2120,143 @@
return (acpi_SetInteger(ACPI_ROOT_OBJECT, "_PIC", model));
}
+/*
+ * DEPRECATED. This interface has serious deficiencies and will be
+ * removed.
+ *
+ * Immediately enter the sleep state. In the old model, acpiconf(8) ran
+ * rc.suspend and rc.resume so we don't have to notify devd(8) to do this.
+ */
+ACPI_STATUS
+acpi_SetSleepState(struct acpi_softc *sc, int state)
+{
+ static int once;
+
+ if (!once) {
+ printf(
+"warning: acpi_SetSleepState deprecated, need to update your software\n");
+ once = 1;
+ }
+ return (acpi_EnterSleepState(sc, state));
+}
+
+static void
+acpi_sleep_force(void *arg)
+{
+ struct acpi_softc *sc;
+
+ printf("acpi: suspend request timed out, forcing sleep now\n");
+ sc = arg;
+ acpi_EnterSleepState(sc, sc->acpi_next_sstate);
+}
+
+/*
+ * Request that the system enter the given suspend state. All /dev/apm
+ * devices and devd(8) will be notified. Userland then has a chance to
+ * save state and acknowledge the request. The system sleeps once all
+ * acks are in.
+ */
+int
+acpi_ReqSleepState(struct acpi_softc *sc, int state)
+{
+ struct apm_clone_data *clone;
+
+ /* S5 (soft-off) should be entered via shutdown_nice(). */
+ if (state < ACPI_STATE_S1 || state > ACPI_STATE_S4)
+ return (EINVAL);
+
+ /* If a suspend request is already in progress, just return. */
+ ACPI_LOCK(acpi);
+ if (sc->acpi_next_sstate != 0) {
+ ACPI_UNLOCK(acpi);
+ return (0);
+ }
+
+ /* Record the pending state and notify all apm devices. */
+ sc->acpi_next_sstate = state;
+ STAILQ_FOREACH(clone, &sc->apm_cdevs, entries) {
+printf("considering clone of %s\n", devtoname(clone->cdev));
+ clone->notify_status = APM_EV_NONE;
+ if ((clone->flags & ACPI_EVF_DEVD) == 0) {
+printf("acpi ev got %d, waking listener %p\n", state, clone);
+ selwakeuppri(&clone->sel_read, PZERO);
+ KNOTE_UNLOCKED(&clone->sel_read.si_note, 0);
+ }
+ }
+
+ /* Now notify devd(8) also. */
+ acpi_UserNotify("Suspend", ACPI_ROOT_OBJECT, state);
+
+ /*
+ * Set a timeout to fire if userland doesn't ack the suspend request
+ * in time. This way we still eventually go to sleep if we were
+ * overheating or running low on battery, even if userland is hung.
+ * We cancel this timeout once all userland acks are in or the
+ * suspend request is aborted.
+ */
+ callout_reset(&sc->susp_force_to, 10 * hz, acpi_sleep_force, sc);
+ ACPI_UNLOCK(acpi);
+ return (0);
+}
+
+/*
+ * Acknowledge (or reject) a pending sleep state. The caller has
+ * prepared for suspend and is now ready for it to proceed. If the
+ * error argument is non-zero, it indicates suspend should be cancelled
+ * and gives an errno value describing why. Once all votes are in,
+ * we suspend the system.
+ */
+int
+acpi_AckSleepState(struct apm_clone_data *clone, int error)
+{
+ struct acpi_softc *sc;
+ int sleeping;
+
+ /* If no pending sleep state, return an error. */
+ ACPI_LOCK(acpi);
+ sc = clone->acpi_sc;
+ if (sc->acpi_next_sstate == 0) {
+ ACPI_UNLOCK(acpi);
+ return (ENXIO);
+ }
+
+ /* Caller wants to abort suspend process. */
+ if (error) {
+ sc->acpi_next_sstate = 0;
+ callout_stop(&sc->susp_force_to);
+ printf("acpi: listener on %s cancelled the pending suspend\n",
+ devtoname(clone->cdev));
+ ACPI_UNLOCK(acpi);
+ return (0);
+ }
+
+ /*
+ * Mark this device as acking the suspend request. Then, walk through
+ * all devices, seeing if they agree yet. We only count devices that
+ * are writable since read-only devices couldn't ack the request.
+ */
+ clone->notify_status = APM_EV_ACKED;
+ sleeping = TRUE;
+ STAILQ_FOREACH(clone, &sc->apm_cdevs, entries) {
+ if ((clone->flags & ACPI_EVF_WRITE) != 0 &&
+ clone->notify_status != APM_EV_ACKED) {
+ sleeping = FALSE;
+ break;
+ }
+ }
+
+ /* If all devices have voted "yes", we will suspend now. */
+ if (sleeping) {
+printf("ack: all devs said yes, sleeping now\n");
+ callout_stop(&sc->susp_force_to);
+ }
+ ACPI_UNLOCK(acpi);
+ if (sleeping)
+ acpi_EnterSleepState(sc, sc->acpi_next_sstate);
+
+ return (0);
+}
+
static void
acpi_sleep_enable(void *arg)
{
@@ -2134,12 +2273,12 @@
};
/*
- * Set the system sleep state
+ * Enter the desired system sleep state.
*
* Currently we support S1-S5 but S4 is only S4BIOS
*/
-ACPI_STATUS
-acpi_SetSleepState(struct acpi_softc *sc, int state)
+static ACPI_STATUS
+acpi_EnterSleepState(struct acpi_softc *sc, int state)
{
ACPI_STATUS status;
UINT8 TypeA;
@@ -2251,6 +2390,7 @@
* Back out state according to how far along we got in the suspend
* process. This handles both the error and success cases.
*/
+ sc->acpi_next_sstate = 0;
if (slp_state >= ACPI_SS_GPE_SET) {
acpi_wake_prep_walk(state);
sc->acpi_sstate = ACPI_STATE_S0;
@@ -2264,7 +2404,10 @@
/* Allow another sleep request after a while. */
if (state != ACPI_STATE_S5)
- timeout(acpi_sleep_enable, (caddr_t)sc, hz * ACPI_MINIMUM_AWAKETIME);
+ timeout(acpi_sleep_enable, sc, hz * ACPI_MINIMUM_AWAKETIME);
+
+ /* Run /etc/rc.resume after we are back. */
+ acpi_UserNotify("Resume", ACPI_ROOT_OBJECT, state);
mtx_unlock(&Giant);
return_ACPI_STATUS (status);
@@ -2574,11 +2717,14 @@
static void
acpi_system_eventhandler_sleep(void *arg, int state)
{
+ int ret;
ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state);
- if (state >= ACPI_STATE_S0 && state <= ACPI_S_STATES_MAX)
- acpi_SetSleepState((struct acpi_softc *)arg, state);
+ /* Request that the system prepare to enter the given suspend state. */
+ ret = acpi_ReqSleepState((struct acpi_softc *)arg, state);
+ if (ret != 0)
+ printf("acpi: request to enter state S%d failed\n", state);
return_VOID;
}
@@ -2840,7 +2986,15 @@
/* Core system ioctls. */
switch (cmd) {
- case ACPIIO_SETSLPSTATE:
+ case ACPIIO_REQSLPSTATE:
+ state = *(int *)addr;
+ error = acpi_ReqSleepState(sc, state);
+ break;
+ case ACPIIO_ACKSLPSTATE:
+ error = *(int *)addr;
+ error = acpi_AckSleepState(sc->acpi_clone, error);
+ break;
+ case ACPIIO_SETSLPSTATE: /* DEPRECATED */
error = EINVAL;
state = *(int *)addr;
if (state >= ACPI_STATE_S0 && state <= ACPI_S_STATES_MAX)
Index: sys/dev/acpica/acpiio.h
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpiio.h,v
retrieving revision 1.16
diff -u -r1.16 acpiio.h
--- sys/dev/acpica/acpiio.h 23 Oct 2005 00:16:41 -0000 1.16
+++ sys/dev/acpica/acpiio.h 17 Jun 2007 16:52:50 -0000
@@ -33,7 +33,13 @@
/*
* Core ACPI subsystem ioctls
*/
-#define ACPIIO_SETSLPSTATE _IOW('P', 3, int)
+#define ACPIIO_SETSLPSTATE _IOW('P', 3, int) /* DEPRECATED */
+
+/* Request S1-5 sleep state. User is notified and then sleep proceeds. */
+#define ACPIIO_REQSLPSTATE _IOW('P', 4, int)
+
+/* Allow suspend to continue (0) or abort it (errno). */
+#define ACPIIO_ACKSLPSTATE _IOW('P', 5, int)
struct acpi_battinfo {
int cap; /* percent */
Index: sys/dev/acpica/acpivar.h
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpivar.h,v
retrieving revision 1.106
diff -u -r1.106 acpivar.h
--- sys/dev/acpica/acpivar.h 15 Jun 2007 18:02:34 -0000 1.106
+++ sys/dev/acpica/acpivar.h 17 Jun 2007 16:57:25 -0000
@@ -39,12 +39,14 @@
#include <sys/ktr.h>
#include <sys/lock.h>
#include <sys/mutex.h>
+#include <sys/selinfo.h>
#include <sys/sx.h>
#include <sys/sysctl.h>
#include <machine/bus.h>
#include <machine/resource.h>
+struct apm_clone_data;
struct acpi_softc {
device_t acpi_dev;
struct cdev *acpi_dev_t;
@@ -76,6 +78,11 @@
bus_dmamap_t acpi_wakemap;
vm_offset_t acpi_wakeaddr;
vm_paddr_t acpi_wakephys;
+
+ int acpi_next_sstate; /* Next suspend Sx state. */
+ struct apm_clone_data *acpi_clone; /* Pseudo-dev for devd(8). */
+ STAILQ_HEAD(,apm_clone_data) apm_cdevs; /* All apm/apmctl/acpi cdevs. */
+ struct callout susp_force_to; /* Force suspend if no acks. */
};
struct acpi_device {
@@ -89,6 +96,22 @@
struct resource_list ad_rl;
};
+/* Track device (/dev/{apm,apmctl} and /dev/acpi) notification status. */
+struct apm_clone_data {
+ STAILQ_ENTRY(apm_clone_data) entries;
+ struct cdev *cdev;
+ int flags;
+#define ACPI_EVF_NONE 0 /* /dev/apm semantics */
+#define ACPI_EVF_DEVD 1 /* /dev/acpi is handled via devd(8) */
+#define ACPI_EVF_WRITE 2 /* Device instance is opened writable. */
+ int notify_status;
+#define APM_EV_NONE 0 /* Device not yet aware of pending sleep. */
+#define APM_EV_NOTIFIED 1 /* Device saw next sleep state. */
+#define APM_EV_ACKED 2 /* Device agreed sleep can occur. */
+ struct acpi_softc *acpi_sc;
+ struct selinfo sel_read;
+};
+
#define ACPI_PRW_MAX_POWERRES 8
struct acpi_prw_data {
@@ -304,6 +327,8 @@
ACPI_RESOURCE *res);
ACPI_STATUS acpi_OverrideInterruptLevel(UINT32 InterruptNumber);
ACPI_STATUS acpi_SetIntrModel(int model);
+int acpi_ReqSleepState(struct acpi_softc *sc, int state);
+int acpi_AckSleepState(struct apm_clone_data *clone, int error);
ACPI_STATUS acpi_SetSleepState(struct acpi_softc *sc, int state);
int acpi_wake_init(device_t dev, int type);
int acpi_wake_set_enable(device_t dev, int enable);
Index: sys/i386/acpica/acpi_machdep.c
===================================================================
RCS file: /home/ncvs/src/sys/i386/acpica/acpi_machdep.c,v
retrieving revision 1.35
diff -u -r1.35 acpi_machdep.c
--- sys/i386/acpica/acpi_machdep.c 31 Mar 2007 23:23:41 -0000 1.35
+++ sys/i386/acpica/acpi_machdep.c 17 Jun 2007 16:52:50 -0000
@@ -33,6 +33,8 @@
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/poll.h>
#include <sys/sysctl.h>
#include <sys/uio.h>
#include <vm/vm.h>
@@ -46,8 +48,6 @@
* APM driver emulation
*/
-#include <sys/selinfo.h>
-
#include <machine/apm_bios.h>
#include <machine/pc/bios.h>
@@ -64,21 +64,31 @@
static int intr_model = ACPI_INTR_PIC;
static int apm_active;
+static struct clonedevs *apm_clones;
+
+MALLOC_DEFINE(M_APMDEV, "apmdev", "APM device emulation");
-static d_open_t apmopen;
-static d_close_t apmclose;
-static d_write_t apmwrite;
-static d_ioctl_t apmioctl;
-static d_poll_t apmpoll;
+static d_open_t apmopen;
+static d_close_t apmclose;
+static d_write_t apmwrite;
+static d_ioctl_t apmioctl;
+static d_poll_t apmpoll;
+static d_kqfilter_t apmkqfilter;
+static void apmreadfiltdetach(struct knote *kn);
+static int apmreadfilt(struct knote *kn, long hint);
+static struct filterops apm_readfiltops =
+ { 1, NULL, apmreadfiltdetach, apmreadfilt };
static struct cdevsw apm_cdevsw = {
.d_version = D_VERSION,
+ .d_flags = D_TRACKCLOSE,
.d_open = apmopen,
.d_close = apmclose,
.d_write = apmwrite,
.d_ioctl = apmioctl,
.d_poll = apmpoll,
.d_name = "apm",
+ .d_kqfilter = apmkqfilter
};
static int
@@ -202,44 +212,159 @@
return (0);
}
+/* Create single-use devices for /dev/apm and /dev/apmctl. */
+static void
+apm_clone(void *arg, struct ucred *cred, char *name, int namelen,
+ struct cdev **dev)
+{
+ int ctl_dev, unit;
+
+ if (*dev != NULL)
+ return;
+ if (strcmp(name, "apmctl") == 0)
+ ctl_dev = TRUE;
+ else if (strcmp(name, "apm") == 0)
+ ctl_dev = FALSE;
+ else
+ return;
+
+ /* Always create a new device and unit number. */
+ unit = -1;
+ if (clone_create(&apm_clones, &apm_cdevsw, &unit, dev, 0)) {
+ if (ctl_dev) {
+ *dev = make_dev(&apm_cdevsw, unit2minor(unit),
+ UID_ROOT, GID_OPERATOR, 0660, "apmctl%d", unit);
+ } else {
+ *dev = make_dev(&apm_cdevsw, unit2minor(unit),
+ UID_ROOT, GID_OPERATOR, 0664, "apm%d", unit);
+ }
+ if (*dev != NULL) {
+ dev_ref(*dev);
+ (*dev)->si_flags |= SI_CHEAPCLONE;
+ }
+ }
+}
+
+/* Create a struct for tracking per-device suspend notification. */
+static struct apm_clone_data *
+apm_create_clone(struct cdev *dev, struct acpi_softc *acpi_sc)
+{
+ struct apm_clone_data *clone;
+
+ clone = malloc(sizeof(*clone), M_APMDEV, M_WAITOK);
+ clone->cdev = dev;
+ clone->acpi_sc = acpi_sc;
+ clone->notify_status = APM_EV_NONE;
+ bzero(&clone->sel_read, sizeof(clone->sel_read));
+ knlist_init(&clone->sel_read.si_note, &acpi_mutex, NULL, NULL, NULL);
+
+ /*
+ * The acpi device is always managed by devd(8) and is considered
+ * writable (i.e., ack is required to allow suspend to proceed.)
+ */
+ if (strcmp("acpi", devtoname(dev)) == 0)
+ clone->flags = ACPI_EVF_DEVD | ACPI_EVF_WRITE;
+ else
+ clone->flags = ACPI_EVF_NONE;
+
+ ACPI_LOCK(acpi);
+ STAILQ_INSERT_TAIL(&acpi_sc->apm_cdevs, clone, entries);
+ ACPI_UNLOCK(acpi);
+ return (clone);
+}
+
static int
apmopen(struct cdev *dev, int flag, int fmt, d_thread_t *td)
{
+ struct acpi_softc *acpi_sc;
+ struct apm_clone_data *clone;
+
+ acpi_sc = devclass_get_softc(devclass_find("acpi"), 0);
+ clone = apm_create_clone(dev, acpi_sc);
+ dev->si_drv1 = clone;
+
+ /* If the device is opened for write, record that. */
+ if ((flag & FWRITE) != 0)
+ clone->flags |= ACPI_EVF_WRITE;
+
+printf("%s opened clone %p\n", devtoname(dev), (struct apm_clone_data *)dev->si_drv1);
return (0);
}
static int
apmclose(struct cdev *dev, int flag, int fmt, d_thread_t *td)
{
+ struct apm_clone_data *clone;
+ struct acpi_softc *acpi_sc;
+
+ clone = dev->si_drv1;
+ acpi_sc = clone->acpi_sc;
+
+ /* We are about to lose a reference so check if suspend should occur */
+ if (acpi_sc->acpi_next_sstate != 0 &&
+ clone->notify_status != APM_EV_ACKED)
+ acpi_AckSleepState(clone, 0);
+
+ /* Remove this clone's data from the list and free it. */
+ ACPI_LOCK(acpi);
+ STAILQ_REMOVE(&acpi_sc->apm_cdevs, clone, apm_clone_data, entries);
+ knlist_destroy(&clone->sel_read.si_note);
+printf("%s closed clone %p\n", devtoname(dev), clone);
+ ACPI_UNLOCK(acpi);
+ free(clone, M_APMDEV);
+ /* XXX destroy_dev() needed */
return (0);
}
static int
apmioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, d_thread_t *td)
{
- int error = 0;
+ int error;
+ struct apm_clone_data *clone;
struct acpi_softc *acpi_sc;
- struct apm_info info;
+ struct apm_info info;
+ struct apm_event_info *ev_info;
apm_info_old_t aiop;
- acpi_sc = devclass_get_softc(devclass_find("acpi"), 0);
+ error = 0;
+ clone = dev->si_drv1;
+ acpi_sc = clone->acpi_sc;
switch (cmd) {
case APMIO_SUSPEND:
if ((flag & FWRITE) == 0)
return (EPERM);
- if (apm_active)
- acpi_SetSleepState(acpi_sc, acpi_sc->acpi_suspend_sx);
+printf("apm ioctl suspend\n");
+ if (acpi_sc->acpi_next_sstate == 0)
+ acpi_ReqSleepState(acpi_sc, acpi_sc->acpi_suspend_sx);
else
- error = EINVAL;
+ acpi_AckSleepState(clone, 0);
break;
case APMIO_STANDBY:
if ((flag & FWRITE) == 0)
return (EPERM);
- if (apm_active)
- acpi_SetSleepState(acpi_sc, acpi_sc->acpi_standby_sx);
+printf("apm ioctl standby\n");
+ if (acpi_sc->acpi_next_sstate == 0)
+ acpi_ReqSleepState(acpi_sc, acpi_sc->acpi_standby_sx);
else
- error = EINVAL;
+ acpi_AckSleepState(clone, 0);
+ break;
+ case APMIO_NEXTEVENT:
+ printf("apm nextevent start\n");
+ ACPI_LOCK(acpi);
+ if (acpi_sc->acpi_next_sstate != 0 && clone->notify_status ==
+ APM_EV_NONE) {
+ ev_info = (struct apm_event_info *)addr;
+ if (acpi_sc->acpi_next_sstate <= ACPI_STATE_S3)
+ ev_info->type = PMEV_STANDBYREQ;
+ else
+ ev_info->type = PMEV_SUSPENDREQ;
+ ev_info->index = 0;
+ clone->notify_status = APM_EV_NOTIFIED;
+ printf("apm event returning %d\n", ev_info->type);
+ } else
+ error = EAGAIN;
+ ACPI_UNLOCK(acpi);
break;
case APMIO_GETINFO_OLD:
if (acpi_capm_get_info(&info))
@@ -300,24 +425,72 @@
static int
apmpoll(struct cdev *dev, int events, d_thread_t *td)
{
+ struct apm_clone_data *clone;
+ int revents;
+
+ revents = 0;
+ ACPI_LOCK(acpi);
+ clone = dev->si_drv1;
+ if (clone->acpi_sc->acpi_next_sstate)
+ revents |= events & (POLLIN | POLLRDNORM);
+ else
+ selrecord(td, &clone->sel_read);
+ ACPI_UNLOCK(acpi);
+printf("apm poll returning %x\n", revents);
+ return (revents);
+}
+
+static int
+apmkqfilter(struct cdev *dev, struct knote *kn)
+{
+ struct apm_clone_data *clone;
+
+ ACPI_LOCK(acpi);
+ clone = dev->si_drv1;
+ kn->kn_hook = clone;
+ kn->kn_fop = &apm_readfiltops;
+ knlist_add(&clone->sel_read.si_note, kn, 0);
+ ACPI_UNLOCK(acpi);
return (0);
}
static void
-acpi_capm_init(struct acpi_softc *sc)
+apmreadfiltdetach(struct knote *kn)
+{
+ struct apm_clone_data *clone;
+
+ ACPI_LOCK(acpi);
+ clone = kn->kn_hook;
+ knlist_remove(&clone->sel_read.si_note, kn, 0);
+ ACPI_UNLOCK(acpi);
+}
+
+static int
+apmreadfilt(struct knote *kn, long hint)
{
- make_dev(&apm_cdevsw, 0, 0, 5, 0664, "apm");
+ struct apm_clone_data *clone;
+ int sleeping;
+
+ ACPI_LOCK(acpi);
+ clone = kn->kn_hook;
+ sleeping = clone->acpi_sc->acpi_next_sstate ? 1 : 0;
+ ACPI_UNLOCK(acpi);
+ return (sleeping);
}
int
acpi_machdep_init(device_t dev)
{
- struct acpi_softc *sc;
+ struct acpi_softc *acpi_sc;
- sc = devclass_get_softc(devclass_find("acpi"), 0);
- acpi_capm_init(sc);
+ acpi_sc = devclass_get_softc(devclass_find("acpi"), 0);
- acpi_install_wakeup_handler(sc);
+ /* Create a clone for /dev/acpi also. */
+ STAILQ_INIT(&acpi_sc->apm_cdevs);
+ acpi_sc->acpi_clone = apm_create_clone(acpi_sc->acpi_dev_t, acpi_sc);
+ clone_setup(&apm_clones);
+ EVENTHANDLER_REGISTER(dev_clone, apm_clone, 0, 1000);
+ acpi_install_wakeup_handler(acpi_sc);
if (intr_model == ACPI_INTR_PIC)
BUS_CONFIG_INTR(dev, AcpiGbl_FADT.SciInterrupt,
@@ -325,8 +498,8 @@
else
acpi_SetIntrModel(intr_model);
- SYSCTL_ADD_UINT(&sc->acpi_sysctl_ctx,
- SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO,
+ SYSCTL_ADD_UINT(&acpi_sc->acpi_sysctl_ctx,
+ SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO,
"reset_video", CTLFLAG_RW, &acpi_reset_video, 0,
"Call the VESA reset BIOS vector on the resume path");
Index: usr.sbin/acpi/acpiconf/acpiconf.c
===================================================================
RCS file: /home/ncvs/src/usr.sbin/acpi/acpiconf/acpiconf.c,v
retrieving revision 1.25
diff -u -r1.25 acpiconf.c
--- usr.sbin/acpi/acpiconf/acpiconf.c 28 Jul 2005 19:41:52 -0000 1.25
+++ usr.sbin/acpi/acpiconf/acpiconf.c 17 Jun 2007 16:52:50 -0000
@@ -40,8 +40,6 @@
#include <contrib/dev/acpica/acpi.h>
#define ACPIDEV "/dev/acpi"
-#define RC_SUSPEND_PATH "/etc/rc.suspend"
-#define RC_RESUME_PATH "/etc/rc.resume"
static int acpifd;
@@ -55,32 +53,27 @@
err(EX_OSFILE, ACPIDEV);
}
-static int
+/* Prepare to sleep and then wait for the signal that sleeping can occur. */
+static void
acpi_sleep(int sleep_type)
{
- char cmd[64];
int ret;
+
+ /* Notify OS that we want to sleep. devd(8) gets this notify. */
+ ret = ioctl(acpifd, ACPIIO_REQSLPSTATE, &sleep_type);
+ if (ret != 0)
+ err(EX_IOERR, "request sleep type (%d) failed", sleep_type);
+}
- /* Run the suspend rc script, if available. */
- if (access(RC_SUSPEND_PATH, X_OK) == 0) {
- snprintf(cmd, sizeof(cmd), "%s acpi %d", RC_SUSPEND_PATH,
- sleep_type);
- system(cmd);
- }
-
- ret = ioctl(acpifd, ACPIIO_SETSLPSTATE, &sleep_type);
-
- /* Run the resume rc script, if available. */
- if (access(RC_RESUME_PATH, X_OK) == 0) {
- snprintf(cmd, sizeof(cmd), "%s acpi %d", RC_RESUME_PATH,
- sleep_type);
- system(cmd);
- }
+/* Ack or abort a pending suspend request. */
+static void
+acpi_sleep_ack(int err_val)
+{
+ int ret;
+ ret = ioctl(acpifd, ACPIIO_ACKSLPSTATE, &err_val);
if (ret != 0)
- err(EX_IOERR, "sleep type (%d) failed", sleep_type);
-
- return (0);
+ err(EX_IOERR, "ack sleep type failed");
}
/* should be a acpi define, but doesn't appear to be */
@@ -183,7 +176,7 @@
static void
usage(const char* prog)
{
- printf("usage: %s [-h] [-i batt] [-s 1-5]\n", prog);
+ printf("usage: %s [-h] [-i batt] [-k ack] [-s 1-4]\n", prog);
exit(0);
}
@@ -200,17 +193,20 @@
sleep_type = -1;
acpi_init();
- while ((c = getopt(argc, argv, "hi:s:")) != -1) {
+ while ((c = getopt(argc, argv, "hi:k:s:")) != -1) {
switch (c) {
case 'i':
acpi_battinfo(atoi(optarg));
break;
+ case 'k':
+ acpi_sleep_ack(atoi(optarg));
+ break;
case 's':
if (optarg[0] == 'S')
sleep_type = optarg[1] - '0';
else
sleep_type = optarg[0] - '0';
- if (sleep_type < 0 || sleep_type > 5)
+ if (sleep_type < 1 || sleep_type > 4)
errx(EX_USAGE, "invalid sleep type (%d)",
sleep_type);
break;
@@ -223,10 +219,8 @@
argc -= optind;
argv += optind;
- if (sleep_type != -1) {
- sleep(1); /* wait 1 sec. for key-release event */
+ if (sleep_type != -1)
acpi_sleep(sleep_type);
- }
close(acpifd);
exit (0);
--------------040100070106020900000700--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?46756BBE.5030603>
