Skip site navigation (1)Skip section navigation (2)
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>