Commit b729c09a452a659cdf4d29d83943aa798c5c4ffc

Authored by Russell King
Committed by Russell King
1 parent 0215ffb08c

[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>

Showing 1 changed file with 123 additions and 55 deletions Side-by-side Diff

arch/arm/kernel/apm.c
... ... @@ -12,7 +12,6 @@
12 12 */
13 13 #include <linux/module.h>
14 14 #include <linux/poll.h>
15   -#include <linux/timer.h>
16 15 #include <linux/slab.h>
17 16 #include <linux/proc_fs.h>
18 17 #include <linux/miscdevice.h>
... ... @@ -26,6 +25,7 @@
26 25 #include <linux/init.h>
27 26 #include <linux/completion.h>
28 27 #include <linux/kthread.h>
  28 +#include <linux/delay.h>
29 29  
30 30 #include <asm/apm.h> /* apm_power_info */
31 31 #include <asm/system.h>
... ... @@ -71,7 +71,8 @@
71 71 #define SUSPEND_PENDING 1 /* suspend pending read */
72 72 #define SUSPEND_READ 2 /* suspend read, pending ack */
73 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 77 struct apm_queue queue;
77 78 };
... ... @@ -101,6 +102,7 @@
101 102 static DEFINE_SPINLOCK(kapmd_queue_lock);
102 103 static struct apm_queue kapmd_queue;
103 104  
  105 +static DECLARE_MUTEX(state_lock);
104 106  
105 107 static const char driver_version[] = "1.13"; /* no spaces */
106 108  
107 109  
108 110  
109 111  
110 112  
111 113  
112 114  
113 115  
114 116  
115 117  
116 118  
117 119  
... ... @@ -148,38 +150,60 @@
148 150 q->events[q->event_head] = event;
149 151 }
150 152  
151   -static void queue_event_one_user(struct apm_user *as, apm_event_t event)
  153 +static void queue_event(apm_event_t event)
152 154 {
153   - if (as->suser && as->writer) {
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;
  155 + struct apm_user *as;
163 156  
164   - as->suspend_state = SUSPEND_PENDING;
165   - suspends_pending++;
166   - break;
167   - }
  157 + down_read(&user_list_lock);
  158 + list_for_each_entry(as, &apm_user_list, list) {
  159 + if (as->reader)
  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  
172   -static 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 + */
  174 +static int queue_suspend_event(apm_event_t event, struct apm_user *sender)
173 175 {
174 176 struct apm_user *as;
  177 + int ret = 1;
175 178  
  179 + down(&state_lock);
176 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 + */
177 186 list_for_each_entry(as, &apm_user_list, list) {
178   - if (as != sender && as->reader)
179   - queue_event_one_user(as, event);
  187 + if (as != sender && as->reader && as->writer && as->suser &&
  188 + as->suspend_state != SUSPEND_NONE) {
  189 + ret = -EBUSY;
  190 + goto out;
  191 + }
180 192 }
  193 +
  194 + list_for_each_entry(as, &apm_user_list, list) {
  195 + if (as != sender && as->reader && as->writer && as->suser) {
  196 + as->suspend_state = SUSPEND_PENDING;
  197 + suspends_pending++;
  198 + queue_add_event(&as->queue, event);
  199 + ret = 0;
  200 + }
  201 + }
  202 + out:
181 203 up_read(&user_list_lock);
  204 + up(&state_lock);
182 205 wake_up_interruptible(&apm_waitqueue);
  206 + return ret;
183 207 }
184 208  
185 209 static void apm_suspend(void)
186 210  
187 211  
188 212  
... ... @@ -191,17 +215,22 @@
191 215 * Anyone on the APM queues will think we're still suspended.
192 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 221 * Finally, wake up anyone who is sleeping on the suspend.
198 222 */
  223 + down(&state_lock);
199 224 down_read(&user_list_lock);
200 225 list_for_each_entry(as, &apm_user_list, list) {
201   - as->suspend_result = err;
202   - as->suspend_state = SUSPEND_DONE;
  226 + if (as->suspend_state == SUSPEND_WAIT ||
  227 + as->suspend_state == SUSPEND_ACKED) {
  228 + as->suspend_result = err;
  229 + as->suspend_state = SUSPEND_DONE;
  230 + }
203 231 }
204 232 up_read(&user_list_lock);
  233 + up(&state_lock);
205 234  
206 235 wake_up(&apm_suspend_waitqueue);
207 236 }
208 237  
... ... @@ -227,8 +256,11 @@
227 256 if (copy_to_user(buf, &event, sizeof(event)))
228 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 262 as->suspend_state = SUSPEND_READ;
  263 + up(&state_lock);
232 264  
233 265 buf += sizeof(event);
234 266 i -= sizeof(event);
235 267  
... ... @@ -270,9 +302,13 @@
270 302  
271 303 switch (cmd) {
272 304 case APM_IOC_SUSPEND:
  305 + down(&state_lock);
  306 +
273 307 as->suspend_result = -EINTR;
274 308  
275 309 if (as->suspend_state == SUSPEND_READ) {
  310 + int pending;
  311 +
276 312 /*
277 313 * If we read a suspend command from /dev/apm_bios,
278 314 * then the corresponding APM_IOC_SUSPEND ioctl is
279 315  
280 316  
281 317  
282 318  
283 319  
284 320  
285 321  
286 322  
... ... @@ -280,47 +316,66 @@
280 316 */
281 317 as->suspend_state = SUSPEND_ACKED;
282 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 342 } else {
  343 + up(&state_lock);
  344 +
284 345 /*
285 346 * Otherwise it is a request to suspend the system.
286 347 * Queue an event for all readers, and expect an
287 348 * acknowledge from all writers who haven't already
288 349 * acknowledged.
289 350 */
290   - queue_event(APM_USER_SUSPEND, as);
291   - }
  351 + err = queue_suspend_event(APM_USER_SUSPEND, as);
  352 + if (err < 0)
  353 + break;
292 354  
293   - /*
294   - * If there are no further acknowledges required, suspend
295   - * the system.
296   - */
297   - if (suspends_pending == 0)
298   - apm_suspend();
  355 + if (err > 0)
  356 + apm_suspend();
299 357  
300   - /*
301   - * Wait for the suspend/resume to complete. If there are
302   - * pending acknowledges, we wait here for them.
303   - *
304   - * Note that we need to ensure that the PM subsystem does
305   - * not kick us out of the wait when it suspends the threads.
306   - */
307   - flags = current->flags;
308   - current->flags |= PF_NOFREEZE;
  358 + /*
  359 + * Wait for the suspend/resume to complete. If there
  360 + * are pending acknowledges, we wait here for them.
  361 + *
  362 + * Note: we need to ensure that the PM subsystem does
  363 + * not kick us out of the wait when it suspends the
  364 + * threads.
  365 + */
  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 369 wait_event_interruptible(apm_suspend_waitqueue,
319 370 as->suspend_state == SUSPEND_DONE);
  371 + }
320 372  
321 373 current->flags = flags;
  374 +
  375 + down(&state_lock);
322 376 err = as->suspend_result;
323 377 as->suspend_state = SUSPEND_NONE;
  378 + up(&state_lock);
324 379 break;
325 380 }
326 381  
... ... @@ -330,6 +385,8 @@
330 385 static int apm_release(struct inode * inode, struct file * filp)
331 386 {
332 387 struct apm_user *as = filp->private_data;
  388 + int pending = 0;
  389 +
333 390 filp->private_data = NULL;
334 391  
335 392 down_write(&user_list_lock);
336 393  
337 394  
... ... @@ -342,11 +399,14 @@
342 399 * need to balance suspends_pending, which means the
343 400 * possibility of sleeping.
344 401 */
  402 + down(&state_lock);
345 403 if (as->suspend_state != SUSPEND_NONE) {
346 404 suspends_pending -= 1;
347   - if (suspends_pending == 0)
348   - apm_suspend();
  405 + pending = suspends_pending == 0;
349 406 }
  407 + up(&state_lock);
  408 + if (pending)
  409 + apm_suspend();
350 410  
351 411 kfree(as);
352 412 return 0;
... ... @@ -470,6 +530,7 @@
470 530 {
471 531 do {
472 532 apm_event_t event;
  533 + int ret;
473 534  
474 535 wait_event_interruptible(kapmd_wait,
475 536 !queue_empty(&kapmd_queue) || kthread_should_stop());
476 537  
... ... @@ -489,13 +550,20 @@
489 550  
490 551 case APM_LOW_BATTERY:
491 552 case APM_POWER_STATUS_CHANGE:
492   - queue_event(event, NULL);
  553 + queue_event(event);
493 554 break;
494 555  
495 556 case APM_USER_SUSPEND:
496 557 case APM_SYS_SUSPEND:
497   - queue_event(event, NULL);
498   - if (suspends_pending == 0)
  558 + ret = queue_suspend_event(event, NULL);
  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 567 apm_suspend();
500 568 break;
501 569