diff -u --recursive linux-clean/arch/i386/kernel/apm.c linux-hacked-pmpolicy/arch/i386/kernel/apm.c
--- linux-clean/arch/i386/kernel/apm.c	Sat Dec 30 00:21:33 2000
+++ linux-hacked-pmpolicy/arch/i386/kernel/apm.c	Mon Jan  8 21:15:23 2001
@@ -148,6 +148,8 @@
  *   1.14: Make connection version persist across module unload/load.
  *         Enable and engage power management earlier.
  *         Disengage power management on module unload.
+ *   ---   Modified to support new pm_event API (John Fremlin
+ *         <vii@penguinpowered.com>)
  *
  * APM 1.1 Reference:
  *
@@ -884,30 +886,65 @@
 #endif
 }
 
+static int soft_suspend()
+{
+	struct pm_event pe;
+	memset(&pe,0,sizeof pe);
+
+	pe.pe_state = PM_STATE_SLEEP;
+	pe.pe_source = PME_SRC_SOFT;
+	pe.pe_flags = PME_FLAGS_REJECTABLE;
+
+	return pm_request_event(&pe);
+}
+
 static int send_event(apm_event_t event)
 {
+	struct pm_event pe;
+	memset(&pe,0,sizeof pe);
+
 	switch (event) {
 	case APM_SYS_SUSPEND:
+		pe.pe_state = PM_STATE_SLEEP;
+		pe.pe_source = PME_SRC_SYS;
+		pe.pe_flags = PME_FLAGS_REJECTABLE;
+		break;
+		
 	case APM_CRITICAL_SUSPEND:
+		pe.pe_state = PM_STATE_SLEEP;
+		pe.pe_source = PME_SRC_SYS;
+		break;
+			
 	case APM_USER_SUSPEND:
-		/* map all suspends to ACPI D3 */
-		if (pm_send_all(PM_SUSPEND, (void *)3)) {
-			if (event == APM_CRITICAL_SUSPEND) {
-				printk(KERN_CRIT "apm: Critical suspend was vetoed, expect armageddon\n" );
-				return 0;
-			}
-			if (apm_info.connection_version > 0x100)
-				apm_set_power_state(APM_STATE_REJECT);
-			return 0;
-		}
+		pe.pe_state = PM_STATE_SLEEP;
+		pe.pe_source = PME_SRC_HWUSER;
+		pe.pe_flags = PME_FLAGS_REJECTABLE;
 		break;
-	case APM_NORMAL_RESUME:
+
 	case APM_CRITICAL_RESUME:
-		/* map all resumes to ACPI D0 */
-		(void) pm_send_all(PM_RESUME, (void *)0);
+		pe.pe_state = PM_STATE_WAKEFUL;
+		pe.pe_source = PME_SRC_SYS;
+		break;
+		
+	case APM_NORMAL_RESUME:
+		pe.pe_state = PM_STATE_WAKEFUL;
+		pe.pe_source = PME_SRC_HWUSER;
 		break;
+		
+	default:
+		return 1;
 	}
 
+	if(pm_prepare_for_event(&pe))
+	{
+		if (event == APM_CRITICAL_SUSPEND) {
+			printk(KERN_CRIT "apm: critical suspend was vetoed, expect armageddon\n" );
+			return 0;
+		}
+		if (apm_info.connection_version > 0x100)
+			apm_set_power_state(APM_STATE_REJECT);
+		return 0;
+	}
 	return 1;
 }
 
@@ -925,8 +962,10 @@
 		err = APM_SUCCESS;
 	if (err != APM_SUCCESS)
 		apm_error("suspend", err);
-	send_event(APM_NORMAL_RESUME);
 	sti();
+	/* We mustn't pm_event_lock because whoever is waiting for the
+	 * end of this suspend is holding the lock */
+	send_event(APM_NORMAL_RESUME);
 	queue_event(APM_NORMAL_RESUME, NULL);
 	for (as = user_list; as != NULL; as = as->next) {
 		as->suspend_wait = 0;
@@ -947,6 +986,21 @@
 		apm_error("standby", err);
 }
 
+static int apm_enter_state(pm_state_t*ps)
+{
+	switch(*ps){
+	case PM_STATE_SLEEP:
+		return suspend();
+	case PM_STATE_WAKEFUL:
+		return 0;
+	case PM_STATE_POWEROFF:
+		apm_power_off();
+		return 2; /* it didn't work, did it? */
+	default:
+		return 1;
+	}
+}
+
 static apm_event_t get_event(void)
 {
 	int		error;
@@ -1019,12 +1073,17 @@
 			 */
 			if (waiting_for_resume)
 				return;
+			
+			pm_event_lock();
+			
 			if (send_event(event)) {
 				queue_event(event, NULL);
 				waiting_for_resume = 1;
 				if (suspends_pending <= 0)
 					(void) suspend();
 			}
+			pm_event_unlock();
+
 			break;
 
 		case APM_NORMAL_RESUME:
@@ -1036,7 +1095,10 @@
 			if ((event != APM_NORMAL_RESUME)
 			    || (ignore_normal_resume == 0)) {
 				set_time();
+				pm_event_lock();
 				send_event(event);
+				pm_event_unlock();
+				
 				queue_event(event, NULL);
 			}
 			break;
@@ -1053,12 +1115,16 @@
 			break;
 
 		case APM_CRITICAL_SUSPEND:
+			pm_event_lock();
 			send_event(event);
 			/*
 			 * We can only hope it worked - we are not allowed
 			 * to reject a critical suspend.
 			 */
 			(void) suspend();
+			
+			pm_event_unlock();
+			
 			break;
 		}
 	}
@@ -1240,14 +1306,16 @@
 			as->suspends_read--;
 			as->suspends_pending--;
 			suspends_pending--;
-		} else if (!send_event(APM_USER_SUSPEND))
-			return -EAGAIN;
-		else
-			queue_event(APM_USER_SUSPEND, as);
+		}
+		
 		if (suspends_pending <= 0) {
-			if (suspend() != APM_SUCCESS)
+			if (soft_suspend() != 0)
 				return -EIO;
+
+			queue_event(APM_USER_SUSPEND, as);
 		} else {
+			queue_event(APM_USER_SUSPEND, as);
+			
 			as->suspend_wait = 1;
 			add_wait_queue(&apm_suspend_waitqueue, &wait);
 			while (1) {
@@ -1285,7 +1353,7 @@
 	if (as->suspends_pending > 0) {
 		suspends_pending -= as->suspends_pending;
 		if (suspends_pending <= 0)
-			(void) suspend();
+			(void) soft_suspend();
 	}
 	if (user_list == as)
 		user_list = as->next;
@@ -1526,6 +1594,9 @@
 #ifdef CONFIG_MAGIC_SYSRQ
 	sysrq_power_off = apm_power_off;
 #endif
+
+	pm_enter_state = apm_enter_state;
+
 	if (smp_num_cpus == 1) {
 #if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
 		console_blank_hook = apm_console_blank;
diff -u --recursive linux-clean/include/linux/pm.h linux-hacked-pmpolicy/include/linux/pm.h
--- linux-clean/include/linux/pm.h	Fri Jan  5 21:30:21 2001
+++ linux-hacked-pmpolicy/include/linux/pm.h	Sat Jan  6 16:38:41 2001
@@ -27,6 +27,50 @@
 #include <linux/list.h>
 
 /*
+ * Power management events
+ */
+
+typedef int pm_state_t;
+typedef int pm_source_t;
+
+struct pm_event
+{
+	pm_state_t pe_state;
+	pm_source_t pe_source;
+	unsigned pe_flags;
+}
+;
+
+/* The states: */
+enum{
+	PM_STATE_SLEEP,
+	PM_STATE_WAKEFUL,
+	PM_STATE_POWEROFF
+};
+
+/* The sources: */
+
+enum {
+	PME_SRC_SOFT,
+		/* The user clicked suspend in the GUI, or a program decided
+		    by itself */
+		   
+	PME_SRC_HWUSER,
+		/* The user pressed the offbutton, or closed the laptop lid or something */
+
+	PME_SRC_SYS
+		/* The power management system got bored and decided to assert itself */
+}
+;
+
+/* The flags: */
+
+#define PME_FLAGS_REJECTABLE	0x1
+	//	1 == yes, it can be vetoed
+	//	0 == no, it's going to happen anyway
+
+
+/*
  * Power management requests
  */
 enum
@@ -109,10 +153,12 @@
 
 #ifdef CONFIG_PM
 
+extern int (*pm_enter_state)(pm_state_t*ps);
 extern int pm_active;
 
 #define PM_IS_ACTIVE() (pm_active != 0)
 
+
 /*
  * Register a device with power management
  */
@@ -140,6 +186,38 @@
  */
 int pm_send_all(pm_request_t rqst, void *data);
 
+
+/*
+ * Ask pm system to enter a particular state
+ */
+int pm_request_event(struct pm_event*pe);
+	// returns non-zero on failure.
+
+/*
+ * Low level function only for PM drivers that are going to put the
+ * system in particular state and want to handle the actual system state
+ * change themselves, but want all drivers etc. notified.
+ *
+ * You should use pm_event_lock and pm_event_unlock around this
+ * procedure!
+ *
+ */
+int pm_prepare_for_event(struct pm_event*pe);
+	// prepares system for event but does not call pm_enter_state
+	// returns non-zero if the event was rejected
+
+/*
+ * Access control: power management events should be serialized.
+ *
+ * Example of how to lock:
+ * If you're going to pm_prepare_for_event and then call
+ * custom_do_system_suspend you should pm_event_lock before you start,
+ * and pm_event_unlock after you've called custom_do_system_suspend.
+ */
+void pm_event_lock();
+void pm_event_unlock();
+
+
 /*
  * Find a device
  */
@@ -171,6 +249,24 @@
 extern inline int pm_send_all(pm_request_t rqst, void *data)
 {
 	return 0;
+}
+
+extern inline int pm_request_event(struct pm_event*pe)
+{
+	return 1;
+}
+
+extern inline int pm_prepare_for_event(struct pm_event*pe)
+{
+	return 0;
+}
+
+extern inline void pm_event_lock() 
+{
+}
+
+extrn inline void pm_event_unlock()
+{
 }
 
 extern inline struct pm_dev *pm_find(pm_dev_t type, struct pm_dev *from)
diff -u --recursive linux-clean/include/linux/sysctl.h linux-hacked-pmpolicy/include/linux/sysctl.h
--- linux-clean/include/linux/sysctl.h	Fri Jan  5 21:29:45 2001
+++ linux-hacked-pmpolicy/include/linux/sysctl.h	Tue Jan  2 14:43:17 2001
@@ -117,6 +117,7 @@
 	KERN_OVERFLOWGID=47,	/* int: overflow GID */
 	KERN_SHMPATH=48,	/* string: path to shm fs */
 	KERN_HOTPLUG=49,	/* string: path to hotplug policy agent */
+	KERN_POWERMANAGER=50,   /* string: path to PM policy agent */
 };
 
 
diff -u --recursive linux-clean/kernel/pm.c linux-hacked-pmpolicy/kernel/pm.c
--- linux-clean/kernel/pm.c	Fri May 12 19:21:20 2000
+++ linux-hacked-pmpolicy/kernel/pm.c	Sat Jan  6 17:00:48 2001
@@ -16,14 +16,29 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Changes:
+ *
+ * 2001-01-02	Added support for userspace rejection of PM events
+ *		John Fremlin <vii@penguinpowered.com>
  */
 
+#define __KERNEL_SYSCALLS__
+
+#include <linux/sched.h>
 #include <linux/module.h>
 #include <linux/spinlock.h>
 #include <linux/slab.h>
+#include <asm/uaccess.h>
+#include <asm/semaphore.h>
+#include <linux/kmod.h> // for usermodehelper
+#include <linux/unistd.h>
+
 #include <linux/pm.h>
 
 int pm_active;
+int (*pm_enter_state)(pm_state_t*ps); /* = 0 */
+static DECLARE_MUTEX(pm_event_sem);
 
 static spinlock_t pm_devs_lock = SPIN_LOCK_UNLOCKED;
 static LIST_HEAD(pm_devs);
@@ -186,8 +201,9 @@
  *	Zero is returned on success. If a suspend fails then the status
  *	from the device that vetoes the suspend is returned.
  *
- *	BUGS: what stops two power management requests occuring in parallel
- *	and conflicting.
+ *	Use pm_request_event or a pm_event_lock, pm_prepare_for_event,
+ *	custom_do_event, pm_event_unlock instead of this function to avoid
+ *	racing.
  */
  
 int pm_send_all(pm_request_t rqst, void *data)
@@ -211,6 +227,227 @@
 	return 0;
 }
 
+static inline char* state2a(pm_state_t* s)
+{
+	switch(*s){
+	case PM_STATE_SLEEP:
+		return "sleep";
+	case PM_STATE_WAKEFUL:
+		return "wake";
+	case PM_STATE_POWEROFF:
+		return "poweroff";
+	default:
+		BUG();
+		return "unknown";
+	}
+}
+
+static inline char* source2a(pm_source_t* s)
+{
+	switch(*s){
+	case PME_SRC_SOFT:
+		return "soft";
+	case PME_SRC_HWUSER:
+		return "user";
+	case PME_SRC_SYS:
+		return "sys";
+	default:
+		BUG();
+		return "unknown";
+	}
+}
+
+/* 
+ *  For more examples of doing this kind of thing, see kernel/kmod.c
+ *  but especially drivers/net/hamradio/baycom_epp.c.
+ */
+
+/* If you change the size of this variable, also change
+ * kernel/sysctl.c as it is exported to procfs
+ */
+char pm_policyhelper_path[256] = "/sbin/powermanager";
+
+static int exec_policyhelper(void*p)
+{
+	static char * envp[] = {
+		"HOME=/",
+		"TERM=linux",
+		"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
+		0 };
+
+	struct pm_event*pe=(struct pm_event*)p;
+	char* source = source2a(&pe->pe_source);
+	char* state = state2a(&pe->pe_state);
+	
+	char*argv[] = 
+		{
+			pm_policyhelper_path,
+			source,
+			state,
+			0
+		}
+	;
+
+	int ret;
+
+	ret = exec_usermodehelper(pm_policyhelper_path,argv,envp);
+	
+	printk(KERN_ERR "pm: unable to exec \"%s\" (ret == %d, errno == %d)\n",
+		       pm_policyhelper_path,
+		       ret,
+		       errno);
+	do_exit(ret);
+}
+
+/*
+ *  Return 0 -> reject event, non-zero accept
+ */	 
+static int event_policy(struct pm_event*pe)
+{
+	pid_t pid;
+	int ret;
+	int err;
+
+	if (!current->fs->root) {
+		printk(KERN_ERR "pm: unable to query userspace as root fs not mounted\n");
+		return -1;
+	}
+	
+	pid = kernel_thread(exec_policyhelper, pe, CLONE_VFORK);
+	if (pid < 0) {
+		printk(KERN_ERR "pm: fork failed (errno == %d)\n", -pid);
+		return -1;
+	}
+
+	{
+		/* Allow ret to be in kernel space. */
+		mm_segment_t fs = get_fs();
+		set_fs(KERNEL_DS); 
+		err = waitpid(pid, &ret, __WCLONE);
+		set_fs(fs);
+	}
+
+	if (err != pid) {
+		printk(KERN_ERR "pm: waitpid (pid==%d) failed (errno = %d, ret = %d)\n", pid, err, ret);
+		return -1;
+	}
+
+	if(ret > 0) {
+		printk(KERN_DEBUG "pm: userspace policy vetoed event (returning %d)\n",
+		       ret);
+		return 0;
+	}
+	if(ret == 0) {
+//		printk(KERN_DEBUG "pm: userspace policy accepted event\n");
+		return 1;
+	}
+
+	printk(KERN_DEBUG "pm: userspace policy command returned failure: %d\n", ret );
+	return -1;
+}
+
+
+/**
+ *	pm_event_lock - block until any currently processing PM event is finished.
+ *
+ *	It is needed because of the following race: you decide to wake
+ *	the machine up from suspend, and notify everybody you're doing
+ *	this. While your pm_prepare_for_event is sleeping, some other
+ *	thread decides to suspend the machine to a deep sleep or
+ *	something and calls pm_prepare_for_event. The two
+ *	pm_prepare_for_event threads now race: if the second wins, the
+ *	machine is suspended then immediately woken up by the
+ *	first. The result is that all PM capable devices are in
+ *	suspended animation but the computer is awake and won't try to
+ *	wake them up.
+ *
+ *	You should therefore use pm_event_lock and pm_event_unlock
+ *	around pm_prepare_for_event. In fact you should almost
+ *	certainly use pm_request_event, which does this for you.
+ *
+ */
+
+void pm_event_lock()
+{
+	down(&pm_event_sem);
+}
+void pm_event_unlock()
+{
+	up(&pm_event_sem);
+}
+
+
+int pm_prepare_for_event(struct pm_event*pe) 
+{
+	pm_request_t req;
+	void*data;
+
+	if(pe->pe_flags & PME_FLAGS_REJECTABLE)
+	{
+		if(!event_policy(pe))
+			return 1;
+	}
+	
+	switch(pe->pe_state){
+	case PM_STATE_SLEEP:
+		req = PM_SUSPEND;
+		data = (void*)3; /* Map suspend to ACPI D3 */
+		break;
+	case PM_STATE_WAKEFUL:
+		req = PM_RESUME;
+		data = 0;
+		break;
+	case PM_STATE_POWEROFF:
+		return 0;
+	default:
+		BUG();
+		return 2;
+	}
+
+	if(pm_send_all(req,data))
+		return 1;
+	
+	return 0;
+}
+
+/**
+ *	pm_request_event - ask the PM system to enter a state
+ *	@pe: state and request orginator
+ *
+ *	pm_request_event reserves the right to sleep and definitely
+ *	will do if the pe_flags of @pe have the REJECTABLE bit set.
+ *
+ *	pm_request_event will notify all PM aware drivers, and
+ *	possibly ask userspace if the event should go ahead, depending
+ *	on the pe_flags of @pe.
+ *
+ *	A return of 0 indicates that the event was successfully
+ *	initiated, otherwise non-zero is returned.
+ */
+
+int pm_request_event(struct pm_event*pe)
+{
+	int v;
+
+	pm_event_lock();
+	
+	v=pm_prepare_for_event(pe);
+	if(v)
+		goto out;
+
+	if(!pm_enter_state)
+	{
+		v=2;
+		goto out;
+	}
+
+	v = pm_enter_state(&pe->pe_state);
+	
+ out:
+	pm_event_unlock();
+	return v;
+}
+
 /**
  *	pm_find  - find a device
  *	@type: type of device
@@ -241,5 +478,11 @@
 EXPORT_SYMBOL(pm_unregister_all);
 EXPORT_SYMBOL(pm_send);
 EXPORT_SYMBOL(pm_send_all);
+EXPORT_SYMBOL(pm_event_lock);
+EXPORT_SYMBOL(pm_event_unlock);
+EXPORT_SYMBOL(pm_prepare_for_event);
+EXPORT_SYMBOL(pm_request_event);
 EXPORT_SYMBOL(pm_find);
 EXPORT_SYMBOL(pm_active);
+EXPORT_SYMBOL(pm_enter_state);
+EXPORT_SYMBOL(pm_policyhelper_path);
diff -u --recursive linux-clean/kernel/sysctl.c linux-hacked-pmpolicy/kernel/sysctl.c
--- linux-clean/kernel/sysctl.c	Tue Jan  2 09:26:17 2001
+++ linux-hacked-pmpolicy/kernel/sysctl.c	Tue Jan  2 14:43:44 2001
@@ -58,6 +58,9 @@
 #ifdef CONFIG_HOTPLUG
 extern char hotplug_path[];
 #endif
+#ifdef CONFIG_PM
+extern char pm_policyhelper_path[];
+#endif
 #ifdef CONFIG_CHR_DEV_SG
 extern int sg_big_buff;
 #endif
@@ -195,6 +198,10 @@
 	{KERN_HOTPLUG, "hotplug", &hotplug_path, 256,
 	 0644, NULL, &proc_dostring, &sysctl_string },
 #endif
+#ifdef CONFIG_PM
+	{KERN_POWERMANAGER, "powermanager",&pm_policyhelper_path, 256,
+	 0644, NULL, &proc_dostring, &sysctl_string },
+#endif	
 #ifdef CONFIG_CHR_DEV_SG
 	{KERN_SG_BIG_BUFF, "sg-big-buff", &sg_big_buff, sizeof (int),
 	 0444, NULL, &proc_dointvec},

