summaryrefslogtreecommitdiffstats
path: root/arch/arm/mm/fault.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mm/fault.c')
-rw-r--r--arch/arm/mm/fault.c59
1 files changed, 45 insertions, 14 deletions
diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c
index 92f5801f99c1..23b0b03af5ea 100644
--- a/arch/arm/mm/fault.c
+++ b/arch/arm/mm/fault.c
@@ -393,6 +393,9 @@ do_translation_fault(unsigned long addr, unsigned int fsr,
if (addr < TASK_SIZE)
return do_page_fault(addr, fsr, regs);
+ if (user_mode(regs))
+ goto bad_area;
+
index = pgd_index(addr);
/*
@@ -410,7 +413,16 @@ do_translation_fault(unsigned long addr, unsigned int fsr,
pmd_k = pmd_offset(pgd_k, addr);
pmd = pmd_offset(pgd, addr);
- if (pmd_none(*pmd_k))
+ /*
+ * On ARM one Linux PGD entry contains two hardware entries (see page
+ * tables layout in pgtable.h). We normally guarantee that we always
+ * fill both L1 entries. But create_mapping() doesn't follow the rule.
+ * It can create inidividual L1 entries, so here we have to call
+ * pmd_none() check for the entry really corresponded to address, not
+ * for the first of pair.
+ */
+ index = (addr >> SECTION_SHIFT) & 1;
+ if (pmd_none(pmd_k[index]))
goto bad_area;
copy_pmd(pmd, pmd_k);
@@ -460,15 +472,10 @@ static struct fsr_info {
* defines these to be "precise" aborts.
*/
{ do_bad, SIGSEGV, 0, "vector exception" },
- { do_bad, SIGILL, BUS_ADRALN, "alignment exception" },
+ { do_bad, SIGBUS, BUS_ADRALN, "alignment exception" },
{ do_bad, SIGKILL, 0, "terminal exception" },
- { do_bad, SIGILL, BUS_ADRALN, "alignment exception" },
-/* Do we need runtime check ? */
-#if __LINUX_ARM_ARCH__ < 6
+ { do_bad, SIGBUS, BUS_ADRALN, "alignment exception" },
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
-#else
- { do_translation_fault, SIGSEGV, SEGV_MAPERR, "I-cache maintenance fault" },
-#endif
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" },
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
{ do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" },
@@ -505,13 +512,15 @@ static struct fsr_info {
void __init
hook_fault_code(int nr, int (*fn)(unsigned long, unsigned int, struct pt_regs *),
- int sig, const char *name)
+ int sig, int code, const char *name)
{
- if (nr >= 0 && nr < ARRAY_SIZE(fsr_info)) {
- fsr_info[nr].fn = fn;
- fsr_info[nr].sig = sig;
- fsr_info[nr].name = name;
- }
+ if (nr < 0 || nr >= ARRAY_SIZE(fsr_info))
+ BUG();
+
+ fsr_info[nr].fn = fn;
+ fsr_info[nr].sig = sig;
+ fsr_info[nr].code = code;
+ fsr_info[nr].name = name;
}
/*
@@ -591,3 +600,25 @@ do_PrefetchAbort(unsigned long addr, unsigned int ifsr, struct pt_regs *regs)
arm_notify_die("", regs, &info, ifsr, 0);
}
+static int __init exceptions_init(void)
+{
+ if (cpu_architecture() >= CPU_ARCH_ARMv6) {
+ hook_fault_code(4, do_translation_fault, SIGSEGV, SEGV_MAPERR,
+ "I-cache maintenance fault");
+ }
+
+ if (cpu_architecture() >= CPU_ARCH_ARMv7) {
+ /*
+ * TODO: Access flag faults introduced in ARMv6K.
+ * Runtime check for 'K' extension is needed
+ */
+ hook_fault_code(3, do_bad, SIGSEGV, SEGV_MAPERR,
+ "section access flag fault");
+ hook_fault_code(6, do_bad, SIGSEGV, SEGV_MAPERR,
+ "section access flag fault");
+ }
+
+ return 0;
+}
+
+arch_initcall(exceptions_init);