From owner-freebsd-acpi@FreeBSD.ORG Sun Jun 17 20:43:32 2007 Return-Path: X-Original-To: acpi@freebsd.org Delivered-To: freebsd-acpi@FreeBSD.ORG Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52]) by hub.freebsd.org (Postfix) with ESMTP id B827116A469 for ; Sun, 17 Jun 2007 20:43:32 +0000 (UTC) (envelope-from nate@root.org) Received: from root.org (root.org [67.118.192.226]) by mx1.freebsd.org (Postfix) with ESMTP id 9288013C484 for ; Sun, 17 Jun 2007 20:43:32 +0000 (UTC) (envelope-from nate@root.org) Received: (qmail 46818 invoked from network); 17 Jun 2007 17:13:41 -0000 Received: from ppp-71-139-42-13.dsl.snfc21.pacbell.net (HELO ?10.0.0.15?) (nate-mail@71.139.42.13) by root.org with ESMTPA; 17 Jun 2007 17:13:41 -0000 Message-ID: <46756BBE.5030603@root.org> Date: Sun, 17 Jun 2007 10:13:34 -0700 From: Nate Lawson User-Agent: Thunderbird 2.0.0.0 (X11/20070511) MIME-Version: 1.0 To: current X-Enigmail-Version: 0.95.0 Content-Type: multipart/mixed; boundary="------------040100070106020900000700" Cc: acpi@freebsd.org, arch@freebsd.org Subject: patch: acpi usermode suspend API change X-BeenThere: freebsd-acpi@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: ACPI and power management development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 17 Jun 2007 20:43:32 -0000 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 #include #include +#include #include #include #include #include +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 #include #include +#include +#include #include #include #include @@ -45,8 +47,6 @@ * APM driver emulation */ -#include - #include #include @@ -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 #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 #include #include +#include #include #include #include #include +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 #include #include +#include +#include #include #include #include @@ -46,8 +48,6 @@ * APM driver emulation */ -#include - #include #include @@ -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 #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--