aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRussell King <rmk@dyn-67.arm.linux.org.uk>2006-11-07 16:00:22 -0500
committerRussell King <rmk+kernel@arm.linux.org.uk>2006-11-30 07:24:45 -0500
commitb729c09a452a659cdf4d29d83943aa798c5c4ffc (patch)
tree165d3dc0a5f0221ad897e93ef940bd8e6f295ab1
parent0215ffb08ce99e2bb59eca114a99499a4d06e704 (diff)
[ARM] Improve reliability of APM-emulation suspend
The APM emulation can sometimes cause suspend to fail to work due to apparantly waiting for some process to acknowledge an event when it actually has already done so. We re-jig the event handling to work around this behaviour. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r--arch/arm/kernel/apm.c178
1 files changed, 123 insertions, 55 deletions
diff --git a/arch/arm/kernel/apm.c b/arch/arm/kernel/apm.c
index ecf4f9472d94..bbe98e3b860b 100644
--- a/arch/arm/kernel/apm.c
+++ b/arch/arm/kernel/apm.c
@@ -12,7 +12,6 @@
12 */ 12 */
13#include <linux/module.h> 13#include <linux/module.h>
14#include <linux/poll.h> 14#include <linux/poll.h>
15#include <linux/timer.h>
16#include <linux/slab.h> 15#include <linux/slab.h>
17#include <linux/proc_fs.h> 16#include <linux/proc_fs.h>
18#include <linux/miscdevice.h> 17#include <linux/miscdevice.h>
@@ -26,6 +25,7 @@
26#include <linux/init.h> 25#include <linux/init.h>
27#include <linux/completion.h> 26#include <linux/completion.h>
28#include <linux/kthread.h> 27#include <linux/kthread.h>
28#include <linux/delay.h>
29 29
30#include <asm/apm.h> /* apm_power_info */ 30#include <asm/apm.h> /* apm_power_info */
31#include <asm/system.h> 31#include <asm/system.h>
@@ -71,7 +71,8 @@ struct apm_user {
71#define SUSPEND_PENDING 1 /* suspend pending read */ 71#define SUSPEND_PENDING 1 /* suspend pending read */
72#define SUSPEND_READ 2 /* suspend read, pending ack */ 72#define SUSPEND_READ 2 /* suspend read, pending ack */
73#define SUSPEND_ACKED 3 /* suspend acked */ 73#define SUSPEND_ACKED 3 /* suspend acked */
74#define SUSPEND_DONE 4 /* suspend completed */ 74#define SUSPEND_WAIT 4 /* waiting for suspend */
75#define SUSPEND_DONE 5 /* suspend completed */
75 76
76 struct apm_queue queue; 77 struct apm_queue queue;
77}; 78};
@@ -101,6 +102,7 @@ static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
101static DEFINE_SPINLOCK(kapmd_queue_lock); 102static DEFINE_SPINLOCK(kapmd_queue_lock);
102static struct apm_queue kapmd_queue; 103static struct apm_queue kapmd_queue;
103 104
105static DECLARE_MUTEX(state_lock);
104 106
105static const char driver_version[] = "1.13"; /* no spaces */ 107static const char driver_version[] = "1.13"; /* no spaces */
106 108
@@ -148,38 +150,60 @@ static void queue_add_event(struct apm_queue *q, apm_event_t event)
148 q->events[q->event_head] = event; 150 q->events[q->event_head] = event;
149} 151}
150 152
151static void queue_event_one_user(struct apm_user *as, apm_event_t event) 153static void queue_event(apm_event_t event)
152{ 154{
153 if (as->suser && as->writer) { 155 struct apm_user *as;
154 switch (event) {
155 case APM_SYS_SUSPEND:
156 case APM_USER_SUSPEND:
157 /*
158 * If this user already has a suspend pending,
159 * don't queue another one.
160 */
161 if (as->suspend_state != SUSPEND_NONE)
162 return;
163 156
164 as->suspend_state = SUSPEND_PENDING; 157 down_read(&user_list_lock);
165 suspends_pending++; 158 list_for_each_entry(as, &apm_user_list, list) {
166 break; 159 if (as->reader)
167 } 160 queue_add_event(&as->queue, event);
168 } 161 }
169 queue_add_event(&as->queue, event); 162 up_read(&user_list_lock);
163 wake_up_interruptible(&apm_waitqueue);
170} 164}
171 165
172static void queue_event(apm_event_t event, struct apm_user *sender) 166/*
167 * queue_suspend_event - queue an APM suspend event.
168 *
169 * Check that we're in a state where we can suspend. If not,
170 * return -EBUSY. Otherwise, queue an event to all "writer"
171 * users. If there are no "writer" users, return '1' to
172 * indicate that we can immediately suspend.
173 */
174static int queue_suspend_event(apm_event_t event, struct apm_user *sender)
173{ 175{
174 struct apm_user *as; 176 struct apm_user *as;
177 int ret = 1;
175 178
179 down(&state_lock);
176 down_read(&user_list_lock); 180 down_read(&user_list_lock);
181
182 /*
183 * If a thread is still processing, we can't suspend, so reject
184 * the request.
185 */
186 list_for_each_entry(as, &apm_user_list, list) {
187 if (as != sender && as->reader && as->writer && as->suser &&
188 as->suspend_state != SUSPEND_NONE) {
189 ret = -EBUSY;
190 goto out;
191 }
192 }
193
177 list_for_each_entry(as, &apm_user_list, list) { 194 list_for_each_entry(as, &apm_user_list, list) {
178 if (as != sender && as->reader) 195 if (as != sender && as->reader && as->writer && as->suser) {
179 queue_event_one_user(as, event); 196 as->suspend_state = SUSPEND_PENDING;
197 suspends_pending++;
198 queue_add_event(&as->queue, event);
199 ret = 0;
200 }
180 } 201 }
202 out:
181 up_read(&user_list_lock); 203 up_read(&user_list_lock);
204 up(&state_lock);
182 wake_up_interruptible(&apm_waitqueue); 205 wake_up_interruptible(&apm_waitqueue);
206 return ret;
183} 207}
184 208
185static void apm_suspend(void) 209static void apm_suspend(void)
@@ -191,17 +215,22 @@ static void apm_suspend(void)
191 * Anyone on the APM queues will think we're still suspended. 215 * Anyone on the APM queues will think we're still suspended.
192 * Send a message so everyone knows we're now awake again. 216 * Send a message so everyone knows we're now awake again.
193 */ 217 */
194 queue_event(APM_NORMAL_RESUME, NULL); 218 queue_event(APM_NORMAL_RESUME);
195 219
196 /* 220 /*
197 * Finally, wake up anyone who is sleeping on the suspend. 221 * Finally, wake up anyone who is sleeping on the suspend.
198 */ 222 */
223 down(&state_lock);
199 down_read(&user_list_lock); 224 down_read(&user_list_lock);
200 list_for_each_entry(as, &apm_user_list, list) { 225 list_for_each_entry(as, &apm_user_list, list) {
201 as->suspend_result = err; 226 if (as->suspend_state == SUSPEND_WAIT ||
202 as->suspend_state = SUSPEND_DONE; 227 as->suspend_state == SUSPEND_ACKED) {
228 as->suspend_result = err;
229 as->suspend_state = SUSPEND_DONE;
230 }
203 } 231 }
204 up_read(&user_list_lock); 232 up_read(&user_list_lock);
233 up(&state_lock);
205 234
206 wake_up(&apm_suspend_waitqueue); 235 wake_up(&apm_suspend_waitqueue);
207} 236}
@@ -227,8 +256,11 @@ static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t
227 if (copy_to_user(buf, &event, sizeof(event))) 256 if (copy_to_user(buf, &event, sizeof(event)))
228 break; 257 break;
229 258
230 if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND) 259 down(&state_lock);
260 if (as->suspend_state == SUSPEND_PENDING &&
261 (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
231 as->suspend_state = SUSPEND_READ; 262 as->suspend_state = SUSPEND_READ;
263 up(&state_lock);
232 264
233 buf += sizeof(event); 265 buf += sizeof(event);
234 i -= sizeof(event); 266 i -= sizeof(event);
@@ -270,9 +302,13 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
270 302
271 switch (cmd) { 303 switch (cmd) {
272 case APM_IOC_SUSPEND: 304 case APM_IOC_SUSPEND:
305 down(&state_lock);
306
273 as->suspend_result = -EINTR; 307 as->suspend_result = -EINTR;
274 308
275 if (as->suspend_state == SUSPEND_READ) { 309 if (as->suspend_state == SUSPEND_READ) {
310 int pending;
311
276 /* 312 /*
277 * If we read a suspend command from /dev/apm_bios, 313 * If we read a suspend command from /dev/apm_bios,
278 * then the corresponding APM_IOC_SUSPEND ioctl is 314 * then the corresponding APM_IOC_SUSPEND ioctl is
@@ -280,47 +316,66 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
280 */ 316 */
281 as->suspend_state = SUSPEND_ACKED; 317 as->suspend_state = SUSPEND_ACKED;
282 suspends_pending--; 318 suspends_pending--;
319 pending = suspends_pending == 0;
320 up(&state_lock);
321
322 /*
323 * If there are no further acknowledges required,
324 * suspend the system.
325 */
326 if (pending)
327 apm_suspend();
328
329 /*
330 * Wait for the suspend/resume to complete. If there
331 * are pending acknowledges, we wait here for them.
332 *
333 * Note: we need to ensure that the PM subsystem does
334 * not kick us out of the wait when it suspends the
335 * threads.
336 */
337 flags = current->flags;
338 current->flags |= PF_NOFREEZE;
339
340 wait_event(apm_suspend_waitqueue,
341 as->suspend_state == SUSPEND_DONE);
283 } else { 342 } else {
343 up(&state_lock);
344
284 /* 345 /*
285 * Otherwise it is a request to suspend the system. 346 * Otherwise it is a request to suspend the system.
286 * Queue an event for all readers, and expect an 347 * Queue an event for all readers, and expect an
287 * acknowledge from all writers who haven't already 348 * acknowledge from all writers who haven't already
288 * acknowledged. 349 * acknowledged.
289 */ 350 */
290 queue_event(APM_USER_SUSPEND, as); 351 err = queue_suspend_event(APM_USER_SUSPEND, as);
291 } 352 if (err < 0)
353 break;
292 354
293 /* 355 if (err > 0)
294 * If there are no further acknowledges required, suspend 356 apm_suspend();
295 * the system.
296 */
297 if (suspends_pending == 0)
298 apm_suspend();
299 357
300 /* 358 /*
301 * Wait for the suspend/resume to complete. If there are 359 * Wait for the suspend/resume to complete. If there
302 * pending acknowledges, we wait here for them. 360 * are pending acknowledges, we wait here for them.
303 * 361 *
304 * Note that we need to ensure that the PM subsystem does 362 * Note: we need to ensure that the PM subsystem does
305 * not kick us out of the wait when it suspends the threads. 363 * not kick us out of the wait when it suspends the
306 */ 364 * threads.
307 flags = current->flags; 365 */
308 current->flags |= PF_NOFREEZE; 366 flags = current->flags;
367 current->flags |= PF_NOFREEZE;
309 368
310 /*
311 * Note: do not allow a thread which is acking the suspend
312 * to escape until the resume is complete.
313 */
314 if (as->suspend_state == SUSPEND_ACKED)
315 wait_event(apm_suspend_waitqueue,
316 as->suspend_state == SUSPEND_DONE);
317 else
318 wait_event_interruptible(apm_suspend_waitqueue, 369 wait_event_interruptible(apm_suspend_waitqueue,
319 as->suspend_state == SUSPEND_DONE); 370 as->suspend_state == SUSPEND_DONE);
371 }
320 372
321 current->flags = flags; 373 current->flags = flags;
374
375 down(&state_lock);
322 err = as->suspend_result; 376 err = as->suspend_result;
323 as->suspend_state = SUSPEND_NONE; 377 as->suspend_state = SUSPEND_NONE;
378 up(&state_lock);
324 break; 379 break;
325 } 380 }
326 381
@@ -330,6 +385,8 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
330static int apm_release(struct inode * inode, struct file * filp) 385static int apm_release(struct inode * inode, struct file * filp)
331{ 386{
332 struct apm_user *as = filp->private_data; 387 struct apm_user *as = filp->private_data;
388 int pending = 0;
389
333 filp->private_data = NULL; 390 filp->private_data = NULL;
334 391
335 down_write(&user_list_lock); 392 down_write(&user_list_lock);
@@ -342,11 +399,14 @@ static int apm_release(struct inode * inode, struct file * filp)
342 * need to balance suspends_pending, which means the 399 * need to balance suspends_pending, which means the
343 * possibility of sleeping. 400 * possibility of sleeping.
344 */ 401 */
402 down(&state_lock);
345 if (as->suspend_state != SUSPEND_NONE) { 403 if (as->suspend_state != SUSPEND_NONE) {
346 suspends_pending -= 1; 404 suspends_pending -= 1;
347 if (suspends_pending == 0) 405 pending = suspends_pending == 0;
348 apm_suspend();
349 } 406 }
407 up(&state_lock);
408 if (pending)
409 apm_suspend();
350 410
351 kfree(as); 411 kfree(as);
352 return 0; 412 return 0;
@@ -470,6 +530,7 @@ static int kapmd(void *arg)
470{ 530{
471 do { 531 do {
472 apm_event_t event; 532 apm_event_t event;
533 int ret;
473 534
474 wait_event_interruptible(kapmd_wait, 535 wait_event_interruptible(kapmd_wait,
475 !queue_empty(&kapmd_queue) || kthread_should_stop()); 536 !queue_empty(&kapmd_queue) || kthread_should_stop());
@@ -489,13 +550,20 @@ static int kapmd(void *arg)
489 550
490 case APM_LOW_BATTERY: 551 case APM_LOW_BATTERY:
491 case APM_POWER_STATUS_CHANGE: 552 case APM_POWER_STATUS_CHANGE:
492 queue_event(event, NULL); 553 queue_event(event);
493 break; 554 break;
494 555
495 case APM_USER_SUSPEND: 556 case APM_USER_SUSPEND:
496 case APM_SYS_SUSPEND: 557 case APM_SYS_SUSPEND:
497 queue_event(event, NULL); 558 ret = queue_suspend_event(event, NULL);
498 if (suspends_pending == 0) 559 if (ret < 0) {
560 /*
561 * We were busy. Try again in 50ms.
562 */
563 queue_add_event(&kapmd_queue, event);
564 msleep(50);
565 }
566 if (ret > 0)
499 apm_suspend(); 567 apm_suspend();
500 break; 568 break;
501 569