diff options
Diffstat (limited to 'arch/powerpc/kernel/rtas.c')
| -rw-r--r-- | arch/powerpc/kernel/rtas.c | 257 | 
1 files changed, 208 insertions, 49 deletions
diff --git a/arch/powerpc/kernel/rtas.c b/arch/powerpc/kernel/rtas.c index 8fe8bc61c10..8b4c857c142 100644 --- a/arch/powerpc/kernel/rtas.c +++ b/arch/powerpc/kernel/rtas.c @@ -15,15 +15,17 @@  #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> @@ -32,15 +34,15 @@  #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/udbg.h>  #include <asm/syscalls.h>  #include <asm/smp.h> -#include <asm/atomic.h> +#include <linux/atomic.h>  #include <asm/time.h>  #include <asm/mmu.h> +#include <asm/topology.h>  struct rtas_t rtas = {  	.lock = __ARCH_SPIN_LOCK_UNLOCKED @@ -89,7 +91,7 @@ static void unlock_rtas(unsigned long flags)   * are designed only for very early low-level debugging, which   * is why the token is hard-coded to 10.   */ -static void call_rtas_display_status(char c) +static void call_rtas_display_status(unsigned char c)  {  	struct rtas_args *args = &rtas.args;  	unsigned long s; @@ -98,11 +100,11 @@ static void call_rtas_display_status(char c)  		return;  	s = lock_rtas(); -	args->token = 10; -	args->nargs = 1; -	args->nret  = 1; -	args->rets  = (rtas_arg_t *)&(args->args[1]); -	args->args[0] = (unsigned char)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)); @@ -202,7 +204,7 @@ void rtas_progress(char *s, unsigned short hex)  {  	struct device_node *root;  	int width; -	const int *p; +	const __be32 *p;  	char *os;  	static int display_character, set_indicator;  	static int display_width, display_lines, form_feed; @@ -219,13 +221,13 @@ void rtas_progress(char *s, unsigned short hex)  		if ((root = of_find_node_by_path("/rtas"))) {  			if ((p = of_get_property(root,  					"ibm,display-line-length", NULL))) -				display_width = *p; +				display_width = be32_to_cpu(*p);  			if ((p = of_get_property(root,  					"ibm,form-feed", NULL))) -				form_feed = *p; +				form_feed = be32_to_cpu(*p);  			if ((p = of_get_property(root,  					"ibm,display-number-of-lines", NULL))) -				display_lines = *p; +				display_lines = be32_to_cpu(*p);  			row_width = of_get_property(root,  					"ibm,display-truncation-length", NULL);  			of_node_put(root); @@ -320,11 +322,11 @@ EXPORT_SYMBOL(rtas_progress);		/* needed by rtas_flash module */  int rtas_token(const char *service)  { -	const int *tokp; +	const __be32 *tokp;  	if (rtas.dev == NULL)  		return RTAS_UNKNOWN_SERVICE;  	tokp = of_get_property(rtas.dev, service, NULL); -	return tokp ? *tokp : RTAS_UNKNOWN_SERVICE; +	return tokp ? be32_to_cpu(*tokp) : RTAS_UNKNOWN_SERVICE;  }  EXPORT_SYMBOL(rtas_token); @@ -378,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; @@ -431,13 +433,13 @@ int rtas_call(int token, int nargs, int nret, int *outputs, ...)  	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) @@ -447,13 +449,13 @@ 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;  	unlock_rtas(s); @@ -493,7 +495,7 @@ unsigned int rtas_busy_delay(int status)  	might_sleep();  	ms = rtas_busy_delay_time(status); -	if (ms) +	if (ms && need_resched())  		msleep(ms);  	return ms; @@ -586,8 +588,8 @@ bool rtas_indicator_present(int token, int *maxindex)  {  	int proplen, count, i;  	const struct indicator_elem { -		u32 token; -		u32 maxindex; +		__be32 token; +		__be32 maxindex;  	} *indicators;  	indicators = of_get_property(rtas.dev, "rtas-indicators", &proplen); @@ -597,10 +599,10 @@ bool rtas_indicator_present(int token, int *maxindex)  	count = proplen / sizeof(struct indicator_elem);  	for (i = 0; i < count; i++) { -		if (indicators[i].token != token) +		if (__be32_to_cpu(indicators[i].token) != token)  			continue;  		if (maxindex) -			*maxindex = indicators[i].maxindex; +			*maxindex = __be32_to_cpu(indicators[i].maxindex);  		return true;  	} @@ -728,6 +730,7 @@ static int __rtas_suspend_last_cpu(struct rtas_suspend_me_data *data, int wake_w  		rc = atomic_read(&data->error);  	atomic_set(&data->error, rc); +	pSeries_coalesce_init();  	if (wake_when_done) {  		atomic_set(&data->done, 1); @@ -805,6 +808,95 @@ 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; @@ -812,6 +904,8 @@ int rtas_ibm_suspend_me(struct rtas_args *args)  	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; @@ -835,12 +929,26 @@ int rtas_ibm_suspend_me(struct rtas_args *args)  		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  	 */ @@ -852,6 +960,16 @@ int rtas_ibm_suspend_me(struct rtas_args *args)  	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 */ @@ -861,12 +979,50 @@ int rtas_ibm_suspend_me(struct rtas_args *args)  }  #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)) @@ -875,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. */ @@ -886,14 +1045,14 @@ asmlinkage int ppc_rtas(struct rtas_args __user *uargs)  			   nargs * sizeof(rtas_arg_t)) != 0)  		return -EFAULT; -	if (args.token == RTAS_UNKNOWN_SERVICE) +	if (token == RTAS_UNKNOWN_SERVICE)  		return -EINVAL;  	args.rets = &args.args[nargs]; -	memset(args.rets, 0, args.nret * sizeof(rtas_arg_t)); +	memset(args.rets, 0, nret * sizeof(rtas_arg_t));  	/* Need to handle ibm,suspend_me call specially */ -	if (args.token == ibm_suspend_me_token) { +	if (token == ibm_suspend_me_token) {  		rc = rtas_ibm_suspend_me(&args);  		if (rc)  			return rc; @@ -910,7 +1069,7 @@ asmlinkage int ppc_rtas(struct rtas_args __user *uargs)  	/* 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);  	unlock_rtas(flags); @@ -925,7 +1084,7 @@ asmlinkage int ppc_rtas(struct rtas_args __user *uargs)  	/* 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; @@ -945,19 +1104,19 @@ void __init rtas_initialize(void)  	 */  	rtas.dev = of_find_node_by_name(NULL, "rtas");  	if (rtas.dev) { -		const u32 *basep, *entryp, *sizep; +		const __be32 *basep, *entryp, *sizep;  		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; +			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;  	} @@ -983,7 +1142,7 @@ void __init rtas_initialize(void)  int __init early_init_dt_scan_rtas(unsigned long node,  		const char *uname, int depth, void *data)  { -	u32 *basep, *entryp, *sizep; +	const u32 *basep, *entryp, *sizep;  	if (depth != 1 || strcmp(uname, "rtas") != 0)  		return 0; @@ -1020,7 +1179,7 @@ int __init early_init_dt_scan_rtas(unsigned long node,  static arch_spinlock_t timebase_lock;  static u64 timebase = 0; -void __cpuinit rtas_give_timebase(void) +void rtas_give_timebase(void)  {  	unsigned long flags; @@ -1037,7 +1196,7 @@ void __cpuinit rtas_give_timebase(void)  	local_irq_restore(flags);  } -void __cpuinit rtas_take_timebase(void) +void rtas_take_timebase(void)  {  	while (!timebase)  		barrier();  | 
