summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-04-25 07:44:26 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-04-25 07:44:26 -0700
commit129d6eb266e0848c9bf45da6e30291688c12b5ad (patch)
tree6709cec6768c96c3f7da2d248f7fdf9b55f62271
parent27d128c1cff64c3b8012cc56dd5a1391bb4f1821 (diff)
parentc6e61c06d6061750597e79c598acb5dead44c35b (diff)
Merge tag 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rmk/linux
Pull ARM updates from Russell King: - fix a race condition handling PG_dcache_clean - further cleanups for the fault handling, allowing RT to be enabled - fixing nzones validation in adfs filesystem driver - fix for module unwinding * tag 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rmk/linux: ARM: 9463/1: Allow to enable RT ARM: 9472/1: fix race condition on PG_dcache_clean in __sync_icache_dcache() ARM: 9471/1: module: fix unwind section relocation out of range error fs/adfs: validate nzones in adfs_validate_bblk() ARM: provide individual is_translation_fault() and is_permission_fault() ARM: move FSR fault status definitions before fsr_fs() ARM: use BIT() and GENMASK() for fault status register fields ARM: move is_permission_fault() and is_translation_fault() to fault.h ARM: move vmalloc() lazy-page table population ARM: ensure interrupts are enabled in __do_user_fault()
-rw-r--r--arch/arm/Kconfig1
-rw-r--r--arch/arm/kernel/module-plts.c12
-rw-r--r--arch/arm/mm/fault.c158
-rw-r--r--arch/arm/mm/fault.h42
-rw-r--r--arch/arm/mm/flush.c4
-rw-r--r--fs/adfs/super.c3
6 files changed, 128 insertions, 92 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index ec33376f8e2b..71fc5dd4123f 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -42,6 +42,7 @@ config ARM
select ARCH_SUPPORTS_CFI
select ARCH_SUPPORTS_HUGETLBFS if ARM_LPAE
select ARCH_SUPPORTS_PER_VMA_LOCK
+ select ARCH_SUPPORTS_RT
select ARCH_USE_BUILTIN_BSWAP
select ARCH_USE_CMPXCHG_LOCKREF
select ARCH_USE_MEMTEST
diff --git a/arch/arm/kernel/module-plts.c b/arch/arm/kernel/module-plts.c
index 354ce16d83cb..b5338fe59706 100644
--- a/arch/arm/kernel/module-plts.c
+++ b/arch/arm/kernel/module-plts.c
@@ -225,6 +225,18 @@ int module_frob_arch_sections(Elf_Ehdr *ehdr, Elf_Shdr *sechdrs,
mod->arch.init.plt = s;
else if (s->sh_type == SHT_SYMTAB)
syms = (Elf32_Sym *)s->sh_addr;
+#if defined(CONFIG_ARM_UNWIND) && !defined(CONFIG_VMSPLIT_3G)
+ else if (s->sh_type == ELF_SECTION_UNWIND ||
+ (strncmp(".ARM.extab", secstrings + s->sh_name, 10) == 0)) {
+ /*
+ * To avoid the possible relocation out of range issue for
+ * R_ARM_PREL31, mark unwind section .ARM.extab and .ARM.exidx as
+ * executable so they will be allocated along with .text section to
+ * meet +/-1GB range requirement of the R_ARM_PREL31 relocation
+ */
+ s->sh_flags |= SHF_EXECINSTR;
+ }
+#endif
}
if (!mod->arch.core.plt || !mod->arch.init.plt) {
diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c
index ed4330cc3f4e..e62cc4be5adf 100644
--- a/arch/arm/mm/fault.c
+++ b/arch/arm/mm/fault.c
@@ -115,32 +115,6 @@ static inline bool is_write_fault(unsigned int fsr)
return (fsr & FSR_WRITE) && !(fsr & FSR_CM);
}
-static inline bool is_translation_fault(unsigned int fsr)
-{
- int fs = fsr_fs(fsr);
-#ifdef CONFIG_ARM_LPAE
- if ((fs & FS_MMU_NOLL_MASK) == FS_TRANS_NOLL)
- return true;
-#else
- if (fs == FS_L1_TRANS || fs == FS_L2_TRANS)
- return true;
-#endif
- return false;
-}
-
-static inline bool is_permission_fault(unsigned int fsr)
-{
- int fs = fsr_fs(fsr);
-#ifdef CONFIG_ARM_LPAE
- if ((fs & FS_MMU_NOLL_MASK) == FS_PERM_NOLL)
- return true;
-#else
- if (fs == FS_L1_PERM || fs == FS_L2_PERM)
- return true;
-#endif
- return false;
-}
-
static void die_kernel_fault(const char *msg, struct mm_struct *mm,
unsigned long addr, unsigned int fsr,
struct pt_regs *regs)
@@ -190,7 +164,8 @@ __do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
/*
* Something tried to access memory that isn't in our memory map..
- * User mode accesses just cause a SIGSEGV
+ * User mode accesses just cause a SIGSEGV. Ensure interrupts are enabled
+ * for preempt RT.
*/
static void
__do_user_fault(unsigned long addr, unsigned int fsr, unsigned int sig,
@@ -198,6 +173,8 @@ __do_user_fault(unsigned long addr, unsigned int fsr, unsigned int sig,
{
struct task_struct *tsk = current;
+ local_irq_enable();
+
#ifdef CONFIG_DEBUG_USER
if (((user_debug & UDBG_SEGV) && (sig == SIGSEGV)) ||
((user_debug & UDBG_BUS) && (sig == SIGBUS))) {
@@ -258,6 +235,70 @@ static inline bool ttbr0_usermode_access_allowed(struct pt_regs *regs)
}
#endif
+/*
+ * Handle a vmalloc fault, copying the non-leaf page table entries from
+ * init_mm.pgd. Any kernel context can trigger this, so we must not sleep
+ * or enable interrupts. Having two CPUs execute this for the same page is
+ * no problem, we'll just copy the same data twice.
+ *
+ * Returns false on failure.
+ */
+static bool __kprobes __maybe_unused vmalloc_fault(unsigned long addr)
+{
+ unsigned int index;
+ pgd_t *pgd, *pgd_k;
+ p4d_t *p4d, *p4d_k;
+ pud_t *pud, *pud_k;
+ pmd_t *pmd, *pmd_k;
+
+ index = pgd_index(addr);
+
+ pgd = cpu_get_pgd() + index;
+ pgd_k = init_mm.pgd + index;
+
+ p4d = p4d_offset(pgd, addr);
+ p4d_k = p4d_offset(pgd_k, addr);
+
+ if (p4d_none(*p4d_k))
+ return false;
+ if (!p4d_present(*p4d))
+ set_p4d(p4d, *p4d_k);
+
+ pud = pud_offset(p4d, addr);
+ pud_k = pud_offset(p4d_k, addr);
+
+ if (pud_none(*pud_k))
+ return false;
+ if (!pud_present(*pud))
+ set_pud(pud, *pud_k);
+
+ pmd = pmd_offset(pud, addr);
+ pmd_k = pmd_offset(pud_k, addr);
+
+#ifdef CONFIG_ARM_LPAE
+ /*
+ * Only one hardware entry per PMD with LPAE.
+ */
+ index = 0;
+#else
+ /*
+ * 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;
+#endif
+ if (pmd_none(pmd_k[index]))
+ return false;
+
+ copy_pmd(pmd, pmd_k);
+
+ return true;
+}
+
static int __kprobes
do_kernel_address_page_fault(struct mm_struct *mm, unsigned long addr,
unsigned int fsr, struct pt_regs *regs)
@@ -268,6 +309,7 @@ do_kernel_address_page_fault(struct mm_struct *mm, unsigned long addr,
* should not be faulting in kernel space, which includes the
* vector/khelper page. Handle the branch predictor hardening
* while interrupts are still disabled, then send a SIGSEGV.
+ * Note that __do_user_fault() will enable interrupts.
*/
harden_branch_predictor();
__do_user_fault(addr, fsr, SIGSEGV, SEGV_MAPERR, regs);
@@ -492,10 +534,9 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
* directly to do_kernel_address_page_fault() to handle.
*
* Otherwise, we're probably faulting in the vmalloc() area, so try to fix
- * that up. Note that we must not take any locks or enable interrupts in
- * this case.
+ * that up via vmalloc_fault().
*
- * If vmalloc() fixup fails, that means the non-leaf page tables did not
+ * If vmalloc_fault() fails, that means the non-leaf page tables did not
* contain an entry for this address, so handle this via
* do_kernel_address_page_fault().
*/
@@ -504,65 +545,12 @@ static int __kprobes
do_translation_fault(unsigned long addr, unsigned int fsr,
struct pt_regs *regs)
{
- unsigned int index;
- pgd_t *pgd, *pgd_k;
- p4d_t *p4d, *p4d_k;
- pud_t *pud, *pud_k;
- pmd_t *pmd, *pmd_k;
-
if (addr < TASK_SIZE)
return do_page_fault(addr, fsr, regs);
- if (user_mode(regs))
- goto bad_area;
-
- index = pgd_index(addr);
-
- pgd = cpu_get_pgd() + index;
- pgd_k = init_mm.pgd + index;
-
- p4d = p4d_offset(pgd, addr);
- p4d_k = p4d_offset(pgd_k, addr);
-
- if (p4d_none(*p4d_k))
- goto bad_area;
- if (!p4d_present(*p4d))
- set_p4d(p4d, *p4d_k);
-
- pud = pud_offset(p4d, addr);
- pud_k = pud_offset(p4d_k, addr);
-
- if (pud_none(*pud_k))
- goto bad_area;
- if (!pud_present(*pud))
- set_pud(pud, *pud_k);
-
- pmd = pmd_offset(pud, addr);
- pmd_k = pmd_offset(pud_k, addr);
-
-#ifdef CONFIG_ARM_LPAE
- /*
- * Only one hardware entry per PMD with LPAE.
- */
- index = 0;
-#else
- /*
- * 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;
-#endif
- if (pmd_none(pmd_k[index]))
- goto bad_area;
-
- copy_pmd(pmd, pmd_k);
- return 0;
+ if (!user_mode(regs) && vmalloc_fault(addr))
+ return 0;
-bad_area:
do_kernel_address_page_fault(current->mm, addr, fsr, regs);
return 0;
diff --git a/arch/arm/mm/fault.h b/arch/arm/mm/fault.h
index e8f8c1902544..207f1b06941d 100644
--- a/arch/arm/mm/fault.h
+++ b/arch/arm/mm/fault.h
@@ -5,12 +5,9 @@
/*
* Fault status register encodings. We steal bit 31 for our own purposes.
*/
-#define FSR_LNX_PF (1 << 31)
-#define FSR_CM (1 << 13)
-#define FSR_WRITE (1 << 11)
-#define FSR_FS4 (1 << 10)
-#define FSR_FS3_0 (15)
-#define FSR_FS5_0 (0x3f)
+#define FSR_LNX_PF BIT(31)
+#define FSR_CM BIT(13)
+#define FSR_WRITE BIT(11)
#ifdef CONFIG_ARM_LPAE
#define FSR_FS_AEA 17
@@ -18,10 +15,26 @@
#define FS_PERM_NOLL 0xC
#define FS_MMU_NOLL_MASK 0x3C
+#define FSR_FS5_0 GENMASK(5, 0)
+
static inline int fsr_fs(unsigned int fsr)
{
return fsr & FSR_FS5_0;
}
+
+static inline bool is_translation_fault(unsigned int fsr)
+{
+ int fs = fsr_fs(fsr);
+
+ return (fs & FS_MMU_NOLL_MASK) == FS_TRANS_NOLL;
+}
+
+static inline bool is_permission_fault(unsigned int fsr)
+{
+ int fs = fsr_fs(fsr);
+
+ return (fs & FS_MMU_NOLL_MASK) == FS_PERM_NOLL;
+}
#else
#define FSR_FS_AEA 22
#define FS_L1_TRANS 0x5
@@ -29,10 +42,27 @@ static inline int fsr_fs(unsigned int fsr)
#define FS_L1_PERM 0xD
#define FS_L2_PERM 0xF
+#define FSR_FS4 BIT(10)
+#define FSR_FS3_0 GENMASK(3, 0)
+
static inline int fsr_fs(unsigned int fsr)
{
return (fsr & FSR_FS3_0) | (fsr & FSR_FS4) >> 6;
}
+
+static inline bool is_translation_fault(unsigned int fsr)
+{
+ int fs = fsr_fs(fsr);
+
+ return fs == FS_L1_TRANS || fs == FS_L2_TRANS;
+}
+
+static inline bool is_permission_fault(unsigned int fsr)
+{
+ int fs = fsr_fs(fsr);
+
+ return fs == FS_L1_PERM || fs == FS_L2_PERM;
+}
#endif
void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs);
diff --git a/arch/arm/mm/flush.c b/arch/arm/mm/flush.c
index 19470d938b23..4d7ef5cc36b6 100644
--- a/arch/arm/mm/flush.c
+++ b/arch/arm/mm/flush.c
@@ -304,8 +304,10 @@ void __sync_icache_dcache(pte_t pteval)
else
mapping = NULL;
- if (!test_and_set_bit(PG_dcache_clean, &folio->flags.f))
+ if (!test_bit(PG_dcache_clean, &folio->flags.f)) {
__flush_dcache_folio(mapping, folio);
+ set_bit(PG_dcache_clean, &folio->flags.f);
+ }
if (pte_exec(pteval))
__flush_icache_all();
diff --git a/fs/adfs/super.c b/fs/adfs/super.c
index 2c5b2076acf9..a4cd0a5159dd 100644
--- a/fs/adfs/super.c
+++ b/fs/adfs/super.c
@@ -317,6 +317,9 @@ static int adfs_validate_bblk(struct super_block *sb, struct buffer_head *bh,
if (adfs_checkdiscrecord(dr))
return -EILSEQ;
+ if ((dr->nzones | dr->nzones_high << 8) == 0)
+ return -EILSEQ;
+
*drp = dr;
return 0;
}