Merge tag 'ronx-next' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux...
[cascardo/linux.git] / arch / arm / mm / init.c
index 92bba32..67b1542 100644 (file)
@@ -29,6 +29,7 @@
 #include <asm/prom.h>
 #include <asm/sections.h>
 #include <asm/setup.h>
+#include <asm/system_info.h>
 #include <asm/tlb.h>
 #include <asm/fixmap.h>
 
@@ -570,7 +571,7 @@ void __init mem_init(void)
                        MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
                        MLK(ITCM_OFFSET, (unsigned long) itcm_end),
 #endif
-                       MLK(FIXADDR_START, FIXADDR_TOP),
+                       MLK(FIXADDR_START, FIXADDR_END),
                        MLM(VMALLOC_START, VMALLOC_END),
                        MLM(PAGE_OFFSET, (unsigned long)high_memory),
 #ifdef CONFIG_HIGHMEM
@@ -615,7 +616,145 @@ void __init mem_init(void)
        }
 }
 
-void free_initmem(void)
+#ifdef CONFIG_ARM_KERNMEM_PERMS
+struct section_perm {
+       unsigned long start;
+       unsigned long end;
+       pmdval_t mask;
+       pmdval_t prot;
+       pmdval_t clear;
+};
+
+static struct section_perm nx_perms[] = {
+       /* Make pages tables, etc before _stext RW (set NX). */
+       {
+               .start  = PAGE_OFFSET,
+               .end    = (unsigned long)_stext,
+               .mask   = ~PMD_SECT_XN,
+               .prot   = PMD_SECT_XN,
+       },
+       /* Make init RW (set NX). */
+       {
+               .start  = (unsigned long)__init_begin,
+               .end    = (unsigned long)_sdata,
+               .mask   = ~PMD_SECT_XN,
+               .prot   = PMD_SECT_XN,
+       },
+#ifdef CONFIG_DEBUG_RODATA
+       /* Make rodata NX (set RO in ro_perms below). */
+       {
+               .start  = (unsigned long)__start_rodata,
+               .end    = (unsigned long)__init_begin,
+               .mask   = ~PMD_SECT_XN,
+               .prot   = PMD_SECT_XN,
+       },
+#endif
+};
+
+#ifdef CONFIG_DEBUG_RODATA
+static struct section_perm ro_perms[] = {
+       /* Make kernel code and rodata RX (set RO). */
+       {
+               .start  = (unsigned long)_stext,
+               .end    = (unsigned long)__init_begin,
+#ifdef CONFIG_ARM_LPAE
+               .mask   = ~PMD_SECT_RDONLY,
+               .prot   = PMD_SECT_RDONLY,
+#else
+               .mask   = ~(PMD_SECT_APX | PMD_SECT_AP_WRITE),
+               .prot   = PMD_SECT_APX | PMD_SECT_AP_WRITE,
+               .clear  = PMD_SECT_AP_WRITE,
+#endif
+       },
+};
+#endif
+
+/*
+ * Updates section permissions only for the current mm (sections are
+ * copied into each mm). During startup, this is the init_mm. Is only
+ * safe to be called with preemption disabled, as under stop_machine().
+ */
+static inline void section_update(unsigned long addr, pmdval_t mask,
+                                 pmdval_t prot)
+{
+       struct mm_struct *mm;
+       pmd_t *pmd;
+
+       mm = current->active_mm;
+       pmd = pmd_offset(pud_offset(pgd_offset(mm, addr), addr), addr);
+
+#ifdef CONFIG_ARM_LPAE
+       pmd[0] = __pmd((pmd_val(pmd[0]) & mask) | prot);
+#else
+       if (addr & SECTION_SIZE)
+               pmd[1] = __pmd((pmd_val(pmd[1]) & mask) | prot);
+       else
+               pmd[0] = __pmd((pmd_val(pmd[0]) & mask) | prot);
+#endif
+       flush_pmd_entry(pmd);
+       local_flush_tlb_kernel_range(addr, addr + SECTION_SIZE);
+}
+
+/* Make sure extended page tables are in use. */
+static inline bool arch_has_strict_perms(void)
+{
+       if (cpu_architecture() < CPU_ARCH_ARMv6)
+               return false;
+
+       return !!(get_cr() & CR_XP);
+}
+
+#define set_section_perms(perms, field)        {                               \
+       size_t i;                                                       \
+       unsigned long addr;                                             \
+                                                                       \
+       if (!arch_has_strict_perms())                                   \
+               return;                                                 \
+                                                                       \
+       for (i = 0; i < ARRAY_SIZE(perms); i++) {                       \
+               if (!IS_ALIGNED(perms[i].start, SECTION_SIZE) ||        \
+                   !IS_ALIGNED(perms[i].end, SECTION_SIZE)) {          \
+                       pr_err("BUG: section %lx-%lx not aligned to %lx\n", \
+                               perms[i].start, perms[i].end,           \
+                               SECTION_SIZE);                          \
+                       continue;                                       \
+               }                                                       \
+                                                                       \
+               for (addr = perms[i].start;                             \
+                    addr < perms[i].end;                               \
+                    addr += SECTION_SIZE)                              \
+                       section_update(addr, perms[i].mask,             \
+                                      perms[i].field);                 \
+       }                                                               \
+}
+
+static inline void fix_kernmem_perms(void)
+{
+       set_section_perms(nx_perms, prot);
+}
+
+#ifdef CONFIG_DEBUG_RODATA
+void mark_rodata_ro(void)
+{
+       set_section_perms(ro_perms, prot);
+}
+
+void set_kernel_text_rw(void)
+{
+       set_section_perms(ro_perms, clear);
+}
+
+void set_kernel_text_ro(void)
+{
+       set_section_perms(ro_perms, prot);
+}
+#endif /* CONFIG_DEBUG_RODATA */
+
+#else
+static inline void fix_kernmem_perms(void) { }
+#endif /* CONFIG_ARM_KERNMEM_PERMS */
+
+void free_tcmmem(void)
 {
 #ifdef CONFIG_HAVE_TCM
        extern char __tcm_start, __tcm_end;
@@ -623,6 +762,12 @@ void free_initmem(void)
        poison_init_mem(&__tcm_start, &__tcm_end - &__tcm_start);
        free_reserved_area(&__tcm_start, &__tcm_end, -1, "TCM link");
 #endif
+}
+
+void free_initmem(void)
+{
+       fix_kernmem_perms();
+       free_tcmmem();
 
        poison_init_mem(__init_begin, __init_end - __init_begin);
        if (!machine_is_integrator() && !machine_is_cintegrator())