Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/char/apm-emulation.c
26278 views
1
// SPDX-License-Identifier: GPL-2.0-only
2
/*
3
* bios-less APM driver for ARM Linux
4
* Jamey Hicks <[email protected]>
5
* adapted from the APM BIOS driver for Linux by Stephen Rothwell ([email protected])
6
*
7
* APM 1.2 Reference:
8
* Intel Corporation, Microsoft Corporation. Advanced Power Management
9
* (APM) BIOS Interface Specification, Revision 1.2, February 1996.
10
*
11
* This document is available from Microsoft at:
12
* http://www.microsoft.com/whdc/archive/amp_12.mspx
13
*/
14
#include <linux/module.h>
15
#include <linux/poll.h>
16
#include <linux/slab.h>
17
#include <linux/mutex.h>
18
#include <linux/proc_fs.h>
19
#include <linux/seq_file.h>
20
#include <linux/miscdevice.h>
21
#include <linux/apm_bios.h>
22
#include <linux/capability.h>
23
#include <linux/sched.h>
24
#include <linux/suspend.h>
25
#include <linux/apm-emulation.h>
26
#include <linux/freezer.h>
27
#include <linux/device.h>
28
#include <linux/kernel.h>
29
#include <linux/list.h>
30
#include <linux/init.h>
31
#include <linux/completion.h>
32
#include <linux/kthread.h>
33
#include <linux/delay.h>
34
35
/*
36
* One option can be changed at boot time as follows:
37
* apm=on/off enable/disable APM
38
*/
39
40
/*
41
* Maximum number of events stored
42
*/
43
#define APM_MAX_EVENTS 16
44
45
struct apm_queue {
46
unsigned int event_head;
47
unsigned int event_tail;
48
apm_event_t events[APM_MAX_EVENTS];
49
};
50
51
/*
52
* thread states (for threads using a writable /dev/apm_bios fd):
53
*
54
* SUSPEND_NONE: nothing happening
55
* SUSPEND_PENDING: suspend event queued for thread and pending to be read
56
* SUSPEND_READ: suspend event read, pending acknowledgement
57
* SUSPEND_ACKED: acknowledgement received from thread (via ioctl),
58
* waiting for resume
59
* SUSPEND_ACKTO: acknowledgement timeout
60
* SUSPEND_DONE: thread had acked suspend and is now notified of
61
* resume
62
*
63
* SUSPEND_WAIT: this thread invoked suspend and is waiting for resume
64
*
65
* A thread migrates in one of three paths:
66
* NONE -1-> PENDING -2-> READ -3-> ACKED -4-> DONE -5-> NONE
67
* -6-> ACKTO -7-> NONE
68
* NONE -8-> WAIT -9-> NONE
69
*
70
* While in PENDING or READ, the thread is accounted for in the
71
* suspend_acks_pending counter.
72
*
73
* The transitions are invoked as follows:
74
* 1: suspend event is signalled from the core PM code
75
* 2: the suspend event is read from the fd by the userspace thread
76
* 3: userspace thread issues the APM_IOC_SUSPEND ioctl (as ack)
77
* 4: core PM code signals that we have resumed
78
* 5: APM_IOC_SUSPEND ioctl returns
79
*
80
* 6: the notifier invoked from the core PM code timed out waiting
81
* for all relevant threds to enter ACKED state and puts those
82
* that haven't into ACKTO
83
* 7: those threads issue APM_IOC_SUSPEND ioctl too late,
84
* get an error
85
*
86
* 8: userspace thread issues the APM_IOC_SUSPEND ioctl (to suspend),
87
* ioctl code invokes pm_suspend()
88
* 9: pm_suspend() returns indicating resume
89
*/
90
enum apm_suspend_state {
91
SUSPEND_NONE,
92
SUSPEND_PENDING,
93
SUSPEND_READ,
94
SUSPEND_ACKED,
95
SUSPEND_ACKTO,
96
SUSPEND_WAIT,
97
SUSPEND_DONE,
98
};
99
100
/*
101
* The per-file APM data
102
*/
103
struct apm_user {
104
struct list_head list;
105
106
unsigned int suser: 1;
107
unsigned int writer: 1;
108
unsigned int reader: 1;
109
110
int suspend_result;
111
enum apm_suspend_state suspend_state;
112
113
struct apm_queue queue;
114
};
115
116
/*
117
* Local variables
118
*/
119
static atomic_t suspend_acks_pending = ATOMIC_INIT(0);
120
static atomic_t userspace_notification_inhibit = ATOMIC_INIT(0);
121
static int apm_disabled;
122
static struct task_struct *kapmd_tsk;
123
124
static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
125
static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
126
127
/*
128
* This is a list of everyone who has opened /dev/apm_bios
129
*/
130
static DECLARE_RWSEM(user_list_lock);
131
static LIST_HEAD(apm_user_list);
132
133
/*
134
* kapmd info. kapmd provides us a process context to handle
135
* "APM" events within - specifically necessary if we're going
136
* to be suspending the system.
137
*/
138
static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
139
static DEFINE_SPINLOCK(kapmd_queue_lock);
140
static struct apm_queue kapmd_queue;
141
142
static DEFINE_MUTEX(state_lock);
143
144
145
/*
146
* Compatibility cruft until the IPAQ people move over to the new
147
* interface.
148
*/
149
static void __apm_get_power_status(struct apm_power_info *info)
150
{
151
}
152
153
/*
154
* This allows machines to provide their own "apm get power status" function.
155
*/
156
void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
157
EXPORT_SYMBOL(apm_get_power_status);
158
159
160
/*
161
* APM event queue management.
162
*/
163
static inline int queue_empty(struct apm_queue *q)
164
{
165
return q->event_head == q->event_tail;
166
}
167
168
static inline apm_event_t queue_get_event(struct apm_queue *q)
169
{
170
q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
171
return q->events[q->event_tail];
172
}
173
174
static void queue_add_event(struct apm_queue *q, apm_event_t event)
175
{
176
q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
177
if (q->event_head == q->event_tail) {
178
static int notified;
179
180
if (notified++ == 0)
181
printk(KERN_ERR "apm: an event queue overflowed\n");
182
q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
183
}
184
q->events[q->event_head] = event;
185
}
186
187
static void queue_event(apm_event_t event)
188
{
189
struct apm_user *as;
190
191
down_read(&user_list_lock);
192
list_for_each_entry(as, &apm_user_list, list) {
193
if (as->reader)
194
queue_add_event(&as->queue, event);
195
}
196
up_read(&user_list_lock);
197
wake_up_interruptible(&apm_waitqueue);
198
}
199
200
static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
201
{
202
struct apm_user *as = fp->private_data;
203
apm_event_t event;
204
int i = count, ret = 0;
205
206
if (count < sizeof(apm_event_t))
207
return -EINVAL;
208
209
if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
210
return -EAGAIN;
211
212
wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
213
214
while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
215
event = queue_get_event(&as->queue);
216
217
ret = -EFAULT;
218
if (copy_to_user(buf, &event, sizeof(event)))
219
break;
220
221
mutex_lock(&state_lock);
222
if (as->suspend_state == SUSPEND_PENDING &&
223
(event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
224
as->suspend_state = SUSPEND_READ;
225
mutex_unlock(&state_lock);
226
227
buf += sizeof(event);
228
i -= sizeof(event);
229
}
230
231
if (i < count)
232
ret = count - i;
233
234
return ret;
235
}
236
237
static __poll_t apm_poll(struct file *fp, poll_table * wait)
238
{
239
struct apm_user *as = fp->private_data;
240
241
poll_wait(fp, &apm_waitqueue, wait);
242
return queue_empty(&as->queue) ? 0 : EPOLLIN | EPOLLRDNORM;
243
}
244
245
/*
246
* apm_ioctl - handle APM ioctl
247
*
248
* APM_IOC_SUSPEND
249
* This IOCTL is overloaded, and performs two functions. It is used to:
250
* - initiate a suspend
251
* - acknowledge a suspend read from /dev/apm_bios.
252
* Only when everyone who has opened /dev/apm_bios with write permission
253
* has acknowledge does the actual suspend happen.
254
*/
255
static long
256
apm_ioctl(struct file *filp, u_int cmd, u_long arg)
257
{
258
struct apm_user *as = filp->private_data;
259
int err = -EINVAL;
260
261
if (!as->suser || !as->writer)
262
return -EPERM;
263
264
switch (cmd) {
265
case APM_IOC_SUSPEND:
266
mutex_lock(&state_lock);
267
268
as->suspend_result = -EINTR;
269
270
switch (as->suspend_state) {
271
case SUSPEND_READ:
272
/*
273
* If we read a suspend command from /dev/apm_bios,
274
* then the corresponding APM_IOC_SUSPEND ioctl is
275
* interpreted as an acknowledge.
276
*/
277
as->suspend_state = SUSPEND_ACKED;
278
atomic_dec(&suspend_acks_pending);
279
mutex_unlock(&state_lock);
280
281
/*
282
* suspend_acks_pending changed, the notifier needs to
283
* be woken up for this
284
*/
285
wake_up(&apm_suspend_waitqueue);
286
287
/*
288
* Wait for the suspend/resume to complete. If there
289
* are pending acknowledges, we wait here for them.
290
* wait_event_freezable() is interruptible and pending
291
* signal can cause busy looping. We aren't doing
292
* anything critical, chill a bit on each iteration.
293
*/
294
while (wait_event_freezable(apm_suspend_waitqueue,
295
as->suspend_state != SUSPEND_ACKED))
296
msleep(10);
297
break;
298
case SUSPEND_ACKTO:
299
as->suspend_result = -ETIMEDOUT;
300
mutex_unlock(&state_lock);
301
break;
302
default:
303
as->suspend_state = SUSPEND_WAIT;
304
mutex_unlock(&state_lock);
305
306
/*
307
* Otherwise it is a request to suspend the system.
308
* Just invoke pm_suspend(), we'll handle it from
309
* there via the notifier.
310
*/
311
as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
312
}
313
314
mutex_lock(&state_lock);
315
err = as->suspend_result;
316
as->suspend_state = SUSPEND_NONE;
317
mutex_unlock(&state_lock);
318
break;
319
}
320
321
return err;
322
}
323
324
static int apm_release(struct inode * inode, struct file * filp)
325
{
326
struct apm_user *as = filp->private_data;
327
328
filp->private_data = NULL;
329
330
down_write(&user_list_lock);
331
list_del(&as->list);
332
up_write(&user_list_lock);
333
334
/*
335
* We are now unhooked from the chain. As far as new
336
* events are concerned, we no longer exist.
337
*/
338
mutex_lock(&state_lock);
339
if (as->suspend_state == SUSPEND_PENDING ||
340
as->suspend_state == SUSPEND_READ)
341
atomic_dec(&suspend_acks_pending);
342
mutex_unlock(&state_lock);
343
344
wake_up(&apm_suspend_waitqueue);
345
346
kfree(as);
347
return 0;
348
}
349
350
static int apm_open(struct inode * inode, struct file * filp)
351
{
352
struct apm_user *as;
353
354
as = kzalloc(sizeof(*as), GFP_KERNEL);
355
if (as) {
356
/*
357
* XXX - this is a tiny bit broken, when we consider BSD
358
* process accounting. If the device is opened by root, we
359
* instantly flag that we used superuser privs. Who knows,
360
* we might close the device immediately without doing a
361
* privileged operation -- cevans
362
*/
363
as->suser = capable(CAP_SYS_ADMIN);
364
as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
365
as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
366
367
down_write(&user_list_lock);
368
list_add(&as->list, &apm_user_list);
369
up_write(&user_list_lock);
370
371
filp->private_data = as;
372
}
373
374
return as ? 0 : -ENOMEM;
375
}
376
377
static const struct file_operations apm_bios_fops = {
378
.owner = THIS_MODULE,
379
.read = apm_read,
380
.poll = apm_poll,
381
.unlocked_ioctl = apm_ioctl,
382
.open = apm_open,
383
.release = apm_release,
384
.llseek = noop_llseek,
385
};
386
387
static struct miscdevice apm_device = {
388
.minor = APM_MINOR_DEV,
389
.name = "apm_bios",
390
.fops = &apm_bios_fops
391
};
392
393
394
#ifdef CONFIG_PROC_FS
395
/*
396
* Arguments, with symbols from linux/apm_bios.h.
397
*
398
* 0) Linux driver version (this will change if format changes)
399
* 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
400
* 2) APM flags from APM Installation Check (0x00):
401
* bit 0: APM_16_BIT_SUPPORT
402
* bit 1: APM_32_BIT_SUPPORT
403
* bit 2: APM_IDLE_SLOWS_CLOCK
404
* bit 3: APM_BIOS_DISABLED
405
* bit 4: APM_BIOS_DISENGAGED
406
* 3) AC line status
407
* 0x00: Off-line
408
* 0x01: On-line
409
* 0x02: On backup power (BIOS >= 1.1 only)
410
* 0xff: Unknown
411
* 4) Battery status
412
* 0x00: High
413
* 0x01: Low
414
* 0x02: Critical
415
* 0x03: Charging
416
* 0x04: Selected battery not present (BIOS >= 1.2 only)
417
* 0xff: Unknown
418
* 5) Battery flag
419
* bit 0: High
420
* bit 1: Low
421
* bit 2: Critical
422
* bit 3: Charging
423
* bit 7: No system battery
424
* 0xff: Unknown
425
* 6) Remaining battery life (percentage of charge):
426
* 0-100: valid
427
* -1: Unknown
428
* 7) Remaining battery life (time units):
429
* Number of remaining minutes or seconds
430
* -1: Unknown
431
* 8) min = minutes; sec = seconds
432
*/
433
static int proc_apm_show(struct seq_file *m, void *v)
434
{
435
static const char driver_version[] = "1.13"; /* no spaces */
436
437
struct apm_power_info info;
438
char *units;
439
440
info.ac_line_status = 0xff;
441
info.battery_status = 0xff;
442
info.battery_flag = 0xff;
443
info.battery_life = -1;
444
info.time = -1;
445
info.units = -1;
446
447
if (apm_get_power_status)
448
apm_get_power_status(&info);
449
450
switch (info.units) {
451
default: units = "?"; break;
452
case 0: units = "min"; break;
453
case 1: units = "sec"; break;
454
}
455
456
seq_printf(m, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
457
driver_version, APM_32_BIT_SUPPORT,
458
info.ac_line_status, info.battery_status,
459
info.battery_flag, info.battery_life,
460
info.time, units);
461
462
return 0;
463
}
464
#endif
465
466
static int kapmd(void *arg)
467
{
468
do {
469
apm_event_t event;
470
471
wait_event_interruptible(kapmd_wait,
472
!queue_empty(&kapmd_queue) || kthread_should_stop());
473
474
if (kthread_should_stop())
475
break;
476
477
spin_lock_irq(&kapmd_queue_lock);
478
event = 0;
479
if (!queue_empty(&kapmd_queue))
480
event = queue_get_event(&kapmd_queue);
481
spin_unlock_irq(&kapmd_queue_lock);
482
483
switch (event) {
484
case 0:
485
break;
486
487
case APM_LOW_BATTERY:
488
case APM_POWER_STATUS_CHANGE:
489
queue_event(event);
490
break;
491
492
case APM_USER_SUSPEND:
493
case APM_SYS_SUSPEND:
494
pm_suspend(PM_SUSPEND_MEM);
495
break;
496
497
case APM_CRITICAL_SUSPEND:
498
atomic_inc(&userspace_notification_inhibit);
499
pm_suspend(PM_SUSPEND_MEM);
500
atomic_dec(&userspace_notification_inhibit);
501
break;
502
}
503
} while (1);
504
505
return 0;
506
}
507
508
static int apm_suspend_notifier(struct notifier_block *nb,
509
unsigned long event,
510
void *dummy)
511
{
512
struct apm_user *as;
513
int err;
514
unsigned long apm_event;
515
516
/* short-cut emergency suspends */
517
if (atomic_read(&userspace_notification_inhibit))
518
return NOTIFY_DONE;
519
520
switch (event) {
521
case PM_SUSPEND_PREPARE:
522
case PM_HIBERNATION_PREPARE:
523
apm_event = (event == PM_SUSPEND_PREPARE) ?
524
APM_USER_SUSPEND : APM_USER_HIBERNATION;
525
/*
526
* Queue an event to all "writer" users that we want
527
* to suspend and need their ack.
528
*/
529
mutex_lock(&state_lock);
530
down_read(&user_list_lock);
531
532
list_for_each_entry(as, &apm_user_list, list) {
533
if (as->suspend_state != SUSPEND_WAIT && as->reader &&
534
as->writer && as->suser) {
535
as->suspend_state = SUSPEND_PENDING;
536
atomic_inc(&suspend_acks_pending);
537
queue_add_event(&as->queue, apm_event);
538
}
539
}
540
541
up_read(&user_list_lock);
542
mutex_unlock(&state_lock);
543
wake_up_interruptible(&apm_waitqueue);
544
545
/*
546
* Wait for the suspend_acks_pending variable to drop to
547
* zero, meaning everybody acked the suspend event (or the
548
* process was killed.)
549
*
550
* If the app won't answer within a short while we assume it
551
* locked up and ignore it.
552
*/
553
err = wait_event_interruptible_timeout(
554
apm_suspend_waitqueue,
555
atomic_read(&suspend_acks_pending) == 0,
556
5*HZ);
557
558
/* timed out */
559
if (err == 0) {
560
/*
561
* Move anybody who timed out to "ack timeout" state.
562
*
563
* We could time out and the userspace does the ACK
564
* right after we time out but before we enter the
565
* locked section here, but that's fine.
566
*/
567
mutex_lock(&state_lock);
568
down_read(&user_list_lock);
569
list_for_each_entry(as, &apm_user_list, list) {
570
if (as->suspend_state == SUSPEND_PENDING ||
571
as->suspend_state == SUSPEND_READ) {
572
as->suspend_state = SUSPEND_ACKTO;
573
atomic_dec(&suspend_acks_pending);
574
}
575
}
576
up_read(&user_list_lock);
577
mutex_unlock(&state_lock);
578
}
579
580
/* let suspend proceed */
581
if (err >= 0)
582
return NOTIFY_OK;
583
584
/* interrupted by signal */
585
return notifier_from_errno(err);
586
587
case PM_POST_SUSPEND:
588
case PM_POST_HIBERNATION:
589
apm_event = (event == PM_POST_SUSPEND) ?
590
APM_NORMAL_RESUME : APM_HIBERNATION_RESUME;
591
/*
592
* Anyone on the APM queues will think we're still suspended.
593
* Send a message so everyone knows we're now awake again.
594
*/
595
queue_event(apm_event);
596
597
/*
598
* Finally, wake up anyone who is sleeping on the suspend.
599
*/
600
mutex_lock(&state_lock);
601
down_read(&user_list_lock);
602
list_for_each_entry(as, &apm_user_list, list) {
603
if (as->suspend_state == SUSPEND_ACKED) {
604
/*
605
* TODO: maybe grab error code, needs core
606
* changes to push the error to the notifier
607
* chain (could use the second parameter if
608
* implemented)
609
*/
610
as->suspend_result = 0;
611
as->suspend_state = SUSPEND_DONE;
612
}
613
}
614
up_read(&user_list_lock);
615
mutex_unlock(&state_lock);
616
617
wake_up(&apm_suspend_waitqueue);
618
return NOTIFY_OK;
619
620
default:
621
return NOTIFY_DONE;
622
}
623
}
624
625
static struct notifier_block apm_notif_block = {
626
.notifier_call = apm_suspend_notifier,
627
};
628
629
static int __init apm_init(void)
630
{
631
int ret;
632
633
if (apm_disabled) {
634
printk(KERN_NOTICE "apm: disabled on user request.\n");
635
return -ENODEV;
636
}
637
638
kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
639
if (IS_ERR(kapmd_tsk)) {
640
ret = PTR_ERR(kapmd_tsk);
641
kapmd_tsk = NULL;
642
goto out;
643
}
644
wake_up_process(kapmd_tsk);
645
646
#ifdef CONFIG_PROC_FS
647
proc_create_single("apm", 0, NULL, proc_apm_show);
648
#endif
649
650
ret = misc_register(&apm_device);
651
if (ret)
652
goto out_stop;
653
654
ret = register_pm_notifier(&apm_notif_block);
655
if (ret)
656
goto out_unregister;
657
658
return 0;
659
660
out_unregister:
661
misc_deregister(&apm_device);
662
out_stop:
663
remove_proc_entry("apm", NULL);
664
kthread_stop(kapmd_tsk);
665
out:
666
return ret;
667
}
668
669
static void __exit apm_exit(void)
670
{
671
unregister_pm_notifier(&apm_notif_block);
672
misc_deregister(&apm_device);
673
remove_proc_entry("apm", NULL);
674
675
kthread_stop(kapmd_tsk);
676
}
677
678
module_init(apm_init);
679
module_exit(apm_exit);
680
681
MODULE_AUTHOR("Stephen Rothwell");
682
MODULE_DESCRIPTION("Advanced Power Management");
683
MODULE_LICENSE("GPL");
684
685
#ifndef MODULE
686
static int __init apm_setup(char *str)
687
{
688
while ((str != NULL) && (*str != '\0')) {
689
if (strncmp(str, "off", 3) == 0)
690
apm_disabled = 1;
691
if (strncmp(str, "on", 2) == 0)
692
apm_disabled = 0;
693
str = strchr(str, ',');
694
if (str != NULL)
695
str += strspn(str, ", \t");
696
}
697
return 1;
698
}
699
700
__setup("apm=", apm_setup);
701
#endif
702
703
/**
704
* apm_queue_event - queue an APM event for kapmd
705
* @event: APM event
706
*
707
* Queue an APM event for kapmd to process and ultimately take the
708
* appropriate action. Only a subset of events are handled:
709
* %APM_LOW_BATTERY
710
* %APM_POWER_STATUS_CHANGE
711
* %APM_USER_SUSPEND
712
* %APM_SYS_SUSPEND
713
* %APM_CRITICAL_SUSPEND
714
*/
715
void apm_queue_event(apm_event_t event)
716
{
717
unsigned long flags;
718
719
spin_lock_irqsave(&kapmd_queue_lock, flags);
720
queue_add_event(&kapmd_queue, event);
721
spin_unlock_irqrestore(&kapmd_queue_lock, flags);
722
723
wake_up_interruptible(&kapmd_wait);
724
}
725
EXPORT_SYMBOL(apm_queue_event);
726
727