Merge commit 'v2.6.36-rc3' into x86/memblock
[linux-2.6.git] / arch / powerpc / kernel / rtas.c
index 1662777..7333fdb 100644 (file)
@@ -47,14 +47,6 @@ struct rtas_t rtas = {
 };
 EXPORT_SYMBOL(rtas);
 
-struct rtas_suspend_me_data {
-       atomic_t working; /* number of cpus accessing this struct */
-       atomic_t done;
-       int token; /* ibm,suspend-me */
-       int error;
-       struct completion *complete; /* wait on this until working == 0 */
-};
-
 DEFINE_SPINLOCK(rtas_data_buf_lock);
 EXPORT_SYMBOL(rtas_data_buf_lock);
 
@@ -714,14 +706,53 @@ void rtas_os_term(char *str)
 
 static int ibm_suspend_me_token = RTAS_UNKNOWN_SERVICE;
 #ifdef CONFIG_PPC_PSERIES
-static void rtas_percpu_suspend_me(void *info)
+static int __rtas_suspend_last_cpu(struct rtas_suspend_me_data *data, int wake_when_done)
+{
+       u16 slb_size = mmu_slb_size;
+       int rc = H_MULTI_THREADS_ACTIVE;
+       int cpu;
+
+       slb_set_size(SLB_MIN_SIZE);
+       printk(KERN_DEBUG "calling ibm,suspend-me on cpu %i\n", smp_processor_id());
+
+       while (rc == H_MULTI_THREADS_ACTIVE && !atomic_read(&data->done) &&
+              !atomic_read(&data->error))
+               rc = rtas_call(data->token, 0, 1, NULL);
+
+       if (rc || atomic_read(&data->error)) {
+               printk(KERN_DEBUG "ibm,suspend-me returned %d\n", rc);
+               slb_set_size(slb_size);
+       }
+
+       if (atomic_read(&data->error))
+               rc = atomic_read(&data->error);
+
+       atomic_set(&data->error, rc);
+
+       if (wake_when_done) {
+               atomic_set(&data->done, 1);
+
+               for_each_online_cpu(cpu)
+                       plpar_hcall_norets(H_PROD, get_hard_smp_processor_id(cpu));
+       }
+
+       if (atomic_dec_return(&data->working) == 0)
+               complete(data->complete);
+
+       return rc;
+}
+
+int rtas_suspend_last_cpu(struct rtas_suspend_me_data *data)
+{
+       atomic_inc(&data->working);
+       return __rtas_suspend_last_cpu(data, 0);
+}
+
+static int __rtas_suspend_cpu(struct rtas_suspend_me_data *data, int wake_when_done)
 {
        long rc = H_SUCCESS;
        unsigned long msr_save;
-       u16 slb_size = mmu_slb_size;
        int cpu;
-       struct rtas_suspend_me_data *data =
-               (struct rtas_suspend_me_data *)info;
 
        atomic_inc(&data->working);
 
@@ -729,7 +760,7 @@ static void rtas_percpu_suspend_me(void *info)
        msr_save = mfmsr();
        mtmsr(msr_save & ~(MSR_EE));
 
-       while (rc == H_SUCCESS && !atomic_read(&data->done))
+       while (rc == H_SUCCESS && !atomic_read(&data->done) && !atomic_read(&data->error))
                rc = plpar_hcall_norets(H_JOIN);
 
        mtmsr(msr_save);
@@ -741,33 +772,37 @@ static void rtas_percpu_suspend_me(void *info)
                /* All other cpus are in H_JOIN, this cpu does
                 * the suspend.
                 */
-               slb_set_size(SLB_MIN_SIZE);
-               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);
-                       slb_set_size(slb_size);
-               }
+               return __rtas_suspend_last_cpu(data, wake_when_done);
        } else {
                printk(KERN_ERR "H_JOIN on cpu %i failed with rc = %ld\n",
                       smp_processor_id(), rc);
-               data->error = rc;
+               atomic_set(&data->error, rc);
        }
 
-       atomic_set(&data->done, 1);
+       if (wake_when_done) {
+               atomic_set(&data->done, 1);
 
-       /* 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));
+               /* 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:
        if (atomic_dec_return(&data->working) == 0)
                complete(data->complete);
+       return rc;
+}
+
+int rtas_suspend_cpu(struct rtas_suspend_me_data *data)
+{
+       return __rtas_suspend_cpu(data, 0);
+}
+
+static void rtas_percpu_suspend_me(void *info)
+{
+       __rtas_suspend_cpu((struct rtas_suspend_me_data *)info, 1);
 }
 
 static int rtas_ibm_suspend_me(struct rtas_args *args)
@@ -802,22 +837,22 @@ static int rtas_ibm_suspend_me(struct rtas_args *args)
 
        atomic_set(&data.working, 0);
        atomic_set(&data.done, 0);
+       atomic_set(&data.error, 0);
        data.token = rtas_token("ibm,suspend-me");
-       data.error = 0;
        data.complete = &done;
 
        /* Call function on all CPUs.  One of us will make the
         * rtas call
         */
        if (on_each_cpu(rtas_percpu_suspend_me, &data, 0))
-               data.error = -EINVAL;
+               atomic_set(&data.error, -EINVAL);
 
        wait_for_completion(&done);
 
-       if (data.error != 0)
+       if (atomic_read(&data.error) != 0)
                printk(KERN_ERR "Error doing global join\n");
 
-       return data.error;
+       return atomic_read(&data.error);
 }
 #else /* CONFIG_PPC_PSERIES */
 static int rtas_ibm_suspend_me(struct rtas_args *args)