c79b954bf6
This patch implements 4 levels of translation tables since 3 levels of page tables with 4KB pages cannot support 40-bit physical address space described in [1] due to the following issue. It is a restriction that kernel logical memory map with 4KB + 3 levels (0xffffffc000000000-0xffffffffffffffff) cannot cover RAM region from 544GB to 1024GB in [1]. Specifically, ARM64 kernel fails to create mapping for this region in map_mem function since __phys_to_virt for this region reaches to address overflow. If SoC design follows the document, [1], over 32GB RAM would be placed from 544GB. Even 64GB system is supposed to use the region from 544GB to 576GB for only 32GB RAM. Naturally, it would reach to enable 4 levels of page tables to avoid hacking __virt_to_phys and __phys_to_virt. However, it is recommended 4 levels of page table should be only enabled if memory map is too sparse or there is about 512GB RAM. References ---------- [1]: Principles of ARM Memory Maps, White Paper, Issue C Signed-off-by: Jungseok Lee <jays.lee@samsung.com> Reviewed-by: Sungjinn Chung <sungjinn.chung@samsung.com> Acked-by: Kukjin Kim <kgene.kim@samsung.com> Reviewed-by: Christoffer Dall <christoffer.dall@linaro.org> Reviewed-by: Steve Capper <steve.capper@linaro.org> [catalin.marinas@arm.com: MEMBLOCK_INITIAL_LIMIT removed, same as PUD_SIZE] [catalin.marinas@arm.com: early_ioremap_init() updated for 4 levels] [catalin.marinas@arm.com: 48-bit VA depends on BROKEN until KVM is fixed] Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> Tested-by: Jungseok Lee <jungseoklee85@gmail.com>
356 lines
8.1 KiB
C
356 lines
8.1 KiB
C
/*
|
|
* Based on arch/arm/kernel/traps.c
|
|
*
|
|
* Copyright (C) 1995-2009 Russell King
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/signal.h>
|
|
#include <linux/personality.h>
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/hardirq.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kexec.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/syscalls.h>
|
|
|
|
#include <asm/atomic.h>
|
|
#include <asm/debug-monitors.h>
|
|
#include <asm/traps.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/exception.h>
|
|
#include <asm/system_misc.h>
|
|
|
|
static const char *handler[]= {
|
|
"Synchronous Abort",
|
|
"IRQ",
|
|
"FIQ",
|
|
"Error"
|
|
};
|
|
|
|
int show_unhandled_signals = 1;
|
|
|
|
/*
|
|
* Dump out the contents of some memory nicely...
|
|
*/
|
|
static void dump_mem(const char *lvl, const char *str, 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);
|
|
|
|
printk("%s%s(0x%016lx to 0x%016lx)\n", lvl, str, 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 int val;
|
|
if (__get_user(val, (unsigned int *)p) == 0)
|
|
sprintf(str + i * 9, " %08x", val);
|
|
else
|
|
sprintf(str + i * 9, " ????????");
|
|
}
|
|
}
|
|
printk("%s%04lx:%s\n", lvl, first & 0xffff, str);
|
|
}
|
|
|
|
set_fs(fs);
|
|
}
|
|
|
|
static void dump_backtrace_entry(unsigned long where, unsigned long stack)
|
|
{
|
|
print_ip_sym(where);
|
|
if (in_exception_text(where))
|
|
dump_mem("", "Exception stack", stack,
|
|
stack + sizeof(struct pt_regs));
|
|
}
|
|
|
|
static void dump_instr(const char *lvl, 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;
|
|
|
|
/*
|
|
* 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);
|
|
|
|
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;
|
|
}
|
|
}
|
|
printk("%sCode: %s\n", lvl, str);
|
|
|
|
set_fs(fs);
|
|
}
|
|
|
|
static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
|
|
{
|
|
struct stackframe frame;
|
|
const register unsigned long current_sp asm ("sp");
|
|
|
|
pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);
|
|
|
|
if (!tsk)
|
|
tsk = current;
|
|
|
|
if (regs) {
|
|
frame.fp = regs->regs[29];
|
|
frame.sp = regs->sp;
|
|
frame.pc = regs->pc;
|
|
} else if (tsk == current) {
|
|
frame.fp = (unsigned long)__builtin_frame_address(0);
|
|
frame.sp = current_sp;
|
|
frame.pc = (unsigned long)dump_backtrace;
|
|
} else {
|
|
/*
|
|
* task blocked in __switch_to
|
|
*/
|
|
frame.fp = thread_saved_fp(tsk);
|
|
frame.sp = thread_saved_sp(tsk);
|
|
frame.pc = thread_saved_pc(tsk);
|
|
}
|
|
|
|
pr_emerg("Call trace:\n");
|
|
while (1) {
|
|
unsigned long where = frame.pc;
|
|
int ret;
|
|
|
|
ret = unwind_frame(&frame);
|
|
if (ret < 0)
|
|
break;
|
|
dump_backtrace_entry(where, frame.sp);
|
|
}
|
|
}
|
|
|
|
void show_stack(struct task_struct *tsk, unsigned long *sp)
|
|
{
|
|
dump_backtrace(NULL, tsk);
|
|
barrier();
|
|
}
|
|
|
|
#ifdef CONFIG_PREEMPT
|
|
#define S_PREEMPT " PREEMPT"
|
|
#else
|
|
#define S_PREEMPT ""
|
|
#endif
|
|
#ifdef CONFIG_SMP
|
|
#define S_SMP " SMP"
|
|
#else
|
|
#define S_SMP ""
|
|
#endif
|
|
|
|
static int __die(const char *str, int err, struct thread_info *thread,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct task_struct *tsk = thread->task;
|
|
static int die_counter;
|
|
int ret;
|
|
|
|
pr_emerg("Internal error: %s: %x [#%d]" S_PREEMPT S_SMP "\n",
|
|
str, err, ++die_counter);
|
|
|
|
/* trap and error numbers are mostly meaningless on ARM */
|
|
ret = notify_die(DIE_OOPS, str, regs, err, 0, SIGSEGV);
|
|
if (ret == NOTIFY_STOP)
|
|
return ret;
|
|
|
|
print_modules();
|
|
__show_regs(regs);
|
|
pr_emerg("Process %.*s (pid: %d, stack limit = 0x%p)\n",
|
|
TASK_COMM_LEN, tsk->comm, task_pid_nr(tsk), thread + 1);
|
|
|
|
if (!user_mode(regs) || in_interrupt()) {
|
|
dump_mem(KERN_EMERG, "Stack: ", regs->sp,
|
|
THREAD_SIZE + (unsigned long)task_stack_page(tsk));
|
|
dump_backtrace(regs, tsk);
|
|
dump_instr(KERN_EMERG, regs);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static DEFINE_RAW_SPINLOCK(die_lock);
|
|
|
|
/*
|
|
* This function is protected against re-entrancy.
|
|
*/
|
|
void die(const char *str, struct pt_regs *regs, int err)
|
|
{
|
|
struct thread_info *thread = current_thread_info();
|
|
int ret;
|
|
|
|
oops_enter();
|
|
|
|
raw_spin_lock_irq(&die_lock);
|
|
console_verbose();
|
|
bust_spinlocks(1);
|
|
ret = __die(str, err, thread, regs);
|
|
|
|
if (regs && kexec_should_crash(thread->task))
|
|
crash_kexec(regs);
|
|
|
|
bust_spinlocks(0);
|
|
add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE);
|
|
raw_spin_unlock_irq(&die_lock);
|
|
oops_exit();
|
|
|
|
if (in_interrupt())
|
|
panic("Fatal exception in interrupt");
|
|
if (panic_on_oops)
|
|
panic("Fatal exception");
|
|
if (ret != NOTIFY_STOP)
|
|
do_exit(SIGSEGV);
|
|
}
|
|
|
|
void arm64_notify_die(const char *str, struct pt_regs *regs,
|
|
struct siginfo *info, int err)
|
|
{
|
|
if (user_mode(regs)) {
|
|
current->thread.fault_address = 0;
|
|
current->thread.fault_code = err;
|
|
force_sig_info(info->si_signo, info, current);
|
|
} else {
|
|
die(str, regs, err);
|
|
}
|
|
}
|
|
|
|
asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
|
|
{
|
|
siginfo_t info;
|
|
void __user *pc = (void __user *)instruction_pointer(regs);
|
|
|
|
/* check for AArch32 breakpoint instructions */
|
|
if (!aarch32_break_handler(regs))
|
|
return;
|
|
|
|
if (show_unhandled_signals && unhandled_signal(current, SIGILL) &&
|
|
printk_ratelimit()) {
|
|
pr_info("%s[%d]: undefined instruction: pc=%p\n",
|
|
current->comm, task_pid_nr(current), pc);
|
|
dump_instr(KERN_INFO, regs);
|
|
}
|
|
|
|
info.si_signo = SIGILL;
|
|
info.si_errno = 0;
|
|
info.si_code = ILL_ILLOPC;
|
|
info.si_addr = pc;
|
|
|
|
arm64_notify_die("Oops - undefined instruction", regs, &info, 0);
|
|
}
|
|
|
|
long compat_arm_syscall(struct pt_regs *regs);
|
|
|
|
asmlinkage long do_ni_syscall(struct pt_regs *regs)
|
|
{
|
|
#ifdef CONFIG_COMPAT
|
|
long ret;
|
|
if (is_compat_task()) {
|
|
ret = compat_arm_syscall(regs);
|
|
if (ret != -ENOSYS)
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
if (show_unhandled_signals && printk_ratelimit()) {
|
|
pr_info("%s[%d]: syscall %d\n", current->comm,
|
|
task_pid_nr(current), (int)regs->syscallno);
|
|
dump_instr("", regs);
|
|
if (user_mode(regs))
|
|
__show_regs(regs);
|
|
}
|
|
|
|
return sys_ni_syscall();
|
|
}
|
|
|
|
/*
|
|
* bad_mode handles the impossible case in the exception vector.
|
|
*/
|
|
asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr)
|
|
{
|
|
siginfo_t info;
|
|
void __user *pc = (void __user *)instruction_pointer(regs);
|
|
console_verbose();
|
|
|
|
pr_crit("Bad mode in %s handler detected, code 0x%08x\n",
|
|
handler[reason], esr);
|
|
__show_regs(regs);
|
|
|
|
info.si_signo = SIGILL;
|
|
info.si_errno = 0;
|
|
info.si_code = ILL_ILLOPC;
|
|
info.si_addr = pc;
|
|
|
|
arm64_notify_die("Oops - bad mode", regs, &info, 0);
|
|
}
|
|
|
|
void __pte_error(const char *file, int line, unsigned long val)
|
|
{
|
|
pr_crit("%s:%d: bad pte %016lx.\n", file, line, val);
|
|
}
|
|
|
|
void __pmd_error(const char *file, int line, unsigned long val)
|
|
{
|
|
pr_crit("%s:%d: bad pmd %016lx.\n", file, line, val);
|
|
}
|
|
|
|
void __pud_error(const char *file, int line, unsigned long val)
|
|
{
|
|
pr_crit("%s:%d: bad pud %016lx.\n", file, line, val);
|
|
}
|
|
|
|
void __pgd_error(const char *file, int line, unsigned long val)
|
|
{
|
|
pr_crit("%s:%d: bad pgd %016lx.\n", file, line, val);
|
|
}
|
|
|
|
void __init trap_init(void)
|
|
{
|
|
return;
|
|
}
|