diff options
Diffstat (limited to 'arch/sparc64/kernel/unaligned.c')
-rw-r--r-- | arch/sparc64/kernel/unaligned.c | 690 |
1 files changed, 0 insertions, 690 deletions
diff --git a/arch/sparc64/kernel/unaligned.c b/arch/sparc64/kernel/unaligned.c deleted file mode 100644 index 203ddfad9f2..00000000000 --- a/arch/sparc64/kernel/unaligned.c +++ /dev/null @@ -1,690 +0,0 @@ -/* - * 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; -} |