x86: cpumask: update 32-bit APM not to mug current->cpus_allowed
authorRusty Russell <rusty@rustcorp.com.au>
Wed, 11 Mar 2009 06:01:29 +0000 (16:31 +1030)
committerIngo Molnar <mingo@elte.hu>
Wed, 18 Mar 2009 12:51:45 +0000 (13:51 +0100)
Impact: cleanup, avoid cpumask games

The APM code wants to run on CPU 0: we create an "on_cpu0" wrapper
which uses work_on_cpu() if we're not already on cpu 0.

This introduces a new failure mode: -ENOMEM, so we add an explicit
err arg and handle Linux-style errnos in apm_err().

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Stephen Rothwell <sfr@canb.auug.org.au>
LKML-Reference: <200903111631.29787.rusty@rustcorp.com.au>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
arch/x86/kernel/apm_32.c

index 10033fe718e0ad574f57a4f54c9d67efb72cb23e..c1941be9fb1777e57cb09190023422e8d17af2e8 100644 (file)
@@ -466,7 +466,7 @@ static const lookup_t error_table[] = {
  *     @err: APM BIOS return code
  *
  *     Write a meaningful log entry to the kernel log in the event of
- *     an APM error.
+ *     an APM error.  Note that this also handles (negative) kernel errors.
  */
 
 static void apm_error(char *str, int err)
@@ -478,42 +478,13 @@ static void apm_error(char *str, int err)
                        break;
        if (i < ERROR_COUNT)
                printk(KERN_NOTICE "apm: %s: %s\n", str, error_table[i].msg);
+       else if (err < 0)
+               printk(KERN_NOTICE "apm: %s: linux error code %i\n", str, err);
        else
                printk(KERN_NOTICE "apm: %s: unknown error code %#2.2x\n",
                       str, err);
 }
 
-/*
- * Lock APM functionality to physical CPU 0
- */
-
-#ifdef CONFIG_SMP
-
-static cpumask_t apm_save_cpus(void)
-{
-       cpumask_t x = current->cpus_allowed;
-       /* Some bioses don't like being called from CPU != 0 */
-       set_cpus_allowed(current, cpumask_of_cpu(0));
-       BUG_ON(smp_processor_id() != 0);
-       return x;
-}
-
-static inline void apm_restore_cpus(cpumask_t mask)
-{
-       set_cpus_allowed(current, mask);
-}
-
-#else
-
-/*
- *     No CPU lockdown needed on a uniprocessor
- */
-
-#define apm_save_cpus()                (current->cpus_allowed)
-#define apm_restore_cpus(x)    (void)(x)
-
-#endif
-
 /*
  * These are the actual BIOS calls.  Depending on APM_ZERO_SEGS and
  * apm_info.allow_ints, we are being really paranoid here!  Not only
@@ -568,16 +539,23 @@ static inline void apm_irq_restore(unsigned long flags)
 #      define APM_DO_RESTORE_SEGS
 #endif
 
+struct apm_bios_call {
+       u32 func;
+       /* In and out */
+       u32 ebx;
+       u32 ecx;
+       /* Out only */
+       u32 eax;
+       u32 edx;
+       u32 esi;
+
+       /* Error: -ENOMEM, or bits 8-15 of eax */
+       int err;
+};
+
 /**
- *     apm_bios_call   -       Make an APM BIOS 32bit call
- *     @func: APM function to execute
- *     @ebx_in: EBX register for call entry
- *     @ecx_in: ECX register for call entry
- *     @eax: EAX register return
- *     @ebx: EBX register return
- *     @ecx: ECX register return
- *     @edx: EDX register return
- *     @esi: ESI register return
+ *     __apm_bios_call - Make an APM BIOS 32bit call
+ *     @_call: pointer to struct apm_bios_call.
  *
  *     Make an APM call using the 32bit protected mode interface. The
  *     caller is responsible for knowing if APM BIOS is configured and
@@ -586,79 +564,141 @@ static inline void apm_irq_restore(unsigned long flags)
  *     flag is loaded into AL.  If there is an error, then the error
  *     code is returned in AH (bits 8-15 of eax) and this function
  *     returns non-zero.
+ *
+ *     Note: this makes the call on the current CPU.
  */
-
-static u8 apm_bios_call(u32 func, u32 ebx_in, u32 ecx_in,
-       u32 *eax, u32 *ebx, u32 *ecx, u32 *edx, u32 *esi)
+static long __apm_bios_call(void *_call)
 {
        APM_DECL_SEGS
        unsigned long           flags;
-       cpumask_t               cpus;
        int                     cpu;
        struct desc_struct      save_desc_40;
        struct desc_struct      *gdt;
-
-       cpus = apm_save_cpus();
+       struct apm_bios_call    *call = _call;
 
        cpu = get_cpu();
+       BUG_ON(cpu != 0);
        gdt = get_cpu_gdt_table(cpu);
        save_desc_40 = gdt[0x40 / 8];
        gdt[0x40 / 8] = bad_bios_desc;
 
        apm_irq_save(flags);
        APM_DO_SAVE_SEGS;
-       apm_bios_call_asm(func, ebx_in, ecx_in, eax, ebx, ecx, edx, esi);
+       apm_bios_call_asm(call->func, call->ebx, call->ecx,
+                         &call->eax, &call->ebx, &call->ecx, &call->edx,
+                         &call->esi);
        APM_DO_RESTORE_SEGS;
        apm_irq_restore(flags);
        gdt[0x40 / 8] = save_desc_40;
        put_cpu();
-       apm_restore_cpus(cpus);
 
-       return *eax & 0xff;
+       return call->eax & 0xff;
+}
+
+/* Run __apm_bios_call or __apm_bios_call_simple on CPU 0 */
+static int on_cpu0(long (*fn)(void *), struct apm_bios_call *call)
+{
+       int ret;
+
+       /* Don't bother with work_on_cpu in the common case, so we don't
+        * have to worry about OOM or overhead. */
+       if (get_cpu() == 0) {
+               ret = fn(call);
+               put_cpu();
+       } else {
+               put_cpu();
+               ret = work_on_cpu(0, fn, call);
+       }
+
+       /* work_on_cpu can fail with -ENOMEM */
+       if (ret < 0)
+               call->err = ret;
+       else
+               call->err = (call->eax >> 8) & 0xff;
+
+       return ret;
 }
 
 /**
- *     apm_bios_call_simple    -       make a simple APM BIOS 32bit call
- *     @func: APM function to invoke
- *     @ebx_in: EBX register value for BIOS call
- *     @ecx_in: ECX register value for BIOS call
- *     @eax: EAX register on return from the BIOS call
+ *     apm_bios_call   -       Make an APM BIOS 32bit call (on CPU 0)
+ *     @call: the apm_bios_call registers.
+ *
+ *     If there is an error, it is returned in @call.err.
+ */
+static int apm_bios_call(struct apm_bios_call *call)
+{
+       return on_cpu0(__apm_bios_call, call);
+}
+
+/**
+ *     __apm_bios_call_simple - Make an APM BIOS 32bit call (on CPU 0)
+ *     @_call: pointer to struct apm_bios_call.
  *
  *     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
- *     off for a long time on some laptops.
+ *     (bits 8-15 of eax) and this function returns non-zero (it can
+ *     also return -ENOMEM). This is used for simpler BIOS operations.
+ *     This call may hold interrupts off for a long time on some laptops.
+ *
+ *     Note: this makes the call on the current CPU.
  */
-
-static u8 apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax)
+static long __apm_bios_call_simple(void *_call)
 {
        u8                      error;
        APM_DECL_SEGS
        unsigned long           flags;
-       cpumask_t               cpus;
        int                     cpu;
        struct desc_struct      save_desc_40;
        struct desc_struct      *gdt;
-
-       cpus = apm_save_cpus();
+       struct apm_bios_call    *call = _call;
 
        cpu = get_cpu();
+       BUG_ON(cpu != 0);
        gdt = get_cpu_gdt_table(cpu);
        save_desc_40 = gdt[0x40 / 8];
        gdt[0x40 / 8] = bad_bios_desc;
 
        apm_irq_save(flags);
        APM_DO_SAVE_SEGS;
-       error = apm_bios_call_simple_asm(func, ebx_in, ecx_in, eax);
+       error = apm_bios_call_simple_asm(call->func, call->ebx, call->ecx,
+                                        &call->eax);
        APM_DO_RESTORE_SEGS;
        apm_irq_restore(flags);
        gdt[0x40 / 8] = save_desc_40;
        put_cpu();
-       apm_restore_cpus(cpus);
        return error;
 }
 
+/**
+ *     apm_bios_call_simple    -       make a simple APM BIOS 32bit call
+ *     @func: APM function to invoke
+ *     @ebx_in: EBX register value for BIOS call
+ *     @ecx_in: ECX register value for BIOS call
+ *     @eax: EAX register on return from the BIOS call
+ *     @err: bits
+ *
+ *     Make a BIOS call that returns one value only, or just status.
+ *     If there is an error, then the error code is returned in @err
+ *     and this function returns non-zero. This is used for simpler
+ *     BIOS operations.  This call may hold interrupts off for a long
+ *     time on some laptops.
+ */
+static int apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax,
+                               int *err)
+{
+       struct apm_bios_call call;
+       int ret;
+
+       call.func = func;
+       call.ebx = ebx_in;
+       call.ecx = ecx_in;
+
+       ret = on_cpu0(__apm_bios_call_simple, &call);
+       *eax = call.eax;
+       *err = call.err;
+       return ret;
+}
+
 /**
  *     apm_driver_version      -       APM driver version
  *     @val:   loaded with the APM version on return
@@ -678,9 +718,10 @@ static u8 apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax)
 static int apm_driver_version(u_short *val)
 {
        u32 eax;
+       int err;
 
-       if (apm_bios_call_simple(APM_FUNC_VERSION, 0, *val, &eax))
-               return (eax >> 8) & 0xff;
+       if (apm_bios_call_simple(APM_FUNC_VERSION, 0, *val, &eax, &err))
+               return err;
        *val = eax;
        return APM_SUCCESS;
 }
@@ -701,22 +742,21 @@ static int apm_driver_version(u_short *val)
  *     that APM 1.2 is in use. If no messges are pending the value 0x80
  *     is returned (No power management events pending).
  */
-
 static int apm_get_event(apm_event_t *event, apm_eventinfo_t *info)
 {
-       u32 eax;
-       u32 ebx;
-       u32 ecx;
-       u32 dummy;
+       struct apm_bios_call call;
 
-       if (apm_bios_call(APM_FUNC_GET_EVENT, 0, 0, &eax, &ebx, &ecx,
-                         &dummy, &dummy))
-               return (eax >> 8) & 0xff;
-       *event = ebx;
+       call.func = APM_FUNC_GET_EVENT;
+       call.ebx = call.ecx = 0;
+
+       if (apm_bios_call(&call))
+               return call.err;
+
+       *event = call.ebx;
        if (apm_info.connection_version < 0x0102)
                *info = ~0; /* indicate info not valid */
        else
-               *info = ecx;
+               *info = call.ecx;
        return APM_SUCCESS;
 }
 
@@ -737,9 +777,10 @@ static int apm_get_event(apm_event_t *event, apm_eventinfo_t *info)
 static int set_power_state(u_short what, u_short state)
 {
        u32 eax;
+       int err;
 
-       if (apm_bios_call_simple(APM_FUNC_SET_STATE, what, state, &eax))
-               return (eax >> 8) & 0xff;
+       if (apm_bios_call_simple(APM_FUNC_SET_STATE, what, state, &eax, &err))
+               return err;
        return APM_SUCCESS;
 }
 
@@ -770,6 +811,7 @@ static int apm_do_idle(void)
        u8 ret = 0;
        int idled = 0;
        int polling;
+       int err;
 
        polling = !!(current_thread_info()->status & TS_POLLING);
        if (polling) {
@@ -782,7 +824,7 @@ static int apm_do_idle(void)
        }
        if (!need_resched()) {
                idled = 1;
-               ret = apm_bios_call_simple(APM_FUNC_IDLE, 0, 0, &eax);
+               ret = apm_bios_call_simple(APM_FUNC_IDLE, 0, 0, &eax, &err);
        }
        if (polling)
                current_thread_info()->status |= TS_POLLING;
@@ -797,8 +839,7 @@ static int apm_do_idle(void)
                 * Only report the failure the first 5 times.
                 */
                if (++t < 5) {
-                       printk(KERN_DEBUG "apm_do_idle failed (%d)\n",
-                              (eax >> 8) & 0xff);
+                       printk(KERN_DEBUG "apm_do_idle failed (%d)\n", err);
                        t = jiffies;
                }
                return -1;
@@ -816,9 +857,10 @@ static int apm_do_idle(void)
 static void apm_do_busy(void)
 {
        u32 dummy;
+       int err;
 
        if (clock_slowed || ALWAYS_CALL_BUSY) {
-               (void)apm_bios_call_simple(APM_FUNC_BUSY, 0, 0, &dummy);
+               (void)apm_bios_call_simple(APM_FUNC_BUSY, 0, 0, &dummy, &err);
                clock_slowed = 0;
        }
 }
@@ -937,7 +979,7 @@ static void apm_power_off(void)
 
        /* Some bioses don't like being called from CPU != 0 */
        if (apm_info.realmode_power_off) {
-               (void)apm_save_cpus();
+               set_cpus_allowed_ptr(current, cpumask_of(0));
                machine_real_restart(po_bios_call, sizeof(po_bios_call));
        } else {
                (void)set_system_power_state(APM_STATE_OFF);
@@ -956,12 +998,13 @@ static void apm_power_off(void)
 static int apm_enable_power_management(int enable)
 {
        u32 eax;
+       int err;
 
        if ((enable == 0) && (apm_info.bios.flags & APM_BIOS_DISENGAGED))
                return APM_NOT_ENGAGED;
        if (apm_bios_call_simple(APM_FUNC_ENABLE_PM, APM_DEVICE_BALL,
-                                enable, &eax))
-               return (eax >> 8) & 0xff;
+                                enable, &eax, &err))
+               return err;
        if (enable)
                apm_info.bios.flags &= ~APM_BIOS_DISABLED;
        else
@@ -986,24 +1029,23 @@ static int apm_enable_power_management(int enable)
 
 static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
 {
-       u32 eax;
-       u32 ebx;
-       u32 ecx;
-       u32 edx;
-       u32 dummy;
+       struct apm_bios_call call;
+
+       call.func = APM_FUNC_GET_STATUS;
+       call.ebx = APM_DEVICE_ALL;
+       call.ecx = 0;
 
        if (apm_info.get_power_status_broken)
                return APM_32_UNSUPPORTED;
-       if (apm_bios_call(APM_FUNC_GET_STATUS, APM_DEVICE_ALL, 0,
-                         &eax, &ebx, &ecx, &edx, &dummy))
-               return (eax >> 8) & 0xff;
-       *status = ebx;
-       *bat = ecx;
+       if (apm_bios_call(&call))
+               return call.err;
+       *status = call.ebx;
+       *bat = call.ecx;
        if (apm_info.get_power_status_swabinminutes) {
-               *life = swab16((u16)edx);
+               *life = swab16((u16)call.edx);
                *life |= 0x8000;
        } else
-               *life = edx;
+               *life = call.edx;
        return APM_SUCCESS;
 }
 
@@ -1048,12 +1090,14 @@ static int apm_get_battery_status(u_short which, u_short *status,
 static int apm_engage_power_management(u_short device, int enable)
 {
        u32 eax;
+       int err;
 
        if ((enable == 0) && (device == APM_DEVICE_ALL)
            && (apm_info.bios.flags & APM_BIOS_DISABLED))
                return APM_DISABLED;
-       if (apm_bios_call_simple(APM_FUNC_ENGAGE_PM, device, enable, &eax))
-               return (eax >> 8) & 0xff;
+       if (apm_bios_call_simple(APM_FUNC_ENGAGE_PM, device, enable,
+                                &eax, &err))
+               return err;
        if (device == APM_DEVICE_ALL) {
                if (enable)
                        apm_info.bios.flags &= ~APM_BIOS_DISENGAGED;
@@ -1682,16 +1726,14 @@ static int apm(void *unused)
        char            *power_stat;
        char            *bat_stat;
 
-#ifdef CONFIG_SMP
        /* 2002/08/01 - WT
         * This is to avoid random crashes at boot time during initialization
         * on SMP systems in case of "apm=power-off" mode. Seen on ASUS A7M266D.
         * Some bioses don't like being called from CPU != 0.
         * Method suggested by Ingo Molnar.
         */
-       set_cpus_allowed(current, cpumask_of_cpu(0));
+       set_cpus_allowed_ptr(current, cpumask_of(0));
        BUG_ON(smp_processor_id() != 0);
-#endif
 
        if (apm_info.connection_version == 0) {
                apm_info.connection_version = apm_info.bios.version;