Commit aa97cacb authored by Russell King's avatar Russell King

[ARM] Convert suspend to a state machine.

The original version had issues when two suspend events came
in at around the same time, causing APM to get confused:
threads became stuck in APM_IOC_SUSPEND and suspends_pending
incremented on each apm --suspend call.

Now, we only add a suspend event to a users queue and increment
suspends_pending if the user isn't already in the middle of
handling a suspend event.
parent c039f765
...@@ -63,11 +63,14 @@ struct apm_user { ...@@ -63,11 +63,14 @@ struct apm_user {
unsigned int suser: 1; unsigned int suser: 1;
unsigned int writer: 1; unsigned int writer: 1;
unsigned int reader: 1; unsigned int reader: 1;
unsigned int suspend_wait: 1;
int suspend_result;
int suspends_pending; int suspend_result;
unsigned int suspends_read; 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; struct apm_queue queue;
}; };
...@@ -141,18 +144,23 @@ static void queue_add_event(struct apm_queue *q, apm_event_t event) ...@@ -141,18 +144,23 @@ static void queue_add_event(struct apm_queue *q, apm_event_t event)
static void queue_event_one_user(struct apm_user *as, apm_event_t event) static void queue_event_one_user(struct apm_user *as, apm_event_t event)
{ {
queue_add_event(&as->queue, event); if (as->suser && as->writer) {
if (!as->suser || !as->writer)
return;
switch (event) { switch (event) {
case APM_SYS_SUSPEND: case APM_SYS_SUSPEND:
case APM_USER_SUSPEND: case APM_USER_SUSPEND:
as->suspends_pending++; /*
* 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++; suspends_pending++;
break; break;
} }
}
queue_add_event(&as->queue, event);
} }
static void queue_event(apm_event_t event, struct apm_user *sender) static void queue_event(apm_event_t event, struct apm_user *sender)
...@@ -185,7 +193,7 @@ static int apm_suspend(void) ...@@ -185,7 +193,7 @@ static int apm_suspend(void)
down_read(&user_list_lock); down_read(&user_list_lock);
list_for_each_entry(as, &apm_user_list, list) { list_for_each_entry(as, &apm_user_list, list) {
as->suspend_result = err; as->suspend_result = err;
as->suspend_wait = 0; as->suspend_state = SUSPEND_DONE;
} }
up_read(&user_list_lock); up_read(&user_list_lock);
...@@ -215,7 +223,7 @@ static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t ...@@ -215,7 +223,7 @@ static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t
break; break;
if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND) if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)
as->suspends_read++; as->suspend_state = SUSPEND_READ;
buf += sizeof(event); buf += sizeof(event);
i -= sizeof(event); i -= sizeof(event);
...@@ -256,16 +264,23 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) ...@@ -256,16 +264,23 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
switch (cmd) { switch (cmd) {
case APM_IOC_SUSPEND: case APM_IOC_SUSPEND:
as->suspend_result = -EINTR;
if (as->suspend_state == SUSPEND_READ) {
/* /*
* If we read a suspend command from /dev/apm_bios, * If we read a suspend command from /dev/apm_bios,
* then the corresponding APM_IOC_SUSPEND ioctl is * then the corresponding APM_IOC_SUSPEND ioctl is
* interpreted as an acknowledge. * interpreted as an acknowledge.
*/ */
if (as->suspends_read > 0) { as->suspend_state = SUSPEND_ACKED;
as->suspends_read--;
as->suspends_pending--;
suspends_pending--; suspends_pending--;
} else { } 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); queue_event(APM_USER_SUSPEND, as);
} }
...@@ -275,14 +290,14 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) ...@@ -275,14 +290,14 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
* Last one to bed turns the lights out. * Last one to bed turns the lights out.
*/ */
if (suspends_pending > 0) { if (suspends_pending > 0) {
as->suspend_wait = 1;
err = wait_event_interruptible(apm_suspend_waitqueue, err = wait_event_interruptible(apm_suspend_waitqueue,
as->suspend_wait == 0); as->suspend_state == SUSPEND_DONE);
if (err == 0) if (err == 0)
err = as->suspend_result; err = as->suspend_result;
} else { } else {
err = apm_suspend(); err = apm_suspend();
} }
as->suspend_state = SUSPEND_NONE;
break; break;
} }
...@@ -304,9 +319,9 @@ static int apm_release(struct inode * inode, struct file * filp) ...@@ -304,9 +319,9 @@ static int apm_release(struct inode * inode, struct file * filp)
* need to balance suspends_pending, which means the * need to balance suspends_pending, which means the
* possibility of sleeping. * possibility of sleeping.
*/ */
if (as->suspends_pending > 0) { if (as->suspend_state != SUSPEND_NONE) {
suspends_pending -= as->suspends_pending; suspends_pending -= 1;
if (suspends_pending <= 0) if (suspends_pending == 0)
apm_suspend(); apm_suspend();
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment