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

Subversion Repositories or1k

[/] [or1k/] [trunk/] [linux/] [linux-2.4/] [drivers/] [macintosh/] [apm_emu.c] - Blame information for rev 1774

Go to most recent revision | Details | Compare with Previous | View Log

Line No. Rev Author Line
1 1275 phoenix
/* APM emulation layer for PowerMac
2
 *
3
 * Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org)
4
 *
5
 * Lots of code inherited from apm.c, see appropriate notice in
6
 *  arch/i386/kernel/apm.c
7
 *
8
 * This program is free software; you can redistribute it and/or modify it
9
 * under the terms of the GNU General Public License as published by the
10
 * Free Software Foundation; either version 2, or (at your option) any
11
 * later version.
12
 *
13
 * This program is distributed in the hope that it will be useful, but
14
 * WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * General Public License for more details.
17
 *
18
 *
19
 */
20
 
21
#include <linux/config.h>
22
#include <linux/module.h>
23
 
24
#include <linux/poll.h>
25
#include <linux/types.h>
26
#include <linux/stddef.h>
27
#include <linux/timer.h>
28
#include <linux/fcntl.h>
29
#include <linux/slab.h>
30
#include <linux/stat.h>
31
#include <linux/proc_fs.h>
32
#include <linux/miscdevice.h>
33
#include <linux/apm_bios.h>
34
#include <linux/init.h>
35
#include <linux/sched.h>
36
#include <linux/pm.h>
37
#include <linux/kernel.h>
38
#include <linux/smp_lock.h>
39
 
40
#include <linux/adb.h>
41
#include <linux/pmu.h>
42
 
43
#include <asm/system.h>
44
#include <asm/uaccess.h>
45
#include <asm/machdep.h>
46
 
47
#undef DEBUG
48
 
49
#ifdef DEBUG
50
#define DBG(args...) printk(KERN_DEBUG args)
51
//#define DBG(args...) xmon_printf(args)
52
#else
53
#define DBG(args...) do { } while (0)
54
#endif
55
 
56
/*
57
 * The apm_bios device is one of the misc char devices.
58
 * This is its minor number.
59
 */
60
#define APM_MINOR_DEV   134
61
 
62
/*
63
 * Maximum number of events stored
64
 */
65
#define APM_MAX_EVENTS          20
66
 
67
#define FAKE_APM_BIOS_VERSION   0x0101
68
 
69
#define APM_USER_NOTIFY_TIMEOUT (5*HZ)
70
 
71
/*
72
 * The per-file APM data
73
 */
74
struct apm_user {
75
        int             magic;
76
        struct apm_user *       next;
77
        int             suser: 1;
78
        int             suspend_waiting: 1;
79
        int             suspends_pending;
80
        int             suspends_read;
81
        int             event_head;
82
        int             event_tail;
83
        apm_event_t     events[APM_MAX_EVENTS];
84
};
85
 
86
/*
87
 * The magic number in apm_user
88
 */
89
#define APM_BIOS_MAGIC          0x4101
90
 
91
/*
92
 * Local variables
93
 */
94
static int                      suspends_pending;
95
 
96
static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
97
static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
98
static struct apm_user *        user_list;
99
 
100
static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when);
101
static struct pmu_sleep_notifier apm_sleep_notifier = {
102
        apm_notify_sleep,
103
        SLEEP_LEVEL_USERLAND,
104
};
105
 
106
static char                     driver_version[] = "0.5";       /* no spaces */
107
 
108
#ifdef DEBUG
109
static char *   apm_event_name[] = {
110
        "system standby",
111
        "system suspend",
112
        "normal resume",
113
        "critical resume",
114
        "low battery",
115
        "power status change",
116
        "update time",
117
        "critical suspend",
118
        "user standby",
119
        "user suspend",
120
        "system standby resume",
121
        "capabilities change"
122
};
123
#define NR_APM_EVENT_NAME       \
124
                (sizeof(apm_event_name) / sizeof(apm_event_name[0]))
125
 
126
#endif
127
 
128
static int queue_empty(struct apm_user *as)
129
{
130
        return as->event_head == as->event_tail;
131
}
132
 
133
static apm_event_t get_queued_event(struct apm_user *as)
134
{
135
        as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
136
        return as->events[as->event_tail];
137
}
138
 
139
static void queue_event(apm_event_t event, struct apm_user *sender)
140
{
141
        struct apm_user *       as;
142
 
143
        DBG("apm_emu: queue_event(%s)\n", apm_event_name[event-1]);
144
        if (user_list == NULL)
145
                return;
146
        for (as = user_list; as != NULL; as = as->next) {
147
                if (as == sender)
148
                        continue;
149
                as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
150
                if (as->event_head == as->event_tail) {
151
                        static int notified;
152
 
153
                        if (notified++ == 0)
154
                            printk(KERN_ERR "apm_emu: an event queue overflowed\n");
155
                        as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
156
                }
157
                as->events[as->event_head] = event;
158
                if (!as->suser)
159
                        continue;
160
                switch (event) {
161
                case APM_SYS_SUSPEND:
162
                case APM_USER_SUSPEND:
163
                        as->suspends_pending++;
164
                        suspends_pending++;
165
                        break;
166
                case APM_NORMAL_RESUME:
167
                        as->suspend_waiting = 0;
168
                        break;
169
                }
170
        }
171
        wake_up_interruptible(&apm_waitqueue);
172
}
173
 
174
static int check_apm_user(struct apm_user *as, const char *func)
175
{
176
        if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
177
                printk(KERN_ERR "apm_emu: %s passed bad filp\n", func);
178
                return 1;
179
        }
180
        return 0;
181
}
182
 
183
static ssize_t do_read(struct file *fp, char *buf, size_t count, loff_t *ppos)
184
{
185
        struct apm_user *       as;
186
        size_t                  i;
187
        apm_event_t             event;
188
        DECLARE_WAITQUEUE(wait, current);
189
 
190
        as = fp->private_data;
191
        if (check_apm_user(as, "read"))
192
                return -EIO;
193
        if (count < sizeof(apm_event_t))
194
                return -EINVAL;
195
        if (queue_empty(as)) {
196
                if (fp->f_flags & O_NONBLOCK)
197
                        return -EAGAIN;
198
                add_wait_queue(&apm_waitqueue, &wait);
199
repeat:
200
                set_current_state(TASK_INTERRUPTIBLE);
201
                if (queue_empty(as) && !signal_pending(current)) {
202
                        schedule();
203
                        goto repeat;
204
                }
205
                set_current_state(TASK_RUNNING);
206
                remove_wait_queue(&apm_waitqueue, &wait);
207
        }
208
        i = count;
209
        while ((i >= sizeof(event)) && !queue_empty(as)) {
210
                event = get_queued_event(as);
211
                DBG("apm_emu: do_read, returning: %s\n", apm_event_name[event-1]);
212
                if (copy_to_user(buf, &event, sizeof(event))) {
213
                        if (i < count)
214
                                break;
215
                        return -EFAULT;
216
                }
217
                switch (event) {
218
                case APM_SYS_SUSPEND:
219
                case APM_USER_SUSPEND:
220
                        as->suspends_read++;
221
                        break;
222
                }
223
                buf += sizeof(event);
224
                i -= sizeof(event);
225
        }
226
        if (i < count)
227
                return count - i;
228
        if (signal_pending(current))
229
                return -ERESTARTSYS;
230
        return 0;
231
}
232
 
233
static unsigned int do_poll(struct file *fp, poll_table * wait)
234
{
235
        struct apm_user * as;
236
 
237
        as = fp->private_data;
238
        if (check_apm_user(as, "poll"))
239
                return 0;
240
        poll_wait(fp, &apm_waitqueue, wait);
241
        if (!queue_empty(as))
242
                return POLLIN | POLLRDNORM;
243
        return 0;
244
}
245
 
246
static int do_ioctl(struct inode * inode, struct file *filp,
247
                    u_int cmd, u_long arg)
248
{
249
        struct apm_user *       as;
250
        DECLARE_WAITQUEUE(wait, current);
251
 
252
        as = filp->private_data;
253
        if (check_apm_user(as, "ioctl"))
254
                return -EIO;
255
        if (!as->suser)
256
                return -EPERM;
257
        switch (cmd) {
258
        case APM_IOC_SUSPEND:
259
                /* If a suspend message was sent to userland, we
260
                 * consider this as a confirmation message
261
                 */
262
                if (as->suspends_read > 0) {
263
                        as->suspends_read--;
264
                        as->suspends_pending--;
265
                        suspends_pending--;
266
                } else {
267
                        // Route to PMU suspend ?
268
                        break;
269
                }
270
                as->suspend_waiting = 1;
271
                add_wait_queue(&apm_waitqueue, &wait);
272
                DBG("apm_emu: ioctl waking up sleep waiter !\n");
273
                wake_up(&apm_suspend_waitqueue);
274
                mb();
275
                while(as->suspend_waiting && !signal_pending(current)) {
276
                        set_current_state(TASK_INTERRUPTIBLE);
277
                        schedule();
278
                }
279
                set_current_state(TASK_RUNNING);
280
                remove_wait_queue(&apm_waitqueue, &wait);
281
                break;
282
        default:
283
                return -EINVAL;
284
        }
285
        return 0;
286
}
287
 
288
static int do_release(struct inode * inode, struct file * filp)
289
{
290
        struct apm_user *       as;
291
 
292
        as = filp->private_data;
293
        if (check_apm_user(as, "release"))
294
                return 0;
295
        filp->private_data = NULL;
296
        lock_kernel();
297
        if (as->suspends_pending > 0) {
298
                suspends_pending -= as->suspends_pending;
299
                if (suspends_pending <= 0)
300
                        wake_up(&apm_suspend_waitqueue);
301
        }
302
        if (user_list == as)
303
                user_list = as->next;
304
        else {
305
                struct apm_user *       as1;
306
 
307
                for (as1 = user_list;
308
                     (as1 != NULL) && (as1->next != as);
309
                     as1 = as1->next)
310
                        ;
311
                if (as1 == NULL)
312
                        printk(KERN_ERR "apm: filp not in user list\n");
313
                else
314
                        as1->next = as->next;
315
        }
316
        unlock_kernel();
317
        kfree(as);
318
        return 0;
319
}
320
 
321
static int do_open(struct inode * inode, struct file * filp)
322
{
323
        struct apm_user *       as;
324
 
325
        as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL);
326
        if (as == NULL) {
327
                printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n",
328
                       sizeof(*as));
329
                return -ENOMEM;
330
        }
331
        as->magic = APM_BIOS_MAGIC;
332
        as->event_tail = as->event_head = 0;
333
        as->suspends_pending = 0;
334
        as->suspends_read = 0;
335
        /*
336
         * XXX - this is a tiny bit broken, when we consider BSD
337
         * process accounting. If the device is opened by root, we
338
         * instantly flag that we used superuser privs. Who knows,
339
         * we might close the device immediately without doing a
340
         * privileged operation -- cevans
341
         */
342
        as->suser = capable(CAP_SYS_ADMIN);
343
        as->next = user_list;
344
        user_list = as;
345
        filp->private_data = as;
346
 
347
        DBG("apm_emu: opened by %s, suser: %d\n", current->comm, (int)as->suser);
348
 
349
        return 0;
350
}
351
 
352
/* Wait for all clients to ack the suspend request. APM API
353
 * doesn't provide a way to NAK, but this could be added
354
 * here.
355
 */
356
static int wait_all_suspend(void)
357
{
358
        DECLARE_WAITQUEUE(wait, current);
359
 
360
        add_wait_queue(&apm_suspend_waitqueue, &wait);
361
        DBG("apm_emu: wait_all_suspend(), suspends_pending: %d\n", suspends_pending);
362
        while(suspends_pending > 0) {
363
                set_current_state(TASK_UNINTERRUPTIBLE);
364
                schedule();
365
        }
366
        set_current_state(TASK_RUNNING);
367
        remove_wait_queue(&apm_suspend_waitqueue, &wait);
368
 
369
        DBG("apm_emu: wait_all_suspend() - complete !\n");
370
 
371
        return 1;
372
}
373
 
374
static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when)
375
{
376
        switch(when) {
377
                case PBOOK_SLEEP_REQUEST:
378
                        queue_event(APM_SYS_SUSPEND, NULL);
379
                        if (!wait_all_suspend())
380
                                return PBOOK_SLEEP_REFUSE;
381
                        break;
382
                case PBOOK_SLEEP_REJECT:
383
                case PBOOK_WAKE:
384
                        queue_event(APM_NORMAL_RESUME, NULL);
385
                        break;
386
        }
387
        return PBOOK_SLEEP_OK;
388
}
389
 
390
#define APM_CRITICAL            10
391
#define APM_LOW                 30
392
 
393
static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length)
394
{
395
        /* Arguments, with symbols from linux/apm_bios.h.  Information is
396
           from the Get Power Status (0x0a) call unless otherwise noted.
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
        unsigned short  ac_line_status = 0xff;
434
        unsigned short  battery_status = 0xff;
435
        unsigned short  battery_flag   = 0xff;
436
        int             percentage     = -1;
437
        int             time_units     = -1;
438
        int             real_count     = 0;
439
        int             i;
440
        char *          p = buf;
441
        char            charging       = 0;
442
        long            charge         = -1;
443
        long            current        = 0;
444
        unsigned long   btype          = 0;
445
 
446
        ac_line_status = ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0);
447
        for (i=0; i<pmu_battery_count; i++) {
448
                if (pmu_batteries[i].flags & PMU_BATT_PRESENT) {
449
                        if (percentage < 0)
450
                                percentage = 0;
451
                        if (charge < 0)
452
                                charge = 0;
453
                        percentage += (pmu_batteries[i].charge * 100) /
454
                                pmu_batteries[i].max_charge;
455
                        charge += pmu_batteries[i].charge;
456
                        current += pmu_batteries[i].current;
457
                        if (btype == 0)
458
                                btype = (pmu_batteries[i].flags & PMU_BATT_TYPE_MASK);
459
                        real_count++;
460
                        if ((pmu_batteries[i].flags & PMU_BATT_CHARGING))
461
                                charging++;
462
                }
463
        }
464
        if (real_count) {
465
                if (current < 0) {
466
                        if (btype == PMU_BATT_TYPE_SMART)
467
                                time_units = (charge * 59) / (current * -1);
468
                        else
469
                                time_units = (charge * 16440) / (current * -60);
470
                }
471
                percentage /= real_count;
472
                if (charging > 0) {
473
                        battery_status = 0x03;
474
                        battery_flag = 0x08;
475
                } else if (percentage <= APM_CRITICAL) {
476
                        battery_status = 0x02;
477
                        battery_flag = 0x04;
478
                } else if (percentage <= APM_LOW) {
479
                        battery_status = 0x01;
480
                        battery_flag = 0x02;
481
                } else {
482
                        battery_status = 0x00;
483
                        battery_flag = 0x01;
484
                }
485
        }
486
        p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
487
                     driver_version,
488
                     (FAKE_APM_BIOS_VERSION >> 8) & 0xff,
489
                     FAKE_APM_BIOS_VERSION & 0xff,
490
                     0,
491
                     ac_line_status,
492
                     battery_status,
493
                     battery_flag,
494
                     percentage,
495
                     time_units,
496
                     "min");
497
 
498
        return p - buf;
499
}
500
 
501
static struct file_operations apm_bios_fops = {
502
        owner:          THIS_MODULE,
503
        read:           do_read,
504
        poll:           do_poll,
505
        ioctl:          do_ioctl,
506
        open:           do_open,
507
        release:        do_release,
508
};
509
 
510
static struct miscdevice apm_device = {
511
        APM_MINOR_DEV,
512
        "apm_bios",
513
        &apm_bios_fops
514
};
515
 
516
static int __init apm_emu_init(void)
517
{
518
        struct proc_dir_entry *apm_proc;
519
        int retval;
520
 
521
        if (sys_ctrler != SYS_CTRLER_PMU) {
522
                printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n");
523
                return -ENODEV;
524
        }
525
 
526
        apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
527
        if (!apm_proc) {
528
                printk(KERN_ERR "apm_emu: create_proc_info_entry failed.\n");
529
                return -ENOENT;
530
        }
531
        SET_MODULE_OWNER(apm_proc);
532
 
533
        retval = misc_register(&apm_device);
534
        if (retval < 0) {
535
                printk(KERN_ERR "apm_emu: misc_register failed\n");
536
                remove_proc_entry("apm", NULL);
537
                return retval;
538
        }
539
 
540
        pmu_register_sleep_notifier(&apm_sleep_notifier);
541
 
542
        printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version);
543
 
544
        return 0;
545
}
546
 
547
static void __exit apm_emu_exit(void)
548
{
549
        pmu_unregister_sleep_notifier(&apm_sleep_notifier);
550
        misc_deregister(&apm_device);
551
        remove_proc_entry("apm", NULL);
552
 
553
        printk(KERN_INFO "apm_emu: APM Emulation removed.\n");
554
}
555
 
556
module_init(apm_emu_init);
557
module_exit(apm_emu_exit);
558
 
559
MODULE_AUTHOR("Benjamin Herrenschmidt");
560
MODULE_DESCRIPTION("APM emulation layer for PowerMac");
561
MODULE_LICENSE("GPL");
562
EXPORT_NO_SYMBOLS;
563
 

powered by: WebSVN 2.1.0

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