* [This document is available from Microsoft at:
* http://www.microsoft.com/hwdev/busbios/amp_12.htm]
*/
-#include <linux/config.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/timer.h>
#include <linux/proc_fs.h>
#include <linux/miscdevice.h>
#include <linux/apm_bios.h>
+#include <linux/capability.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/device.h>
/*
* Maximum number of events stored
*/
-#define APM_MAX_EVENTS 20
+#define APM_MAX_EVENTS 16
+
+struct apm_queue {
+ unsigned int event_head;
+ unsigned int event_tail;
+ apm_event_t events[APM_MAX_EVENTS];
+};
/*
* The per-file APM data
struct apm_user {
struct list_head list;
- int suser: 1;
- int writer: 1;
- int reader: 1;
- int suspend_wait: 1;
- int suspend_result;
-
- int suspends_pending;
- int standbys_pending;
- unsigned int suspends_read;
- unsigned int standbys_read;
+ unsigned int suser: 1;
+ unsigned int writer: 1;
+ unsigned int reader: 1;
- int event_head;
- int event_tail;
- apm_event_t events[APM_MAX_EVENTS];
+ int suspend_result;
+ unsigned int suspend_state;
+#define SUSPEND_NONE 0 /* no suspend pending */
+#define SUSPEND_PENDING 1 /* suspend pending read */
+#define SUSPEND_READ 2 /* suspend read, pending ack */
+#define SUSPEND_ACKED 3 /* suspend acked */
+#define SUSPEND_DONE 4 /* suspend completed */
+
+ struct apm_queue queue;
};
/*
* Local variables
*/
static int suspends_pending;
-static int standbys_pending;
static int apm_disabled;
+static int arm_apm_active;
static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
/*
* This is a list of everyone who has opened /dev/apm_bios
*/
-static spinlock_t user_list_lock = SPIN_LOCK_UNLOCKED;
+static DECLARE_RWSEM(user_list_lock);
static LIST_HEAD(apm_user_list);
/*
- * The kapmd info.
+ * kapmd info. kapmd provides us a process context to handle
+ * "APM" events within - specifically necessary if we're going
+ * to be suspending the system.
*/
-static struct task_struct *kapmd;
+static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
static DECLARE_COMPLETION(kapmd_exit);
+static DEFINE_SPINLOCK(kapmd_queue_lock);
+static struct apm_queue kapmd_queue;
+
static const char driver_version[] = "1.13"; /* no spaces */
*/
static void __apm_get_power_status(struct apm_power_info *info)
{
-#if 0 && defined(CONFIG_SA1100_H3600) && defined(CONFIG_TOUCHSCREEN_H3600)
- extern int h3600_apm_get_power_status(u_char *, u_char *, u_char *,
- u_char *, u_short *);
-
- if (machine_is_h3600()) {
- int dx;
- h3600_apm_get_power_status(&info->ac_line_status,
- &info->battery_status, &info->battery_flag,
- &info->battery_life, &dx);
- info->time = dx & 0x7fff;
- info->units = dx & 0x8000 ? 0 : 1;
- }
-#endif
}
/*
void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
EXPORT_SYMBOL(apm_get_power_status);
-static int queue_empty(struct apm_user *as)
+
+/*
+ * APM event queue management.
+ */
+static inline int queue_empty(struct apm_queue *q)
{
- return as->event_head == as->event_tail;
+ return q->event_head == q->event_tail;
}
-static apm_event_t get_queued_event(struct apm_user *as)
+static inline apm_event_t queue_get_event(struct apm_queue *q)
{
- as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
- return as->events[as->event_tail];
+ q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
+ return q->events[q->event_tail];
}
-static void queue_event_one_user(struct apm_user *as, apm_event_t event)
+static void queue_add_event(struct apm_queue *q, apm_event_t event)
{
- as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
- if (as->event_head == as->event_tail) {
+ q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
+ if (q->event_head == q->event_tail) {
static int notified;
if (notified++ == 0)
printk(KERN_ERR "apm: an event queue overflowed\n");
- as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
+ q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
}
- as->events[as->event_head] = event;
-
- if (!as->suser || !as->writer)
- return;
-
- switch (event) {
- case APM_SYS_SUSPEND:
- case APM_USER_SUSPEND:
- as->suspends_pending++;
- suspends_pending++;
- break;
+ q->events[q->event_head] = event;
+}
- case APM_SYS_STANDBY:
- case APM_USER_STANDBY:
- as->standbys_pending++;
- standbys_pending++;
- break;
+static void queue_event_one_user(struct apm_user *as, apm_event_t event)
+{
+ if (as->suser && as->writer) {
+ switch (event) {
+ case APM_SYS_SUSPEND:
+ case APM_USER_SUSPEND:
+ /*
+ * If this user already has a suspend pending,
+ * don't queue another one.
+ */
+ if (as->suspend_state != SUSPEND_NONE)
+ return;
+
+ as->suspend_state = SUSPEND_PENDING;
+ suspends_pending++;
+ break;
+ }
}
+ queue_add_event(&as->queue, event);
}
static void queue_event(apm_event_t event, struct apm_user *sender)
{
- struct list_head *l;
-
- spin_lock(&user_list_lock);
- list_for_each(l, &apm_user_list) {
- struct apm_user *as = list_entry(l, struct apm_user, list);
+ struct apm_user *as;
+ down_read(&user_list_lock);
+ list_for_each_entry(as, &apm_user_list, list) {
if (as != sender && as->reader)
queue_event_one_user(as, event);
}
- spin_unlock(&user_list_lock);
+ up_read(&user_list_lock);
wake_up_interruptible(&apm_waitqueue);
}
-static int apm_suspend(void)
+static void apm_suspend(void)
{
- struct list_head *l;
+ struct apm_user *as;
int err = pm_suspend(PM_SUSPEND_MEM);
/*
/*
* Finally, wake up anyone who is sleeping on the suspend.
*/
- spin_lock(&user_list_lock);
- list_for_each(l, &apm_user_list) {
- struct apm_user *as = list_entry(l, struct apm_user, list);
-
+ down_read(&user_list_lock);
+ list_for_each_entry(as, &apm_user_list, list) {
as->suspend_result = err;
- as->suspend_wait = 0;
+ as->suspend_state = SUSPEND_DONE;
}
- spin_unlock(&user_list_lock);
+ up_read(&user_list_lock);
- wake_up_interruptible(&apm_suspend_waitqueue);
- return err;
+ wake_up(&apm_suspend_waitqueue);
}
-static ssize_t apm_read(struct file *fp, char *buf, size_t count, loff_t *ppos)
+static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
{
struct apm_user *as = fp->private_data;
apm_event_t event;
- int i = count, ret = 0, nonblock = fp->f_flags & O_NONBLOCK;
+ int i = count, ret = 0;
if (count < sizeof(apm_event_t))
return -EINVAL;
- if (queue_empty(as) && nonblock)
+ if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
return -EAGAIN;
- wait_event_interruptible(apm_waitqueue, !queue_empty(as));
+ wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
- while ((i >= sizeof(event)) && !queue_empty(as)) {
- event = get_queued_event(as);
- printk(" apm_read: event=%d\n", event);
+ while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
+ event = queue_get_event(&as->queue);
ret = -EFAULT;
if (copy_to_user(buf, &event, sizeof(event)))
break;
- switch (event) {
- case APM_SYS_SUSPEND:
- case APM_USER_SUSPEND:
- as->suspends_read++;
- break;
-
- case APM_SYS_STANDBY:
- case APM_USER_STANDBY:
- as->standbys_read++;
- break;
- }
+ if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)
+ as->suspend_state = SUSPEND_READ;
buf += sizeof(event);
i -= sizeof(event);
static unsigned int apm_poll(struct file *fp, poll_table * wait)
{
- struct apm_user * as = fp->private_data;
+ struct apm_user *as = fp->private_data;
poll_wait(fp, &apm_waitqueue, wait);
- return queue_empty(as) ? 0 : POLLIN | POLLRDNORM;
+ return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
}
/*
apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
{
struct apm_user *as = filp->private_data;
+ unsigned long flags;
int err = -EINVAL;
if (!as->suser || !as->writer)
return -EPERM;
switch (cmd) {
- case APM_IOC_STANDBY:
- break;
-
case APM_IOC_SUSPEND:
- /*
- * If we read a suspend command from /dev/apm_bios,
- * then the corresponding APM_IOC_SUSPEND ioctl is
- * interpreted as an acknowledge.
- */
- if (as->suspends_read > 0) {
- as->suspends_read--;
- as->suspends_pending--;
+ as->suspend_result = -EINTR;
+
+ if (as->suspend_state == SUSPEND_READ) {
+ /*
+ * If we read a suspend command from /dev/apm_bios,
+ * then the corresponding APM_IOC_SUSPEND ioctl is
+ * interpreted as an acknowledge.
+ */
+ as->suspend_state = SUSPEND_ACKED;
suspends_pending--;
} else {
+ /*
+ * Otherwise it is a request to suspend the system.
+ * Queue an event for all readers, and expect an
+ * acknowledge from all writers who haven't already
+ * acknowledged.
+ */
queue_event(APM_USER_SUSPEND, as);
}
/*
- * If there are outstanding suspend requests for other
- * people on /dev/apm_bios, we must sleep for them.
- * Last one to bed turns the lights out.
+ * If there are no further acknowledges required, suspend
+ * the system.
*/
- if (suspends_pending > 0) {
- as->suspend_wait = 1;
- err = wait_event_interruptible(apm_suspend_waitqueue,
- as->suspend_wait == 0);
- if (err == 0)
- err = as->suspend_result;
- } else {
- err = apm_suspend();
- }
+ if (suspends_pending == 0)
+ apm_suspend();
+
+ /*
+ * Wait for the suspend/resume to complete. If there are
+ * pending acknowledges, we wait here for them.
+ *
+ * Note that we need to ensure that the PM subsystem does
+ * not kick us out of the wait when it suspends the threads.
+ */
+ flags = current->flags;
+ current->flags |= PF_NOFREEZE;
+
+ /*
+ * Note: do not allow a thread which is acking the suspend
+ * to escape until the resume is complete.
+ */
+ if (as->suspend_state == SUSPEND_ACKED)
+ wait_event(apm_suspend_waitqueue,
+ as->suspend_state == SUSPEND_DONE);
+ else
+ wait_event_interruptible(apm_suspend_waitqueue,
+ as->suspend_state == SUSPEND_DONE);
+
+ current->flags = flags;
+ err = as->suspend_result;
+ as->suspend_state = SUSPEND_NONE;
break;
}
struct apm_user *as = filp->private_data;
filp->private_data = NULL;
- spin_lock(&user_list_lock);
+ down_write(&user_list_lock);
list_del(&as->list);
- spin_unlock(&user_list_lock);
+ up_write(&user_list_lock);
/*
* We are now unhooked from the chain. As far as new
* events are concerned, we no longer exist. However, we
- * need to balance standbys_pending and suspends_pending,
- * which means the possibility of sleeping.
+ * need to balance suspends_pending, which means the
+ * possibility of sleeping.
*/
- if (as->standbys_pending > 0) {
- standbys_pending -= as->standbys_pending;
-// if (standbys_pending <= 0)
-// standby();
- }
- if (as->suspends_pending > 0) {
- suspends_pending -= as->suspends_pending;
- if (suspends_pending <= 0)
+ if (as->suspend_state != SUSPEND_NONE) {
+ suspends_pending -= 1;
+ if (suspends_pending == 0)
apm_suspend();
}
{
struct apm_user *as;
- as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL);
+ as = (struct apm_user *)kzalloc(sizeof(*as), GFP_KERNEL);
if (as) {
- memset(as, 0, sizeof(*as));
-
/*
* XXX - this is a tiny bit broken, when we consider BSD
* process accounting. If the device is opened by root, we
as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
- spin_lock(&user_list_lock);
+ down_write(&user_list_lock);
list_add(&as->list, &apm_user_list);
- spin_unlock(&user_list_lock);
+ up_write(&user_list_lock);
filp->private_data = as;
}
info.ac_line_status = 0xff;
info.battery_status = 0xff;
info.battery_flag = 0xff;
- info.battery_life = 255;
+ info.battery_life = -1;
info.time = -1;
info.units = -1;
}
#endif
-#if 0
-static int kapmd(void *startup)
+static int kapmd(void *arg)
{
- struct task_struct *tsk = current;
+ daemonize("kapmd");
+ current->flags |= PF_NOFREEZE;
- daemonize();
- strcpy(tsk->comm, "kapmd");
- kapmd = tsk;
+ do {
+ apm_event_t event;
- spin_lock_irq(&tsk->sigmask_lock);
- siginitsetinv(&tsk->blocked, sigmask(SIGQUIT));
- recalc_sigpending(tsk);
- spin_unlock_irq(&tsk->sigmask_lock);
+ wait_event_interruptible(kapmd_wait,
+ !queue_empty(&kapmd_queue) || !arm_apm_active);
- complete((struct completion *)startup);
+ if (!arm_apm_active)
+ break;
- do {
- set_task_state(tsk, TASK_INTERRUPTIBLE);
- schedule();
- } while (!signal_pending(tsk));
+ spin_lock_irq(&kapmd_queue_lock);
+ event = 0;
+ if (!queue_empty(&kapmd_queue))
+ event = queue_get_event(&kapmd_queue);
+ spin_unlock_irq(&kapmd_queue_lock);
+
+ switch (event) {
+ case 0:
+ break;
+
+ case APM_LOW_BATTERY:
+ case APM_POWER_STATUS_CHANGE:
+ queue_event(event, NULL);
+ break;
+
+ case APM_USER_SUSPEND:
+ case APM_SYS_SUSPEND:
+ queue_event(event, NULL);
+ if (suspends_pending == 0)
+ apm_suspend();
+ break;
+
+ case APM_CRITICAL_SUSPEND:
+ apm_suspend();
+ break;
+ }
+ } while (1);
complete_and_exit(&kapmd_exit, 0);
}
-#endif
static int __init apm_init(void)
{
-// struct completion startup = COMPLETION_INITIALIZER(startup);
int ret;
if (apm_disabled) {
return -ENODEV;
}
- if (PM_IS_ACTIVE()) {
- printk(KERN_NOTICE "apm: overridden by ACPI.\n");
- return -EINVAL;
- }
-
-// ret = kernel_thread(kapmd, &startup, CLONE_FS | CLONE_FILES);
-// if (ret)
-// return ret;
-// wait_for_completion(&startup);
+ arm_apm_active = 1;
- pm_active = 1;
+ ret = kernel_thread(kapmd, NULL, CLONE_KERNEL);
+ if (ret < 0) {
+ arm_apm_active = 0;
+ return ret;
+ }
#ifdef CONFIG_PROC_FS
create_proc_info_entry("apm", 0, NULL, apm_get_info);
ret = misc_register(&apm_device);
if (ret != 0) {
- pm_active = 0;
remove_proc_entry("apm", NULL);
- send_sig(SIGQUIT, kapmd, 1);
+
+ arm_apm_active = 0;
+ wake_up(&kapmd_wait);
wait_for_completion(&kapmd_exit);
}
{
misc_deregister(&apm_device);
remove_proc_entry("apm", NULL);
- pm_active = 0;
-// send_sig(SIGQUIT, kapmd, 1);
-// wait_for_completion(&kapmd_exit);
+
+ arm_apm_active = 0;
+ wake_up(&kapmd_wait);
+ wait_for_completion(&kapmd_exit);
}
module_init(apm_init);
__setup("apm=", apm_setup);
#endif
+
+/**
+ * apm_queue_event - queue an APM event for kapmd
+ * @event: APM event
+ *
+ * Queue an APM event for kapmd to process and ultimately take the
+ * appropriate action. Only a subset of events are handled:
+ * %APM_LOW_BATTERY
+ * %APM_POWER_STATUS_CHANGE
+ * %APM_USER_SUSPEND
+ * %APM_SYS_SUSPEND
+ * %APM_CRITICAL_SUSPEND
+ */
+void apm_queue_event(apm_event_t event)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&kapmd_queue_lock, flags);
+ queue_add_event(&kapmd_queue, event);
+ spin_unlock_irqrestore(&kapmd_queue_lock, flags);
+
+ wake_up_interruptible(&kapmd_wait);
+}
+EXPORT_SYMBOL(apm_queue_event);