From 787a3446cafee59e31deaec662d74f86bff38999 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 3 Aug 2026 19:41:18 +0100 Subject: [PATCH 01/13] ARM: 9486/1: set !LPAE MAX_PHYSMEM_BITS to 32 and remove supersection mappings An ARM randconfig with CONFIG_GET_FREE_REGION=y and CONFIG_WERROR=y fails to build: include/linux/minmax.h:86:37: error: conversion from 'long long unsigned int' to 'resource_size_t' {aka 'unsigned int'} changes value from '68719476735' to '4294967295' [-Werror=overflow] Commit 14b80582c43e ("resource: Introduce alloc_free_mem_region()") made kernel/resource.c clamp against (1ULL << MAX_PHYSMEM_BITS) - 1. resource_size_t is 32-bit without LPAE, but ARM's default MAX_PHYSMEM_BITS is 36. That default was added by commit db57f88e4ccb ("ARM: 8411/1: Add default SPARSEMEM settings") because several platforms needed at least 36 physical address bits. !LPAE could map addresses above 32 bits only through supersections. CONFIG_IO_36, added by commit 23bdf86aa06e ("[ARM] 3377/2: add support for intel xsc3 core"), enabled these for XSC3 I/O. IXP23xx placed static I/O mappings above 4GiB until its removal in 2012, and IOP13xx ioremap()ed its PCIe windows there until its removal in 2019. The only remaining XSC3 platform, PXA3xx, has nothing above 4GiB. Supersection address bits overlap the descriptor domain field, so IO_36 also renumbered the domains to make DOMAIN_IO zero. Remove IO_36, the alternate domain numbering, and ioremap()'s supersection path. Reject !LPAE ioremap ranges extending past 4GiB before phys_addr_t truncation, drop the now-unneeded high static-mapping exclusion, and reduce ioremap alignment from 16MiB to the 2MiB covered by a PMD's pair of sections. The other supersection user, create_36bit_mapping(), was added by commit 0b7cd62ecdc1 ("[ARM] 3017/1: Add support for 36-bit addresses to create_mapping()") for IXP2350 and carried IXP23xx's high static mappings. Before this change, its only remaining call path was efi_create_mapping() -> create_mapping_late(), for an EFI runtime region above 4GiB. No real-world user of this !LPAE path is known. AArch32 UEFI requires RAM described by its memory map to be identity-mapped during boot services, which a 32-bit virtual address space cannot provide above 4GiB. Systems needing wider physical addressing are expected to use LPAE. Remove create_36bit_mapping() and the supersection definitions. If firmware nevertheless advertises such a region on !LPAE, efi_create_mapping() rejects it before __pfn_to_phys() truncates its address to a low alias. EFI runtime services remain disabled, but the kernel continues to boot. LPAE kernels are unaffected. With no remaining !LPAE mappings above 4GiB, set MAX_PHYSMEM_BITS to 32 for !LPAE and retain 36 for LPAE, where phys_addr_t is 64-bit. Assisted-by: Claude:claude-fable-5 Link: https://lore.kernel.org/all/3c79d2dc-8fbd-40fc-8b2d-9d1b4f8ed34e@app.fastmail.com/ Link: https://lore.kernel.org/all/CAK8P3a2MCdUbN0QSb+M3g5_6HjPsaQwtKxFjADMZWomdry4-Ww@mail.gmail.com/ Fixes: 14b80582c43e ("resource: Introduce alloc_free_mem_region()") Signed-off-by: Karl Mehltretter Reviewed-by: Arnd Bergmann Signed-off-by: Russell King --- arch/arm/include/asm/domain.h | 17 ----- arch/arm/include/asm/memory.h | 4 +- arch/arm/include/asm/pgtable-2level-hwdef.h | 1 - arch/arm/include/asm/pgtable-2level.h | 7 -- arch/arm/include/asm/sparsemem.h | 4 ++ arch/arm/kernel/efi.c | 13 ++++ arch/arm/mm/Kconfig | 7 -- arch/arm/mm/ioremap.c | 53 ++------------ arch/arm/mm/mmu.c | 76 +-------------------- 9 files changed, 27 insertions(+), 155 deletions(-) diff --git a/arch/arm/include/asm/domain.h b/arch/arm/include/asm/domain.h index d48859fdf32c..f91c2a72b9dc 100644 --- a/arch/arm/include/asm/domain.h +++ b/arch/arm/include/asm/domain.h @@ -18,27 +18,10 @@ * DOMAIN_IO - domain 2 includes all IO only * DOMAIN_USER - domain 1 includes all user memory only * DOMAIN_KERNEL - domain 0 includes all kernel memory only - * - * The domain numbering depends on whether we support 36 physical - * address for I/O or not. Addresses above the 32 bit boundary can - * only be mapped using supersections and supersections can only - * be set for domain 0. We could just default to DOMAIN_IO as zero, - * but there may be systems with supersection support and no 36-bit - * addressing. In such cases, we want to map system memory with - * supersections to reduce TLB misses and footprint. - * - * 36-bit addressing and supersections are only available on - * CPUs based on ARMv6+ or the Intel XSC3 core. */ -#ifndef CONFIG_IO_36 #define DOMAIN_KERNEL 0 #define DOMAIN_USER 1 #define DOMAIN_IO 2 -#else -#define DOMAIN_KERNEL 2 -#define DOMAIN_USER 1 -#define DOMAIN_IO 0 -#endif #define DOMAIN_VECTORS 3 /* diff --git a/arch/arm/include/asm/memory.h b/arch/arm/include/asm/memory.h index 7c2fa7dcec6d..e25749bfbf90 100644 --- a/arch/arm/include/asm/memory.h +++ b/arch/arm/include/asm/memory.h @@ -89,9 +89,9 @@ #if !defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE) /* - * Allow 16MB-aligned ioremap pages + * Allow 2MB-aligned ioremap pages */ -#define IOREMAP_MAX_ORDER 24 +#define IOREMAP_MAX_ORDER 21 #endif #define VECTORS_BASE UL(0xffff0000) diff --git a/arch/arm/include/asm/pgtable-2level-hwdef.h b/arch/arm/include/asm/pgtable-2level-hwdef.h index 556937e1790e..3fa254ac8a26 100644 --- a/arch/arm/include/asm/pgtable-2level-hwdef.h +++ b/arch/arm/include/asm/pgtable-2level-hwdef.h @@ -35,7 +35,6 @@ #define PMD_SECT_APX (_AT(pmdval_t, 1) << 15) /* v6 */ #define PMD_SECT_S (_AT(pmdval_t, 1) << 16) /* v6 */ #define PMD_SECT_nG (_AT(pmdval_t, 1) << 17) /* v6 */ -#define PMD_SECT_SUPER (_AT(pmdval_t, 1) << 18) /* v6 */ #define PMD_SECT_AF (_AT(pmdval_t, 0)) #define PMD_SECT_UNCACHED (_AT(pmdval_t, 0)) diff --git a/arch/arm/include/asm/pgtable-2level.h b/arch/arm/include/asm/pgtable-2level.h index 6b5392e20f41..7e1c08fdd029 100644 --- a/arch/arm/include/asm/pgtable-2level.h +++ b/arch/arm/include/asm/pgtable-2level.h @@ -96,13 +96,6 @@ #define SECTION_SIZE (1UL << SECTION_SHIFT) #define SECTION_MASK (~(SECTION_SIZE-1)) -/* - * ARMv6 supersection address mask and size definitions. - */ -#define SUPERSECTION_SHIFT 24 -#define SUPERSECTION_SIZE (1UL << SUPERSECTION_SHIFT) -#define SUPERSECTION_MASK (~(SUPERSECTION_SIZE-1)) - #define USER_PTRS_PER_PGD (TASK_SIZE / PGDIR_SIZE) /* diff --git a/arch/arm/include/asm/sparsemem.h b/arch/arm/include/asm/sparsemem.h index 421e3415338a..6b512be3a34a 100644 --- a/arch/arm/include/asm/sparsemem.h +++ b/arch/arm/include/asm/sparsemem.h @@ -19,7 +19,11 @@ * These can be overridden in your mach/memory.h. */ #if !defined(MAX_PHYSMEM_BITS) || !defined(SECTION_SIZE_BITS) +#ifdef CONFIG_ARM_LPAE #define MAX_PHYSMEM_BITS 36 +#else +#define MAX_PHYSMEM_BITS 32 +#endif #define SECTION_SIZE_BITS 28 #endif diff --git a/arch/arm/kernel/efi.c b/arch/arm/kernel/efi.c index 6f9ec7d28a71..c14275257a8a 100644 --- a/arch/arm/kernel/efi.c +++ b/arch/arm/kernel/efi.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,18 @@ int __init efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md) .length = md->num_pages * EFI_PAGE_SIZE, }; + /* + * Non-LPAE page tables cannot map EFI runtime regions extending + * beyond the 32-bit physical address space. + */ + if (!IS_ENABLED(CONFIG_ARM_LPAE) && + (md->phys_addr >= SZ_4G || + md->num_pages > (SZ_4G - md->phys_addr) / EFI_PAGE_SIZE)) { + pr_warn("EFI runtime region at 0x%llx exceeds 32-bit physical address space\n", + md->phys_addr); + return -ERANGE; + } + /* * Order is important here: memory regions may have all of the * bits below set (and usually do), so we check them in order of diff --git a/arch/arm/mm/Kconfig b/arch/arm/mm/Kconfig index 6574659dfb61..41d7b495ec45 100644 --- a/arch/arm/mm/Kconfig +++ b/arch/arm/mm/Kconfig @@ -328,7 +328,6 @@ config CPU_XSC3 select CPU_PABRT_LEGACY select CPU_THUMB_CAPABLE select CPU_TLB_V4WBI if MMU - select IO_36 # Marvell PJ1 (Mohawk) config CPU_MOHAWK @@ -676,12 +675,6 @@ config CPU_V7M_NUM_IRQ If unsure, keep default value. -# -# CPU supports 36-bit I/O -# -config IO_36 - bool - comment "Processor Features" config ARM_LPAE diff --git a/arch/arm/mm/ioremap.c b/arch/arm/mm/ioremap.c index 27e64f782cb3..539b6c2995af 100644 --- a/arch/arm/mm/ioremap.c +++ b/arch/arm/mm/ioremap.c @@ -30,8 +30,6 @@ #include #include -#include -#include #include #include #include @@ -236,39 +234,6 @@ remap_area_sections(unsigned long virt, unsigned long pfn, return 0; } -static int -remap_area_supersections(unsigned long virt, unsigned long pfn, - size_t size, const struct mem_type *type) -{ - unsigned long addr = virt, end = virt + size; - pmd_t *pmd = pmd_off_k(addr); - - /* - * Remove and free any PTE-based mapping, and - * sync the current kernel mapping. - */ - unmap_area_sections(virt, size); - do { - unsigned long super_pmd_val, i; - - super_pmd_val = __pfn_to_phys(pfn) | type->prot_sect | - PMD_SECT_SUPER; - super_pmd_val |= ((pfn >> (32 - PAGE_SHIFT)) & 0xf) << 20; - - for (i = 0; i < 8; i++) { - pmd[0] = __pmd(super_pmd_val); - pmd[1] = __pmd(super_pmd_val); - flush_pmd_entry(pmd); - - addr += PMD_SIZE; - pmd += 2; - } - - pfn += SUPERSECTION_SIZE >> PAGE_SHIFT; - } while (addr < end); - - return 0; -} #endif static void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn, @@ -278,15 +243,17 @@ static void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn, int err; unsigned long addr; struct vm_struct *area; - phys_addr_t paddr = __pfn_to_phys(pfn); + phys_addr_t paddr; #ifndef CONFIG_ARM_LPAE /* - * High mappings must be supersection aligned + * The physical range must not extend beyond the 32-bit + * phys_addr_t */ - if (pfn >= 0x100000 && (paddr & ~SUPERSECTION_MASK)) + if (((u64)pfn << PAGE_SHIFT) + offset + size > SZ_4G) return NULL; #endif + paddr = __pfn_to_phys(pfn); type = get_mem_type(mtype); if (!type) @@ -300,7 +267,7 @@ static void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn, /* * Try to reuse one of the static mapping whenever possible. */ - if (size && !(sizeof(phys_addr_t) == 4 && pfn >= 0x100000)) { + if (size) { struct static_vm *svm; svm = find_static_vm_paddr(paddr, size, mtype); @@ -326,13 +293,7 @@ static void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn, area->phys_addr = paddr; #if !defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE) - if (DOMAIN_IO == 0 && - (((cpu_architecture() >= CPU_ARCH_ARMv6) && (get_cr() & CR_XP)) || - cpu_is_xsc3()) && pfn >= 0x100000 && - !((paddr | size | addr) & ~SUPERSECTION_MASK)) { - area->flags |= VM_ARM_SECTION_MAPPING; - err = remap_area_supersections(addr, pfn, size, type); - } else if (!((paddr | size | addr) & ~PMD_MASK)) { + if (!((paddr | size | addr) & ~PMD_MASK)) { area->flags |= VM_ARM_SECTION_MAPPING; err = remap_area_sections(addr, pfn, size, type); } else diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c index 23b87b5ef7f1..5d0ae2529872 100644 --- a/arch/arm/mm/mmu.c +++ b/arch/arm/mm/mmu.c @@ -854,69 +854,6 @@ static void __init alloc_init_p4d(pgd_t *pgd, unsigned long addr, } while (p4d++, addr = next, addr != end); } -#ifndef CONFIG_ARM_LPAE -static void __init create_36bit_mapping(struct mm_struct *mm, - struct map_desc *md, - const struct mem_type *type, - bool ng) -{ - unsigned long addr, length, end; - phys_addr_t phys; - pgd_t *pgd; - - addr = md->virtual; - phys = __pfn_to_phys(md->pfn); - length = PAGE_ALIGN(md->length); - - if (!(cpu_architecture() >= CPU_ARCH_ARMv6 || cpu_is_xsc3())) { - pr_err("MM: CPU does not support supersection mapping for 0x%08llx at 0x%08lx\n", - (long long)__pfn_to_phys((u64)md->pfn), addr); - return; - } - - /* N.B. ARMv6 supersections are only defined to work with domain 0. - * Since domain assignments can in fact be arbitrary, the - * 'domain == 0' check below is required to insure that ARMv6 - * supersections are only allocated for domain 0 regardless - * of the actual domain assignments in use. - */ - if (type->domain) { - pr_err("MM: invalid domain in supersection mapping for 0x%08llx at 0x%08lx\n", - (long long)__pfn_to_phys((u64)md->pfn), addr); - return; - } - - if ((addr | length | __pfn_to_phys(md->pfn)) & ~SUPERSECTION_MASK) { - pr_err("MM: cannot create mapping for 0x%08llx at 0x%08lx invalid alignment\n", - (long long)__pfn_to_phys((u64)md->pfn), addr); - return; - } - - /* - * Shift bits [35:32] of address into bits [23:20] of PMD - * (See ARMv6 spec). - */ - phys |= (((md->pfn >> (32 - PAGE_SHIFT)) & 0xF) << 20); - - pgd = pgd_offset(mm, addr); - end = addr + length; - do { - p4d_t *p4d = p4d_offset(pgd, addr); - pud_t *pud = pud_offset(p4d, addr); - pmd_t *pmd = pmd_offset(pud, addr); - int i; - - for (i = 0; i < 16; i++) - *pmd++ = __pmd(phys | type->prot_sect | PMD_SECT_SUPER | - (ng ? PMD_SECT_nG : 0)); - - addr += SUPERSECTION_SIZE; - phys += SUPERSECTION_SIZE; - pgd += SUPERSECTION_SIZE >> PGDIR_SHIFT; - } while (addr != end); -} -#endif /* !CONFIG_ARM_LPAE */ - static void __init __create_mapping(struct mm_struct *mm, struct map_desc *md, void *(*alloc)(unsigned long sz), bool ng) @@ -928,16 +865,6 @@ static void __init __create_mapping(struct mm_struct *mm, struct map_desc *md, type = &mem_types[md->type]; -#ifndef CONFIG_ARM_LPAE - /* - * Catch 36-bit addresses - */ - if (md->pfn >= 0x100000) { - create_36bit_mapping(mm, md, type, ng); - return; - } -#endif - addr = md->virtual & PAGE_MASK; phys = __pfn_to_phys(md->pfn); length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK)); @@ -964,8 +891,7 @@ static void __init __create_mapping(struct mm_struct *mm, struct map_desc *md, * Create the page directory entries and any necessary * page tables for the mapping specified by `md'. We * are able to cope here with varying sizes and address - * offsets, and we take full advantage of sections and - * supersections. + * offsets, and we take full advantage of sections. */ static void __init create_mapping(struct map_desc *md) { From f6e551e7d515579b952422c93415470fb672d848 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 8 Aug 2026 22:08:26 +0100 Subject: [PATCH 02/13] ARM: 9488/1: smp: set current pointer in assembly before jumping into C With CONFIG_KCOV_INSTRUMENT_ALL=y, secondary CPUs fail to come online on ARMv6K/ARMv7 SMP: CPU1: failed to boot: -38 The compiler inserts a call to __sanitizer_cov_trace_pc() at the entry of secondary_start_kernel(), before set_current(task). The KCOV hook dereferences current while TPIDRURO is still uninitialized, causing a data abort before cpu_init() has installed the exception stacks. The secondary CPU consequently never completes startup. Initialize current in the secondary assembly handoff, as __mmap_switched already does for the boot CPU. With current set in assembly, the set_current() call in secondary_start_kernel() is redundant. Drop it together with the function and the task argument. The hotplug resuscitate path in arch_cpu_idle_dead() needs no hand-off either, as smp_ops.cpu_die() can only return if the CPU kept its register state, TPIDRURO included. Assisted-by: Claude:claude-fable-5 Fixes: 50596b7559bf ("ARM: smp: Store current pointer in TPIDRURO register if available") Cc: stable@vger.kernel.org Signed-off-by: Karl Mehltretter Reviewed-by: Ard Biesheuvel Signed-off-by: Russell King --- arch/arm/include/asm/smp.h | 2 +- arch/arm/kernel/head-nommu.S | 1 + arch/arm/kernel/head.S | 1 + arch/arm/kernel/smp.c | 15 ++------------- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 8c05a7f374d8..84aa6a663e81 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -43,7 +43,7 @@ extern void set_smp_ipi_range(int ipi_base, int nr_ipi); * Called from platform specific assembly code, this is the * secondary CPU entry point. */ -asmlinkage void secondary_start_kernel(struct task_struct *task); +asmlinkage void secondary_start_kernel(void); /* diff --git a/arch/arm/kernel/head-nommu.S b/arch/arm/kernel/head-nommu.S index b9d6818f1ee1..f80bbfbd3a6f 100644 --- a/arch/arm/kernel/head-nommu.S +++ b/arch/arm/kernel/head-nommu.S @@ -116,6 +116,7 @@ ENTRY(secondary_startup) ldr r7, __secondary_data @ reload r7 ldr sp, [r7, #12] @ set up the stack pointer ldr r0, [r7, #16] @ set up task pointer + set_current r0, r1 mov fp, #0 b secondary_start_kernel ENDPROC(secondary_startup) diff --git a/arch/arm/kernel/head.S b/arch/arm/kernel/head.S index f22c50d4bd41..a22403a629ef 100644 --- a/arch/arm/kernel/head.S +++ b/arch/arm/kernel/head.S @@ -442,6 +442,7 @@ ENTRY(__secondary_switched) adr_l r7, secondary_data + 12 @ get secondary_data.stack ldr sp, [r7] ldr r0, [r7, #4] @ get secondary_data.task + set_current r0, r1 mov fp, #0 b secondary_start_kernel ENDPROC(__secondary_switched) diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index b5fb4697bc3f..a9f4600fe4d0 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -371,12 +371,9 @@ void __noreturn arch_cpu_idle_dead(void) */ __asm__("mov sp, %0\n" " mov fp, #0\n" - " mov r0, %1\n" " b secondary_start_kernel" : - : "r" (task_stack_page(current) + THREAD_SIZE - 8), - "r" (current) - : "r0"); + : "r" (task_stack_page(current) + THREAD_SIZE - 8)); unreachable(); } @@ -397,23 +394,15 @@ static void smp_store_cpu_info(unsigned int cpuid) check_cpu_icache_size(cpuid); } -static void set_current(struct task_struct *cur) -{ - /* Set TPIDRURO */ - asm("mcr p15, 0, %0, c13, c0, 3" :: "r"(cur) : "memory"); -} - /* * This is the secondary CPU boot entry. We're using this CPUs * idle thread stack, but a set of temporary page tables. */ -asmlinkage void secondary_start_kernel(struct task_struct *task) +asmlinkage void secondary_start_kernel(void) { struct mm_struct *mm = &init_mm; unsigned int cpu; - set_current(task); - secondary_biglittle_init(); /* From 80dafbcb705cead6d25cb821cf29c47da8860c8c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 13 Aug 2026 04:02:36 +0100 Subject: [PATCH 03/13] ARM: 9491/1: alignment: restore uaccess state on emulation fault The alignment handlers enable access to user memory while emulating unaligned instructions. If a checked access faults, its exception table fixup jumps to the handler's fault label and bypasses uaccess_restore(). This leaves DOMAIN_USER as DOMAIN_CLIENT with SW domain PAN, or leaves TTBR0 page table walks enabled with TTBR0 PAN, while the fault is handled. An ARM926 QEMU test using LDMIA across an unmapped page boundary reports DOMAIN_USER=1 at TYPE_FAULT before this change and DOMAIN_USER=0 after it. SW domain PAN is the default on ARMv5, ARMv6, and non-LPAE ARMv7 builds. Keep the saved uaccess state at function scope and track whether it was saved. Restore it at the fault label only when needed. Paths that did not enable user access continue to skip the restore. Assisted-by: Codex:gpt-5.6-sol Fixes: 274e91b81ed2 ("ARM: alignment: fix alignment handling for uaccess changes") Cc: stable@vger.kernel.org Reviewed-by: Linus Walleij Signed-off-by: Karl Mehltretter Signed-off-by: Russell King --- arch/arm/mm/alignment.c | 51 ++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/arch/arm/mm/alignment.c b/arch/arm/mm/alignment.c index 812380f30ae3..2b470a419dda 100644 --- a/arch/arm/mm/alignment.c +++ b/arch/arm/mm/alignment.c @@ -341,6 +341,8 @@ static int do_alignment_ldrhstrh(unsigned long addr, u32 instr, struct pt_regs *regs) { unsigned int rd = RD_BITS(instr); + unsigned int ua_flags; + bool ua_saved = false; ai_half += 1; @@ -364,10 +366,11 @@ do_alignment_ldrhstrh(unsigned long addr, u32 instr, struct pt_regs *regs) user: if (LDST_L_BIT(instr)) { unsigned long val; - unsigned int __ua_flags = uaccess_save_and_enable(); + ua_flags = uaccess_save_and_enable(); + ua_saved = true; get16t_unaligned_check(val, addr); - uaccess_restore(__ua_flags); + uaccess_restore(ua_flags); /* signed half-word? */ if (instr & 0x40) @@ -375,14 +378,17 @@ do_alignment_ldrhstrh(unsigned long addr, u32 instr, struct pt_regs *regs) regs->uregs[rd] = val; } else { - unsigned int __ua_flags = uaccess_save_and_enable(); + ua_flags = uaccess_save_and_enable(); + ua_saved = true; put16t_unaligned_check(regs->uregs[rd], addr); - uaccess_restore(__ua_flags); + uaccess_restore(ua_flags); } return TYPE_LDST; fault: + if (ua_saved) + uaccess_restore(ua_flags); return TYPE_FAULT; } @@ -391,6 +397,8 @@ do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs) { unsigned int rd = RD_BITS(instr); unsigned int rd2; + unsigned int ua_flags; + bool ua_saved = false; int load; if ((instr & 0xfe000000) == 0xe8000000) { @@ -425,26 +433,30 @@ do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs) user: if (load) { unsigned long val, val2; - unsigned int __ua_flags = uaccess_save_and_enable(); + ua_flags = uaccess_save_and_enable(); + ua_saved = true; get32t_unaligned_check(val, addr); get32t_unaligned_check(val2, addr + 4); - uaccess_restore(__ua_flags); + uaccess_restore(ua_flags); regs->uregs[rd] = val; regs->uregs[rd2] = val2; } else { - unsigned int __ua_flags = uaccess_save_and_enable(); + ua_flags = uaccess_save_and_enable(); + ua_saved = true; put32t_unaligned_check(regs->uregs[rd], addr); put32t_unaligned_check(regs->uregs[rd2], addr + 4); - uaccess_restore(__ua_flags); + uaccess_restore(ua_flags); } return TYPE_LDST; bad: return TYPE_ERROR; fault: + if (ua_saved) + uaccess_restore(ua_flags); return TYPE_FAULT; } @@ -452,6 +464,8 @@ static int do_alignment_ldrstr(unsigned long addr, u32 instr, struct pt_regs *regs) { unsigned int rd = RD_BITS(instr); + unsigned int ua_flags; + bool ua_saved = false; ai_word += 1; @@ -469,18 +483,22 @@ do_alignment_ldrstr(unsigned long addr, u32 instr, struct pt_regs *regs) trans: if (LDST_L_BIT(instr)) { unsigned int val; - unsigned int __ua_flags = uaccess_save_and_enable(); + ua_flags = uaccess_save_and_enable(); + ua_saved = true; get32t_unaligned_check(val, addr); - uaccess_restore(__ua_flags); + uaccess_restore(ua_flags); regs->uregs[rd] = val; } else { - unsigned int __ua_flags = uaccess_save_and_enable(); + ua_flags = uaccess_save_and_enable(); + ua_saved = true; put32t_unaligned_check(regs->uregs[rd], addr); - uaccess_restore(__ua_flags); + uaccess_restore(ua_flags); } return TYPE_LDST; fault: + if (ua_saved) + uaccess_restore(ua_flags); return TYPE_FAULT; } @@ -502,6 +520,8 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs) { unsigned int rd, rn, correction, nr_regs, regbits; unsigned long eaddr, newaddr; + unsigned int ua_flags; + bool ua_saved = false; if (LDM_S_BIT(instr)) goto bad; @@ -547,7 +567,8 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs) #endif if (user_mode(regs)) { - unsigned int __ua_flags = uaccess_save_and_enable(); + ua_flags = uaccess_save_and_enable(); + ua_saved = true; for (regbits = REGMASK_BITS(instr), rd = 0; regbits; regbits >>= 1, rd += 1) if (regbits & 1) { @@ -559,7 +580,7 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs) put32t_unaligned_check(regs->uregs[rd], eaddr); eaddr += 4; } - uaccess_restore(__ua_flags); + uaccess_restore(ua_flags); } else { for (regbits = REGMASK_BITS(instr), rd = 0; regbits; regbits >>= 1, rd += 1) @@ -581,6 +602,8 @@ do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs) return TYPE_DONE; fault: + if (ua_saved) + uaccess_restore(ua_flags); regs->ARM_pc -= correction; return TYPE_FAULT; From 9f878a6c2207f0c12f34a715a6a05cfc95f47460 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 15 Aug 2026 02:41:10 +0100 Subject: [PATCH 04/13] ARM: 9492/1: alignment: fix LSR #32 and ASR #32 offset decoding The register-offset form of LDR/STR may apply a shift to Rm. Per DecodeImmShift() (ARM ARM DDI0406C section A8.4.3, "Pseudocode details of instruction-specified shifts and rotates"), an imm5 of 0 encodes a shift of 32 for LSR and ASR; only LSL treats 0 as "no shift", and ROR with 0 encodes RRX. do_alignment() special-cases RRX but not LSR or ASR, and IS_SHIFT() does not filter these encodings out, so the block is entered with shiftval == 0 and the offset becomes Rm instead of 0 (LSR #32) or the replicated sign of Rm (ASR #32). do_alignment_finish_ldst() applies the offset to the base-register writeback of the post-indexed form, so the emulated access itself uses the correct faulting address but Rn is left holding the wrong value. Reproduced on ARM926EJ-S (versatile_defconfig, CONFIG_ALIGNMENT_TRAP=y, gcc 13.3.0) with a misaligned base and Rm = 0x1000: ldr r0, [r1], r2, lsr #32 Rn advanced by 0x1000, must be unchanged ldr r0, [r1], r2, asr #32 Rn advanced by 0x1000, must be unchanged ldr r0, [r1], r2, asr #32 with Rm negative, Rn must decrease by 1 All three are correct with the patch applied, while a lsr #1 control case is emulated correctly both before and after. Assisted-by: Claude:claude-fable-5 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reviewed-by: Linus Walleij Signed-off-by: Karl Mehltretter Signed-off-by: Russell King --- arch/arm/mm/alignment.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/arm/mm/alignment.c b/arch/arm/mm/alignment.c index 2b470a419dda..f6c1e6b61179 100644 --- a/arch/arm/mm/alignment.c +++ b/arch/arm/mm/alignment.c @@ -915,11 +915,17 @@ do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs) break; case SHIFT_LSR: - offset.un >>= shiftval; + if (shiftval == 0) + offset.un = 0; + else + offset.un >>= shiftval; break; case SHIFT_ASR: - offset.sn >>= shiftval; + if (shiftval == 0) + offset.sn >>= 31; + else + offset.sn >>= shiftval; break; case SHIFT_RORRRX: From 74f79365839720bdb0c6bb4fe85c5b2a929ff5d1 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Mon, 21 Sep 2026 09:52:00 +0100 Subject: [PATCH 05/13] ARM: 9519/1: dma-mapping: Fix wrong free function in __iommu_alloc_simple() error path In __iommu_alloc_simple(), when IOMMU mapping creation fails, the error path unconditionally called __free_from_pool() regardless of the allocation path taken. For the COHERENT case, memory is allocated via __alloc_simple_buffer(), which does not use the atomic pool. Calling __free_from_pool() on such memory silently returns without freeing, causing a page memory leak. Fix this by matching the free function to the allocation path: - COHERENT: use __dma_free_buffer(virt_to_page(addr), size) - non-COHERENT: use __free_from_pool(addr, size) Fixes: 565068221b905 ("ARM: 8561/4: dma-mapping: Fix the coherent case when iommu is used") Signed-off-by: Li RongQing Reviewed-by: Marek Szyprowski Signed-off-by: Marek Szyprowski Signed-off-by: Russell King --- arch/arm/mm/dma-mapping.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c index 7761099dde9e..fb2eb0ed7bb8 100644 --- a/arch/arm/mm/dma-mapping.c +++ b/arch/arm/mm/dma-mapping.c @@ -1056,7 +1056,10 @@ static void *__iommu_alloc_simple(struct device *dev, size_t size, gfp_t gfp, return addr; err_mapping: - __free_from_pool(addr, size); + if (coherent_flag == COHERENT) + __dma_free_buffer(virt_to_page(addr), size); + else + __free_from_pool(addr, size); return NULL; } From 41a3f4bc3e4ae4722fd24743bf3f286cea14e3dc Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 21 Sep 2026 21:36:28 +0100 Subject: [PATCH 06/13] ARM: 9520/1: cacheinfo: avoid out-of-bounds write in populate_cache_leaves() populate_cache_leaves() advances its bounds-checking index once per cache level, but split instruction/data caches consume two entries. CLIDR-based allocation supplies enough entries. Early allocation from the device tree can supply fewer and expose an out-of-bounds write. Count each written leaf and stop before a split level that does not fit. This prepares ARM for DT-based early allocation and matches commit 875d742cf532 ("arm64: cacheinfo: Avoid out-of-bounds write to cacheinfo array"). Assisted-by: LLM Fixes: a9ff94477836 ("ARM: 9433/2: implement cacheinfo support") Cc: stable@vger.kernel.org Tested-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20260912195552.76673-2-kmehltretter@gmail.com/ Signed-off-by: Karl Mehltretter Signed-off-by: Russell King --- arch/arm/kernel/cacheinfo.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/arch/arm/kernel/cacheinfo.c b/arch/arm/kernel/cacheinfo.c index e1469b641780..860eeb03cfe5 100644 --- a/arch/arm/kernel/cacheinfo.c +++ b/arch/arm/kernel/cacheinfo.c @@ -151,7 +151,7 @@ int populate_cache_leaves(unsigned int cpu) unsigned int level, idx; enum cache_type type; struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); - struct cacheinfo *this_leaf = this_cpu_ci->info_list; + struct cacheinfo *infos = this_cpu_ci->info_list; unsigned int arch = cpu_architecture(); /* CLIDR is not present before ARMv7/v7m */ @@ -159,13 +159,15 @@ int populate_cache_leaves(unsigned int cpu) return -EOPNOTSUPP; for (idx = 0, level = 1; level <= this_cpu_ci->num_levels && - idx < this_cpu_ci->num_leaves; idx++, level++) { + idx < this_cpu_ci->num_leaves; level++) { type = get_cache_type(level); if (type == CACHE_TYPE_SEPARATE) { - ci_leaf_init(this_leaf++, CACHE_TYPE_DATA, level); - ci_leaf_init(this_leaf++, CACHE_TYPE_INST, level); + if (idx + 1 >= this_cpu_ci->num_leaves) + break; + ci_leaf_init(&infos[idx++], CACHE_TYPE_DATA, level); + ci_leaf_init(&infos[idx++], CACHE_TYPE_INST, level); } else { - ci_leaf_init(this_leaf++, type, level); + ci_leaf_init(&infos[idx++], type, level); } } From 4fd69e8a9ebc42e292637ec2ebfb6bcf2d2f6c0e Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 21 Sep 2026 21:44:33 +0100 Subject: [PATCH 07/13] ARM: 9521/1: cacheinfo: count external caches in early_cache_level() early_cache_level() counts only caches described by CLIDR. An external PL310 is absent from CLIDR, and its arm,pl310-cache compatible is not accepted by init_of_cache_level(), so early allocation falls back to an incomplete count. init_cache_level() later adds the DT-described L2, forcing reallocation on the secondary CPU with interrupts disabled. This defeats early allocation on PREEMPT_RT. Use init_cache_level() for early sizing too, so the initial allocation includes external caches described by the device tree. Assisted-by: LLM Cc: stable@vger.kernel.org Tested-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20260912195552.76673-3-kmehltretter@gmail.com/ Signed-off-by: Karl Mehltretter Signed-off-by: Russell King --- arch/arm/kernel/cacheinfo.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/arch/arm/kernel/cacheinfo.c b/arch/arm/kernel/cacheinfo.c index 860eeb03cfe5..31591c947254 100644 --- a/arch/arm/kernel/cacheinfo.c +++ b/arch/arm/kernel/cacheinfo.c @@ -111,13 +111,6 @@ static int detect_cache_level(unsigned int *level_p, unsigned int *leaves_p) return 0; } -int early_cache_level(unsigned int cpu) -{ - struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); - - return detect_cache_level(&this_cpu_ci->num_levels, &this_cpu_ci->num_leaves); -} - int init_cache_level(unsigned int cpu) { unsigned int level, leaves; @@ -146,6 +139,11 @@ int init_cache_level(unsigned int cpu) return 0; } +int early_cache_level(unsigned int cpu) +{ + return init_cache_level(cpu); +} + int populate_cache_leaves(unsigned int cpu) { unsigned int level, idx; From 2315f2c46e0813d010e7b3ea40f2769621453e27 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 21 Sep 2026 21:45:20 +0100 Subject: [PATCH 08/13] ARM: 9522/1: cacheinfo: guard the CLIDR read in populate_cache_leaves() ARM1176 and ARM11 MPCore can be reported as ARMv7 by cpu_architecture() even though they lack CLIDR. populate_cache_leaves() therefore needs the CTR-format check used by detect_cache_level(). DT-based early allocation bypasses init_cache_level(), so its check no longer protects populate_cache_leaves(). A combined ARMv6/ARMv7 SMP kernel can reach this path on BCM2835, whose DT describes its caches. Share the CLIDR capability check between detection and population. Return -ENOENT from population when CLIDR is unavailable, preserving the existing absence of cacheinfo and avoiding a new topology warning. Assisted-by: LLM Cc: stable@vger.kernel.org Tested-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20260912195552.76673-4-kmehltretter@gmail.com/ Signed-off-by: Karl Mehltretter Signed-off-by: Russell King --- arch/arm/kernel/cacheinfo.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/arch/arm/kernel/cacheinfo.c b/arch/arm/kernel/cacheinfo.c index 31591c947254..993c8a134786 100644 --- a/arch/arm/kernel/cacheinfo.c +++ b/arch/arm/kernel/cacheinfo.c @@ -80,19 +80,21 @@ static void ci_leaf_init(struct cacheinfo *this_leaf, this_leaf->type = type; } +static bool clidr_present(void) +{ + /* CLIDR is not present before ARMv7/v7m */ + if (cpu_architecture() < CPU_ARCH_ARMv7) + return false; + + /* Don't try reading CLIDR if CTR declares old format */ + return FIELD_GET(CTR_FORMAT_MASK, read_cpuid_cachetype()) == CTR_FORMAT_ARMV7; +} + static int detect_cache_level(unsigned int *level_p, unsigned int *leaves_p) { unsigned int ctype, level, leaves; - u32 ctr, format; - /* CLIDR is not present before ARMv7/v7m */ - if (cpu_architecture() < CPU_ARCH_ARMv7) - return -EOPNOTSUPP; - - /* Don't try reading CLIDR if CTR declares old format */ - ctr = read_cpuid_cachetype(); - format = FIELD_GET(CTR_FORMAT_MASK, ctr); - if (format != CTR_FORMAT_ARMV7) + if (!clidr_present()) return -EOPNOTSUPP; for (level = 1, leaves = 0; level <= MAX_CACHE_LEVEL; level++) { @@ -150,11 +152,10 @@ int populate_cache_leaves(unsigned int cpu) enum cache_type type; struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); struct cacheinfo *infos = this_cpu_ci->info_list; - unsigned int arch = cpu_architecture(); - /* CLIDR is not present before ARMv7/v7m */ - if (arch < CPU_ARCH_ARMv7) - return -EOPNOTSUPP; + /* The device tree can describe caches CLIDR cannot fill in. */ + if (!clidr_present()) + return -ENOENT; for (idx = 0, level = 1; level <= this_cpu_ci->num_levels && idx < this_cpu_ci->num_leaves; level++) { From 2d4234f99900403842b2c031fefb4c5816ac4df8 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 21 Sep 2026 21:46:11 +0100 Subject: [PATCH 09/13] ARM: 9523/1: topology: allocate the cacheinfo early on the boot CPU ARM secondary CPUs allocate cacheinfo in detect_cache_attributes(), called from secondary_start_kernel() with interrupts disabled. With PREEMPT_RT, the allocation takes a sleeping lock and triggers "BUG: sleeping function called from invalid context". Call fetch_cache_info() for each possible CPU from init_cpu_topology() on the boot CPU, following commit 5944ce092b97 ("arch_topology: Build cacheinfo from primary CPU"). This supplies the arrays before secondary CPUs populate them. Suppress -ENOENT and -EOPNOTSUPP, which leave cache detection to the existing late path. The CLIDR fallback uses the boot CPU's cache geometry. A secondary CPU with more cache leaves can still require late reallocation and trigger the warning. Assisted-by: LLM Fixes: a9ff94477836 ("ARM: 9433/2: implement cacheinfo support") Cc: stable@vger.kernel.org Tested-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20260912195552.76673-5-kmehltretter@gmail.com/ Signed-off-by: Karl Mehltretter Signed-off-by: Russell King --- arch/arm/kernel/topology.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/arm/kernel/topology.c b/arch/arm/kernel/topology.c index 2336ee2aa44a..e6b50cd43ce5 100644 --- a/arch/arm/kernel/topology.c +++ b/arch/arm/kernel/topology.c @@ -12,6 +12,7 @@ */ #include +#include #include #include #include @@ -238,8 +239,20 @@ void store_cpu_topology(unsigned int cpuid) */ void __init init_cpu_topology(void) { + int cpu, ret; + reset_cpu_topology(); smp_wmb(); parse_dt_topology(); + + for_each_possible_cpu(cpu) { + ret = fetch_cache_info(cpu); + if (!ret) + continue; + /* CPUs without a usable CLIDR return -EOPNOTSUPP. */ + if (ret != -ENOENT && ret != -EOPNOTSUPP) + pr_err("Early cacheinfo failed, ret = %d\n", ret); + return; + } } From 1eb87d8f77379d4e4b279487b8c859fb64bd1afb Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 26 Sep 2026 17:06:03 +0100 Subject: [PATCH 10/13] ARM: 9524/1: mm: align hugetlb mappings to the huge page size mmap(MAP_HUGETLB) on 32-bit Arm can return an address which is not aligned to the huge page size. In a QEMU virt guest, unmapping the VMA then hits the alignment check in __unmap_hugepage_range(): kernel BUG at mm/hugetlb.c:5213! Internal error: Oops - BUG: 0 [#1] SMP ARM PC is at __unmap_hugepage_range+0x540/0x584 LR is at __zap_vma_range+0x13a0/0x1410 Commit 7bd3f1e1a9ae ("mm: make hugetlb mappings go through mm_get_unmapped_area_vmflags") made hugetlb mappings use the architecture's arch_get_unmapped_area(). The generic implementations handle hugetlb alignment, but the Arm implementations only account for the optional SHMLBA cache-colouring constraint. A 4 KiB-page kernel can therefore align the mapping to 4 KiB or 16 KiB instead of the required 2 MiB. Set the alignment mask from the file's hstate for hugetlb mappings in both Arm implementations. Huge-page alignment also satisfies the SHMLBA constraint. Assisted-by: LLM Fixes: 7bd3f1e1a9ae ("mm: make hugetlb mappings go through mm_get_unmapped_area_vmflags") Cc: stable@vger.kernel.org Acked-by: Muchun Song Link: https://lore.kernel.org/r/20260901085740.66283-1-kmehltretter@gmail.com/ Signed-off-by: Karl Mehltretter Signed-off-by: Russell King --- arch/arm/mm/mmap.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arch/arm/mm/mmap.c b/arch/arm/mm/mmap.c index 3dbb383c26d5..dbb3f07a7785 100644 --- a/arch/arm/mm/mmap.c +++ b/arch/arm/mm/mmap.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #define COLOUR_ALIGN(addr,pgoff) \ @@ -72,7 +73,10 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, info.length = len; info.low_limit = mm->mmap_base; info.high_limit = TASK_SIZE; - info.align_mask = do_align ? (PAGE_MASK & (SHMLBA - 1)) : 0; + if (filp && is_file_hugepages(filp)) + info.align_mask = huge_page_mask_align(filp); + else + info.align_mask = do_align ? (PAGE_MASK & (SHMLBA - 1)) : 0; info.align_offset = pgoff << PAGE_SHIFT; return vm_unmapped_area(&info); } @@ -123,7 +127,10 @@ arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0, info.length = len; info.low_limit = FIRST_USER_ADDRESS; info.high_limit = mm->mmap_base; - info.align_mask = do_align ? (PAGE_MASK & (SHMLBA - 1)) : 0; + if (filp && is_file_hugepages(filp)) + info.align_mask = huge_page_mask_align(filp); + else + info.align_mask = do_align ? (PAGE_MASK & (SHMLBA - 1)) : 0; info.align_offset = pgoff << PAGE_SHIFT; addr = vm_unmapped_area(&info); From f8ebc827f2b0aed23295886543c0823c908dcee1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 10 Sep 2026 21:05:39 +0100 Subject: [PATCH 11/13] ARM: 9482/5: decompressor: always set endianess on xscale In BE32 configurations, endianess is always set to BE in Control Register C1, originally to allow booting BE kernels from a LE bootloader on IXP12xx. To also allow the opposite and allow booting LE kernels from a BE IXP4xx bootloader, moving this code into head-xscale.S along with the recommended serialization sequence and flip the B bit into the correct position regardless of its original state. Note that both the endianess switch sequence and the cp15wait I add here should be harmless on all supported ARMv4/v4t/v5 CPUs, but the existing cache flush code is not actually defined on ARM720 and may cause problems in multiplatform kernels that include both Xscale and CLPS711x. Signed-off-by: Arnd Bergmann Signed-off-by: Linus Walleij Signed-off-by: Russell King --- arch/arm/boot/compressed/Makefile | 8 -------- arch/arm/boot/compressed/big-endian.S | 14 -------------- arch/arm/boot/compressed/head-xscale.S | 18 ++++++++++++++++++ 3 files changed, 18 insertions(+), 22 deletions(-) delete mode 100644 arch/arm/boot/compressed/big-endian.S diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile index e3f550d62857..5993dd1d0ee9 100644 --- a/arch/arm/boot/compressed/Makefile +++ b/arch/arm/boot/compressed/Makefile @@ -41,14 +41,6 @@ ifeq ($(CONFIG_PXA_SHARPSL_DETECT_MACH_ID),y) OBJS += head-sharpsl.o endif -ifeq ($(CONFIG_CPU_ENDIAN_BE32),y) -ifeq ($(CONFIG_CPU_CP15),y) -OBJS += big-endian.o -else -# The endian should be set by h/w design. -endif -endif - # # We now have a PIC decompressor implementation. Decompressors running # from RAM should not define ZTEXTADDR. Decompressors running directly diff --git a/arch/arm/boot/compressed/big-endian.S b/arch/arm/boot/compressed/big-endian.S deleted file mode 100644 index 0e092c36da2f..000000000000 --- a/arch/arm/boot/compressed/big-endian.S +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * linux/arch/arm/boot/compressed/big-endian.S - * - * Switch CPU into big endian mode. - * Author: Nicolas Pitre - */ - - .section ".start", "ax" - - mrc p15, 0, r0, c1, c0, 0 @ read control reg - orr r0, r0, #(1 << 7) @ enable big endian mode - mcr p15, 0, r0, c1, c0, 0 @ write control reg - diff --git a/arch/arm/boot/compressed/head-xscale.S b/arch/arm/boot/compressed/head-xscale.S index 20fa44d59f82..d6c3d0a76550 100644 --- a/arch/arm/boot/compressed/head-xscale.S +++ b/arch/arm/boot/compressed/head-xscale.S @@ -7,6 +7,7 @@ */ #include +#include .section ".start", "ax" @@ -14,6 +15,23 @@ __XScale_start: @ Preserve r8/r7 i.e. kernel entry values + @ set correct endianness for ixp4xx regardless of bootloader + mrc p15, 0, r0, c1, c0, 0 +#ifdef CONFIG_CPU_BIG_ENDIAN + orr r0, r0, #CR_B @ enable big endian mode +#else + bic r0, r0, #CR_B +#endif + mcr p15, 0, r0, c1, c0, 0 +0: mrc p15, 0, r0, c1, c0, 0 + tst r0, #0x80 +#ifdef CONFIG_CPU_BIG_ENDIAN + beq 0b +#else + bne 0b +#endif + sub pc, pc, #4 @ flush instruction pipeline + @ Data cache might be active. @ Be sure to flush kernel binary out of the cache, @ whatever state it is, before it is turned off. From 358515f9f951e3140adda3795d0d0441db3f7436 Mon Sep 17 00:00:00 2001 From: Onkarnath Date: Tue, 11 Aug 2026 10:52:19 +0100 Subject: [PATCH 12/13] ARM: 9490/1: backtrace-clang: fix wrong sp usage for unwinding show_stack() can be called for any task, but c_backtrace is always unwinding frames based on "sp" register. Which leads to printing backtrace of current task instead of required task. Try with normal TC: ================== for_each_process(p){ sched_show_task(p); } [7.433271] task:kthreadd state:S stack:0 pid:2 tgid:2 ppid:0 task_flags:0x208040 flags:0x00000000 [7.433633] Call trace: [7.433640] [<80113418>] (dump_backtrace) from [<80113510>] (show_stack+0x14/0x18) .. [7.433676] [<8016749c>] (sched_show_task) from [<803cb324>] (meminfo_proc_show+0x6c/0x930) [7.434019] r5:8158e300 r4:8b4882d0 [7.434024] [<803cb324>] (meminfo_proc_show) from [<80365788>] (seq_read_iter+0x148/0x4bc) [7.434045] [<80365788>] (seq_read_iter) from [<803c044c>] (proc_reg_read_iter+0xb8/0xc4) [7.434060] [<803c044c>] (proc_reg_read_iter) from [<80377104>] (copy_splice_read+0x228/0x308) It should unwind frame based on passed "fp". (CONFIG_UNWINDER_FRAME_POINTER=y) Extra sp manipulation is needed only when c_backtrace() is called from dump_backtrace() with "frame == fp" i.e. NULL regs and not current task. (Suggested by Ard Biesheuvel) Otherwise passed "fp" should be used to unwind backtraces. With fix: ========= [13.933732] task:kthreadd state:S stack:0 pid:2 tgid:2 ppid:0 task_flags:0x208040 flags:0x00000000 [13.934165] Call trace: [13.934604] [<80afa1a8>] (schedule) from [<8015426c>] (kthreadd+0x124/0x208) [13.934654] r10:8100bbf0 r4:8116f440 [13.934664] [<8015426c>] (kthreadd) from [<8010010c>] (ret_from_fork+0x14/0x28) [13.934691] Exception stack(0xf081df9c to 0xf081dfe4) Suggested-by: Ard Biesheuvel Reviewed-by: Nick Desaulniers Signed-off-by: Onkarnath Signed-off-by: Maninder Singh Signed-off-by: Russell King --- arch/arm/lib/backtrace-clang.S | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/arm/lib/backtrace-clang.S b/arch/arm/lib/backtrace-clang.S index 290c52a60fc6..9a10828992af 100644 --- a/arch/arm/lib/backtrace-clang.S +++ b/arch/arm/lib/backtrace-clang.S @@ -107,8 +107,10 @@ ENDPROC(c_backtrace) /* * Switches the current frame to be the frame for dump_stack. + * (Only in case of current task using current registers) */ - add frame, sp, #24 @ switch to false frame + cmp frame, fp + addeq frame, sp, #24 @ switch to false frame for_each_frame: tst frame, mask @ Check for address exceptions bne no_frame From c2bbeff6952a84282333ebcff59f9fde449f60dd Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Sat, 29 Aug 2026 18:37:35 +0100 Subject: [PATCH 13/13] ARM: 9493/1: asid: Do not replace active_asids if already 0 Under some uncommon timing conditions, a generation check and xchg(active_asids, A1) in check_and_switch_context() on P1 can race with an ASID roll-over on P2. If P2 has not seen the update to active_asids[P1], it can re-allocate A1 to a new task T2 on P2. P1 ends up waiting on the spinlock since the xchg() returned 0 while P2 can go through a second ASID roll-over with (T2,A1,G2) active on P2. This roll-over copies active_asids[P1] == A1,G1 into reserved_asids[P1] and active_asids[P2] == A1,G2 into reserved_asids[P2]. A subsequent scheduling of T1 on P1 and T2 on P2 would match reserved_asids and get their generation bumped to G3: P1 P2 -- -- TTBR0.BADDR = T0 TTBR0.ASID = A0 asid_generation = G1 check_and_switch_context(T1,A1,G1) generation match check_and_switch_context(T2,A0,G0) new_context() ASID roll-over asid_generation = G2 flush_context() active_asids[P1] = 0 asid_map[A1] = 0 reserved_asids[P1] = A0,G0 xchg(active_asids, A1) active_asids[P1] = A1,G1 xchg returns 0 spin_lock_irqsave() allocated ASID (T2,A1,G2) asid_map[A1] = 1 active_asids[P2] = A1,G2 ... check_and_switch_context(T3,A0,G0) new_context() ASID roll-over asid_generation = G3 flush_context() active_asids[P1] = 0 asid_map[A1] = 1 reserved_asids[P1] = A1,G1 reserved_asids[P2] = A1,G2 allocated ASID (T3,A2,G3) asid_map[A2] = 1 active_asids[P2] = A2,G3 new_context() check_update_reserved_asid(A1,G1) matches reserved_asid[P1] reserved_asid[P1] = A1,G3 updated T1 ASID to (T1,A1,G3) check_and_switch_context(T2,A1,G2) new_context() check_update_reserved_asid(A1,G2) matches reserved_asids[P2] reserved_asids[P2] = A1,G3 updated T2 ASID to (T2,A1,G3) At this point, we have two tasks, T1 and T2 both using ASID A1 with the latest generation G3. Any of them is allowed to be scheduled on the other CPU leading to two different tasks with the same ASID on the same CPU. This patch changes the xchg to cmpxchg so that the active_asids is only updated if non-zero to avoid a race with an ASID roll-over on a different CPU. Cc: Will Deacon Signed-off-by: Catalin Marinas Tested-by: Karl Mehltretter Signed-off-by: Karl Mehltretter Signed-off-by: Russell King --- arch/arm/mm/context.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/arch/arm/mm/context.c b/arch/arm/mm/context.c index 4204ffa2d104..7c4e1e4ba77b 100644 --- a/arch/arm/mm/context.c +++ b/arch/arm/mm/context.c @@ -238,7 +238,7 @@ void check_and_switch_context(struct mm_struct *mm, struct task_struct *tsk) { unsigned long flags; unsigned int cpu = smp_processor_id(); - u64 asid; + u64 asid, old_active_asid; check_vmalloc_seq(mm); @@ -250,8 +250,17 @@ void check_and_switch_context(struct mm_struct *mm, struct task_struct *tsk) cpu_set_reserved_ttbr0(); asid = atomic64_read(&mm->context.id); - if (!((asid ^ atomic64_read(&asid_generation)) >> ASID_BITS) - && atomic64_xchg(&per_cpu(active_asids, cpu), asid)) + + /* + * If our active_asids is zero, we are racing with an ASID roll-over + * on a different CPU, so skip the update (using cmpxchg if non-zero) + * and take the slow path. + */ + old_active_asid = atomic64_read(&per_cpu(active_asids, cpu)); + if (old_active_asid && + !((asid ^ atomic64_read(&asid_generation)) >> ASID_BITS) && + atomic64_cmpxchg(&per_cpu(active_asids, cpu), + old_active_asid, asid)) goto switch_mm_fastpath; raw_spin_lock_irqsave(&cpu_asid_lock, flags);