Commit 89bd55d1 authored by Rusty Russell's avatar Rusty Russell Committed by Ingo Molnar

x86: cpumask: update 32-bit APM not to mug current->cpus_allowed

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