From patchwork Wed Nov 8 05:54:52 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Greentime Hu X-Patchwork-Id: 835626 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="JNP8QMrR"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3yWx4H5dLLz9s0Z for ; Wed, 8 Nov 2017 17:20:31 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754037AbdKHGUR (ORCPT ); Wed, 8 Nov 2017 01:20:17 -0500 Received: from mail-pl0-f68.google.com ([209.85.160.68]:53453 "EHLO mail-pl0-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753998AbdKHGUG (ORCPT ); Wed, 8 Nov 2017 01:20:06 -0500 Received: by mail-pl0-f68.google.com with SMTP id g15so667284plk.10; Tue, 07 Nov 2017 22:20:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :in-reply-to:references; bh=CFdrfCff2v4vAqK8Mf/nGf2qHrUAxvWsFVHPqShlZmg=; b=JNP8QMrREgK0uEplHjpG57Xi0MRAsl95ZRizD1H4ir+4L3f8B5Sw93t+K2QNicnTt/ NBOPrxP3yqvoCX9XtqBaUds1XWtWu55szrUjLKuaXEEAqemwwqAkMKONc2R512h3w0x7 /jcxEjT7dMbG5KrKGX3g4vXErV3Y+WinD8Q/IAU2mToAbLVkWz9C4RW8mGq5vHwHXVvM L8bOHbHLaRJZIG9tJ2I0ERGGO8pk5Vv07G51W5zJiD0aNZRNcvmXFZK93cVFWWv9smOn 4p0z6fsTzLhpOqdQKJjURRlOd7dpjRSZmHU7lW0QlI78MDHVgm1doDhgf9keJtcVOilJ XNCQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:in-reply-to:references; bh=CFdrfCff2v4vAqK8Mf/nGf2qHrUAxvWsFVHPqShlZmg=; b=akfMpOR0E6BHa6pfmlrYo7xSRC+7khsmBomfYb/X7kAj0N0yAoUdOrisomWPcM9T6z enwulhdSbR3Xd2Bn4cbqrkXcHcM++PnnIm4wBI3Regsm9Ve5K3vd1NM2I21ZUDapbKvX FkO3r9jvp1bwrT0zu+EW08cZrC+Rg+Ubz0CsaIppSGnNWxjOnnJpCwZVWKrkagSb8d9q 7Sc8/EXTpTIIyIwNnhkh1ufK1CLeP8sSNpc9xQEnfqTmDxdvR5qnNwLhxA1S4x8t73Le OyN50+n0TKOqx4fnJiZjh23kfNNHY+eV/Y7hr5qyAiQqlzFPi7iJ4ZuzsAHq8DfIWZ7X dzBA== X-Gm-Message-State: AJaThX5Xt3YkJhzjNzMtIWOALfwARqhV4FONevgLJju3mlmQOm1Tf+is uAFdYT6NQkZsQJDaImAY9sk= X-Google-Smtp-Source: ABhQp+RPmLhZMMuadIXZ1iOe6aInZ9M1Q6usqo7ym0XbiXx30ZwJbI++7OFqUAGQgu/XsXsSdWquCQ== X-Received: by 10.159.206.193 with SMTP id x1mr1249214plo.319.1510122005493; Tue, 07 Nov 2017 22:20:05 -0800 (PST) Received: from app09.andestech.com ([118.163.51.199]) by smtp.gmail.com with ESMTPSA id a4sm6581339pfj.72.2017.11.07.22.20.02 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 07 Nov 2017 22:20:04 -0800 (PST) From: Greentime Hu To: greentime@andestech.com, linux-kernel@vger.kernel.org, arnd@arndb.de, linux-arch@vger.kernel.org, tglx@linutronix.de, jason@lakedaemon.net, marc.zyngier@arm.com, robh+dt@kernel.org, netdev@vger.kernel.org Cc: green.hu@gmail.com, Vincent Chen Subject: [PATCH 04/31] nds32: Exception handling Date: Wed, 8 Nov 2017 13:54:52 +0800 Message-Id: <9caaf73b79b22357ef957382eb7c0151a2d7d189.1510118606.git.green.hu@gmail.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: References: In-Reply-To: References: Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Greentime Hu Signed-off-by: Vincent Chen Signed-off-by: Greentime Hu --- arch/nds32/include/asm/ptrace.h | 79 ++++++ arch/nds32/kernel/ex-entry.S | 169 ++++++++++++ arch/nds32/kernel/ex-exit.S | 207 ++++++++++++++ arch/nds32/kernel/stacktrace.c | 60 +++++ arch/nds32/kernel/traps.c | 442 ++++++++++++++++++++++++++++++ arch/nds32/mm/alignment.c | 564 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 1521 insertions(+) create mode 100644 arch/nds32/include/asm/ptrace.h create mode 100644 arch/nds32/kernel/ex-entry.S create mode 100644 arch/nds32/kernel/ex-exit.S create mode 100644 arch/nds32/kernel/stacktrace.c create mode 100644 arch/nds32/kernel/traps.c create mode 100644 arch/nds32/mm/alignment.c diff --git a/arch/nds32/include/asm/ptrace.h b/arch/nds32/include/asm/ptrace.h new file mode 100644 index 0000000..2c9c03d --- /dev/null +++ b/arch/nds32/include/asm/ptrace.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2017 Andes Technology Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __ASM_NDS32_PTRACE_H +#define __ASM_NDS32_PTRACE_H + +#define PTRACE_GETREGS 12 +#define PTRACE_SETREGS 13 +#define PTRACE_GETFPREGS 14 +#define PTRACE_SETFPREGS 15 + +#include + +#ifndef __ASSEMBLY__ + +struct pt_regs { + union { + struct user_pt_regs user_regs; + struct { + long uregs[26]; + long fp; + long gp; + long lp; + long sp; + long ipc; +#if defined(CONFIG_HWZOL) + long lb; + long le; + long lc; +#else + long dummy[3]; +#endif + long syscallno; + }; + }; + long orig_r0; + long ir0; + long ipsw; + long pipsw; + long pipc; + long pp0; + long pp1; + long fucop_ctl; + long osp; +}; + +#include +extern void show_regs(struct pt_regs *); +/* Avoid circular header include via sched.h */ +struct task_struct; +extern void send_sigtrap(struct task_struct *tsk, struct pt_regs *regs, + int error_code, int si_code); + +#define arch_has_single_step() (1) +#define user_mode(regs) (((regs)->ipsw & PSW_mskPOM) == 0) +#define interrupts_enabled(regs) (!!((regs)->ipsw & PSW_mskGIE)) +#define valid_user_regs(regs) (user_mode(regs) && interrupts_enabled(regs)) +#define regs_return_value(regs) ((regs)->uregs[0]) +#define instruction_pointer(regs) ((regs)->ipc) +#define user_stack_pointer(regs) ((regs)->sp) +#define profile_pc(regs) instruction_pointer(regs) + +#define ARCH_HAS_USER_SINGLE_STEP_INFO + +#endif /* __ASSEMBLY__ */ +#endif diff --git a/arch/nds32/kernel/ex-entry.S b/arch/nds32/kernel/ex-entry.S new file mode 100644 index 0000000..3e977cf --- /dev/null +++ b/arch/nds32/kernel/ex-entry.S @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2005-2017 Andes Technology Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + + .macro push_zol +#ifdef CONFIG_HWZOL + mfusr $r14, $LB + mfusr $r15, $LE + mfusr $r16, $LC +#endif + .endm + + .macro save_user_regs + + smw.adm $sp, [$sp], $sp, #0x1 + addi $sp, $sp, -OSP_OFFSET /* move $SP to the bottom of pt_regs*/ + + /*push $r0 ~ $r25*/ + smw.bim $r0, [$sp], $r25 + /*push $fp, $gp, $lp*/ + smw.bim $sp, [$sp], $sp, #0xe + + mfsr $r12, $SP_USR + mfsr $r13, $IPC +#ifdef CONFIG_HWZOL + push_zol +#endif + movi $r17, -1 + move $r18, $r0 + mfsr $r19, $PSW + mfsr $r20, $IPSW + mfsr $r21, $P_IPSW + mfsr $r22, $P_IPC + mfsr $r23, $P_P0 + mfsr $r24, $P_P1 + smw.bim $r12, [$sp], $r24, #0 + addi $sp, $sp, -FUCOP_CTL_OFFSET + +/*Initialize kernel space $fp*/ + andi $p0, $r20, #PSW_mskPOM + movi $p1, #0x0 + cmovz $fp, $p1, $p0 + + andi $r16, $r19, #PSW_mskINTL + slti $r17, $r16, #4 + bnez $r17, 1f + addi $r17, $r19, #-2 + mtsr $r17, $PSW + isb +1: + /* If it was superuser mode, we don't need to update $r25*/ + bnez $p0, 2f + la $p0, __entry_task + lw $r25, [$p0] +2: + .endm + + .text + +/* + * Exception Vector + */ +exception_handlers: + .long unhandled_exceptions !Reset/NMI + .long unhandled_exceptions !TLB fill + .long do_page_fault !PTE not present + .long do_dispatch_tlb_misc !TLB misc + .long unhandled_exceptions !TLB VLPT + .long unhandled_exceptions !Machine Error + .long do_debug_trap !Debug related + .long do_dispatch_general !General exception + .long eh_syscall !Syscall + .long asm_do_IRQ !IRQ + +common_exception_handler: + save_user_regs + mfsr $p0, $ITYPE + andi $p0, $p0, #ITYPE_mskVECTOR + srli $p0, $p0, #ITYPE_offVECTOR + andi $p1, $p0, #NDS32_VECTOR_mskNONEXCEPTION + bnez $p1, 1f + sethi $lp, hi20(ret_from_exception) + ori $lp, $lp, lo12(ret_from_exception) + sethi $p1, hi20(exception_handlers) + ori $p1, $p1, lo12(exception_handlers) + lw $p1, [$p1+$p0<<2] + move $r0, $p0 + mfsr $r1, $EVA + mfsr $r2, $ITYPE + move $r3, $sp + mfsr $r4, $OIPC + /* enable gie if it is enabled in IPSW. */ + mfsr $r21, $PSW + andi $r20, $r20, #PSW_mskGIE /* r20 is $IPSW*/ + or $r21, $r21, $r20 + mtsr $r21, $PSW + dsb + jr $p1 + + /* syscall */ +1: + addi $p1, $p0, #-NDS32_VECTOR_offEXCEPTION + bnez $p1, 2f + sethi $lp, hi20(ret_from_exception) + ori $lp, $lp, lo12(ret_from_exception) + sethi $p1, hi20(exception_handlers) + ori $p1, $p1, lo12(exception_handlers) + lwi $p1, [$p1+#NDS32_VECTOR_offEXCEPTION<<2] + jr $p1 + + /* interrupt */ +2: +#ifdef CONFIG_TRACE_IRQFLAGS + jal arch_trace_hardirqs_off +#endif + move $r0, $sp + sethi $lp, hi20(ret_from_intr) + ori $lp, $lp, lo12(ret_from_intr) + sethi $p0, hi20(exception_handlers) + ori $p0, $p0, lo12(exception_handlers) + lwi $p0, [$p0+#NDS32_VECTOR_offINTERRUPT<<2] + jr $p0 + + .macro EXCEPTION_VECTOR_DEBUG + .align 4 + mfsr $p0, $EDM_CTL + andi $p0, $p0, EDM_CTL_mskV3_EDM_MODE + tnez $p0, SWID_RAISE_INTERRUPT_LEVEL + .endm + + .macro EXCEPTION_VECTOR + .align 4 + sethi $p0, hi20(common_exception_handler) + ori $p0, $p0, lo12(common_exception_handler) + jral.ton $p0, $p0 + .endm + + .section ".text.init", #alloc, #execinstr + .global exception_vector +exception_vector: +.rept 5 + EXCEPTION_VECTOR +.endr + EXCEPTION_VECTOR_DEBUG +.rept 122 + EXCEPTION_VECTOR +.endr + .align 4 + .global exception_vector_end +exception_vector_end: diff --git a/arch/nds32/kernel/ex-exit.S b/arch/nds32/kernel/ex-exit.S new file mode 100644 index 0000000..c7cda60 --- /dev/null +++ b/arch/nds32/kernel/ex-exit.S @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2005-2017 Andes Technology Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#define why $r8 + + + .macro pop_zol +#ifdef CONFIG_HWZOL + mtusr $r14, $LB + mtusr $r15, $LE + mtusr $r16, $LC +#endif + .endm + + .macro restore_user_regs_first + setgie.d + isb + + addi $sp, $sp, FUCOP_CTL_OFFSET + + lmw.adm $r12, [$sp], $r24, #0x0 + mtsr $r12, $SP_USR + mtsr $r13, $IPC +#ifdef CONFIG_HWZOL + pop_zol +#endif + mtsr $r19, $PSW + mtsr $r20, $IPSW + mtsr $r21, $P_IPSW + mtsr $r22, $P_IPC + mtsr $r23, $P_P0 + mtsr $r24, $P_P1 + lmw.adm $sp, [$sp], $sp, #0xe + .endm + + .macro restore_user_regs_last + pop $p0 + cmovn $sp, $p0, $p0 + + iret + nop + + .endm + + .macro restore_user_regs + restore_user_regs_first + lmw.adm $r0, [$sp], $r25, #0x0 + addi $sp, $sp, OSP_OFFSET + restore_user_regs_last + .endm + + .macro fast_restore_user_regs + restore_user_regs_first + lmw.adm $r1, [$sp], $r25, #0x0 + addi $sp, $sp, OSP_OFFSET-4 + restore_user_regs_last + .endm + +#ifdef CONFIG_PREEMPT + .macro preempt_stop + .endm +#else + .macro preempt_stop + setgie.d + isb + .endm +#define resume_kernel no_work_pending +#endif + +ENTRY(ret_from_exception) + preempt_stop +ENTRY(ret_from_intr) + move why, #0 ! not system call + +/* + * judge Kernel or user mode + * + */ + lwi $p0, [$sp+(#IPSW_OFFSET)] ! Check if in nested interrupt + andi $p0, $p0, #PSW_mskINTL + bnez $p0, resume_kernel ! done with iret + j resume_userspace + + +/* + * This is the fast syscall return path. We do as little as + * possible here, and this includes saving $r0 back into the SVC + * stack. + * fixed: tsk - $r25, why - $r8, $r7 - syscall #, $r8 - syscall table pointer + */ +ENTRY(ret_fast_syscall) + gie_disable + lwi $r1, [tsk+#TSK_TI_FLAGS] + andi $p1, $r1, #_TIF_WORK_MASK + bnez $p1, fast_work_pending + fast_restore_user_regs ! iret + +/* + * Ok, we need to do extra processing, + * enter the slow path returning from syscall, while pending work. + */ +fast_work_pending: + swi $r0, [$sp+(#R0_OFFSET)] ! what is different from ret_from_exception + move why, #1 ! come from a syscall +work_pending: + andi $p1, $r1, #_TIF_NEED_RESCHED + bnez $p1, work_resched + + andi $p1, $r1, #_TIF_SIGPENDING|#_TIF_NOTIFY_RESUME + beqz $p1, no_work_pending + + move $r0, $sp ! 'regs' + move $r2, why + gie_enable + bal do_notify_resume + beqz $r0, ret_slow_syscall + bgtz $r0, 1f + movi $r7, #__NR_restart_syscall +1: + b eh_syscall_phase_2 +work_resched: + bal schedule ! path, return to user mode + +/* + * "slow" syscall return path. + * "why" tells us if this was a real syscall. + */ +ENTRY(resume_userspace) +ENTRY(ret_slow_syscall) + gie_disable + lwi $p0, [$sp+(#IPSW_OFFSET)] ! Check if in nested interrupt + andi $p0, $p0, #PSW_mskINTL + bnez $p0, no_work_pending ! done with iret + lwi $r1, [tsk+#TSK_TI_FLAGS] + andi $p1, $r1, #_TIF_WORK_MASK + bnez $p1, work_pending ! handle work_resched, sig_pend + +no_work_pending: +#ifdef CONFIG_TRACE_IRQFLAGS + lwi $p0, [$sp+(#IPSW_OFFSET)] + andi $p0, $p0, #0x1 + la $r10, arch_trace_hardirqs_off + la $r9, arch_trace_hardirqs_on + cmovz $r9, $p0, $r10 + jral $r9 +#endif + restore_user_regs ! return from iret + + +/* + * preemptive kernel + */ +#ifdef CONFIG_PREEMPT +resume_kernel: + gie_disable + lwi $t0, [tsk+#TSK_TI_PREEMPT] + bnez $t0, no_work_pending +need_resched: + lwi $t0, [tsk+#TSK_TI_FLAGS] + andi $p1, $t0, #_TIF_NEED_RESCHED + beqz $p1, no_work_pending + + lwi $t0, [$sp+(#IPSW_OFFSET)] ! Interrupts off? + andi $t0, $t0, #1 + beqz $t0, no_work_pending + + jal preempt_schedule_irq + b need_resched +#endif + +/* + * This is how we return from a fork. + */ +ENTRY(ret_from_fork) + bal schedule_tail + beqz $r6, 1f ! r6 stores fn for kernel thread + move $r0, $r7 ! prepare kernel thread arg + jral $r6 +1: + lwi $r1, [tsk+#TSK_TI_FLAGS] ! check for syscall tracing + move why, #1 + andi $p1, $r1, #_TIF_WORK_SYSCALL_LEAVE ! are we tracing syscalls? + beqz $p1, ret_slow_syscall + move $r0, $sp + bal syscall_trace_leave + b ret_slow_syscall diff --git a/arch/nds32/kernel/stacktrace.c b/arch/nds32/kernel/stacktrace.c new file mode 100644 index 0000000..78e816d --- /dev/null +++ b/arch/nds32/kernel/stacktrace.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2017 Andes Technology Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +void save_stack_trace(struct stack_trace *trace) +{ + save_stack_trace_tsk(current, trace); +} + +void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) +{ + unsigned long *fpn; + int skip = trace->skip; + int savesched; + + if (tsk == current) { + __asm__ __volatile__("\tori\t%0, $fp, #0\n":"=r"(fpn)); + savesched = 1; + } else { + fpn = (unsigned long *)thread_saved_fp(tsk); + savesched = 0; + } + + while (!kstack_end(fpn) && !((unsigned long)fpn & 0x3) + && (fpn >= (unsigned long *)TASK_SIZE)) { + unsigned long lpp, fpp; + + lpp = fpn[-1]; + fpp = fpn[FP_OFFSET]; + if (!__kernel_text_address(lpp)) + break; + + if (savesched || !in_sched_functions(lpp)) { + if (skip) { + skip--; + } else { + trace->entries[trace->nr_entries++] = lpp; + if (trace->nr_entries >= trace->max_entries) + break; + } + } + fpn = (unsigned long *)fpp; + } +} diff --git a/arch/nds32/kernel/traps.c b/arch/nds32/kernel/traps.c new file mode 100644 index 0000000..07d8f77 --- /dev/null +++ b/arch/nds32/kernel/traps.c @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2005-2017 Andes Technology Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +extern void show_pte(struct mm_struct *mm, unsigned long addr); + +/* + * Dump out the contents of some memory nicely... + */ +void dump_mem(const char *lvl, unsigned long bottom, unsigned long top) +{ + unsigned long first; + mm_segment_t fs; + int i; + + /* + * We need to switch to kernel mode so that we can use __get_user + * to safely read from kernel space. Note that we now dump the + * code first, just in case the backtrace kills us. + */ + fs = get_fs(); + set_fs(KERNEL_DS); + + pr_emerg("%s(0x%08lx to 0x%08lx)\n", lvl, bottom, top); + + for (first = bottom & ~31; first < top; first += 32) { + unsigned long p; + char str[sizeof(" 12345678") * 8 + 1]; + + memset(str, ' ', sizeof(str)); + str[sizeof(str) - 1] = '\0'; + + for (p = first, i = 0; i < 8 && p < top; i++, p += 4) { + if (p >= bottom && p < top) { + unsigned long val; + if (__get_user(val, (unsigned long *)p) == 0) + sprintf(str + i * 9, " %08lx", val); + else + sprintf(str + i * 9, " ????????"); + } + } + pr_emerg("%s%04lx:%s\n", lvl, first & 0xffff, str); + } + + set_fs(fs); +} + +EXPORT_SYMBOL(dump_mem); + +static void dump_instr(struct pt_regs *regs) +{ + unsigned long addr = instruction_pointer(regs); + mm_segment_t fs; + char str[sizeof("00000000 ") * 5 + 2 + 1], *p = str; + int i; + + return; + /* + * We need to switch to kernel mode so that we can use __get_user + * to safely read from kernel space. Note that we now dump the + * code first, just in case the backtrace kills us. + */ + fs = get_fs(); + set_fs(KERNEL_DS); + + pr_emerg("Code: "); + for (i = -4; i < 1; i++) { + unsigned int val, bad; + + bad = __get_user(val, &((u32 *) addr)[i]); + + if (!bad) { + p += sprintf(p, i == 0 ? "(%08x) " : "%08x ", val); + } else { + p += sprintf(p, "bad PC value"); + break; + } + } + pr_emerg("Code: %s\n", str); + + set_fs(fs); +} + +#ifdef CONFIG_FUNCTION_GRAPH_TRACER +#include +static void +get_real_ret_addr(unsigned long *addr, struct task_struct *tsk, int *graph) +{ + if (*addr == (unsigned long)return_to_handler) { + int index = tsk->curr_ret_stack; + + if (tsk->ret_stack && index >= *graph) { + index -= *graph; + *addr = tsk->ret_stack[index].ret; + (*graph)++; + } + } +} +#else +static inline void +get_real_ret_addr(unsigned long *addr, struct task_struct *tsk, int *graph) +{ +} +#endif + +#define LOOP_TIMES (100) +static void __dump(struct task_struct *tsk, unsigned long *base_reg) +{ + unsigned long ret_addr; + int cnt = LOOP_TIMES, graph = 0; + pr_emerg("Call Trace:\n"); +#ifndef CONFIG_FRAME_POINTER + while (!kstack_end(base_reg)) { + ret_addr = *base_reg++; + if (__kernel_text_address(ret_addr)) { + get_real_ret_addr(&ret_addr, tsk, &graph); + print_ip_sym(ret_addr); + } + if (--cnt < 0) + break; + } +#else + while (!kstack_end((void *)base_reg) && + !((unsigned long)base_reg & 0x3) && + ((unsigned long)base_reg >= TASK_SIZE)) { + unsigned long next_fp; +#if !defined(NDS32_ABI_2) + ret_addr = base_reg[0]; + next_fp = base_reg[1]; +#else + ret_addr = base_reg[-1]; + next_fp = base_reg[FP_OFFSET]; +#endif + if (__kernel_text_address(ret_addr)) { + get_real_ret_addr(&ret_addr, tsk, &graph); + print_ip_sym(ret_addr); + } + + if (--cnt < 0) + break; + + base_reg = (unsigned long *)next_fp; + } +#endif + pr_emerg("\n"); +} + +void dump_stack(void) +{ + unsigned long *base_reg; +#ifndef CONFIG_FRAME_POINTER + __asm__ __volatile__("\tori\t%0, $sp, #0\n":"=r"(base_reg)); +#else + __asm__ __volatile__("\tori\t%0, $fp, #0\n":"=r"(base_reg)); +#endif + __dump(NULL, base_reg); +} + +EXPORT_SYMBOL(dump_stack); + +void show_stack(struct task_struct *tsk, unsigned long *sp) +{ + unsigned long *base_reg; + + if (!tsk) + tsk = current; +#ifndef CONFIG_FRAME_POINTER + if (tsk != current) + base_reg = (unsigned long *)(tsk->thread.cpu_context); + else + __asm__ __volatile__("\tori\t%0, $sp, #0\n":"=r"(base_reg)); +#else + if (tsk != current) + base_reg = (unsigned long *)(tsk->thread.cpu_context.fp); + else + __asm__ __volatile__("\tori\t%0, $fp, #0\n":"=r"(base_reg)); +#endif + __dump(tsk, base_reg); + barrier(); +} + +DEFINE_SPINLOCK(die_lock); + +/* + * This function is protected against re-entrancy. + */ +void die(const char *str, struct pt_regs *regs, int err) +{ + struct task_struct *tsk = current; + static int die_counter; + + console_verbose(); + spin_lock_irq(&die_lock); + bust_spinlocks(1); + + pr_emerg("Internal error: %s: %x [#%d]\n", str, err, ++die_counter); + print_modules(); + pr_emerg("CPU: %i\n", smp_processor_id()); + show_regs(regs); + pr_emerg("Process %s (pid: %d, stack limit = 0x%p)\n", + tsk->comm, tsk->pid, task_thread_info(tsk) + 1); + + if (!user_mode(regs) || in_interrupt()) { + dump_mem("Stack: ", regs->sp, + THREAD_SIZE + (unsigned long)task_thread_info(tsk)); + dump_instr(regs); + dump_stack(); + } + + bust_spinlocks(0); + spin_unlock_irq(&die_lock); + do_exit(SIGSEGV); +} + +EXPORT_SYMBOL(die); + +void die_if_kernel(const char *str, struct pt_regs *regs, int err) +{ + if (user_mode(regs)) + return; + + die(str, regs, err); +} + +int bad_syscall(int n, struct pt_regs *regs) +{ + siginfo_t info; + + if (current->personality != PER_LINUX) { + send_sig(SIGSEGV, current, 1); + return regs->uregs[0]; + } + + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_ILLTRP; + info.si_addr = (void __user *)instruction_pointer(regs) - 4; + + force_sig_info(SIGILL, &info, current); + die_if_kernel("Oops - bad syscall", regs, n); + return regs->uregs[0]; +} + +void __pte_error(const char *file, int line, unsigned long val) +{ + pr_emerg("%s:%d: bad pte %08lx.\n", file, line, val); +} + +void __pmd_error(const char *file, int line, unsigned long val) +{ + pr_emerg("%s:%d: bad pmd %08lx.\n", file, line, val); +} + +void __pgd_error(const char *file, int line, unsigned long val) +{ + pr_emerg("%s:%d: bad pgd %08lx.\n", file, line, val); +} + +extern char *exception_vector, *exception_vector_end; +void __init trap_init(void) +{ + return; +} + +void __init early_trap_init(void) +{ + unsigned long ivb = 0; + unsigned long base = PAGE_OFFSET; + + memcpy((unsigned long *)base, (unsigned long *)&exception_vector, + ((unsigned long)&exception_vector_end - + (unsigned long)&exception_vector)); + ivb = __nds32__mfsr(NDS32_SR_IVB); + /* Check platform support. */ + if (((ivb & IVB_mskNIVIC) >> IVB_offNIVIC) < 2) + panic + ("IVIC mode is not allowed on the platform with interrupt controller\n"); + __nds32__mtsr((ivb & ~IVB_mskESZ) | (IVB_valESZ16 << IVB_offESZ) | + IVB_BASE, NDS32_SR_IVB); + __nds32__mtsr(INT_MASK_INITAIAL_VAL, NDS32_SR_INT_MASK); + + /* + * 0x800 = 128 vectors * 16byte. + * It should be enough to flush a page. + */ + flush_icache_range(base, base + PAGE_SIZE); +} + +void do_debug_trap(unsigned long entry, unsigned long addr, + unsigned long type, struct pt_regs *regs) +{ + if (notify_die(DIE_OOPS, "Oops", regs, addr, type, SIGTRAP) + == NOTIFY_STOP) + return; + + if (user_mode(regs)) { + /* trap_signal */ + send_sigtrap(current, regs, 0, TRAP_BRKPT); + } else { + /* kernel_trap */ + if (!fixup_exception(regs)) + die("unexpected kernel_trap", regs, 0); + } +} + +void unhandled_interruption(struct pt_regs *regs) +{ + siginfo_t si; + pr_emerg("unhandled_interruption\n"); + show_regs(regs); + if (!user_mode(regs)) + do_exit(SIGKILL); + si.si_signo = SIGKILL; + si.si_errno = 0; + force_sig_info(SIGKILL, &si, current); +} + +void unhandled_exceptions(unsigned long entry, unsigned long addr, + unsigned long type, struct pt_regs *regs) +{ + siginfo_t si; + pr_emerg("Unhandled Exception: entry: %lx addr:%lx itype:%lx\n", entry, + addr, type); + show_regs(regs); + if (!user_mode(regs)) + do_exit(SIGKILL); + si.si_signo = SIGKILL; + si.si_errno = 0; + si.si_addr = (void *)addr; + force_sig_info(SIGKILL, &si, current); +} + +extern int do_page_fault(unsigned long entry, unsigned long addr, + unsigned int error_code, struct pt_regs *regs); + +/* + * 2:DEF dispatch for TLB MISC exception handler +*/ + +void do_dispatch_tlb_misc(unsigned long entry, unsigned long addr, + unsigned long type, struct pt_regs *regs) +{ + type = type & (ITYPE_mskINST | ITYPE_mskETYPE); + if ((type & ITYPE_mskETYPE) < 5) { + /* Permission exceptions */ + do_page_fault(entry, addr, type, regs); + } else + unhandled_exceptions(entry, addr, type, regs); +} + +int (*do_unaligned_access) (unsigned long entry, unsigned long addr, + unsigned long type, struct pt_regs * regs) = NULL; + +EXPORT_SYMBOL(do_unaligned_access); + +void do_revinsn(struct pt_regs *regs) +{ + siginfo_t si; + pr_emerg("Reserved Instruction\n"); + show_regs(regs); + if (!user_mode(regs)) + do_exit(SIGILL); + si.si_signo = SIGILL; + si.si_errno = 0; + force_sig_info(SIGILL, &si, current); +} + +void do_dispatch_general(unsigned long entry, unsigned long addr, + unsigned long itype, struct pt_regs *regs, + unsigned long oipc) +{ + unsigned int swid = itype >> ITYPE_offSWID; + unsigned long type = itype & (ITYPE_mskINST | ITYPE_mskETYPE); + if (type == ETYPE_ALIGNMENT_CHECK) { + /* Alignment check */ + if (do_unaligned_access) { + + int ret = do_unaligned_access(entry, addr, type, regs); + + if (ret == 0) + return; + + if (ret == -EFAULT) + pr_emerg + ("Unhandled unaligned access exception\n"); + } + do_page_fault(entry, addr, type, regs); + } else if (type == ETYPE_RESERVED_INSTRUCTION) { + /* Reserved instruction */ + do_revinsn(regs); + } else if (type == ETYPE_TRAP && swid == SWID_RAISE_INTERRUPT_LEVEL) { + /* trap, used on v3 EDM target debugging workaround */ + /* + * DIPC(OIPC) is passed as parameter before + * interrupt is enabled, so the DIPC will not be corrupted + * even though interrupts are coming in + */ + /* + * 1. update ipc + * 2. update pt_regs ipc with oipc + * 3. update pt_regs ipsw (clear DEX) + */ + __asm__ volatile ("mtsr %0, $IPC\n\t"::"r" (oipc)); + regs->ipc = oipc; + if (regs->pipsw & PSW_mskDEX) { + pr_emerg + ("Nested Debug exception is possibly happened\n"); + pr_emerg("ipc:%08x pipc:%08x\n", + (unsigned int)regs->ipc, + (unsigned int)regs->pipc); + } + do_debug_trap(entry, addr, itype, regs); + regs->ipsw &= ~PSW_mskDEX; + } else + unhandled_exceptions(entry, addr, type, regs); +} diff --git a/arch/nds32/mm/alignment.c b/arch/nds32/mm/alignment.c new file mode 100644 index 0000000..05589e7 --- /dev/null +++ b/arch/nds32/mm/alignment.c @@ -0,0 +1,564 @@ +/* + * Copyright (C) 2005-2017 Andes Technology Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include + +#ifdef CONFIG_PROC_FS +extern struct proc_dir_entry *proc_dir_cpu; +#endif + +#define DEBUG(enable, tagged, ...) \ + do{ \ + if (enable) { \ + if (tagged) \ + pr_warn("[ %30s() ] ", __func__); \ + pr_warn(__VA_ARGS__); \ + } \ + } while (0) + +#define RT(inst) (((inst) >> 20) & 0x1FUL) +#define RA(inst) (((inst) >> 15) & 0x1FUL) +#define RB(inst) (((inst) >> 10) & 0x1FUL) +#define SV(inst) (((inst) >> 8) & 0x3UL) +#define IMM(inst) (((inst) >> 0) & 0x3FFFUL) + +#define RA3(inst) (((inst) >> 3) & 0x7UL) +#define RT3(inst) (((inst) >> 6) & 0x7UL) +#define IMM3U(inst) (((inst) >> 0) & 0x7UL) + +#define RA5(inst) (((inst) >> 0) & 0x1FUL) +#define RT4(inst) (((inst) >> 5) & 0xFUL) + +extern int (*do_unaligned_access) + (unsigned long entry, unsigned long addr, + unsigned long type, struct pt_regs * regs); +extern pte_t va_present(struct mm_struct *mm, unsigned long addr); +extern pte_t va_kernel_present(unsigned long addr); +extern int va_readable(struct pt_regs *regs, unsigned long addr); +extern int va_writable(struct pt_regs *regs, unsigned long addr); + +static int mode = 0x3; +module_param(mode, int, S_IWUSR | S_IRUGO); + +static inline unsigned long *idx_to_addr(struct pt_regs *regs, int idx) +{ + /* this should be consistent with ptrace.h */ + if (idx >= 0 && idx <= 25) /* R0-R25 */ + return ®s->uregs[0] + idx; + else if (idx >= 28 && idx <= 30) /* FP, GP, LP */ + return ®s->fp + (idx - 28); + else if (idx == 31) /* SP */ + return ®s->sp; + else + return NULL; /* cause a segfault */ +} + +static inline unsigned long get_inst(unsigned long addr) +{ + /* FIXME: consider 16-bit inst. */ + return be32_to_cpu(get_unaligned((u32 *) addr)); +} + +static inline unsigned long get_data(unsigned long addr, int len) +{ + if (len == 4) + return get_unaligned((u32 *) addr); + else + return get_unaligned((u16 *) addr); +} + +static inline void set_data(unsigned long addr, unsigned long val, int len) +{ + if (len == 4) + put_unaligned(val, (u32 *) addr); + else + put_unaligned(val, (u16 *) addr); +} + +static inline unsigned long sign_extend(unsigned long val, int len) +{ + unsigned long ret = 0; + unsigned char *s, *t; + int i = 0; + + val = cpu_to_le32(val); + + s = (void *)&val; + t = (void *)&ret; + + while (i++ < len) + *t++ = *s++; + + if (((*(t - 1)) & 0x80) && (i < 4)) { + + while (i++ <= 4) + *t++ = 0xff; + } + + return le32_to_cpu(ret); +} + +static inline int do_16(unsigned long inst, struct pt_regs *regs) +{ + int imm, regular, load, len, addr_mode, idx_mode; + unsigned long unaligned_addr, target_val, source_idx, target_idx, + shift = 0; + switch ((inst >> 9) & 0x3F) { + + case 0x12: /* LHI333 */ + imm = 1; + regular = 1; + load = 1; + len = 2; + addr_mode = 3; + idx_mode = 3; + break; + case 0x10: /* LWI333 */ + imm = 1; + regular = 1; + load = 1; + len = 4; + addr_mode = 3; + idx_mode = 3; + break; + case 0x11: /* LWI333.bi */ + imm = 1; + regular = 0; + load = 1; + len = 4; + addr_mode = 3; + idx_mode = 3; + break; + case 0x1A: /* LWI450 */ + imm = 0; + regular = 1; + load = 1; + len = 4; + addr_mode = 5; + idx_mode = 4; + break; + case 0x16: /* SHI333 */ + imm = 1; + regular = 1; + load = 0; + len = 2; + addr_mode = 3; + idx_mode = 3; + break; + case 0x14: /* SWI333 */ + imm = 1; + regular = 1; + load = 0; + len = 4; + addr_mode = 3; + idx_mode = 3; + break; + case 0x15: /* SWI333.bi */ + imm = 1; + regular = 0; + load = 0; + len = 4; + addr_mode = 3; + idx_mode = 3; + break; + case 0x1B: /* SWI450 */ + imm = 0; + regular = 1; + load = 0; + len = 4; + addr_mode = 5; + idx_mode = 4; + break; + + default: + return -EFAULT; + } + + if (addr_mode == 3) { + unaligned_addr = *idx_to_addr(regs, RA3(inst)); + source_idx = RA3(inst); + } else { + unaligned_addr = *idx_to_addr(regs, RA5(inst)); + source_idx = RA5(inst); + } + + if (idx_mode == 3) + target_idx = RT3(inst); + else + target_idx = RT4(inst); + + if (imm) + shift = IMM3U(inst) * len; + + if (regular) + unaligned_addr += shift; + else + *idx_to_addr(regs, source_idx) = unaligned_addr + shift; + + if (load) { + + if (!va_readable(regs, unaligned_addr)) + return -EACCES; + + if (!access_ok(VERIFY_READ, (void *)unaligned_addr, len)) + return -EACCES; + + *idx_to_addr(regs, target_idx) = get_data(unaligned_addr, len); + } else { + + if (!va_writable(regs, unaligned_addr)) + return -EACCES; + + if (!access_ok(VERIFY_WRITE, (void *)unaligned_addr, len)) + return -EACCES; + + target_val = *idx_to_addr(regs, target_idx); + set_data(unaligned_addr, target_val, len); + } + + regs->ipc += 2; + + return 0; +} + +static inline int do_32(unsigned long inst, struct pt_regs *regs) +{ + int imm, regular, load, len, sign_ext; + unsigned long unsligned_addr, target_val, shift; + + unsligned_addr = *idx_to_addr(regs, RA(inst)); + + switch ((inst >> 25) << 1) { + + case 0x02: /* LHI */ + imm = 1; + regular = 1; + load = 1; + len = 2; + sign_ext = 0; + break; + case 0x0A: /* LHI.bi */ + imm = 1; + regular = 0; + load = 1; + len = 2; + sign_ext = 0; + break; + case 0x22: /* LHSI */ + imm = 1; + regular = 1; + load = 1; + len = 2; + sign_ext = 1; + break; + case 0x2A: /* LHSI.bi */ + imm = 1; + regular = 0; + load = 1; + len = 2; + sign_ext = 1; + break; + case 0x04: /* LWI */ + imm = 1; + regular = 1; + load = 1; + len = 4; + sign_ext = 0; + break; + case 0x0C: /* LWI.bi */ + imm = 1; + regular = 0; + load = 1; + len = 4; + sign_ext = 0; + break; + case 0x12: /* SHI */ + imm = 1; + regular = 1; + load = 0; + len = 2; + sign_ext = 0; + break; + case 0x1A: /* SHI.bi */ + imm = 1; + regular = 0; + load = 0; + len = 2; + sign_ext = 0; + break; + case 0x14: /* SWI */ + imm = 1; + regular = 1; + load = 0; + len = 4; + sign_ext = 0; + break; + case 0x1C: /* SWI.bi */ + imm = 1; + regular = 0; + load = 0; + len = 4; + sign_ext = 0; + break; + + default: + switch (inst & 0xff) { + + case 0x01: /* LH */ + imm = 0; + regular = 1; + load = 1; + len = 2; + sign_ext = 0; + break; + case 0x05: /* LH.bi */ + imm = 0; + regular = 0; + load = 1; + len = 2; + sign_ext = 0; + break; + case 0x11: /* LHS */ + imm = 0; + regular = 1; + load = 1; + len = 2; + sign_ext = 1; + break; + case 0x15: /* LHS.bi */ + imm = 0; + regular = 0; + load = 1; + len = 2; + sign_ext = 1; + break; + case 0x02: /* LW */ + imm = 0; + regular = 1; + load = 1; + len = 4; + sign_ext = 0; + break; + case 0x06: /* LW.bi */ + imm = 0; + regular = 0; + load = 1; + len = 4; + sign_ext = 0; + break; + case 0x09: /* SH */ + imm = 0; + regular = 1; + load = 0; + len = 2; + sign_ext = 0; + break; + case 0x0D: /* SH.bi */ + imm = 0; + regular = 0; + load = 0; + len = 2; + sign_ext = 0; + break; + case 0x0A: /* SW */ + imm = 0; + regular = 1; + load = 0; + len = 4; + sign_ext = 0; + break; + case 0x0E: /* SW.bi */ + imm = 0; + regular = 0; + load = 0; + len = 4; + sign_ext = 0; + break; + + default: + return -EFAULT; + } + } + + if (imm) + shift = IMM(inst) * len; + else + shift = *idx_to_addr(regs, RB(inst)) << SV(inst); + + if (regular) + unsligned_addr += shift; + else + *idx_to_addr(regs, RA(inst)) = unsligned_addr + shift; + + if (load) { + + if (!va_readable(regs, unsligned_addr)) + return -EACCES; + + if (!access_ok(VERIFY_READ, (void *)unsligned_addr, len)) + return -EACCES; + + if (sign_ext) + *idx_to_addr(regs, RT(inst)) = + sign_extend(get_data(unsligned_addr, len), len); + else + *idx_to_addr(regs, RT(inst)) = + get_data(unsligned_addr, len); + } else { + + if (!va_writable(regs, unsligned_addr)) + return -EACCES; + + if (!access_ok(VERIFY_WRITE, (void *)unsligned_addr, len)) + return -EACCES; + + target_val = *idx_to_addr(regs, RT(inst)); + set_data(unsligned_addr, target_val, len); + } + + regs->ipc += 4; + + return 0; +} + +static int _do_unaligned_access(unsigned long entry, unsigned long addr, + unsigned long type, struct pt_regs *regs) +{ + unsigned long inst; + int ret = -EFAULT; + + if (user_mode(regs)) { + /* user mode */ + if (!va_present(current->mm, addr)) + return ret; + } else { + /* kernel mode */ + if (!va_kernel_present(addr)) + return ret; + } + + inst = get_inst(regs->ipc); + + DEBUG(mode & 0x04, 1, + "Faulting Addr: 0x%08lx, PC: 0x%08lx [ 0x%08lx ]\n", addr, + regs->ipc, inst); + + if ((user_mode(regs) && (mode & 0x01)) + || (!user_mode(regs) && (mode & 0x02))) { + + mm_segment_t seg = get_fs(); + + set_fs(KERNEL_DS); + + if (inst & 0x80000000) + ret = do_16((inst >> 16) & 0xffff, regs); + else + ret = do_32(inst, regs); + + set_fs(seg); + } + + return ret; +} + +#ifdef CONFIG_PROC_FS + +static int proc_alignment_read(struct file *file, char __user * ubuf, + size_t count, loff_t * ppos) +{ + char p[80]; + int ret; + + ret = sprintf(p, "(0x01) User Mode: %s\n" + "(0x02) Kernel Mode: %s\n" + "(0x04) Warning: %s\n", + mode & 0x01 ? "on" : "off", + mode & 0x02 ? "on" : "off", mode & 0x04 ? "on" : "off"); + + return simple_read_from_buffer(ubuf, count, ppos, p, ret); +} + +#define INPUTLEN 12 /* '0' + 'x' + 8digit + '\n' + '\0' */ + +static ssize_t proc_alignment_write(struct file *file, + const char __user * ubuf, size_t count, + loff_t * ppos) +{ + unsigned long en; + char *endp; + char inbuf[INPUTLEN]; + + if (count > INPUTLEN - 1) + return -EFAULT; + + if (copy_from_user(inbuf, ubuf, count)) + return -EFAULT; + + inbuf[count - 1] = '\0'; + + en = simple_strtoul(inbuf, &endp, 0); + if (en > 0x07) + return -EFAULT; + + mode = en & 0x7; + + return count; +} + +static const struct file_operations fops = { + .open = simple_open, + .read = proc_alignment_read, + .write = proc_alignment_write, +}; +#endif /* CONFIG_PROC_FS */ + +static int __init unaligned_access_init(void) +{ +#ifdef CONFIG_PROC_FS + static struct proc_dir_entry *res_alignment; + + if (!proc_dir_cpu) + if (!(proc_dir_cpu = proc_mkdir("cpu", NULL))) + return -ENOMEM; + + if (! + (res_alignment = + proc_create("alignment", S_IWUSR | S_IRUGO, proc_dir_cpu, &fops))) + return -ENOMEM; + +#endif + do_unaligned_access = _do_unaligned_access; + + return 0; +} + +static void __exit unaligned_access_exit(void) +{ +#ifdef CONFIG_PROC_FS + remove_proc_entry("alignment", proc_dir_cpu); +#endif + do_unaligned_access = NULL; +} + +MODULE_DESCRIPTION("Unaligned Access Handler"); +MODULE_LICENSE("GPL"); + +module_init(unaligned_access_init); +module_exit(unaligned_access_exit);