Commit 8f5c7579 authored by Nathan Lynch's avatar Nathan Lynch Committed by Paul Mackerras

[POWERPC] Fix multiple bugs in rtas_ibm_suspend_me code

There are several issues with the rtas_ibm_suspend_me code, which
enables platform-assisted suspension of an LPAR as covered in PAPR
2.2.

1.) rtas_ibm_suspend_me uses on_each_cpu() to invoke
rtas_percpu_suspend_me on all cpus via IPI:

if (on_each_cpu(rtas_percpu_suspend_me, &data, 1, 0))
...

'data' is on the calling task's stack, but rtas_ibm_suspend_me takes
no measures to ensure that all instances of rtas_percpu_suspend_me are
finished accessing 'data' before returning.  This can result in the
IPI'd cpus accessing random stack data and getting stuck in H_JOIN.

This is addressed by using an atomic count of workers and a completion
on the stack.

2.) rtas_percpu_suspend_me is needlessly calling H_JOIN in a loop.
The only event that can cause a cpu to return from H_JOIN is an H_PROD
from another cpu or a NMI/system reset.  Each cpu need call H_JOIN
only once per suspend operation.

Remove the loop and the now unnecessary 'waiting' state variable.

3.) H_JOIN must be called with MSR[EE] off, but lazy interrupt
disabling may cause the caller of rtas_ibm_suspend_me to call H_JOIN
with it on; the local_irq_disable() in on_each_cpu() is not
sufficient.

Fix this by explicitly saving the MSR and clearing the EE bit before
calling H_JOIN.

4.) H_PROD is being called with the Linux logical cpu number as the
parameter, not the platform interrupt server value.  (It's also being
called for all possible cpus, which is harmless, but unnecessary.)

This is fixed by calling H_PROD for each online cpu using
get_hard_smp_processor_id(cpu) for the argument.
Signed-off-by: default avatarNathan Lynch <ntl@pobox.com>
Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
parent 2ffbb837
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/capability.h> #include <linux/capability.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/smp.h>
#include <linux/completion.h>
#include <linux/cpumask.h>
#include <asm/prom.h> #include <asm/prom.h>
#include <asm/rtas.h> #include <asm/rtas.h>
...@@ -34,6 +37,8 @@ ...@@ -34,6 +37,8 @@
#include <asm/lmb.h> #include <asm/lmb.h>
#include <asm/udbg.h> #include <asm/udbg.h>
#include <asm/syscalls.h> #include <asm/syscalls.h>
#include <asm/smp.h>
#include <asm/atomic.h>
struct rtas_t rtas = { struct rtas_t rtas = {
.lock = SPIN_LOCK_UNLOCKED .lock = SPIN_LOCK_UNLOCKED
...@@ -41,8 +46,10 @@ struct rtas_t rtas = { ...@@ -41,8 +46,10 @@ struct rtas_t rtas = {
EXPORT_SYMBOL(rtas); EXPORT_SYMBOL(rtas);
struct rtas_suspend_me_data { struct rtas_suspend_me_data {
long waiting; atomic_t working; /* number of cpus accessing this struct */
struct rtas_args *args; int token; /* ibm,suspend-me */
int error;
struct completion *complete; /* wait on this until working == 0 */
}; };
DEFINE_SPINLOCK(rtas_data_buf_lock); DEFINE_SPINLOCK(rtas_data_buf_lock);
...@@ -657,50 +664,62 @@ static int ibm_suspend_me_token = RTAS_UNKNOWN_SERVICE; ...@@ -657,50 +664,62 @@ static int ibm_suspend_me_token = RTAS_UNKNOWN_SERVICE;
#ifdef CONFIG_PPC_PSERIES #ifdef CONFIG_PPC_PSERIES
static void rtas_percpu_suspend_me(void *info) static void rtas_percpu_suspend_me(void *info)
{ {
int i;
long rc; long rc;
long flags; unsigned long msr_save;
int cpu;
struct rtas_suspend_me_data *data = struct rtas_suspend_me_data *data =
(struct rtas_suspend_me_data *)info; (struct rtas_suspend_me_data *)info;
/* atomic_inc(&data->working);
* We use "waiting" to indicate our state. As long
* as it is >0, we are still trying to all join up. /* really need to ensure MSR.EE is off for H_JOIN */
* If it goes to 0, we have successfully joined up and msr_save = mfmsr();
* one thread got H_CONTINUE. If any error happens, mtmsr(msr_save & ~(MSR_EE));
* we set it to <0.
*/ rc = plpar_hcall_norets(H_JOIN);
local_irq_save(flags);
do { mtmsr(msr_save);
rc = plpar_hcall_norets(H_JOIN);
smp_rmb();
} while (rc == H_SUCCESS && data->waiting > 0);
if (rc == H_SUCCESS)
goto out;
if (rc == H_CONTINUE) { if (rc == H_SUCCESS) {
data->waiting = 0; /* This cpu was prodded and the suspend is complete. */
data->args->args[data->args->nargs] = goto out;
rtas_call(ibm_suspend_me_token, 0, 1, NULL); } else if (rc == H_CONTINUE) {
for_each_possible_cpu(i) /* All other cpus are in H_JOIN, this cpu does
plpar_hcall_norets(H_PROD,i); * the suspend.
*/
printk(KERN_DEBUG "calling ibm,suspend-me on cpu %i\n",
smp_processor_id());
data->error = rtas_call(data->token, 0, 1, NULL);
if (data->error)
printk(KERN_DEBUG "ibm,suspend-me returned %d\n",
data->error);
} else { } else {
data->waiting = -EBUSY; printk(KERN_ERR "H_JOIN on cpu %i failed with rc = %ld\n",
printk(KERN_ERR "Error on H_JOIN hypervisor call\n"); smp_processor_id(), rc);
data->error = rc;
} }
/* This cpu did the suspend or got an error; in either case,
* we need to prod all other other cpus out of join state.
* Extra prods are harmless.
*/
for_each_online_cpu(cpu)
plpar_hcall_norets(H_PROD, get_hard_smp_processor_id(cpu));
out: out:
local_irq_restore(flags); if (atomic_dec_return(&data->working) == 0)
return; complete(data->complete);
} }
static int rtas_ibm_suspend_me(struct rtas_args *args) static int rtas_ibm_suspend_me(struct rtas_args *args)
{ {
int i;
long state; long state;
long rc; long rc;
unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; unsigned long retbuf[PLPAR_HCALL_BUFSIZE];
struct rtas_suspend_me_data data; struct rtas_suspend_me_data data;
DECLARE_COMPLETION_ONSTACK(done);
if (!rtas_service_present("ibm,suspend-me"))
return -ENOSYS;
/* Make sure the state is valid */ /* Make sure the state is valid */
rc = plpar_hcall(H_VASI_STATE, retbuf, rc = plpar_hcall(H_VASI_STATE, retbuf,
...@@ -721,25 +740,23 @@ static int rtas_ibm_suspend_me(struct rtas_args *args) ...@@ -721,25 +740,23 @@ static int rtas_ibm_suspend_me(struct rtas_args *args)
return 0; return 0;
} }
data.waiting = 1; atomic_set(&data.working, 0);
data.args = args; data.token = rtas_token("ibm,suspend-me");
data.error = 0;
data.complete = &done;
/* Call function on all CPUs. One of us will make the /* Call function on all CPUs. One of us will make the
* rtas call * rtas call
*/ */
if (on_each_cpu(rtas_percpu_suspend_me, &data, 1, 0)) if (on_each_cpu(rtas_percpu_suspend_me, &data, 1, 0))
data.waiting = -EINVAL; data.error = -EINVAL;
if (data.waiting != 0) wait_for_completion(&done);
printk(KERN_ERR "Error doing global join\n");
/* Prod each CPU. This won't hurt, and will wake if (data.error != 0)
* anyone we successfully put to sleep with H_JOIN. printk(KERN_ERR "Error doing global join\n");
*/
for_each_possible_cpu(i)
plpar_hcall_norets(H_PROD, i);
return data.waiting; return data.error;
} }
#else /* CONFIG_PPC_PSERIES */ #else /* CONFIG_PPC_PSERIES */
static int rtas_ibm_suspend_me(struct rtas_args *args) static int rtas_ibm_suspend_me(struct rtas_args *args)
......
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