OpenCores
URL https://opencores.org/ocsvn/test_project/test_project/trunk

Subversion Repositories test_project

[/] [test_project/] [trunk/] [linux_sd_driver/] [drivers/] [char/] [apm-emulation.c] - Blame information for rev 62

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 62 marcus.erl
/*
2
 * bios-less APM driver for ARM Linux
3
 *  Jamey Hicks <jamey@crl.dec.com>
4
 *  adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com)
5
 *
6
 * APM 1.2 Reference:
7
 *   Intel Corporation, Microsoft Corporation. Advanced Power Management
8
 *   (APM) BIOS Interface Specification, Revision 1.2, February 1996.
9
 *
10
 * [This document is available from Microsoft at:
11
 *    http://www.microsoft.com/hwdev/busbios/amp_12.htm]
12
 */
13
#include <linux/module.h>
14
#include <linux/poll.h>
15
#include <linux/slab.h>
16
#include <linux/proc_fs.h>
17
#include <linux/miscdevice.h>
18
#include <linux/apm_bios.h>
19
#include <linux/capability.h>
20
#include <linux/sched.h>
21
#include <linux/suspend.h>
22
#include <linux/apm-emulation.h>
23
#include <linux/freezer.h>
24
#include <linux/device.h>
25
#include <linux/kernel.h>
26
#include <linux/list.h>
27
#include <linux/init.h>
28
#include <linux/completion.h>
29
#include <linux/kthread.h>
30
#include <linux/delay.h>
31
 
32
#include <asm/system.h>
33
 
34
/*
35
 * The apm_bios device is one of the misc char devices.
36
 * This is its minor number.
37
 */
38
#define APM_MINOR_DEV   134
39
 
40
/*
41
 * See Documentation/Config.help for the configuration options.
42
 *
43
 * Various options can be changed at boot time as follows:
44
 * (We allow underscores for compatibility with the modules code)
45
 *      apm=on/off                      enable/disable APM
46
 */
47
 
48
/*
49
 * Maximum number of events stored
50
 */
51
#define APM_MAX_EVENTS          16
52
 
53
struct apm_queue {
54
        unsigned int            event_head;
55
        unsigned int            event_tail;
56
        apm_event_t             events[APM_MAX_EVENTS];
57
};
58
 
59
/*
60
 * The per-file APM data
61
 */
62
struct apm_user {
63
        struct list_head        list;
64
 
65
        unsigned int            suser: 1;
66
        unsigned int            writer: 1;
67
        unsigned int            reader: 1;
68
 
69
        int                     suspend_result;
70
        unsigned int            suspend_state;
71
#define SUSPEND_NONE    0                /* no suspend pending */
72
#define SUSPEND_PENDING 1               /* suspend pending read */
73
#define SUSPEND_READ    2               /* suspend read, pending ack */
74
#define SUSPEND_ACKED   3               /* suspend acked */
75
#define SUSPEND_WAIT    4               /* waiting for suspend */
76
#define SUSPEND_DONE    5               /* suspend completed */
77
 
78
        struct apm_queue        queue;
79
};
80
 
81
/*
82
 * Local variables
83
 */
84
static int suspends_pending;
85
static int apm_disabled;
86
static struct task_struct *kapmd_tsk;
87
 
88
static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
89
static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
90
 
91
/*
92
 * This is a list of everyone who has opened /dev/apm_bios
93
 */
94
static DECLARE_RWSEM(user_list_lock);
95
static LIST_HEAD(apm_user_list);
96
 
97
/*
98
 * kapmd info.  kapmd provides us a process context to handle
99
 * "APM" events within - specifically necessary if we're going
100
 * to be suspending the system.
101
 */
102
static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
103
static DEFINE_SPINLOCK(kapmd_queue_lock);
104
static struct apm_queue kapmd_queue;
105
 
106
static DEFINE_MUTEX(state_lock);
107
 
108
static const char driver_version[] = "1.13";    /* no spaces */
109
 
110
 
111
 
112
/*
113
 * Compatibility cruft until the IPAQ people move over to the new
114
 * interface.
115
 */
116
static void __apm_get_power_status(struct apm_power_info *info)
117
{
118
}
119
 
120
/*
121
 * This allows machines to provide their own "apm get power status" function.
122
 */
123
void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
124
EXPORT_SYMBOL(apm_get_power_status);
125
 
126
 
127
/*
128
 * APM event queue management.
129
 */
130
static inline int queue_empty(struct apm_queue *q)
131
{
132
        return q->event_head == q->event_tail;
133
}
134
 
135
static inline apm_event_t queue_get_event(struct apm_queue *q)
136
{
137
        q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
138
        return q->events[q->event_tail];
139
}
140
 
141
static void queue_add_event(struct apm_queue *q, apm_event_t event)
142
{
143
        q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
144
        if (q->event_head == q->event_tail) {
145
                static int notified;
146
 
147
                if (notified++ == 0)
148
                    printk(KERN_ERR "apm: an event queue overflowed\n");
149
                q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
150
        }
151
        q->events[q->event_head] = event;
152
}
153
 
154
static void queue_event(apm_event_t event)
155
{
156
        struct apm_user *as;
157
 
158
        down_read(&user_list_lock);
159
        list_for_each_entry(as, &apm_user_list, list) {
160
                if (as->reader)
161
                        queue_add_event(&as->queue, event);
162
        }
163
        up_read(&user_list_lock);
164
        wake_up_interruptible(&apm_waitqueue);
165
}
166
 
167
/*
168
 * queue_suspend_event - queue an APM suspend event.
169
 *
170
 * Check that we're in a state where we can suspend.  If not,
171
 * return -EBUSY.  Otherwise, queue an event to all "writer"
172
 * users.  If there are no "writer" users, return '1' to
173
 * indicate that we can immediately suspend.
174
 */
175
static int queue_suspend_event(apm_event_t event, struct apm_user *sender)
176
{
177
        struct apm_user *as;
178
        int ret = 1;
179
 
180
        mutex_lock(&state_lock);
181
        down_read(&user_list_lock);
182
 
183
        /*
184
         * If a thread is still processing, we can't suspend, so reject
185
         * the request.
186
         */
187
        list_for_each_entry(as, &apm_user_list, list) {
188
                if (as != sender && as->reader && as->writer && as->suser &&
189
                    as->suspend_state != SUSPEND_NONE) {
190
                        ret = -EBUSY;
191
                        goto out;
192
                }
193
        }
194
 
195
        list_for_each_entry(as, &apm_user_list, list) {
196
                if (as != sender && as->reader && as->writer && as->suser) {
197
                        as->suspend_state = SUSPEND_PENDING;
198
                        suspends_pending++;
199
                        queue_add_event(&as->queue, event);
200
                        ret = 0;
201
                }
202
        }
203
 out:
204
        up_read(&user_list_lock);
205
        mutex_unlock(&state_lock);
206
        wake_up_interruptible(&apm_waitqueue);
207
        return ret;
208
}
209
 
210
static void apm_suspend(void)
211
{
212
        struct apm_user *as;
213
        int err = pm_suspend(PM_SUSPEND_MEM);
214
 
215
        /*
216
         * Anyone on the APM queues will think we're still suspended.
217
         * Send a message so everyone knows we're now awake again.
218
         */
219
        queue_event(APM_NORMAL_RESUME);
220
 
221
        /*
222
         * Finally, wake up anyone who is sleeping on the suspend.
223
         */
224
        mutex_lock(&state_lock);
225
        down_read(&user_list_lock);
226
        list_for_each_entry(as, &apm_user_list, list) {
227
                if (as->suspend_state == SUSPEND_WAIT ||
228
                    as->suspend_state == SUSPEND_ACKED) {
229
                        as->suspend_result = err;
230
                        as->suspend_state = SUSPEND_DONE;
231
                }
232
        }
233
        up_read(&user_list_lock);
234
        mutex_unlock(&state_lock);
235
 
236
        wake_up(&apm_suspend_waitqueue);
237
}
238
 
239
static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
240
{
241
        struct apm_user *as = fp->private_data;
242
        apm_event_t event;
243
        int i = count, ret = 0;
244
 
245
        if (count < sizeof(apm_event_t))
246
                return -EINVAL;
247
 
248
        if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
249
                return -EAGAIN;
250
 
251
        wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
252
 
253
        while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
254
                event = queue_get_event(&as->queue);
255
 
256
                ret = -EFAULT;
257
                if (copy_to_user(buf, &event, sizeof(event)))
258
                        break;
259
 
260
                mutex_lock(&state_lock);
261
                if (as->suspend_state == SUSPEND_PENDING &&
262
                    (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
263
                        as->suspend_state = SUSPEND_READ;
264
                mutex_unlock(&state_lock);
265
 
266
                buf += sizeof(event);
267
                i -= sizeof(event);
268
        }
269
 
270
        if (i < count)
271
                ret = count - i;
272
 
273
        return ret;
274
}
275
 
276
static unsigned int apm_poll(struct file *fp, poll_table * wait)
277
{
278
        struct apm_user *as = fp->private_data;
279
 
280
        poll_wait(fp, &apm_waitqueue, wait);
281
        return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
282
}
283
 
284
/*
285
 * apm_ioctl - handle APM ioctl
286
 *
287
 * APM_IOC_SUSPEND
288
 *   This IOCTL is overloaded, and performs two functions.  It is used to:
289
 *     - initiate a suspend
290
 *     - acknowledge a suspend read from /dev/apm_bios.
291
 *   Only when everyone who has opened /dev/apm_bios with write permission
292
 *   has acknowledge does the actual suspend happen.
293
 */
294
static int
295
apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
296
{
297
        struct apm_user *as = filp->private_data;
298
        int err = -EINVAL;
299
 
300
        if (!as->suser || !as->writer)
301
                return -EPERM;
302
 
303
        switch (cmd) {
304
        case APM_IOC_SUSPEND:
305
                mutex_lock(&state_lock);
306
 
307
                as->suspend_result = -EINTR;
308
 
309
                if (as->suspend_state == SUSPEND_READ) {
310
                        int pending;
311
 
312
                        /*
313
                         * If we read a suspend command from /dev/apm_bios,
314
                         * then the corresponding APM_IOC_SUSPEND ioctl is
315
                         * interpreted as an acknowledge.
316
                         */
317
                        as->suspend_state = SUSPEND_ACKED;
318
                        suspends_pending--;
319
                        pending = suspends_pending == 0;
320
                        mutex_unlock(&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
                        freezer_do_not_count();
334
 
335
                        wait_event(apm_suspend_waitqueue,
336
                                   as->suspend_state == SUSPEND_DONE);
337
 
338
                        /*
339
                         * Since we are waiting until the suspend is done, the
340
                         * try_to_freeze() in freezer_count() will not trigger
341
                         */
342
                        freezer_count();
343
                } else {
344
                        as->suspend_state = SUSPEND_WAIT;
345
                        mutex_unlock(&state_lock);
346
 
347
                        /*
348
                         * Otherwise it is a request to suspend the system.
349
                         * Queue an event for all readers, and expect an
350
                         * acknowledge from all writers who haven't already
351
                         * acknowledged.
352
                         */
353
                        err = queue_suspend_event(APM_USER_SUSPEND, as);
354
                        if (err < 0) {
355
                                /*
356
                                 * Avoid taking the lock here - this
357
                                 * should be fine.
358
                                 */
359
                                as->suspend_state = SUSPEND_NONE;
360
                                break;
361
                        }
362
 
363
                        if (err > 0)
364
                                apm_suspend();
365
 
366
                        /*
367
                         * Wait for the suspend/resume to complete.  If there
368
                         * are pending acknowledges, we wait here for them.
369
                         */
370
                        wait_event_freezable(apm_suspend_waitqueue,
371
                                         as->suspend_state == SUSPEND_DONE);
372
                }
373
 
374
                mutex_lock(&state_lock);
375
                err = as->suspend_result;
376
                as->suspend_state = SUSPEND_NONE;
377
                mutex_unlock(&state_lock);
378
                break;
379
        }
380
 
381
        return err;
382
}
383
 
384
static int apm_release(struct inode * inode, struct file * filp)
385
{
386
        struct apm_user *as = filp->private_data;
387
        int pending = 0;
388
 
389
        filp->private_data = NULL;
390
 
391
        down_write(&user_list_lock);
392
        list_del(&as->list);
393
        up_write(&user_list_lock);
394
 
395
        /*
396
         * We are now unhooked from the chain.  As far as new
397
         * events are concerned, we no longer exist.  However, we
398
         * need to balance suspends_pending, which means the
399
         * possibility of sleeping.
400
         */
401
        mutex_lock(&state_lock);
402
        if (as->suspend_state != SUSPEND_NONE) {
403
                suspends_pending -= 1;
404
                pending = suspends_pending == 0;
405
        }
406
        mutex_unlock(&state_lock);
407
        if (pending)
408
                apm_suspend();
409
 
410
        kfree(as);
411
        return 0;
412
}
413
 
414
static int apm_open(struct inode * inode, struct file * filp)
415
{
416
        struct apm_user *as;
417
 
418
        as = kzalloc(sizeof(*as), GFP_KERNEL);
419
        if (as) {
420
                /*
421
                 * XXX - this is a tiny bit broken, when we consider BSD
422
                 * process accounting. If the device is opened by root, we
423
                 * instantly flag that we used superuser privs. Who knows,
424
                 * we might close the device immediately without doing a
425
                 * privileged operation -- cevans
426
                 */
427
                as->suser = capable(CAP_SYS_ADMIN);
428
                as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
429
                as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
430
 
431
                down_write(&user_list_lock);
432
                list_add(&as->list, &apm_user_list);
433
                up_write(&user_list_lock);
434
 
435
                filp->private_data = as;
436
        }
437
 
438
        return as ? 0 : -ENOMEM;
439
}
440
 
441
static struct file_operations apm_bios_fops = {
442
        .owner          = THIS_MODULE,
443
        .read           = apm_read,
444
        .poll           = apm_poll,
445
        .ioctl          = apm_ioctl,
446
        .open           = apm_open,
447
        .release        = apm_release,
448
};
449
 
450
static struct miscdevice apm_device = {
451
        .minor          = APM_MINOR_DEV,
452
        .name           = "apm_bios",
453
        .fops           = &apm_bios_fops
454
};
455
 
456
 
457
#ifdef CONFIG_PROC_FS
458
/*
459
 * Arguments, with symbols from linux/apm_bios.h.
460
 *
461
 *   0) Linux driver version (this will change if format changes)
462
 *   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
463
 *   2) APM flags from APM Installation Check (0x00):
464
 *      bit 0: APM_16_BIT_SUPPORT
465
 *      bit 1: APM_32_BIT_SUPPORT
466
 *      bit 2: APM_IDLE_SLOWS_CLOCK
467
 *      bit 3: APM_BIOS_DISABLED
468
 *      bit 4: APM_BIOS_DISENGAGED
469
 *   3) AC line status
470
 *      0x00: Off-line
471
 *      0x01: On-line
472
 *      0x02: On backup power (BIOS >= 1.1 only)
473
 *      0xff: Unknown
474
 *   4) Battery status
475
 *      0x00: High
476
 *      0x01: Low
477
 *      0x02: Critical
478
 *      0x03: Charging
479
 *      0x04: Selected battery not present (BIOS >= 1.2 only)
480
 *      0xff: Unknown
481
 *   5) Battery flag
482
 *      bit 0: High
483
 *      bit 1: Low
484
 *      bit 2: Critical
485
 *      bit 3: Charging
486
 *      bit 7: No system battery
487
 *      0xff: Unknown
488
 *   6) Remaining battery life (percentage of charge):
489
 *      0-100: valid
490
 *      -1: Unknown
491
 *   7) Remaining battery life (time units):
492
 *      Number of remaining minutes or seconds
493
 *      -1: Unknown
494
 *   8) min = minutes; sec = seconds
495
 */
496
static int apm_get_info(char *buf, char **start, off_t fpos, int length)
497
{
498
        struct apm_power_info info;
499
        char *units;
500
        int ret;
501
 
502
        info.ac_line_status = 0xff;
503
        info.battery_status = 0xff;
504
        info.battery_flag   = 0xff;
505
        info.battery_life   = -1;
506
        info.time           = -1;
507
        info.units          = -1;
508
 
509
        if (apm_get_power_status)
510
                apm_get_power_status(&info);
511
 
512
        switch (info.units) {
513
        default:        units = "?";    break;
514
        case 0:  units = "min";  break;
515
        case 1:         units = "sec";  break;
516
        }
517
 
518
        ret = sprintf(buf, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
519
                     driver_version, APM_32_BIT_SUPPORT,
520
                     info.ac_line_status, info.battery_status,
521
                     info.battery_flag, info.battery_life,
522
                     info.time, units);
523
 
524
        return ret;
525
}
526
#endif
527
 
528
static int kapmd(void *arg)
529
{
530
        do {
531
                apm_event_t event;
532
                int ret;
533
 
534
                wait_event_interruptible(kapmd_wait,
535
                                !queue_empty(&kapmd_queue) || kthread_should_stop());
536
 
537
                if (kthread_should_stop())
538
                        break;
539
 
540
                spin_lock_irq(&kapmd_queue_lock);
541
                event = 0;
542
                if (!queue_empty(&kapmd_queue))
543
                        event = queue_get_event(&kapmd_queue);
544
                spin_unlock_irq(&kapmd_queue_lock);
545
 
546
                switch (event) {
547
                case 0:
548
                        break;
549
 
550
                case APM_LOW_BATTERY:
551
                case APM_POWER_STATUS_CHANGE:
552
                        queue_event(event);
553
                        break;
554
 
555
                case APM_USER_SUSPEND:
556
                case APM_SYS_SUSPEND:
557
                        ret = queue_suspend_event(event, NULL);
558
                        if (ret < 0) {
559
                                /*
560
                                 * We were busy.  Try again in 50ms.
561
                                 */
562
                                queue_add_event(&kapmd_queue, event);
563
                                msleep(50);
564
                        }
565
                        if (ret > 0)
566
                                apm_suspend();
567
                        break;
568
 
569
                case APM_CRITICAL_SUSPEND:
570
                        apm_suspend();
571
                        break;
572
                }
573
        } while (1);
574
 
575
        return 0;
576
}
577
 
578
static int __init apm_init(void)
579
{
580
        int ret;
581
 
582
        if (apm_disabled) {
583
                printk(KERN_NOTICE "apm: disabled on user request.\n");
584
                return -ENODEV;
585
        }
586
 
587
        kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
588
        if (IS_ERR(kapmd_tsk)) {
589
                ret = PTR_ERR(kapmd_tsk);
590
                kapmd_tsk = NULL;
591
                return ret;
592
        }
593
        wake_up_process(kapmd_tsk);
594
 
595
#ifdef CONFIG_PROC_FS
596
        create_proc_info_entry("apm", 0, NULL, apm_get_info);
597
#endif
598
 
599
        ret = misc_register(&apm_device);
600
        if (ret != 0) {
601
                remove_proc_entry("apm", NULL);
602
                kthread_stop(kapmd_tsk);
603
        }
604
 
605
        return ret;
606
}
607
 
608
static void __exit apm_exit(void)
609
{
610
        misc_deregister(&apm_device);
611
        remove_proc_entry("apm", NULL);
612
 
613
        kthread_stop(kapmd_tsk);
614
}
615
 
616
module_init(apm_init);
617
module_exit(apm_exit);
618
 
619
MODULE_AUTHOR("Stephen Rothwell");
620
MODULE_DESCRIPTION("Advanced Power Management");
621
MODULE_LICENSE("GPL");
622
 
623
#ifndef MODULE
624
static int __init apm_setup(char *str)
625
{
626
        while ((str != NULL) && (*str != '\0')) {
627
                if (strncmp(str, "off", 3) == 0)
628
                        apm_disabled = 1;
629
                if (strncmp(str, "on", 2) == 0)
630
                        apm_disabled = 0;
631
                str = strchr(str, ',');
632
                if (str != NULL)
633
                        str += strspn(str, ", \t");
634
        }
635
        return 1;
636
}
637
 
638
__setup("apm=", apm_setup);
639
#endif
640
 
641
/**
642
 * apm_queue_event - queue an APM event for kapmd
643
 * @event: APM event
644
 *
645
 * Queue an APM event for kapmd to process and ultimately take the
646
 * appropriate action.  Only a subset of events are handled:
647
 *   %APM_LOW_BATTERY
648
 *   %APM_POWER_STATUS_CHANGE
649
 *   %APM_USER_SUSPEND
650
 *   %APM_SYS_SUSPEND
651
 *   %APM_CRITICAL_SUSPEND
652
 */
653
void apm_queue_event(apm_event_t event)
654
{
655
        unsigned long flags;
656
 
657
        spin_lock_irqsave(&kapmd_queue_lock, flags);
658
        queue_add_event(&kapmd_queue, event);
659
        spin_unlock_irqrestore(&kapmd_queue_lock, flags);
660
 
661
        wake_up_interruptible(&kapmd_wait);
662
}
663
EXPORT_SYMBOL(apm_queue_event);

powered by: WebSVN 2.1.0

© copyright 1999-2025 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.