Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/char/apm-emulation.c
49751 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
* This allows machines to provide their own "apm get power status" function.
147
*/
148
void (*apm_get_power_status)(struct apm_power_info *);
149
EXPORT_SYMBOL(apm_get_power_status);
150
151
152
/*
153
* APM event queue management.
154
*/
155
static inline int queue_empty(struct apm_queue *q)
156
{
157
return q->event_head == q->event_tail;
158
}
159
160
static inline apm_event_t queue_get_event(struct apm_queue *q)
161
{
162
q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
163
return q->events[q->event_tail];
164
}
165
166
static void queue_add_event(struct apm_queue *q, apm_event_t event)
167
{
168
q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
169
if (q->event_head == q->event_tail) {
170
static int notified;
171
172
if (notified++ == 0)
173
printk(KERN_ERR "apm: an event queue overflowed\n");
174
q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
175
}
176
q->events[q->event_head] = event;
177
}
178
179
static void queue_event(apm_event_t event)
180
{
181
struct apm_user *as;
182
183
down_read(&user_list_lock);
184
list_for_each_entry(as, &apm_user_list, list) {
185
if (as->reader)
186
queue_add_event(&as->queue, event);
187
}
188
up_read(&user_list_lock);
189
wake_up_interruptible(&apm_waitqueue);
190
}
191
192
static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
193
{
194
struct apm_user *as = fp->private_data;
195
apm_event_t event;
196
int i = count, ret = 0;
197
198
if (count < sizeof(apm_event_t))
199
return -EINVAL;
200
201
if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
202
return -EAGAIN;
203
204
wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
205
206
while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
207
event = queue_get_event(&as->queue);
208
209
ret = -EFAULT;
210
if (copy_to_user(buf, &event, sizeof(event)))
211
break;
212
213
mutex_lock(&state_lock);
214
if (as->suspend_state == SUSPEND_PENDING &&
215
(event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
216
as->suspend_state = SUSPEND_READ;
217
mutex_unlock(&state_lock);
218
219
buf += sizeof(event);
220
i -= sizeof(event);
221
}
222
223
if (i < count)
224
ret = count - i;
225
226
return ret;
227
}
228
229
static __poll_t apm_poll(struct file *fp, poll_table * wait)
230
{
231
struct apm_user *as = fp->private_data;
232
233
poll_wait(fp, &apm_waitqueue, wait);
234
return queue_empty(&as->queue) ? 0 : EPOLLIN | EPOLLRDNORM;
235
}
236
237
/*
238
* apm_ioctl - handle APM ioctl
239
*
240
* APM_IOC_SUSPEND
241
* This IOCTL is overloaded, and performs two functions. It is used to:
242
* - initiate a suspend
243
* - acknowledge a suspend read from /dev/apm_bios.
244
* Only when everyone who has opened /dev/apm_bios with write permission
245
* has acknowledge does the actual suspend happen.
246
*/
247
static long
248
apm_ioctl(struct file *filp, u_int cmd, u_long arg)
249
{
250
struct apm_user *as = filp->private_data;
251
int err = -EINVAL;
252
253
if (!as->suser || !as->writer)
254
return -EPERM;
255
256
switch (cmd) {
257
case APM_IOC_SUSPEND:
258
mutex_lock(&state_lock);
259
260
as->suspend_result = -EINTR;
261
262
switch (as->suspend_state) {
263
case SUSPEND_READ:
264
/*
265
* If we read a suspend command from /dev/apm_bios,
266
* then the corresponding APM_IOC_SUSPEND ioctl is
267
* interpreted as an acknowledge.
268
*/
269
as->suspend_state = SUSPEND_ACKED;
270
atomic_dec(&suspend_acks_pending);
271
mutex_unlock(&state_lock);
272
273
/*
274
* suspend_acks_pending changed, the notifier needs to
275
* be woken up for this
276
*/
277
wake_up(&apm_suspend_waitqueue);
278
279
/*
280
* Wait for the suspend/resume to complete. If there
281
* are pending acknowledges, we wait here for them.
282
* wait_event_freezable() is interruptible and pending
283
* signal can cause busy looping. We aren't doing
284
* anything critical, chill a bit on each iteration.
285
*/
286
while (wait_event_freezable(apm_suspend_waitqueue,
287
as->suspend_state != SUSPEND_ACKED))
288
msleep(10);
289
break;
290
case SUSPEND_ACKTO:
291
as->suspend_result = -ETIMEDOUT;
292
mutex_unlock(&state_lock);
293
break;
294
default:
295
as->suspend_state = SUSPEND_WAIT;
296
mutex_unlock(&state_lock);
297
298
/*
299
* Otherwise it is a request to suspend the system.
300
* Just invoke pm_suspend(), we'll handle it from
301
* there via the notifier.
302
*/
303
as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
304
}
305
306
mutex_lock(&state_lock);
307
err = as->suspend_result;
308
as->suspend_state = SUSPEND_NONE;
309
mutex_unlock(&state_lock);
310
break;
311
}
312
313
return err;
314
}
315
316
static int apm_release(struct inode * inode, struct file * filp)
317
{
318
struct apm_user *as = filp->private_data;
319
320
filp->private_data = NULL;
321
322
down_write(&user_list_lock);
323
list_del(&as->list);
324
up_write(&user_list_lock);
325
326
/*
327
* We are now unhooked from the chain. As far as new
328
* events are concerned, we no longer exist.
329
*/
330
mutex_lock(&state_lock);
331
if (as->suspend_state == SUSPEND_PENDING ||
332
as->suspend_state == SUSPEND_READ)
333
atomic_dec(&suspend_acks_pending);
334
mutex_unlock(&state_lock);
335
336
wake_up(&apm_suspend_waitqueue);
337
338
kfree(as);
339
return 0;
340
}
341
342
static int apm_open(struct inode * inode, struct file * filp)
343
{
344
struct apm_user *as;
345
346
as = kzalloc(sizeof(*as), GFP_KERNEL);
347
if (as) {
348
/*
349
* XXX - this is a tiny bit broken, when we consider BSD
350
* process accounting. If the device is opened by root, we
351
* instantly flag that we used superuser privs. Who knows,
352
* we might close the device immediately without doing a
353
* privileged operation -- cevans
354
*/
355
as->suser = capable(CAP_SYS_ADMIN);
356
as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
357
as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
358
359
down_write(&user_list_lock);
360
list_add(&as->list, &apm_user_list);
361
up_write(&user_list_lock);
362
363
filp->private_data = as;
364
}
365
366
return as ? 0 : -ENOMEM;
367
}
368
369
static const struct file_operations apm_bios_fops = {
370
.owner = THIS_MODULE,
371
.read = apm_read,
372
.poll = apm_poll,
373
.unlocked_ioctl = apm_ioctl,
374
.open = apm_open,
375
.release = apm_release,
376
.llseek = noop_llseek,
377
};
378
379
static struct miscdevice apm_device = {
380
.minor = APM_MINOR_DEV,
381
.name = "apm_bios",
382
.fops = &apm_bios_fops
383
};
384
385
386
#ifdef CONFIG_PROC_FS
387
/*
388
* Arguments, with symbols from linux/apm_bios.h.
389
*
390
* 0) Linux driver version (this will change if format changes)
391
* 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
392
* 2) APM flags from APM Installation Check (0x00):
393
* bit 0: APM_16_BIT_SUPPORT
394
* bit 1: APM_32_BIT_SUPPORT
395
* bit 2: APM_IDLE_SLOWS_CLOCK
396
* bit 3: APM_BIOS_DISABLED
397
* bit 4: APM_BIOS_DISENGAGED
398
* 3) AC line status
399
* 0x00: Off-line
400
* 0x01: On-line
401
* 0x02: On backup power (BIOS >= 1.1 only)
402
* 0xff: Unknown
403
* 4) Battery status
404
* 0x00: High
405
* 0x01: Low
406
* 0x02: Critical
407
* 0x03: Charging
408
* 0x04: Selected battery not present (BIOS >= 1.2 only)
409
* 0xff: Unknown
410
* 5) Battery flag
411
* bit 0: High
412
* bit 1: Low
413
* bit 2: Critical
414
* bit 3: Charging
415
* bit 7: No system battery
416
* 0xff: Unknown
417
* 6) Remaining battery life (percentage of charge):
418
* 0-100: valid
419
* -1: Unknown
420
* 7) Remaining battery life (time units):
421
* Number of remaining minutes or seconds
422
* -1: Unknown
423
* 8) min = minutes; sec = seconds
424
*/
425
static int proc_apm_show(struct seq_file *m, void *v)
426
{
427
static const char driver_version[] = "1.13"; /* no spaces */
428
429
struct apm_power_info info;
430
char *units;
431
432
info.ac_line_status = 0xff;
433
info.battery_status = 0xff;
434
info.battery_flag = 0xff;
435
info.battery_life = -1;
436
info.time = -1;
437
info.units = -1;
438
439
if (apm_get_power_status)
440
apm_get_power_status(&info);
441
442
switch (info.units) {
443
default: units = "?"; break;
444
case 0: units = "min"; break;
445
case 1: units = "sec"; break;
446
}
447
448
seq_printf(m, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
449
driver_version, APM_32_BIT_SUPPORT,
450
info.ac_line_status, info.battery_status,
451
info.battery_flag, info.battery_life,
452
info.time, units);
453
454
return 0;
455
}
456
#endif
457
458
static int kapmd(void *arg)
459
{
460
do {
461
apm_event_t event;
462
463
wait_event_interruptible(kapmd_wait,
464
!queue_empty(&kapmd_queue) || kthread_should_stop());
465
466
if (kthread_should_stop())
467
break;
468
469
spin_lock_irq(&kapmd_queue_lock);
470
event = 0;
471
if (!queue_empty(&kapmd_queue))
472
event = queue_get_event(&kapmd_queue);
473
spin_unlock_irq(&kapmd_queue_lock);
474
475
switch (event) {
476
case 0:
477
break;
478
479
case APM_LOW_BATTERY:
480
case APM_POWER_STATUS_CHANGE:
481
queue_event(event);
482
break;
483
484
case APM_USER_SUSPEND:
485
case APM_SYS_SUSPEND:
486
pm_suspend(PM_SUSPEND_MEM);
487
break;
488
489
case APM_CRITICAL_SUSPEND:
490
atomic_inc(&userspace_notification_inhibit);
491
pm_suspend(PM_SUSPEND_MEM);
492
atomic_dec(&userspace_notification_inhibit);
493
break;
494
}
495
} while (1);
496
497
return 0;
498
}
499
500
static int apm_suspend_notifier(struct notifier_block *nb,
501
unsigned long event,
502
void *dummy)
503
{
504
struct apm_user *as;
505
int err;
506
unsigned long apm_event;
507
508
/* short-cut emergency suspends */
509
if (atomic_read(&userspace_notification_inhibit))
510
return NOTIFY_DONE;
511
512
switch (event) {
513
case PM_SUSPEND_PREPARE:
514
case PM_HIBERNATION_PREPARE:
515
apm_event = (event == PM_SUSPEND_PREPARE) ?
516
APM_USER_SUSPEND : APM_USER_HIBERNATION;
517
/*
518
* Queue an event to all "writer" users that we want
519
* to suspend and need their ack.
520
*/
521
mutex_lock(&state_lock);
522
down_read(&user_list_lock);
523
524
list_for_each_entry(as, &apm_user_list, list) {
525
if (as->suspend_state != SUSPEND_WAIT && as->reader &&
526
as->writer && as->suser) {
527
as->suspend_state = SUSPEND_PENDING;
528
atomic_inc(&suspend_acks_pending);
529
queue_add_event(&as->queue, apm_event);
530
}
531
}
532
533
up_read(&user_list_lock);
534
mutex_unlock(&state_lock);
535
wake_up_interruptible(&apm_waitqueue);
536
537
/*
538
* Wait for the suspend_acks_pending variable to drop to
539
* zero, meaning everybody acked the suspend event (or the
540
* process was killed.)
541
*
542
* If the app won't answer within a short while we assume it
543
* locked up and ignore it.
544
*/
545
err = wait_event_interruptible_timeout(
546
apm_suspend_waitqueue,
547
atomic_read(&suspend_acks_pending) == 0,
548
5*HZ);
549
550
/* timed out */
551
if (err == 0) {
552
/*
553
* Move anybody who timed out to "ack timeout" state.
554
*
555
* We could time out and the userspace does the ACK
556
* right after we time out but before we enter the
557
* locked section here, but that's fine.
558
*/
559
mutex_lock(&state_lock);
560
down_read(&user_list_lock);
561
list_for_each_entry(as, &apm_user_list, list) {
562
if (as->suspend_state == SUSPEND_PENDING ||
563
as->suspend_state == SUSPEND_READ) {
564
as->suspend_state = SUSPEND_ACKTO;
565
atomic_dec(&suspend_acks_pending);
566
}
567
}
568
up_read(&user_list_lock);
569
mutex_unlock(&state_lock);
570
}
571
572
/* let suspend proceed */
573
if (err >= 0)
574
return NOTIFY_OK;
575
576
/* interrupted by signal */
577
return notifier_from_errno(err);
578
579
case PM_POST_SUSPEND:
580
case PM_POST_HIBERNATION:
581
apm_event = (event == PM_POST_SUSPEND) ?
582
APM_NORMAL_RESUME : APM_HIBERNATION_RESUME;
583
/*
584
* Anyone on the APM queues will think we're still suspended.
585
* Send a message so everyone knows we're now awake again.
586
*/
587
queue_event(apm_event);
588
589
/*
590
* Finally, wake up anyone who is sleeping on the suspend.
591
*/
592
mutex_lock(&state_lock);
593
down_read(&user_list_lock);
594
list_for_each_entry(as, &apm_user_list, list) {
595
if (as->suspend_state == SUSPEND_ACKED) {
596
/*
597
* TODO: maybe grab error code, needs core
598
* changes to push the error to the notifier
599
* chain (could use the second parameter if
600
* implemented)
601
*/
602
as->suspend_result = 0;
603
as->suspend_state = SUSPEND_DONE;
604
}
605
}
606
up_read(&user_list_lock);
607
mutex_unlock(&state_lock);
608
609
wake_up(&apm_suspend_waitqueue);
610
return NOTIFY_OK;
611
612
default:
613
return NOTIFY_DONE;
614
}
615
}
616
617
static struct notifier_block apm_notif_block = {
618
.notifier_call = apm_suspend_notifier,
619
};
620
621
static int __init apm_init(void)
622
{
623
int ret;
624
625
if (apm_disabled) {
626
printk(KERN_NOTICE "apm: disabled on user request.\n");
627
return -ENODEV;
628
}
629
630
kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
631
if (IS_ERR(kapmd_tsk)) {
632
ret = PTR_ERR(kapmd_tsk);
633
kapmd_tsk = NULL;
634
goto out;
635
}
636
wake_up_process(kapmd_tsk);
637
638
#ifdef CONFIG_PROC_FS
639
proc_create_single("apm", 0, NULL, proc_apm_show);
640
#endif
641
642
ret = misc_register(&apm_device);
643
if (ret)
644
goto out_stop;
645
646
ret = register_pm_notifier(&apm_notif_block);
647
if (ret)
648
goto out_unregister;
649
650
return 0;
651
652
out_unregister:
653
misc_deregister(&apm_device);
654
out_stop:
655
remove_proc_entry("apm", NULL);
656
kthread_stop(kapmd_tsk);
657
out:
658
return ret;
659
}
660
661
static void __exit apm_exit(void)
662
{
663
unregister_pm_notifier(&apm_notif_block);
664
misc_deregister(&apm_device);
665
remove_proc_entry("apm", NULL);
666
667
kthread_stop(kapmd_tsk);
668
}
669
670
module_init(apm_init);
671
module_exit(apm_exit);
672
673
MODULE_AUTHOR("Stephen Rothwell");
674
MODULE_DESCRIPTION("Advanced Power Management");
675
MODULE_LICENSE("GPL");
676
677
#ifndef MODULE
678
static int __init apm_setup(char *str)
679
{
680
while ((str != NULL) && (*str != '\0')) {
681
if (strncmp(str, "off", 3) == 0)
682
apm_disabled = 1;
683
if (strncmp(str, "on", 2) == 0)
684
apm_disabled = 0;
685
str = strchr(str, ',');
686
if (str != NULL)
687
str += strspn(str, ", \t");
688
}
689
return 1;
690
}
691
692
__setup("apm=", apm_setup);
693
#endif
694
695
/**
696
* apm_queue_event - queue an APM event for kapmd
697
* @event: APM event
698
*
699
* Queue an APM event for kapmd to process and ultimately take the
700
* appropriate action. Only a subset of events are handled:
701
* %APM_LOW_BATTERY
702
* %APM_POWER_STATUS_CHANGE
703
* %APM_USER_SUSPEND
704
* %APM_SYS_SUSPEND
705
* %APM_CRITICAL_SUSPEND
706
*/
707
void apm_queue_event(apm_event_t event)
708
{
709
unsigned long flags;
710
711
spin_lock_irqsave(&kapmd_queue_lock, flags);
712
queue_add_event(&kapmd_queue, event);
713
spin_unlock_irqrestore(&kapmd_queue_lock, flags);
714
715
wake_up_interruptible(&kapmd_wait);
716
}
717
EXPORT_SYMBOL(apm_queue_event);
718
719