diff options
Diffstat (limited to 'arch/sparc/kernel/unaligned_64.c')
| -rw-r--r-- | arch/sparc/kernel/unaligned_64.c | 690 | 
1 files changed, 690 insertions, 0 deletions
diff --git a/arch/sparc/kernel/unaligned_64.c b/arch/sparc/kernel/unaligned_64.c new file mode 100644 index 00000000000..203ddfad9f2 --- /dev/null +++ b/arch/sparc/kernel/unaligned_64.c @@ -0,0 +1,690 @@ +/* + * unaligned.c: Unaligned load/store trap handling with special + *              cases for the kernel to do them more quickly. + * + * Copyright (C) 1996,2008 David S. Miller (davem@davemloft.net) + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + + +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <asm/asi.h> +#include <asm/ptrace.h> +#include <asm/pstate.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <linux/smp.h> +#include <linux/bitops.h> +#include <asm/fpumacro.h> + +/* #define DEBUG_MNA */ + +enum direction { +	load,    /* ld, ldd, ldh, ldsh */ +	store,   /* st, std, sth, stsh */ +	both,    /* Swap, ldstub, cas, ... */ +	fpld, +	fpst, +	invalid, +}; + +#ifdef DEBUG_MNA +static char *dirstrings[] = { +  "load", "store", "both", "fpload", "fpstore", "invalid" +}; +#endif + +static inline enum direction decode_direction(unsigned int insn) +{ +	unsigned long tmp = (insn >> 21) & 1; + +	if (!tmp) +		return load; +	else { +		switch ((insn>>19)&0xf) { +		case 15: /* swap* */ +			return both; +		default: +			return store; +		} +	} +} + +/* 16 = double-word, 8 = extra-word, 4 = word, 2 = half-word */ +static inline int decode_access_size(unsigned int insn) +{ +	unsigned int tmp; + +	tmp = ((insn >> 19) & 0xf); +	if (tmp == 11 || tmp == 14) /* ldx/stx */ +		return 8; +	tmp &= 3; +	if (!tmp) +		return 4; +	else if (tmp == 3) +		return 16;	/* ldd/std - Although it is actually 8 */ +	else if (tmp == 2) +		return 2; +	else { +		printk("Impossible unaligned trap. insn=%08x\n", insn); +		die_if_kernel("Byte sized unaligned access?!?!", current_thread_info()->kregs); + +		/* GCC should never warn that control reaches the end +		 * of this function without returning a value because +		 * die_if_kernel() is marked with attribute 'noreturn'. +		 * Alas, some versions do... +		 */ + +		return 0; +	} +} + +static inline int decode_asi(unsigned int insn, struct pt_regs *regs) +{ +	if (insn & 0x800000) { +		if (insn & 0x2000) +			return (unsigned char)(regs->tstate >> 24);	/* %asi */ +		else +			return (unsigned char)(insn >> 5);		/* imm_asi */ +	} else +		return ASI_P; +} + +/* 0x400000 = signed, 0 = unsigned */ +static inline int decode_signedness(unsigned int insn) +{ +	return (insn & 0x400000); +} + +static inline void maybe_flush_windows(unsigned int rs1, unsigned int rs2, +				       unsigned int rd, int from_kernel) +{ +	if (rs2 >= 16 || rs1 >= 16 || rd >= 16) { +		if (from_kernel != 0) +			__asm__ __volatile__("flushw"); +		else +			flushw_user(); +	} +} + +static inline long sign_extend_imm13(long imm) +{ +	return imm << 51 >> 51; +} + +static unsigned long fetch_reg(unsigned int reg, struct pt_regs *regs) +{ +	unsigned long value; +	 +	if (reg < 16) +		return (!reg ? 0 : regs->u_regs[reg]); +	if (regs->tstate & TSTATE_PRIV) { +		struct reg_window *win; +		win = (struct reg_window *)(regs->u_regs[UREG_FP] + STACK_BIAS); +		value = win->locals[reg - 16]; +	} else if (test_thread_flag(TIF_32BIT)) { +		struct reg_window32 __user *win32; +		win32 = (struct reg_window32 __user *)((unsigned long)((u32)regs->u_regs[UREG_FP])); +		get_user(value, &win32->locals[reg - 16]); +	} else { +		struct reg_window __user *win; +		win = (struct reg_window __user *)(regs->u_regs[UREG_FP] + STACK_BIAS); +		get_user(value, &win->locals[reg - 16]); +	} +	return value; +} + +static unsigned long *fetch_reg_addr(unsigned int reg, struct pt_regs *regs) +{ +	if (reg < 16) +		return ®s->u_regs[reg]; +	if (regs->tstate & TSTATE_PRIV) { +		struct reg_window *win; +		win = (struct reg_window *)(regs->u_regs[UREG_FP] + STACK_BIAS); +		return &win->locals[reg - 16]; +	} else if (test_thread_flag(TIF_32BIT)) { +		struct reg_window32 *win32; +		win32 = (struct reg_window32 *)((unsigned long)((u32)regs->u_regs[UREG_FP])); +		return (unsigned long *)&win32->locals[reg - 16]; +	} else { +		struct reg_window *win; +		win = (struct reg_window *)(regs->u_regs[UREG_FP] + STACK_BIAS); +		return &win->locals[reg - 16]; +	} +} + +unsigned long compute_effective_address(struct pt_regs *regs, +					unsigned int insn, unsigned int rd) +{ +	unsigned int rs1 = (insn >> 14) & 0x1f; +	unsigned int rs2 = insn & 0x1f; +	int from_kernel = (regs->tstate & TSTATE_PRIV) != 0; + +	if (insn & 0x2000) { +		maybe_flush_windows(rs1, 0, rd, from_kernel); +		return (fetch_reg(rs1, regs) + sign_extend_imm13(insn)); +	} else { +		maybe_flush_windows(rs1, rs2, rd, from_kernel); +		return (fetch_reg(rs1, regs) + fetch_reg(rs2, regs)); +	} +} + +/* This is just to make gcc think die_if_kernel does return... */ +static void __used unaligned_panic(char *str, struct pt_regs *regs) +{ +	die_if_kernel(str, regs); +} + +extern int do_int_load(unsigned long *dest_reg, int size, +		       unsigned long *saddr, int is_signed, int asi); +	 +extern int __do_int_store(unsigned long *dst_addr, int size, +			  unsigned long src_val, int asi); + +static inline int do_int_store(int reg_num, int size, unsigned long *dst_addr, +			       struct pt_regs *regs, int asi, int orig_asi) +{ +	unsigned long zero = 0; +	unsigned long *src_val_p = &zero; +	unsigned long src_val; + +	if (size == 16) { +		size = 8; +		zero = (((long)(reg_num ? +		        (unsigned)fetch_reg(reg_num, regs) : 0)) << 32) | +			(unsigned)fetch_reg(reg_num + 1, regs); +	} else if (reg_num) { +		src_val_p = fetch_reg_addr(reg_num, regs); +	} +	src_val = *src_val_p; +	if (unlikely(asi != orig_asi)) { +		switch (size) { +		case 2: +			src_val = swab16(src_val); +			break; +		case 4: +			src_val = swab32(src_val); +			break; +		case 8: +			src_val = swab64(src_val); +			break; +		case 16: +		default: +			BUG(); +			break; +		}; +	} +	return __do_int_store(dst_addr, size, src_val, asi); +} + +static inline void advance(struct pt_regs *regs) +{ +	regs->tpc   = regs->tnpc; +	regs->tnpc += 4; +	if (test_thread_flag(TIF_32BIT)) { +		regs->tpc &= 0xffffffff; +		regs->tnpc &= 0xffffffff; +	} +} + +static inline int floating_point_load_or_store_p(unsigned int insn) +{ +	return (insn >> 24) & 1; +} + +static inline int ok_for_kernel(unsigned int insn) +{ +	return !floating_point_load_or_store_p(insn); +} + +static void kernel_mna_trap_fault(int fixup_tstate_asi) +{ +	struct pt_regs *regs = current_thread_info()->kern_una_regs; +	unsigned int insn = current_thread_info()->kern_una_insn; +	const struct exception_table_entry *entry; + +	entry = search_exception_tables(regs->tpc); +	if (!entry) { +		unsigned long address; + +		address = compute_effective_address(regs, insn, +						    ((insn >> 25) & 0x1f)); +        	if (address < PAGE_SIZE) { +                	printk(KERN_ALERT "Unable to handle kernel NULL " +			       "pointer dereference in mna handler"); +        	} else +                	printk(KERN_ALERT "Unable to handle kernel paging " +			       "request in mna handler"); +	        printk(KERN_ALERT " at virtual address %016lx\n",address); +		printk(KERN_ALERT "current->{active_,}mm->context = %016lx\n", +			(current->mm ? CTX_HWBITS(current->mm->context) : +			CTX_HWBITS(current->active_mm->context))); +		printk(KERN_ALERT "current->{active_,}mm->pgd = %016lx\n", +			(current->mm ? (unsigned long) current->mm->pgd : +			(unsigned long) current->active_mm->pgd)); +	        die_if_kernel("Oops", regs); +		/* Not reached */ +	} +	regs->tpc = entry->fixup; +	regs->tnpc = regs->tpc + 4; + +	if (fixup_tstate_asi) { +		regs->tstate &= ~TSTATE_ASI; +		regs->tstate |= (ASI_AIUS << 24UL); +	} +} + +static void log_unaligned(struct pt_regs *regs) +{ +	static unsigned long count, last_time; + +	if (time_after(jiffies, last_time + 5 * HZ)) +		count = 0; +	if (count < 5) { +		last_time = jiffies; +		count++; +		printk("Kernel unaligned access at TPC[%lx] %pS\n", +		       regs->tpc, (void *) regs->tpc); +	} +} + +asmlinkage void kernel_unaligned_trap(struct pt_regs *regs, unsigned int insn) +{ +	enum direction dir = decode_direction(insn); +	int size = decode_access_size(insn); +	int orig_asi, asi; + +	current_thread_info()->kern_una_regs = regs; +	current_thread_info()->kern_una_insn = insn; + +	orig_asi = asi = decode_asi(insn, regs); + +	/* If this is a {get,put}_user() on an unaligned userspace pointer, +	 * just signal a fault and do not log the event. +	 */ +	if (asi == ASI_AIUS) { +		kernel_mna_trap_fault(0); +		return; +	} + +	log_unaligned(regs); + +	if (!ok_for_kernel(insn) || dir == both) { +		printk("Unsupported unaligned load/store trap for kernel " +		       "at <%016lx>.\n", regs->tpc); +		unaligned_panic("Kernel does fpu/atomic " +				"unaligned load/store.", regs); + +		kernel_mna_trap_fault(0); +	} else { +		unsigned long addr, *reg_addr; +		int err; + +		addr = compute_effective_address(regs, insn, +						 ((insn >> 25) & 0x1f)); +#ifdef DEBUG_MNA +		printk("KMNA: pc=%016lx [dir=%s addr=%016lx size=%d] " +		       "retpc[%016lx]\n", +		       regs->tpc, dirstrings[dir], addr, size, +		       regs->u_regs[UREG_RETPC]); +#endif +		switch (asi) { +		case ASI_NL: +		case ASI_AIUPL: +		case ASI_AIUSL: +		case ASI_PL: +		case ASI_SL: +		case ASI_PNFL: +		case ASI_SNFL: +			asi &= ~0x08; +			break; +		}; +		switch (dir) { +		case load: +			reg_addr = fetch_reg_addr(((insn>>25)&0x1f), regs); +			err = do_int_load(reg_addr, size, +					  (unsigned long *) addr, +					  decode_signedness(insn), asi); +			if (likely(!err) && unlikely(asi != orig_asi)) { +				unsigned long val_in = *reg_addr; +				switch (size) { +				case 2: +					val_in = swab16(val_in); +					break; +				case 4: +					val_in = swab32(val_in); +					break; +				case 8: +					val_in = swab64(val_in); +					break; +				case 16: +				default: +					BUG(); +					break; +				}; +				*reg_addr = val_in; +			} +			break; + +		case store: +			err = do_int_store(((insn>>25)&0x1f), size, +					   (unsigned long *) addr, regs, +					   asi, orig_asi); +			break; + +		default: +			panic("Impossible kernel unaligned trap."); +			/* Not reached... */ +		} +		if (unlikely(err)) +			kernel_mna_trap_fault(1); +		else +			advance(regs); +	} +} + +static char popc_helper[] = { +0, 1, 1, 2, 1, 2, 2, 3, +1, 2, 2, 3, 2, 3, 3, 4,  +}; + +int handle_popc(u32 insn, struct pt_regs *regs) +{ +	u64 value; +	int ret, i, rd = ((insn >> 25) & 0x1f); +	int from_kernel = (regs->tstate & TSTATE_PRIV) != 0; +	                         +	if (insn & 0x2000) { +		maybe_flush_windows(0, 0, rd, from_kernel); +		value = sign_extend_imm13(insn); +	} else { +		maybe_flush_windows(0, insn & 0x1f, rd, from_kernel); +		value = fetch_reg(insn & 0x1f, regs); +	} +	for (ret = 0, i = 0; i < 16; i++) { +		ret += popc_helper[value & 0xf]; +		value >>= 4; +	} +	if (rd < 16) { +		if (rd) +			regs->u_regs[rd] = ret; +	} else { +		if (test_thread_flag(TIF_32BIT)) { +			struct reg_window32 __user *win32; +			win32 = (struct reg_window32 __user *)((unsigned long)((u32)regs->u_regs[UREG_FP])); +			put_user(ret, &win32->locals[rd - 16]); +		} else { +			struct reg_window __user *win; +			win = (struct reg_window __user *)(regs->u_regs[UREG_FP] + STACK_BIAS); +			put_user(ret, &win->locals[rd - 16]); +		} +	} +	advance(regs); +	return 1; +} + +extern void do_fpother(struct pt_regs *regs); +extern void do_privact(struct pt_regs *regs); +extern void spitfire_data_access_exception(struct pt_regs *regs, +					   unsigned long sfsr, +					   unsigned long sfar); +extern void sun4v_data_access_exception(struct pt_regs *regs, +					unsigned long addr, +					unsigned long type_ctx); + +int handle_ldf_stq(u32 insn, struct pt_regs *regs) +{ +	unsigned long addr = compute_effective_address(regs, insn, 0); +	int freg = ((insn >> 25) & 0x1e) | ((insn >> 20) & 0x20); +	struct fpustate *f = FPUSTATE; +	int asi = decode_asi(insn, regs); +	int flag = (freg < 32) ? FPRS_DL : FPRS_DU; + +	save_and_clear_fpu(); +	current_thread_info()->xfsr[0] &= ~0x1c000; +	if (freg & 3) { +		current_thread_info()->xfsr[0] |= (6 << 14) /* invalid_fp_register */; +		do_fpother(regs); +		return 0; +	} +	if (insn & 0x200000) { +		/* STQ */ +		u64 first = 0, second = 0; +		 +		if (current_thread_info()->fpsaved[0] & flag) { +			first = *(u64 *)&f->regs[freg]; +			second = *(u64 *)&f->regs[freg+2]; +		} +		if (asi < 0x80) { +			do_privact(regs); +			return 1; +		} +		switch (asi) { +		case ASI_P: +		case ASI_S: break; +		case ASI_PL: +		case ASI_SL:  +			{ +				/* Need to convert endians */ +				u64 tmp = __swab64p(&first); +				 +				first = __swab64p(&second); +				second = tmp; +				break; +			} +		default: +			if (tlb_type == hypervisor) +				sun4v_data_access_exception(regs, addr, 0); +			else +				spitfire_data_access_exception(regs, 0, addr); +			return 1; +		} +		if (put_user (first >> 32, (u32 __user *)addr) || +		    __put_user ((u32)first, (u32 __user *)(addr + 4)) || +		    __put_user (second >> 32, (u32 __user *)(addr + 8)) || +		    __put_user ((u32)second, (u32 __user *)(addr + 12))) { +			if (tlb_type == hypervisor) +				sun4v_data_access_exception(regs, addr, 0); +			else +				spitfire_data_access_exception(regs, 0, addr); +		    	return 1; +		} +	} else { +		/* LDF, LDDF, LDQF */ +		u32 data[4] __attribute__ ((aligned(8))); +		int size, i; +		int err; + +		if (asi < 0x80) { +			do_privact(regs); +			return 1; +		} else if (asi > ASI_SNFL) { +			if (tlb_type == hypervisor) +				sun4v_data_access_exception(regs, addr, 0); +			else +				spitfire_data_access_exception(regs, 0, addr); +			return 1; +		} +		switch (insn & 0x180000) { +		case 0x000000: size = 1; break; +		case 0x100000: size = 4; break; +		default: size = 2; break; +		} +		for (i = 0; i < size; i++) +			data[i] = 0; +		 +		err = get_user (data[0], (u32 __user *) addr); +		if (!err) { +			for (i = 1; i < size; i++) +				err |= __get_user (data[i], (u32 __user *)(addr + 4*i)); +		} +		if (err && !(asi & 0x2 /* NF */)) { +			if (tlb_type == hypervisor) +				sun4v_data_access_exception(regs, addr, 0); +			else +				spitfire_data_access_exception(regs, 0, addr); +			return 1; +		} +		if (asi & 0x8) /* Little */ { +			u64 tmp; + +			switch (size) { +			case 1: data[0] = le32_to_cpup(data + 0); break; +			default:*(u64 *)(data + 0) = le64_to_cpup((u64 *)(data + 0)); +				break; +			case 4: tmp = le64_to_cpup((u64 *)(data + 0)); +				*(u64 *)(data + 0) = le64_to_cpup((u64 *)(data + 2)); +				*(u64 *)(data + 2) = tmp; +				break; +			} +		} +		if (!(current_thread_info()->fpsaved[0] & FPRS_FEF)) { +			current_thread_info()->fpsaved[0] = FPRS_FEF; +			current_thread_info()->gsr[0] = 0; +		} +		if (!(current_thread_info()->fpsaved[0] & flag)) { +			if (freg < 32) +				memset(f->regs, 0, 32*sizeof(u32)); +			else +				memset(f->regs+32, 0, 32*sizeof(u32)); +		} +		memcpy(f->regs + freg, data, size * 4); +		current_thread_info()->fpsaved[0] |= flag; +	} +	advance(regs); +	return 1; +} + +void handle_ld_nf(u32 insn, struct pt_regs *regs) +{ +	int rd = ((insn >> 25) & 0x1f); +	int from_kernel = (regs->tstate & TSTATE_PRIV) != 0; +	unsigned long *reg; +	                         +	maybe_flush_windows(0, 0, rd, from_kernel); +	reg = fetch_reg_addr(rd, regs); +	if (from_kernel || rd < 16) { +		reg[0] = 0; +		if ((insn & 0x780000) == 0x180000) +			reg[1] = 0; +	} else if (test_thread_flag(TIF_32BIT)) { +		put_user(0, (int __user *) reg); +		if ((insn & 0x780000) == 0x180000) +			put_user(0, ((int __user *) reg) + 1); +	} else { +		put_user(0, (unsigned long __user *) reg); +		if ((insn & 0x780000) == 0x180000) +			put_user(0, (unsigned long __user *) reg + 1); +	} +	advance(regs); +} + +void handle_lddfmna(struct pt_regs *regs, unsigned long sfar, unsigned long sfsr) +{ +	unsigned long pc = regs->tpc; +	unsigned long tstate = regs->tstate; +	u32 insn; +	u32 first, second; +	u64 value; +	u8 freg; +	int flag; +	struct fpustate *f = FPUSTATE; + +	if (tstate & TSTATE_PRIV) +		die_if_kernel("lddfmna from kernel", regs); +	if (test_thread_flag(TIF_32BIT)) +		pc = (u32)pc; +	if (get_user(insn, (u32 __user *) pc) != -EFAULT) { +		int asi = decode_asi(insn, regs); +		if ((asi > ASI_SNFL) || +		    (asi < ASI_P)) +			goto daex; +		if (get_user(first, (u32 __user *)sfar) || +		     get_user(second, (u32 __user *)(sfar + 4))) { +			if (asi & 0x2) /* NF */ { +				first = 0; second = 0; +			} else +				goto daex; +		} +		save_and_clear_fpu(); +		freg = ((insn >> 25) & 0x1e) | ((insn >> 20) & 0x20); +		value = (((u64)first) << 32) | second; +		if (asi & 0x8) /* Little */ +			value = __swab64p(&value); +		flag = (freg < 32) ? FPRS_DL : FPRS_DU; +		if (!(current_thread_info()->fpsaved[0] & FPRS_FEF)) { +			current_thread_info()->fpsaved[0] = FPRS_FEF; +			current_thread_info()->gsr[0] = 0; +		} +		if (!(current_thread_info()->fpsaved[0] & flag)) { +			if (freg < 32) +				memset(f->regs, 0, 32*sizeof(u32)); +			else +				memset(f->regs+32, 0, 32*sizeof(u32)); +		} +		*(u64 *)(f->regs + freg) = value; +		current_thread_info()->fpsaved[0] |= flag; +	} else { +daex: +		if (tlb_type == hypervisor) +			sun4v_data_access_exception(regs, sfar, sfsr); +		else +			spitfire_data_access_exception(regs, sfsr, sfar); +		return; +	} +	advance(regs); +	return; +} + +void handle_stdfmna(struct pt_regs *regs, unsigned long sfar, unsigned long sfsr) +{ +	unsigned long pc = regs->tpc; +	unsigned long tstate = regs->tstate; +	u32 insn; +	u64 value; +	u8 freg; +	int flag; +	struct fpustate *f = FPUSTATE; + +	if (tstate & TSTATE_PRIV) +		die_if_kernel("stdfmna from kernel", regs); +	if (test_thread_flag(TIF_32BIT)) +		pc = (u32)pc; +	if (get_user(insn, (u32 __user *) pc) != -EFAULT) { +		int asi = decode_asi(insn, regs); +		freg = ((insn >> 25) & 0x1e) | ((insn >> 20) & 0x20); +		value = 0; +		flag = (freg < 32) ? FPRS_DL : FPRS_DU; +		if ((asi > ASI_SNFL) || +		    (asi < ASI_P)) +			goto daex; +		save_and_clear_fpu(); +		if (current_thread_info()->fpsaved[0] & flag) +			value = *(u64 *)&f->regs[freg]; +		switch (asi) { +		case ASI_P: +		case ASI_S: break; +		case ASI_PL: +		case ASI_SL:  +			value = __swab64p(&value); break; +		default: goto daex; +		} +		if (put_user (value >> 32, (u32 __user *) sfar) || +		    __put_user ((u32)value, (u32 __user *)(sfar + 4))) +			goto daex; +	} else { +daex: +		if (tlb_type == hypervisor) +			sun4v_data_access_exception(regs, sfar, sfsr); +		else +			spitfire_data_access_exception(regs, sfsr, sfar); +		return; +	} +	advance(regs); +	return; +}  |