diff --new-file -u --recursive --exclude *~ linux-2.4.4-orig/arch/i386/kernel/apm.c linux-2.4.4-pmevent/arch/i386/kernel/apm.c --- linux-2.4.4-orig/arch/i386/kernel/apm.c Tue May 1 14:33:34 2001 +++ linux-2.4.4-pmevent/arch/i386/kernel/apm.c Wed May 2 13:46:08 2001 @@ -148,6 +148,10 @@ * 1.14: Make connection version persist across module unload/load. * Enable and engage power management earlier. * Disengage power management on module unload. + * 1.15j: Reject all rejectable APM events by default (so + * userspace can decide whether to suspend the machine). + * Minor waitqueue cleanups. + * (John Fremlin ) * * APM 1.1 Reference: * @@ -186,6 +190,7 @@ #include #include #include +#include #include #include @@ -327,7 +332,6 @@ #endif static int suspends_pending; static int standbys_pending; -static int waiting_for_resume; static int ignore_normal_resume; static int bounce_interval = DEFAULT_BOUNCE_INTERVAL; @@ -352,7 +356,7 @@ static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue); static struct apm_user * user_list; -static char driver_version[] = "1.14"; /* no spaces */ +static char driver_version[] = "1.15"; /* no spaces */ static char * apm_event_name[] = { "system standby", @@ -886,33 +890,6 @@ #endif } -static int send_event(apm_event_t event) -{ - switch (event) { - case APM_SYS_SUSPEND: - case APM_CRITICAL_SUSPEND: - 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; - } - break; - case APM_NORMAL_RESUME: - case APM_CRITICAL_RESUME: - /* map all resumes to ACPI D0 */ - (void) pm_send_all(PM_RESUME, (void *)0); - break; - } - - return 1; -} - static int suspend(void) { int err; @@ -927,7 +904,6 @@ err = APM_SUCCESS; if (err != APM_SUCCESS) apm_error("suspend", err); - send_event(APM_NORMAL_RESUME); sti(); queue_event(APM_NORMAL_RESUME, NULL); for (as = user_list; as != NULL; as = as->next) { @@ -968,6 +944,54 @@ return 0; } +static int may_sleep(void) +{ + /* map all suspends to ACPI D3 */ + return (!pm_send_all(PM_SUSPEND, (void *)3)); +} +static int resume_all(void) +{ + /* map all resumes to ACPI D0 */ + (void) pm_send_all(PM_RESUME, (void *)0); +} + +static void announce_event(apm_event_t event) +{ + char*type = PME_NOTIFY; + char*name; + + if (event <= NR_APM_EVENT_NAME) + name = apm_event_name[event - 1]; + else name = "unknown event"; + + switch(event){ + case APM_SYS_STANDBY: + case APM_USER_STANDBY: + case APM_SYS_SUSPEND: + case APM_USER_SUSPEND: + case APM_CRITICAL_SUSPEND: + type = PME_SLEEP; + break; + case APM_NORMAL_RESUME: + case APM_CRITICAL_RESUME: + case APM_STANDBY_RESUME: + type = PME_WAKE; + break; + case APM_CAPABILITY_CHANGE: + type = PME_NOTIFY; + break; + case APM_LOW_BATTERY: + type = PME_EMERGENCY; + break; + case APM_POWER_STATUS_CHANGE: + type = PME_POWERCHANGE; + break; + default: + type = PME_NOTIFY; + } + pme_announce(type,"APM",name); +} + static void check_events(void) { apm_event_t event; @@ -989,56 +1013,36 @@ if (ignore_normal_resume && (event != APM_NORMAL_RESUME)) ignore_normal_resume = 0; + announce_event(event); + switch (event) { case APM_SYS_STANDBY: case APM_USER_STANDBY: - if (send_event(event)) { - queue_event(event, NULL); - if (standbys_pending <= 0) - standby(); - } + queue_event(event, NULL); + if (standbys_pending <= 0) + standby(); break; case APM_USER_SUSPEND: -#ifdef CONFIG_APM_IGNORE_USER_SUSPEND - if (apm_info.connection_version > 0x100) - apm_set_power_state(APM_STATE_REJECT); - break; -#endif case APM_SYS_SUSPEND: - if (ignore_bounce) { - if (apm_info.connection_version > 0x100) - apm_set_power_state(APM_STATE_REJECT); - break; - } - /* - * If we are already processing a SUSPEND, - * then further SUSPEND events from the BIOS - * will be ignored. We also return here to - * cope with the fact that the Thinkpads keep - * sending a SUSPEND event until something else - * happens! + /* Reject all suspend requests and let + * userspace call suspend if they want to */ - if (waiting_for_resume) - return; - if (send_event(event)) { - queue_event(event, NULL); - waiting_for_resume = 1; - if (suspends_pending <= 0) - (void) suspend(); - } + if (apm_info.connection_version > 0x100) + apm_set_power_state(APM_STATE_REJECT); + + queue_event(event, NULL); break; case APM_NORMAL_RESUME: case APM_CRITICAL_RESUME: case APM_STANDBY_RESUME: - waiting_for_resume = 0; last_resume = jiffies; ignore_bounce = 1; + resume_all(); if ((event != APM_NORMAL_RESUME) || (ignore_normal_resume == 0)) { set_time(); - send_event(event); queue_event(event, NULL); } break; @@ -1046,7 +1050,6 @@ case APM_CAPABILITY_CHANGE: case APM_LOW_BATTERY: case APM_POWER_STATUS_CHANGE: - send_event(event); queue_event(event, NULL); break; @@ -1055,12 +1058,17 @@ break; case APM_CRITICAL_SUSPEND: - send_event(event); /* - * We can only hope it worked - we are not allowed + * We are not allowed * to reject a critical suspend. */ - (void) suspend(); + if(!may_sleep()){ + printk(KERN_CRIT "apm: critical suspend was vetoed, expect armageddon\n"); + if (apm_info.connection_version > 0x100) + apm_set_power_state(APM_STATE_REJECT); + } + else + (void) suspend(); break; } } @@ -1151,7 +1159,9 @@ struct apm_user * as; int i; apm_event_t event; - DECLARE_WAITQUEUE(wait, current); + + if (ppos != &fp->f_pos) + return -ESPIPE; as = fp->private_data; if (check_apm_user(as, "read")) @@ -1161,15 +1171,7 @@ if (queue_empty(as)) { if (fp->f_flags & O_NONBLOCK) return -EAGAIN; - add_wait_queue(&apm_waitqueue, &wait); -repeat: - set_current_state(TASK_INTERRUPTIBLE); - if (queue_empty(as) && !signal_pending(current)) { - schedule(); - goto repeat; - } - set_current_state(TASK_RUNNING); - remove_wait_queue(&apm_waitqueue, &wait); + wait_event_interruptible(apm_waitqueue,!queue_empty(as)); } i = count; while ((i >= sizeof(event)) && !queue_empty(as)) { @@ -1217,7 +1219,6 @@ u_int cmd, u_long arg) { struct apm_user * as; - DECLARE_WAITQUEUE(wait, current); as = filp->private_data; if (check_apm_user(as, "ioctl")) @@ -1230,7 +1231,7 @@ as->standbys_read--; as->standbys_pending--; standbys_pending--; - } else if (!send_event(APM_USER_STANDBY)) + } else if (!may_sleep()) return -EAGAIN; else queue_event(APM_USER_STANDBY, as); @@ -1242,7 +1243,7 @@ as->suspends_read--; as->suspends_pending--; suspends_pending--; - } else if (!send_event(APM_USER_SUSPEND)) + } else if (!may_sleep()) return -EAGAIN; else queue_event(APM_USER_SUSPEND, as); @@ -1251,16 +1252,8 @@ return -EIO; } else { as->suspend_wait = 1; - add_wait_queue(&apm_suspend_waitqueue, &wait); - while (1) { - set_current_state(TASK_INTERRUPTIBLE); - if ((as->suspend_wait == 0) - || signal_pending(current)) - break; - schedule(); - } - set_current_state(TASK_RUNNING); - remove_wait_queue(&apm_suspend_waitqueue, &wait); + wait_event_interruptible(apm_suspend_waitqueue, + as->suspend_wait == 0); return as->suspend_result; } break; diff --new-file -u --recursive --exclude *~ linux-2.4.4-orig/include/linux/pmevent.h linux-2.4.4-pmevent/include/linux/pmevent.h --- linux-2.4.4-orig/include/linux/pmevent.h Thu Jan 1 01:00:00 1970 +++ linux-2.4.4-pmevent/include/linux/pmevent.h Wed May 2 13:47:15 2001 @@ -0,0 +1,34 @@ +/* (C) 2001 John Fremlin */ + +#ifndef _LINUX_PMEVENT_H_ +#define _LINUX_PMEVENT_H_ + +#define PME_NOTIFY "NOTIFY" +#define PME_SLEEP "SLEEP" +#define PME_OFF "OFF" +#define PME_WAKE "WAKE" +#define PME_EMERGENCY "EMERGENCY" +#define PME_POWERCHANGE PME_NOTIFY + +#define PME_FLOOD "MSGFLOOD" + /* only generated if a buffer + * internal to the pm event + * system overflows + */ +#ifdef __KERNEL__ +#include + +#ifdef CONFIG_PM +void pme_announce(const char* event_type, + const char* subsystem, + const char* desc); +#else +static inline void pme_announce(const char* event_type, + const char* subsystem, + const char* desc) +{ +} +#endif +#endif + +#endif diff --new-file -u --recursive --exclude *~ linux-2.4.4-orig/kernel/Makefile linux-2.4.4-pmevent/kernel/Makefile --- linux-2.4.4-orig/kernel/Makefile Tue Feb 20 16:45:51 2001 +++ linux-2.4.4-pmevent/kernel/Makefile Tue May 1 18:01:49 2001 @@ -18,7 +18,7 @@ obj-$(CONFIG_UID16) += uid16.o obj-$(CONFIG_MODULES) += ksyms.o -obj-$(CONFIG_PM) += pm.o +obj-$(CONFIG_PM) += pm.o pmevent.o ifneq ($(CONFIG_IA64),y) # According to Alan Modra , the -fno-omit-frame-pointer is diff --new-file -u --recursive --exclude *~ linux-2.4.4-orig/kernel/pmevent.c linux-2.4.4-pmevent/kernel/pmevent.c --- linux-2.4.4-orig/kernel/pmevent.c Thu Jan 1 01:00:00 1970 +++ linux-2.4.4-pmevent/kernel/pmevent.c Wed May 2 13:47:52 2001 @@ -0,0 +1,264 @@ +/* (C) 2001 John Fremlin */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static const char msg_overflow[] = "MSGFLOOD KERNEL messages lost\n"; + +#define N_MSGS 16 +#define MSG_MAXLEN 100 + +static const char ring[N_MSGS][MSG_MAXLEN]; +static unsigned ring_next; /*=0*/ + +struct fop_priv +{ + int fp_overflow; + unsigned fp_ringpos; + unsigned fp_msgpos; + + struct list_head fp_entry; +} +; + +static DECLARE_WAIT_QUEUE_HEAD(waiters); +static LIST_HEAD(listeners); +static spinlock_t listeners_lock = SPIN_LOCK_UNLOCKED; + /* protects listeners, ring, ring_next and all the ring_pos in the fop_priv */ + +/** + * pme_announce - inform userspace listeners of a power management event + * @event_type: one of the PME_* defines in linux/pmevent.h, general event class + * @subsystem: kernel subsystem generating the event (e.g. "APM") + * @desc: longer description of event (e.g. "lid closed") + * + * This function must not be called from interrupt context. + */ + +void pme_announce(const char* event_type, + const char* subsystem, + const char* desc) +{ + unsigned ring_cur; + + /* No snprintf in kernel */ + if(strlen(event_type)+strlen(subsystem)+strlen(desc) + 5 >= + MSG_MAXLEN) { + panic("pme_announce arguments too long\n"); + } + + printk(KERN_DEBUG "pm event: %s %s %s\n",event_type,subsystem,desc); + + spin_lock(&listeners_lock); + + ring_cur = ring_next; + + if(++ring_next >= N_MSGS) + ring_next = 0; + + { + struct list_head *i; + + list_for_each(i,&listeners) { + struct fop_priv *priv = list_entry(i, struct fop_priv, fp_entry); + if(ring_next == priv->fp_ringpos) { + /* we will actually overflow next item, + * but it is easier to complain now + */ + priv->fp_msgpos = 0; + priv->fp_overflow = 1; + } + } + + + } + + sprintf((char*)ring[ring_cur],"%s %s %s\n",event_type,subsystem,desc); + + spin_unlock(&listeners_lock); + + wake_up_all(&waiters); /* Recipe for contention */ +} + +static ssize_t do_read(struct fop_priv*priv,char*buf,size_t count) +{ + ssize_t ret = 0; + char* msg; + + if(!priv->fp_overflow) { + if(priv->fp_ringpos == ring_next) { + ret = ret ? ret : -EAGAIN; + goto out; + } + msg = (char*)ring[priv->fp_ringpos]; + } + else + msg = (char*)msg_overflow; + + msg += priv->fp_msgpos; + ret = strlen(msg); + if(ret <= count){ + if(priv->fp_overflow){ + priv->fp_overflow = 0; + priv->fp_ringpos = ring_next; + /* This means the minimum number of + * messages is lost, which might not + * be an altogether good idea, because + * the reader is not given much chance + * to catch up with the flood + */ + } + + if (++priv->fp_ringpos >= N_MSGS) + priv->fp_ringpos = 0; + + priv->fp_msgpos = 0; + } else { + ret = count; + priv->fp_msgpos += count; + } + + if (copy_to_user(buf, msg, ret)) + ret = -EFAULT; + + out: + return ret; +} + + +static ssize_t fop_read(struct file * file, char * buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + struct fop_priv *priv = (struct fop_priv *)file->private_data; + + if (ppos != &file->f_pos) { + ret = -ESPIPE; + goto out; + } + + retry: + spin_lock(&listeners_lock); + + ret = do_read(priv,buf,count); + + spin_unlock(&listeners_lock); + + if((ret == -EAGAIN) && !(file->f_flags&O_NONBLOCK)) { + if (wait_event_interruptible(waiters, + priv->fp_overflow || priv->fp_ringpos != ring_next) + == -ERESTARTSYS) { + ret = ret ? ret : -ERESTARTSYS; + goto out; + } + goto retry; + } + out: + return ret; +} + +static int fop_open(struct inode * inode, struct file * file) +{ + struct fop_priv*priv; + priv = kmalloc(sizeof *priv,GFP_KERNEL); + if(!priv) + return -ENOMEM; + + memset(priv,0,sizeof *priv); + + file->private_data = priv; + + spin_lock(&listeners_lock); + priv->fp_ringpos = ring_next; + list_add(&priv->fp_entry, &listeners); + spin_unlock(&listeners_lock); + + return 0; +} + +static void do_remove_priv(struct fop_priv *priv) +{ + list_del(&priv->fp_entry); + kfree(priv); +} + +static int fop_release(struct inode * inode, struct file * file) +{ + struct fop_priv *priv = (struct fop_priv *)file->private_data; + + spin_lock(&listeners_lock); + + do_remove_priv(priv); + + spin_unlock(&listeners_lock); + + return 0; +} + +static unsigned int fop_poll(struct file *file, poll_table * wait) +{ + struct fop_priv *priv = (struct fop_priv *)file->private_data; + unsigned ret = 0; + + poll_wait(file, &waiters, wait); + + spin_lock(&listeners_lock); + if(priv->fp_overflow || priv->fp_ringpos != ring_next) + ret = POLLIN | POLLRDNORM; + spin_unlock(&listeners_lock); + + return ret; +} + + +static struct file_operations pme_fops = { + read: fop_read, + poll: fop_poll, + open: fop_open, + release: fop_release, +}; + +static struct miscdevice pme_dev= +{ + PMEVENT_MINOR, + "pmevent", + &pme_fops +}; + +static int __init pme_init(void) +{ + misc_register(&pme_dev); + printk(KERN_INFO "pm event: subsystem ready\n"); + return 0; +} + +static void __exit pme_exit(void) +{ + struct list_head *i,*j; + + misc_deregister(&pme_dev); + printk(KERN_INFO "pm event: subsystem closing\n"); + + spin_lock(&listeners_lock); + + for(i=listeners.next;i!=&listeners;i=j){ + struct fop_priv *priv + = list_entry(i, struct fop_priv, fp_entry); + j = i->next; + do_remove_priv(priv); + } +} + +module_init(pme_init); +module_exit(pme_exit);