diff options
Diffstat (limited to 'arch/powerpc/kernel/rtas.c')
| -rw-r--r-- | arch/powerpc/kernel/rtas.c | 842 |
1 files changed, 676 insertions, 166 deletions
diff --git a/arch/powerpc/kernel/rtas.c b/arch/powerpc/kernel/rtas.c index b7fc2d88495..8b4c857c142 100644 --- a/arch/powerpc/kernel/rtas.c +++ b/arch/powerpc/kernel/rtas.c @@ -15,31 +15,46 @@ #include <linux/kernel.h> #include <linux/types.h> #include <linux/spinlock.h> -#include <linux/module.h> +#include <linux/export.h> #include <linux/init.h> +#include <linux/capability.h> +#include <linux/delay.h> +#include <linux/cpu.h> +#include <linux/smp.h> +#include <linux/completion.h> +#include <linux/cpumask.h> +#include <linux/memblock.h> +#include <linux/slab.h> +#include <linux/reboot.h> #include <asm/prom.h> #include <asm/rtas.h> -#include <asm/semaphore.h> +#include <asm/hvcall.h> #include <asm/machdep.h> +#include <asm/firmware.h> #include <asm/page.h> #include <asm/param.h> -#include <asm/system.h> #include <asm/delay.h> #include <asm/uaccess.h> -#include <asm/lmb.h> -#ifdef CONFIG_PPC64 -#include <asm/systemcfg.h> -#endif +#include <asm/udbg.h> +#include <asm/syscalls.h> +#include <asm/smp.h> +#include <linux/atomic.h> +#include <asm/time.h> +#include <asm/mmu.h> +#include <asm/topology.h> struct rtas_t rtas = { - .lock = SPIN_LOCK_UNLOCKED + .lock = __ARCH_SPIN_LOCK_UNLOCKED }; - EXPORT_SYMBOL(rtas); DEFINE_SPINLOCK(rtas_data_buf_lock); +EXPORT_SYMBOL(rtas_data_buf_lock); + char rtas_data_buf[RTAS_DATA_BUF_SIZE] __cacheline_aligned; +EXPORT_SYMBOL(rtas_data_buf); + unsigned long rtas_rmo_buf; /* @@ -49,32 +64,54 @@ unsigned long rtas_rmo_buf; void (*rtas_flash_term_hook)(int); EXPORT_SYMBOL(rtas_flash_term_hook); +/* RTAS use home made raw locking instead of spin_lock_irqsave + * because those can be called from within really nasty contexts + * such as having the timebase stopped which would lockup with + * normal locks and spinlock debugging enabled + */ +static unsigned long lock_rtas(void) +{ + unsigned long flags; + + local_irq_save(flags); + preempt_disable(); + arch_spin_lock_flags(&rtas.lock, flags); + return flags; +} + +static void unlock_rtas(unsigned long flags) +{ + arch_spin_unlock(&rtas.lock); + local_irq_restore(flags); + preempt_enable(); +} + /* * call_rtas_display_status and call_rtas_display_status_delay * are designed only for very early low-level debugging, which * is why the token is hard-coded to 10. */ -void call_rtas_display_status(unsigned char c) +static void call_rtas_display_status(unsigned char c) { struct rtas_args *args = &rtas.args; unsigned long s; if (!rtas.base) return; - spin_lock_irqsave(&rtas.lock, s); + s = lock_rtas(); - args->token = 10; - args->nargs = 1; - args->nret = 1; - args->rets = (rtas_arg_t *)&(args->args[1]); - args->args[0] = (int)c; + args->token = cpu_to_be32(10); + args->nargs = cpu_to_be32(1); + args->nret = cpu_to_be32(1); + args->rets = &(args->args[1]); + args->args[0] = cpu_to_be32(c); enter_rtas(__pa(args)); - spin_unlock_irqrestore(&rtas.lock, s); + unlock_rtas(s); } -void call_rtas_display_status_delay(unsigned char c) +static void call_rtas_display_status_delay(char c) { static int pending_newline = 0; /* did last write end with unprinted newline? */ static int width = 16; @@ -83,7 +120,7 @@ void call_rtas_display_status_delay(unsigned char c) while (width-- > 0) call_rtas_display_status(' '); width = 16; - udelay(500000); + mdelay(500); pending_newline = 1; } else { if (pending_newline) { @@ -98,13 +135,80 @@ void call_rtas_display_status_delay(unsigned char c) } } +void __init udbg_init_rtas_panel(void) +{ + udbg_putc = call_rtas_display_status_delay; +} + +#ifdef CONFIG_UDBG_RTAS_CONSOLE + +/* If you think you're dying before early_init_dt_scan_rtas() does its + * work, you can hard code the token values for your firmware here and + * hardcode rtas.base/entry etc. + */ +static unsigned int rtas_putchar_token = RTAS_UNKNOWN_SERVICE; +static unsigned int rtas_getchar_token = RTAS_UNKNOWN_SERVICE; + +static void udbg_rtascon_putc(char c) +{ + int tries; + + if (!rtas.base) + return; + + /* Add CRs before LFs */ + if (c == '\n') + udbg_rtascon_putc('\r'); + + /* if there is more than one character to be displayed, wait a bit */ + for (tries = 0; tries < 16; tries++) { + if (rtas_call(rtas_putchar_token, 1, 1, NULL, c) == 0) + break; + udelay(1000); + } +} + +static int udbg_rtascon_getc_poll(void) +{ + int c; + + if (!rtas.base) + return -1; + + if (rtas_call(rtas_getchar_token, 0, 2, &c)) + return -1; + + return c; +} + +static int udbg_rtascon_getc(void) +{ + int c; + + while ((c = udbg_rtascon_getc_poll()) == -1) + ; + + return c; +} + + +void __init udbg_init_rtas_console(void) +{ + udbg_putc = udbg_rtascon_putc; + udbg_getc = udbg_rtascon_getc; + udbg_getc_poll = udbg_rtascon_getc_poll; +} +#endif /* CONFIG_UDBG_RTAS_CONSOLE */ + void rtas_progress(char *s, unsigned short hex) { struct device_node *root; - int width, *p; + int width; + const __be32 *p; char *os; static int display_character, set_indicator; - static int display_width, display_lines, *row_width, form_feed; + static int display_width, display_lines, form_feed; + static const int *row_width; static DEFINE_SPINLOCK(progress_lock); static int current_line; static int pending_newline = 0; /* did last write end with unprinted newline? */ @@ -114,18 +218,19 @@ void rtas_progress(char *s, unsigned short hex) if (display_width == 0) { display_width = 0x10; - if ((root = find_path_device("/rtas"))) { - if ((p = (unsigned int *)get_property(root, + if ((root = of_find_node_by_path("/rtas"))) { + if ((p = of_get_property(root, "ibm,display-line-length", NULL))) - display_width = *p; - if ((p = (unsigned int *)get_property(root, + display_width = be32_to_cpu(*p); + if ((p = of_get_property(root, "ibm,form-feed", NULL))) - form_feed = *p; - if ((p = (unsigned int *)get_property(root, + form_feed = be32_to_cpu(*p); + if ((p = of_get_property(root, "ibm,display-number-of-lines", NULL))) - display_lines = *p; - row_width = (unsigned int *)get_property(root, + display_lines = be32_to_cpu(*p); + row_width = of_get_property(root, "ibm,display-truncation-length", NULL); + of_node_put(root); } display_character = rtas_token("display-character"); set_indicator = rtas_token("set-indicator"); @@ -217,12 +322,19 @@ EXPORT_SYMBOL(rtas_progress); /* needed by rtas_flash module */ int rtas_token(const char *service) { - int *tokp; + const __be32 *tokp; if (rtas.dev == NULL) return RTAS_UNKNOWN_SERVICE; - tokp = (int *) get_property(rtas.dev, service, NULL); - return tokp ? *tokp : RTAS_UNKNOWN_SERVICE; + tokp = of_get_property(rtas.dev, service, NULL); + return tokp ? be32_to_cpu(*tokp) : RTAS_UNKNOWN_SERVICE; +} +EXPORT_SYMBOL(rtas_token); + +int rtas_service_present(const char *service) +{ + return rtas_token(service) != RTAS_UNKNOWN_SERVICE; } +EXPORT_SYMBOL(rtas_service_present); #ifdef CONFIG_RTAS_ERROR_LOGGING /* @@ -248,8 +360,8 @@ int rtas_get_error_log_max(void) EXPORT_SYMBOL(rtas_get_error_log_max); -char rtas_err_buf[RTAS_ERROR_LOG_MAX]; -int rtas_last_error_token; +static char rtas_err_buf[RTAS_ERROR_LOG_MAX]; +static int rtas_last_error_token; /** Return a copy of the detailed error text associated with the * most recent failed call to rtas. Because the error text @@ -268,11 +380,11 @@ static char *__fetch_rtas_last_error(char *altbuf) bufsz = rtas_get_error_log_max(); - err_args.token = rtas_last_error_token; - err_args.nargs = 2; - err_args.nret = 1; - err_args.args[0] = (rtas_arg_t)__pa(rtas_err_buf); - err_args.args[1] = bufsz; + err_args.token = cpu_to_be32(rtas_last_error_token); + err_args.nargs = cpu_to_be32(2); + err_args.nret = cpu_to_be32(1); + err_args.args[0] = cpu_to_be32(__pa(rtas_err_buf)); + err_args.args[1] = cpu_to_be32(bufsz); err_args.args[2] = 0; save_args = rtas.args; @@ -315,20 +427,19 @@ int rtas_call(int token, int nargs, int nret, int *outputs, ...) char *buff_copy = NULL; int ret; - if (token == RTAS_UNKNOWN_SERVICE) + if (!rtas.entry || token == RTAS_UNKNOWN_SERVICE) return -1; - /* Gotta do something different here, use global lock for now... */ - spin_lock_irqsave(&rtas.lock, s); + s = lock_rtas(); rtas_args = &rtas.args; - rtas_args->token = token; - rtas_args->nargs = nargs; - rtas_args->nret = nret; - rtas_args->rets = (rtas_arg_t *)&(rtas_args->args[nargs]); + rtas_args->token = cpu_to_be32(token); + rtas_args->nargs = cpu_to_be32(nargs); + rtas_args->nret = cpu_to_be32(nret); + rtas_args->rets = &(rtas_args->args[nargs]); va_start(list, outputs); for (i = 0; i < nargs; ++i) - rtas_args->args[i] = va_arg(list, rtas_arg_t); + rtas_args->args[i] = cpu_to_be32(va_arg(list, __u32)); va_end(list); for (i = 0; i < nret; ++i) @@ -338,16 +449,15 @@ int rtas_call(int token, int nargs, int nret, int *outputs, ...) /* A -1 return code indicates that the last command couldn't be completed due to a hardware error. */ - if (rtas_args->rets[0] == -1) + if (be32_to_cpu(rtas_args->rets[0]) == -1) buff_copy = __fetch_rtas_last_error(NULL); if (nret > 1 && outputs != NULL) for (i = 0; i < nret-1; ++i) - outputs[i] = rtas_args->rets[i+1]; - ret = (nret > 0)? rtas_args->rets[0]: 0; + outputs[i] = be32_to_cpu(rtas_args->rets[i+1]); + ret = (nret > 0)? be32_to_cpu(rtas_args->rets[0]): 0; - /* Gotta do something different here, use global lock for now... */ - spin_unlock_irqrestore(&rtas.lock, s); + unlock_rtas(s); if (buff_copy) { log_error(buff_copy, ERR_TYPE_RTAS_LOG, 0); @@ -356,28 +466,43 @@ int rtas_call(int token, int nargs, int nret, int *outputs, ...) } return ret; } +EXPORT_SYMBOL(rtas_call); -/* 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; + } + + return ms; +} +EXPORT_SYMBOL(rtas_busy_delay_time); - if (order < 0) - order = 0; /* RTC depends on this for -2 clock busy */ - else if (order > 5) - order = 5; /* bound */ +/* For an RTAS busy status code, perform the hinted delay. */ +unsigned int rtas_busy_delay(int status) +{ + unsigned int ms; - /* Use microseconds for reasonable accuracy */ - for (ms = 1; order > 0; order--) - ms *= 10; + might_sleep(); + ms = rtas_busy_delay_time(status); + if (ms && need_resched()) + msleep(ms); - return ms; + return ms; } +EXPORT_SYMBOL(rtas_busy_delay); -int rtas_error_rc(int rtas_rc) +static int rtas_error_rc(int rtas_rc) { int rc; @@ -399,7 +524,7 @@ int rtas_error_rc(int rtas_rc) break; default: printk(KERN_ERR "%s: unexpected RTAS error %d\n", - __FUNCTION__, rtas_rc); + __func__, rtas_rc); rc = -ERANGE; break; } @@ -421,80 +546,106 @@ int rtas_get_power_level(int powerdomain, int *level) return rtas_error_rc(rc); return rc; } +EXPORT_SYMBOL(rtas_get_power_level); 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); return rc; } +EXPORT_SYMBOL(rtas_set_power_level); 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); return rc; } +EXPORT_SYMBOL(rtas_get_sensor); + +bool rtas_indicator_present(int token, int *maxindex) +{ + int proplen, count, i; + const struct indicator_elem { + __be32 token; + __be32 maxindex; + } *indicators; + + indicators = of_get_property(rtas.dev, "rtas-indicators", &proplen); + if (!indicators) + return false; + + count = proplen / sizeof(struct indicator_elem); + + for (i = 0; i < count; i++) { + if (__be32_to_cpu(indicators[i].token) != token) + continue; + if (maxindex) + *maxindex = __be32_to_cpu(indicators[i].maxindex); + return true; + } + + return false; +} +EXPORT_SYMBOL(rtas_indicator_present); 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); + return rc; +} +EXPORT_SYMBOL(rtas_set_indicator); + +/* + * Ignoring RTAS extended delay + */ +int rtas_set_indicator_fast(int indicator, int index, int new_value) +{ + int rc; + int token = rtas_token("set-indicator"); + + if (token == RTAS_UNKNOWN_SERVICE) + return -ENOENT; + + rc = rtas_call(token, 3, 1, NULL, indicator, index, new_value); + + WARN_ON(rc == -2 || (rc >= 9900 && rc <= 9905)); if (rc < 0) return rtas_error_rc(rc); + return rc; } @@ -534,7 +685,14 @@ void rtas_os_term(char *str) { int status; - if (RTAS_UNKNOWN_SERVICE == rtas_token("ibm,os-term")) + /* + * Firmware with the ibm,extended-os-term property is guaranteed + * to always return from an ibm,os-term call. Earlier versions without + * this property may terminate the partition which we want to avoid + * since it interferes with panic_timeout. + */ + if (RTAS_UNKNOWN_SERVICE == rtas_token("ibm,os-term") || + RTAS_UNKNOWN_SERVICE == rtas_token("ibm,extended-os-term")) return; snprintf(rtas_os_term_buf, 2048, "OS panic: %s", str); @@ -542,22 +700,330 @@ void rtas_os_term(char *str) do { status = rtas_call(rtas_token("ibm,os-term"), 1, 1, NULL, __pa(rtas_os_term_buf)); + } while (rtas_busy_delay(status)); + + if (status != 0) + printk(KERN_EMERG "ibm,os-term call failed %d\n", status); +} + +static int ibm_suspend_me_token = RTAS_UNKNOWN_SERVICE; +#ifdef CONFIG_PPC_PSERIES +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); + pSeries_coalesce_init(); + + 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; + int cpu; + + atomic_inc(&data->working); + + /* really need to ensure MSR.EE is off for H_JOIN */ + msr_save = mfmsr(); + mtmsr(msr_save & ~(MSR_EE)); - if (status == RTAS_BUSY) - udelay(1); - else if (status != 0) - printk(KERN_EMERG "ibm,os-term call failed %d\n", - status); - } while (status == RTAS_BUSY); + while (rc == H_SUCCESS && !atomic_read(&data->done) && !atomic_read(&data->error)) + rc = plpar_hcall_norets(H_JOIN); + + mtmsr(msr_save); + + if (rc == H_SUCCESS) { + /* This cpu was prodded and the suspend is complete. */ + goto out; + } else if (rc == H_CONTINUE) { + /* All other cpus are in H_JOIN, this cpu does + * the suspend. + */ + 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); + atomic_set(&data->error, rc); + } + + 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)); + } +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); +} + +enum rtas_cpu_state { + DOWN, + UP, +}; + +#ifndef CONFIG_SMP +static int rtas_cpu_state_change_mask(enum rtas_cpu_state state, + cpumask_var_t cpus) +{ + if (!cpumask_empty(cpus)) { + cpumask_clear(cpus); + return -EINVAL; + } else + return 0; } +#else +/* On return cpumask will be altered to indicate CPUs changed. + * CPUs with states changed will be set in the mask, + * CPUs with status unchanged will be unset in the mask. */ +static int rtas_cpu_state_change_mask(enum rtas_cpu_state state, + cpumask_var_t cpus) +{ + int cpu; + int cpuret = 0; + int ret = 0; + + if (cpumask_empty(cpus)) + return 0; + for_each_cpu(cpu, cpus) { + switch (state) { + case DOWN: + cpuret = cpu_down(cpu); + break; + case UP: + cpuret = cpu_up(cpu); + break; + } + if (cpuret) { + pr_debug("%s: cpu_%s for cpu#%d returned %d.\n", + __func__, + ((state == UP) ? "up" : "down"), + cpu, cpuret); + if (!ret) + ret = cpuret; + if (state == UP) { + /* clear bits for unchanged cpus, return */ + cpumask_shift_right(cpus, cpus, cpu); + cpumask_shift_left(cpus, cpus, cpu); + break; + } else { + /* clear bit for unchanged cpu, continue */ + cpumask_clear_cpu(cpu, cpus); + } + } + } + return ret; +} +#endif + +int rtas_online_cpus_mask(cpumask_var_t cpus) +{ + int ret; + + ret = rtas_cpu_state_change_mask(UP, cpus); + + if (ret) { + cpumask_var_t tmp_mask; + + if (!alloc_cpumask_var(&tmp_mask, GFP_TEMPORARY)) + return ret; + + /* Use tmp_mask to preserve cpus mask from first failure */ + cpumask_copy(tmp_mask, cpus); + rtas_offline_cpus_mask(tmp_mask); + free_cpumask_var(tmp_mask); + } + + return ret; +} +EXPORT_SYMBOL(rtas_online_cpus_mask); + +int rtas_offline_cpus_mask(cpumask_var_t cpus) +{ + return rtas_cpu_state_change_mask(DOWN, cpus); +} +EXPORT_SYMBOL(rtas_offline_cpus_mask); + +int rtas_ibm_suspend_me(struct rtas_args *args) +{ + long state; + long rc; + unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; + struct rtas_suspend_me_data data; + DECLARE_COMPLETION_ONSTACK(done); + cpumask_var_t offline_mask; + int cpuret; + + if (!rtas_service_present("ibm,suspend-me")) + return -ENOSYS; + + /* Make sure the state is valid */ + rc = plpar_hcall(H_VASI_STATE, retbuf, + ((u64)args->args[0] << 32) | args->args[1]); + + state = retbuf[0]; + + 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; + } + + if (!alloc_cpumask_var(&offline_mask, GFP_TEMPORARY)) + return -ENOMEM; + + atomic_set(&data.working, 0); + atomic_set(&data.done, 0); + atomic_set(&data.error, 0); + data.token = rtas_token("ibm,suspend-me"); + data.complete = &done; + + /* All present CPUs must be online */ + cpumask_andnot(offline_mask, cpu_present_mask, cpu_online_mask); + cpuret = rtas_online_cpus_mask(offline_mask); + if (cpuret) { + pr_err("%s: Could not bring present CPUs online.\n", __func__); + atomic_set(&data.error, cpuret); + goto out; + } + + stop_topology_update(); + + /* Call function on all CPUs. One of us will make the + * rtas call + */ + if (on_each_cpu(rtas_percpu_suspend_me, &data, 0)) + atomic_set(&data.error, -EINVAL); + + wait_for_completion(&done); + + if (atomic_read(&data.error) != 0) + printk(KERN_ERR "Error doing global join\n"); + + start_topology_update(); + + /* Take down CPUs not online prior to suspend */ + cpuret = rtas_offline_cpus_mask(offline_mask); + if (cpuret) + pr_warn("%s: Could not restore CPUs to offline state.\n", + __func__); + +out: + free_cpumask_var(offline_mask); + return atomic_read(&data.error); +} +#else /* CONFIG_PPC_PSERIES */ +int rtas_ibm_suspend_me(struct rtas_args *args) +{ + return -ENOSYS; +} +#endif + +/** + * Find a specific pseries error log in an RTAS extended event log. + * @log: RTAS error/event log + * @section_id: two character section identifier + * + * Returns a pointer to the specified errorlog or NULL if not found. + */ +struct pseries_errorlog *get_pseries_errorlog(struct rtas_error_log *log, + uint16_t section_id) +{ + struct rtas_ext_event_log_v6 *ext_log = + (struct rtas_ext_event_log_v6 *)log->buffer; + struct pseries_errorlog *sect; + unsigned char *p, *log_end; + uint32_t ext_log_length = rtas_error_extended_log_length(log); + uint8_t log_format = rtas_ext_event_log_format(ext_log); + uint32_t company_id = rtas_ext_event_company_id(ext_log); + + /* Check that we understand the format */ + if (ext_log_length < sizeof(struct rtas_ext_event_log_v6) || + log_format != RTAS_V6EXT_LOG_FORMAT_EVENT_LOG || + company_id != RTAS_V6EXT_COMPANY_ID_IBM) + return NULL; + + log_end = log->buffer + ext_log_length; + p = ext_log->vendor_log; + + while (p < log_end) { + sect = (struct pseries_errorlog *)p; + if (pseries_errorlog_id(sect) == section_id) + return sect; + p += pseries_errorlog_length(sect); + } + + return NULL; +} + +/* We assume to be passed big endian arguments */ asmlinkage int ppc_rtas(struct rtas_args __user *uargs) { struct rtas_args args; unsigned long flags; char *buff_copy, *errbuf = NULL; - int nargs; + int nargs, nret, token; + int rc; if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -565,10 +1031,13 @@ asmlinkage int ppc_rtas(struct rtas_args __user *uargs) if (copy_from_user(&args, uargs, 3 * sizeof(u32)) != 0) return -EFAULT; - nargs = args.nargs; + nargs = be32_to_cpu(args.nargs); + nret = be32_to_cpu(args.nret); + token = be32_to_cpu(args.token); + if (nargs > ARRAY_SIZE(args.args) - || args.nret > ARRAY_SIZE(args.args) - || nargs + args.nret > ARRAY_SIZE(args.args)) + || nret > ARRAY_SIZE(args.args) + || nargs + nret > ARRAY_SIZE(args.args)) return -EINVAL; /* Copy in args. */ @@ -576,22 +1045,34 @@ asmlinkage int ppc_rtas(struct rtas_args __user *uargs) nargs * sizeof(rtas_arg_t)) != 0) return -EFAULT; + if (token == RTAS_UNKNOWN_SERVICE) + return -EINVAL; + + args.rets = &args.args[nargs]; + memset(args.rets, 0, nret * sizeof(rtas_arg_t)); + + /* Need to handle ibm,suspend_me call specially */ + if (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); + flags = lock_rtas(); rtas.args = args; enter_rtas(__pa(&rtas.args)); args = rtas.args; - args.rets = &args.args[nargs]; - /* A -1 return code indicates that the last command couldn't be completed due to a hardware error. */ - if (args.rets[0] == -1) + if (be32_to_cpu(args.rets[0]) == -1) errbuf = __fetch_rtas_last_error(buff_copy); - spin_unlock_irqrestore(&rtas.lock, flags); + unlock_rtas(flags); if (buff_copy) { if (errbuf) @@ -599,44 +1080,18 @@ asmlinkage int ppc_rtas(struct rtas_args __user *uargs) kfree(buff_copy); } + copy_return: /* Copy out args. */ if (copy_to_user(uargs->args + nargs, args.args + nargs, - args.nret * sizeof(rtas_arg_t)) != 0) + nret * sizeof(rtas_arg_t)) != 0) return -EFAULT; return 0; } -#ifdef CONFIG_SMP -/* This version can't take the spinlock, because it never returns */ - -struct rtas_args rtas_stop_self_args = { - /* The token is initialized for real in setup_system() */ - .token = RTAS_UNKNOWN_SERVICE, - .nargs = 0, - .nret = 1, - .rets = &rtas_stop_self_args.args[0], -}; - -void rtas_stop_self(void) -{ - struct rtas_args *rtas_args = &rtas_stop_self_args; - - local_irq_disable(); - - BUG_ON(rtas_args->token == RTAS_UNKNOWN_SERVICE); - - printk("cpu %u (hwid %u) Ready to die...\n", - smp_processor_id(), hard_smp_processor_id()); - enter_rtas(__pa(rtas_args)); - - panic("Alas, I survived.\n"); -} -#endif - /* - * Call early during boot, before mem init or bootmem, to retreive the RTAS + * Call early during boot, before mem init or bootmem, to retrieve the RTAS * informations from the device-tree and allocate the RMO buffer for userland * accesses. */ @@ -649,19 +1104,19 @@ void __init rtas_initialize(void) */ rtas.dev = of_find_node_by_name(NULL, "rtas"); if (rtas.dev) { - u32 *basep, *entryp; - u32 *sizep; + const __be32 *basep, *entryp, *sizep; - basep = (u32 *)get_property(rtas.dev, "linux,rtas-base", NULL); - sizep = (u32 *)get_property(rtas.dev, "rtas-size", NULL); + basep = of_get_property(rtas.dev, "linux,rtas-base", NULL); + sizep = of_get_property(rtas.dev, "rtas-size", NULL); if (basep != NULL && sizep != NULL) { - rtas.base = *basep; - rtas.size = *sizep; - entryp = (u32 *)get_property(rtas.dev, "linux,rtas-entry", NULL); + rtas.base = __be32_to_cpu(*basep); + rtas.size = __be32_to_cpu(*sizep); + entryp = of_get_property(rtas.dev, + "linux,rtas-entry", NULL); if (entryp == NULL) /* Ugh */ rtas.entry = rtas.base; else - rtas.entry = *entryp; + rtas.entry = __be32_to_cpu(*entryp); } else rtas.dev = NULL; } @@ -672,26 +1127,81 @@ void __init rtas_initialize(void) * the stop-self token if any */ #ifdef CONFIG_PPC64 - if (systemcfg->platform == PLATFORM_PSERIES_LPAR) - rtas_region = min(lmb.rmo_size, RTAS_INSTANTIATE_MAX); + if (machine_is(pseries) && firmware_has_feature(FW_FEATURE_LPAR)) { + rtas_region = min(ppc64_rma_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); + rtas_rmo_buf = memblock_alloc_base(RTAS_RMOBUF_MAX, PAGE_SIZE, rtas_region); -#ifdef CONFIG_HOTPLUG_CPU - rtas_stop_self_args.token = rtas_token("stop-self"); -#endif /* CONFIG_HOTPLUG_CPU */ #ifdef CONFIG_RTAS_ERROR_LOGGING rtas_last_error_token = rtas_token("rtas-last-error"); #endif } +int __init early_init_dt_scan_rtas(unsigned long node, + const char *uname, int depth, void *data) +{ + const u32 *basep, *entryp, *sizep; -EXPORT_SYMBOL(rtas_token); -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_get_sensor); -EXPORT_SYMBOL(rtas_get_power_level); -EXPORT_SYMBOL(rtas_set_power_level); -EXPORT_SYMBOL(rtas_set_indicator); + if (depth != 1 || strcmp(uname, "rtas") != 0) + return 0; + + basep = of_get_flat_dt_prop(node, "linux,rtas-base", NULL); + entryp = of_get_flat_dt_prop(node, "linux,rtas-entry", NULL); + sizep = of_get_flat_dt_prop(node, "rtas-size", NULL); + + if (basep && entryp && sizep) { + rtas.base = *basep; + rtas.entry = *entryp; + rtas.size = *sizep; + } + +#ifdef CONFIG_UDBG_RTAS_CONSOLE + basep = of_get_flat_dt_prop(node, "put-term-char", NULL); + if (basep) + rtas_putchar_token = *basep; + + basep = of_get_flat_dt_prop(node, "get-term-char", NULL); + if (basep) + rtas_getchar_token = *basep; + + if (rtas_putchar_token != RTAS_UNKNOWN_SERVICE && + rtas_getchar_token != RTAS_UNKNOWN_SERVICE) + udbg_init_rtas_console(); + +#endif + + /* break now */ + return 1; +} + +static arch_spinlock_t timebase_lock; +static u64 timebase = 0; + +void rtas_give_timebase(void) +{ + unsigned long flags; + + local_irq_save(flags); + hard_irq_disable(); + arch_spin_lock(&timebase_lock); + rtas_call(rtas_token("freeze-time-base"), 0, 1, NULL); + timebase = get_tb(); + arch_spin_unlock(&timebase_lock); + + while (timebase) + barrier(); + rtas_call(rtas_token("thaw-time-base"), 0, 1, NULL); + local_irq_restore(flags); +} + +void rtas_take_timebase(void) +{ + while (!timebase) + barrier(); + arch_spin_lock(&timebase_lock); + set_tb(timebase >> 32, timebase & 0xffffffff); + timebase = 0; + arch_spin_unlock(&timebase_lock); +} |
