fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / arch / i386 / kernel / apm.c
index 558d2d2..1990169 100644 (file)
  *   (APM) BIOS Interface Specification, Revision 1.2, February 1996.
  *
  * [This document is available from Microsoft at:
- *    http://www.microsoft.com/hwdev/busbios/amp_12.htm]
+ *    http://www.microsoft.com/whdc/archive/amp_12.mspx]
  */
 
-#include <linux/config.h>
 #include <linux/module.h>
 
 #include <linux/poll.h>
 #include <linux/smp_lock.h>
 #include <linux/dmi.h>
 #include <linux/suspend.h>
+#include <linux/kthread.h>
 
 #include <asm/system.h>
 #include <asm/uaccess.h>
 #include <asm/desc.h>
 #include <asm/i8253.h>
+#include <asm/paravirt.h>
 
 #include "io_ports.h"
 
@@ -374,14 +375,14 @@ static struct {
        unsigned short  segment;
 }                              apm_bios_entry;
 static int                     clock_slowed;
-static int                     idle_threshold = DEFAULT_IDLE_THRESHOLD;
-static int                     idle_period = DEFAULT_IDLE_PERIOD;
+static int                     idle_threshold __read_mostly = DEFAULT_IDLE_THRESHOLD;
+static int                     idle_period __read_mostly = DEFAULT_IDLE_PERIOD;
 static int                     set_pm_idle;
 static int                     suspends_pending;
 static int                     standbys_pending;
 static int                     ignore_sys_suspend;
 static int                     ignore_normal_resume;
-static int                     bounce_interval = DEFAULT_BOUNCE_INTERVAL;
+static int                     bounce_interval __read_mostly = DEFAULT_BOUNCE_INTERVAL;
 
 #ifdef CONFIG_APM_RTC_IS_GMT
 #      define  clock_cmos_diff 0
@@ -390,8 +391,8 @@ static int                  bounce_interval = DEFAULT_BOUNCE_INTERVAL;
 static long                    clock_cmos_diff;
 static int                     got_clock_diff;
 #endif
-static int                     debug;
-static int                     smp;
+static int                     debug __read_mostly;
+static int                     smp __read_mostly;
 static int                     apm_disabled = -1;
 #ifdef CONFIG_SMP
 static int                     power_off;
@@ -403,8 +404,6 @@ static int                  realmode_power_off = 1;
 #else
 static int                     realmode_power_off;
 #endif
-static int                     exit_kapmd;
-static int                     kapmd_running;
 #ifdef CONFIG_APM_ALLOW_INTS
 static int                     allow_ints = 1;
 #else
@@ -416,15 +415,17 @@ static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
 static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
 static struct apm_user *       user_list;
 static DEFINE_SPINLOCK(user_list_lock);
-static struct desc_struct      bad_bios_desc = { 0, 0x00409200 };
+static const struct desc_struct        bad_bios_desc = { 0, 0x00409200 };
 
-static char                    driver_version[] = "1.16ac";    /* no spaces */
+static const char              driver_version[] = "1.16ac";    /* no spaces */
+
+static struct task_struct *kapmd_task;
 
 /*
  *     APM event names taken from the APM 1.2 specification. These are
  *     the message codes that the BIOS uses to tell us about events
  */
-static char *  apm_event_name[] = {
+static const char *    const apm_event_name[] = {
        "system standby",
        "system suspend",
        "normal resume",
@@ -540,12 +541,31 @@ static inline void apm_restore_cpus(cpumask_t mask)
  * Also, we KNOW that for the non error case of apm_bios_call, there
  * is no useful data returned in the low order 8 bits of eax.
  */
-#define APM_DO_CLI     \
-       if (apm_info.allow_ints) \
-               local_irq_enable(); \
-       else \
+
+static inline unsigned long __apm_irq_save(void)
+{
+       unsigned long flags;
+       local_save_flags(flags);
+       if (apm_info.allow_ints) {
+               if (irqs_disabled_flags(flags))
+                       local_irq_enable();
+       } else
                local_irq_disable();
 
+       return flags;
+}
+
+#define apm_irq_save(flags) \
+       do { flags = __apm_irq_save(); } while (0)
+
+static inline void apm_irq_restore(unsigned long flags)
+{
+       if (irqs_disabled_flags(flags))
+               local_irq_disable();
+       else if (irqs_disabled())
+               local_irq_enable();
+}
+
 #ifdef APM_ZERO_SEGS
 #      define APM_DECL_SEGS \
                unsigned int saved_fs; unsigned int saved_gs;
@@ -596,12 +616,11 @@ static u8 apm_bios_call(u32 func, u32 ebx_in, u32 ecx_in,
        save_desc_40 = gdt[0x40 / 8];
        gdt[0x40 / 8] = bad_bios_desc;
 
-       local_save_flags(flags);
-       APM_DO_CLI;
+       apm_irq_save(flags);
        APM_DO_SAVE_SEGS;
        apm_bios_call_asm(func, ebx_in, ecx_in, eax, ebx, ecx, edx, esi);
        APM_DO_RESTORE_SEGS;
-       local_irq_restore(flags);
+       apm_irq_restore(flags);
        gdt[0x40 / 8] = save_desc_40;
        put_cpu();
        apm_restore_cpus(cpus);
@@ -616,7 +635,7 @@ static u8 apm_bios_call(u32 func, u32 ebx_in, u32 ecx_in,
  *     @ecx_in: ECX register value for BIOS call
  *     @eax: EAX register on return from the BIOS call
  *
- *     Make a BIOS call that does only returns one value, or just status.
+ *     Make a BIOS call that returns one value only, or just status.
  *     If there is an error, then the error code is returned in AH
  *     (bits 8-15 of eax) and this function returns non-zero. This is
  *     used for simpler BIOS operations. This call may hold interrupts
@@ -640,12 +659,11 @@ static u8 apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax)
        save_desc_40 = gdt[0x40 / 8];
        gdt[0x40 / 8] = bad_bios_desc;
 
-       local_save_flags(flags);
-       APM_DO_CLI;
+       apm_irq_save(flags);
        APM_DO_SAVE_SEGS;
        error = apm_bios_call_simple_asm(func, ebx_in, ecx_in, eax);
        APM_DO_RESTORE_SEGS;
-       local_irq_restore(flags);
+       apm_irq_restore(flags);
        gdt[0x40 / 8] = save_desc_40;
        put_cpu();
        apm_restore_cpus(cpus);
@@ -764,17 +782,21 @@ static int apm_do_idle(void)
        int     idled = 0;
        int     polling;
 
-       polling = test_thread_flag(TIF_POLLING_NRFLAG);
+       polling = !!(current_thread_info()->status & TS_POLLING);
        if (polling) {
-               clear_thread_flag(TIF_POLLING_NRFLAG);
-               smp_mb__after_clear_bit();
+               current_thread_info()->status &= ~TS_POLLING;
+               /*
+                * TS_POLLING-cleared state must be visible before we
+                * test NEED_RESCHED:
+                */
+               smp_mb();
        }
        if (!need_resched()) {
                idled = 1;
                ret = apm_bios_call_simple(APM_FUNC_IDLE, 0, 0, &eax);
        }
        if (polling)
-               set_thread_flag(TIF_POLLING_NRFLAG);
+               current_thread_info()->status |= TS_POLLING;
 
        if (!idled)
                return 0;
@@ -822,9 +844,7 @@ static void apm_do_busy(void)
 #define IDLE_CALC_LIMIT   (HZ * 100)
 #define IDLE_LEAKY_MAX    16
 
-static void (*original_pm_idle)(void);
-
-extern void default_idle(void);
+static void (*original_pm_idle)(void) __read_mostly;
 
 /**
  * apm_cpu_idle                -       cpu idling for APM capable Linux
@@ -1065,7 +1085,8 @@ static int apm_engage_power_management(u_short device, int enable)
  
 static int apm_console_blank(int blank)
 {
-       int error, i;
+       int error = APM_NOT_ENGAGED; /* silence gcc */
+       int i;
        u_short state;
        static const u_short dev[3] = { 0x100, 0x1FF, 0x101 };
 
@@ -1106,7 +1127,8 @@ static int queue_empty(struct apm_user *as)
 
 static apm_event_t get_queued_event(struct apm_user *as)
 {
-       as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
+       if (++as->event_tail >= APM_MAX_EVENTS)
+               as->event_tail = 0;
        return as->events[as->event_tail];
 }
 
@@ -1120,13 +1142,16 @@ static void queue_event(apm_event_t event, struct apm_user *sender)
        for (as = user_list; as != NULL; as = as->next) {
                if ((as == sender) || (!as->reader))
                        continue;
-               as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
+               if (++as->event_head >= APM_MAX_EVENTS)
+                       as->event_head = 0;
+
                if (as->event_head == as->event_tail) {
                        static int notified;
 
                        if (notified++ == 0)
                            printk(KERN_ERR "apm: an event queue overflowed\n");
-                       as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
+                       if (++as->event_tail >= APM_MAX_EVENTS)
+                               as->event_tail = 0;
                }
                as->events[as->event_head] = event;
                if ((!as->suser) || (!as->writer))
@@ -1152,9 +1177,11 @@ out:
 
 static void set_time(void)
 {
+       struct timespec ts;
        if (got_clock_diff) {   /* Must know time zone in order to set clock */
-               xtime.tv_sec = get_cmos_time() + clock_cmos_diff;
-               xtime.tv_nsec = 0; 
+               ts.tv_sec = get_cmos_time() + clock_cmos_diff;
+               ts.tv_nsec = 0;
+               do_settimeofday(&ts);
        } 
 }
 
@@ -1230,13 +1257,8 @@ static int suspend(int vetoable)
        restore_processor_state();
 
        local_irq_disable();
-       write_seqlock(&xtime_lock);
-       spin_lock(&i8253_lock);
-       reinit_timer();
        set_time();
-
-       spin_unlock(&i8253_lock);
-       write_sequnlock(&xtime_lock);
+       reinit_timer();
 
        if (err == APM_NO_ERROR)
                err = APM_SUCCESS;
@@ -1284,7 +1306,7 @@ static void standby(void)
 static apm_event_t get_event(void)
 {
        int             error;
-       apm_event_t     event;
+       apm_event_t     event = APM_NO_EVENTS; /* silence gcc */
        apm_eventinfo_t info;
 
        static int notified;
@@ -1363,9 +1385,7 @@ static void check_events(void)
                        ignore_bounce = 1;
                        if ((event != APM_NORMAL_RESUME)
                            || (ignore_normal_resume == 0)) {
-                               write_seqlock_irq(&xtime_lock);
                                set_time();
-                               write_sequnlock_irq(&xtime_lock);
                                device_resume();
                                pm_send_all(PM_RESUME, (void *)0);
                                queue_event(event, NULL);
@@ -1381,9 +1401,7 @@ static void check_events(void)
                        break;
 
                case APM_UPDATE_TIME:
-                       write_seqlock_irq(&xtime_lock);
                        set_time();
-                       write_sequnlock_irq(&xtime_lock);
                        break;
 
                case APM_CRITICAL_SUSPEND:
@@ -1428,7 +1446,7 @@ static void apm_mainloop(void)
        set_current_state(TASK_INTERRUPTIBLE);
        for (;;) {
                schedule_timeout(APM_CHECK_TIMEOUT);
-               if (exit_kapmd)
+               if (kthread_should_stop())
                        break;
                /*
                 * Ok, check all events, check for idle (and mark us sleeping
@@ -1590,7 +1608,7 @@ static int do_open(struct inode * inode, struct file * filp)
 {
        struct apm_user *       as;
 
-       as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL);
+       as = kmalloc(sizeof(*as), GFP_KERNEL);
        if (as == NULL) {
                printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n",
                       sizeof(*as));
@@ -1711,12 +1729,6 @@ static int apm(void *unused)
        char *          power_stat;
        char *          bat_stat;
 
-       kapmd_running = 1;
-
-       daemonize("kapmd");
-
-       current->flags |= PF_NOFREEZE;
-
 #ifdef CONFIG_SMP
        /* 2002/08/01 - WT
         * This is to avoid random crashes at boot time during initialization
@@ -1826,7 +1838,6 @@ static int apm(void *unused)
                console_blank_hook = NULL;
 #endif
        }
-       kapmd_running = 0;
 
        return 0;
 }
@@ -2225,11 +2236,11 @@ static int __init apm_init(void)
 {
        struct proc_dir_entry *apm_proc;
        struct desc_struct *gdt;
-       int ret;
+       int err;
 
        dmi_check_system(apm_dmi_table);
 
-       if (apm_info.bios.version == 0) {
+       if (apm_info.bios.version == 0 || paravirt_enabled()) {
                printk(KERN_INFO "apm: BIOS not found.\n");
                return -ENODEV;
        }
@@ -2334,11 +2345,17 @@ static int __init apm_init(void)
        if (apm_proc)
                apm_proc->owner = THIS_MODULE;
 
-       ret = kernel_thread(apm, NULL, CLONE_KERNEL | SIGCHLD);
-       if (ret < 0) {
-               printk(KERN_ERR "apm: disabled - Unable to start kernel thread.\n");
-               return -ENOMEM;
+       kapmd_task = kthread_create(apm, NULL, "kapmd");
+       if (IS_ERR(kapmd_task)) {
+               printk(KERN_ERR "apm: disabled - Unable to start kernel "
+                               "thread.\n");
+               err = PTR_ERR(kapmd_task);
+               kapmd_task = NULL;
+               remove_proc_entry("apm", NULL);
+               return err;
        }
+       kapmd_task->flags |= PF_NOFREEZE;
+       wake_up_process(kapmd_task);
 
        if (num_online_cpus() > 1 && !smp ) {
                printk(KERN_NOTICE
@@ -2346,7 +2363,13 @@ static int __init apm_init(void)
                return 0;
        }
 
-       misc_register(&apm_device);
+       /*
+        * Note we don't actually care if the misc_device cannot be registered.
+        * this driver can do its job without it, even if userspace can't
+        * control it.  just log the error
+        */
+       if (misc_register(&apm_device))
+               printk(KERN_WARNING "apm: Could not register misc device.\n");
 
        if (HZ != 100)
                idle_period = (idle_period * HZ) / 100;
@@ -2382,9 +2405,10 @@ static void __exit apm_exit(void)
        remove_proc_entry("apm", NULL);
        if (power_off)
                pm_power_off = NULL;
-       exit_kapmd = 1;
-       while (kapmd_running)
-               schedule();
+       if (kapmd_task) {
+               kthread_stop(kapmd_task);
+               kapmd_task = NULL;
+       }
 #ifdef CONFIG_PM_LEGACY
        pm_active = 0;
 #endif