#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/capability.h>
#include <linux/delay.h>
#include <asm/prom.h>
#include <asm/rtas.h>
+#include <asm/hvcall.h>
#include <asm/semaphore.h>
#include <asm/machdep.h>
+#include <asm/firmware.h>
#include <asm/page.h>
#include <asm/param.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/lmb.h>
#include <asm/udbg.h>
+#include <asm/syscalls.h>
struct rtas_t rtas = {
.lock = SPIN_LOCK_UNLOCKED
};
+struct rtas_suspend_me_data {
+ long waiting;
+ struct rtas_args *args;
+};
+
EXPORT_SYMBOL(rtas);
DEFINE_SPINLOCK(rtas_data_buf_lock);
return ret;
}
-/* Given an RTAS status code of 990n compute the hinted delay of 10^n
- * (last digit) milliseconds. For now we bound at n=5 (100 sec).
+/* For RTAS_BUSY (-2), delay for 1 millisecond. For an extended busy status
+ * code of 990n, perform the hinted delay of 10^n (last digit) milliseconds.
*/
-unsigned int rtas_extended_busy_delay_time(int status)
+unsigned int rtas_busy_delay_time(int status)
{
- int order = status - 9900;
- unsigned long ms;
+ int order;
+ unsigned int ms = 0;
+
+ if (status == RTAS_BUSY) {
+ ms = 1;
+ } else if (status >= 9900 && status <= 9905) {
+ order = status - 9900;
+ for (ms = 1; order > 0; order--)
+ ms *= 10;
+ }
- if (order < 0)
- order = 0; /* RTC depends on this for -2 clock busy */
- else if (order > 5)
- order = 5; /* bound */
+ return ms;
+}
- /* Use microseconds for reasonable accuracy */
- for (ms = 1; order > 0; order--)
- ms *= 10;
+/* For an RTAS busy status code, perform the hinted delay. */
+unsigned int rtas_busy_delay(int status)
+{
+ unsigned int ms;
+
+ might_sleep();
+ ms = rtas_busy_delay_time(status);
+ if (ms)
+ msleep(ms);
- return ms;
+ return ms;
}
int rtas_error_rc(int rtas_rc)
int rtas_set_power_level(int powerdomain, int level, int *setlevel)
{
int token = rtas_token("set-power-level");
- unsigned int wait_time;
int rc;
if (token == RTAS_UNKNOWN_SERVICE)
return -ENOENT;
- while (1) {
+ do {
rc = rtas_call(token, 2, 2, setlevel, powerdomain, level);
- if (rc == RTAS_BUSY)
- udelay(1);
- else if (rtas_is_extended_busy(rc)) {
- wait_time = rtas_extended_busy_delay_time(rc);
- udelay(wait_time * 1000);
- } else
- break;
- }
+ } while (rtas_busy_delay(rc));
if (rc < 0)
return rtas_error_rc(rc);
int rtas_get_sensor(int sensor, int index, int *state)
{
int token = rtas_token("get-sensor-state");
- unsigned int wait_time;
int rc;
if (token == RTAS_UNKNOWN_SERVICE)
return -ENOENT;
- while (1) {
+ do {
rc = rtas_call(token, 2, 2, state, sensor, index);
- if (rc == RTAS_BUSY)
- udelay(1);
- else if (rtas_is_extended_busy(rc)) {
- wait_time = rtas_extended_busy_delay_time(rc);
- udelay(wait_time * 1000);
- } else
- break;
- }
+ } while (rtas_busy_delay(rc));
if (rc < 0)
return rtas_error_rc(rc);
int rtas_set_indicator(int indicator, int index, int new_value)
{
int token = rtas_token("set-indicator");
- unsigned int wait_time;
int rc;
if (token == RTAS_UNKNOWN_SERVICE)
return -ENOENT;
- while (1) {
+ do {
rc = rtas_call(token, 3, 1, NULL, indicator, index, new_value);
- if (rc == RTAS_BUSY)
- udelay(1);
- else if (rtas_is_extended_busy(rc)) {
- wait_time = rtas_extended_busy_delay_time(rc);
- udelay(wait_time * 1000);
- }
- else
- break;
- }
+ } while (rtas_busy_delay(rc));
if (rc < 0)
return rtas_error_rc(rc);
do {
status = rtas_call(rtas_token("ibm,os-term"), 1, 1, NULL,
__pa(rtas_os_term_buf));
+ } while (rtas_busy_delay(status));
- if (status == RTAS_BUSY)
- udelay(1);
- else if (status != 0)
- printk(KERN_EMERG "ibm,os-term call failed %d\n",
+ if (status != 0)
+ printk(KERN_EMERG "ibm,os-term call failed %d\n",
status);
- } while (status == RTAS_BUSY);
}
+static int ibm_suspend_me_token = RTAS_UNKNOWN_SERVICE;
+#ifdef CONFIG_PPC_PSERIES
+static void rtas_percpu_suspend_me(void *info)
+{
+ int i;
+ long rc;
+ long flags;
+ struct rtas_suspend_me_data *data =
+ (struct rtas_suspend_me_data *)info;
+
+ /*
+ * We use "waiting" to indicate our state. As long
+ * as it is >0, we are still trying to all join up.
+ * If it goes to 0, we have successfully joined up and
+ * one thread got H_CONTINUE. If any error happens,
+ * we set it to <0.
+ */
+ local_irq_save(flags);
+ do {
+ 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) {
+ data->waiting = 0;
+ data->args->args[data->args->nargs] =
+ rtas_call(ibm_suspend_me_token, 0, 1, NULL);
+ for_each_possible_cpu(i)
+ plpar_hcall_norets(H_PROD,i);
+ } else {
+ data->waiting = -EBUSY;
+ printk(KERN_ERR "Error on H_JOIN hypervisor call\n");
+ }
+
+out:
+ local_irq_restore(flags);
+ return;
+}
+
+static int rtas_ibm_suspend_me(struct rtas_args *args)
+{
+ int i;
+ long state;
+ long rc;
+ unsigned long dummy;
+
+ struct rtas_suspend_me_data data;
+
+ /* Make sure the state is valid */
+ rc = plpar_hcall(H_VASI_STATE,
+ ((u64)args->args[0] << 32) | args->args[1],
+ 0, 0, 0,
+ &state, &dummy, &dummy);
+
+ if (rc) {
+ printk(KERN_ERR "rtas_ibm_suspend_me: vasi_state returned %ld\n",rc);
+ return rc;
+ } else if (state == H_VASI_ENABLED) {
+ args->args[args->nargs] = RTAS_NOT_SUSPENDABLE;
+ return 0;
+ } else if (state != H_VASI_SUSPENDING) {
+ printk(KERN_ERR "rtas_ibm_suspend_me: vasi_state returned state %ld\n",
+ state);
+ args->args[args->nargs] = -1;
+ return 0;
+ }
+
+ data.waiting = 1;
+ data.args = args;
+
+ /* Call function on all CPUs. One of us will make the
+ * rtas call
+ */
+ if (on_each_cpu(rtas_percpu_suspend_me, &data, 1, 0))
+ data.waiting = -EINVAL;
+
+ if (data.waiting != 0)
+ printk(KERN_ERR "Error doing global join\n");
+
+ /* Prod each CPU. This won't hurt, and will wake
+ * anyone we successfully put to sleep with H_JOIN.
+ */
+ for_each_possible_cpu(i)
+ plpar_hcall_norets(H_PROD, i);
+
+ return data.waiting;
+}
+#else /* CONFIG_PPC_PSERIES */
+static int rtas_ibm_suspend_me(struct rtas_args *args)
+{
+ return -ENOSYS;
+}
+#endif
asmlinkage int ppc_rtas(struct rtas_args __user *uargs)
{
unsigned long flags;
char *buff_copy, *errbuf = NULL;
int nargs;
+ int rc;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
nargs * sizeof(rtas_arg_t)) != 0)
return -EFAULT;
+ if (args.token == RTAS_UNKNOWN_SERVICE)
+ return -EINVAL;
+
+ /* Need to handle ibm,suspend_me call specially */
+ if (args.token == ibm_suspend_me_token) {
+ rc = rtas_ibm_suspend_me(&args);
+ if (rc)
+ return rc;
+ goto copy_return;
+ }
+
buff_copy = get_errorlog_buffer();
spin_lock_irqsave(&rtas.lock, flags);
kfree(buff_copy);
}
+ copy_return:
/* Copy out args. */
if (copy_to_user(uargs->args + nargs,
args.args + nargs,
* the stop-self token if any
*/
#ifdef CONFIG_PPC64
- if (_machine == PLATFORM_PSERIES_LPAR)
+ if (machine_is(pseries) && firmware_has_feature(FW_FEATURE_LPAR)) {
rtas_region = min(lmb.rmo_size, RTAS_INSTANTIATE_MAX);
+ ibm_suspend_me_token = rtas_token("ibm,suspend-me");
+ }
#endif
rtas_rmo_buf = lmb_alloc_base(RTAS_RMOBUF_MAX, PAGE_SIZE, rtas_region);
EXPORT_SYMBOL(rtas_call);
EXPORT_SYMBOL(rtas_data_buf);
EXPORT_SYMBOL(rtas_data_buf_lock);
-EXPORT_SYMBOL(rtas_extended_busy_delay_time);
+EXPORT_SYMBOL(rtas_busy_delay_time);
+EXPORT_SYMBOL(rtas_busy_delay);
EXPORT_SYMBOL(rtas_get_sensor);
EXPORT_SYMBOL(rtas_get_power_level);
EXPORT_SYMBOL(rtas_set_power_level);