From 83bd31c2840b3640ba08b7afac2908bbb19c4b2c Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Wed, 2 Sep 2026 20:38:56 +0200 Subject: [PATCH 01/57] alpha: enable GENERIC_ENTRY Wire Alpha into the generic entry code for syscall entry/exit and return-to-user handling, while keeping the low-level PALcode return paths Alpha-specific. Move most of the syscall entry/exit logic out of entSys and into C helpers built around the generic entry API. Syscall entry now uses syscall_enter_from_user_mode(), records Alpha-local syscall metadata in thread_info, handles the ptrace/seccomp skip decision, and selects the syscall table target in C. The final target call remains in entry.S so Alpha can preserve its existing syscall ABI and assembly syscall-table wrappers. On return from syscalls, finish Alpha's r0/r19 result encoding and skipped-syscall restart handling in C before calling syscall_exit_to_user_mode(). Non-syscall returns to user mode use a separate alpha_exit_to_user_mode() helper, which disables interrupts, runs irqentry_exit_to_user_mode_prepare(), and then enters the common exit_to_user_mode() path. Keep the remaining PALcode restore handling in assembly. In particular, kernel-mode returns still need Alpha-specific lockdep IRQ-state annotation based on the saved processor status, while user-mode returns are handed to the generic exit-to-user code. Add the generic-entry support bits needed by common code, including thread_info.syscall_work, syscall trace support, ptrace sysemu request numbers, and arch_syscall_is_vdso_sigreturn(). This has been tested by booting Alpha with GENERIC_ENTRY enabled, checking lockdep IRQ-state accounting, running fork/clone-heavy package builds, and running the seccomp as well as strace test suites. Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260902184058.198679-2-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 2 + arch/alpha/include/asm/entry-common.h | 14 ++ arch/alpha/include/asm/ptrace.h | 14 +- arch/alpha/include/asm/stacktrace.h | 20 ++ arch/alpha/include/asm/syscall.h | 11 +- arch/alpha/include/asm/thread_info.h | 30 +-- arch/alpha/kernel/asm-offsets.c | 5 + arch/alpha/kernel/entry.S | 334 +++++++------------------- arch/alpha/kernel/irq_alpha.c | 14 +- arch/alpha/kernel/proto.h | 9 +- arch/alpha/kernel/ptrace.c | 130 +++++----- arch/alpha/kernel/signal.c | 166 ++++++++++--- 12 files changed, 377 insertions(+), 372 deletions(-) create mode 100644 arch/alpha/include/asm/entry-common.h create mode 100644 arch/alpha/include/asm/stacktrace.h diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index e53ef2d88463..51a7391869dd 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -2,6 +2,8 @@ config ALPHA bool default y + select GENERIC_ENTRY + select HAVE_SYSCALL_TRACEPOINTS select ARCH_32BIT_USTAT_F_TINODE select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DMA_OPS if PCI diff --git a/arch/alpha/include/asm/entry-common.h b/arch/alpha/include/asm/entry-common.h new file mode 100644 index 000000000000..a811c73454d2 --- /dev/null +++ b/arch/alpha/include/asm/entry-common.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ARCH_ALPHA_ENTRY_COMMON_H +#define ARCH_ALPHA_ENTRY_COMMON_H + +#include /* For on_thread_stack() */ +#include + +#define arch_exit_to_user_mode_work arch_exit_to_user_mode_work + +static __always_inline void arch_exit_to_user_mode_work(struct pt_regs *regs, + unsigned long ti_work) +{ +} +#endif diff --git a/arch/alpha/include/asm/ptrace.h b/arch/alpha/include/asm/ptrace.h index 8e0a589e2d15..430e8dc27ff7 100644 --- a/arch/alpha/include/asm/ptrace.h +++ b/arch/alpha/include/asm/ptrace.h @@ -3,7 +3,7 @@ #define _ASMAXP_PTRACE_H #include - +#include #define arch_has_single_step() (1) #define user_mode(regs) (((regs)->ps & 8) != 0) @@ -17,7 +17,9 @@ #define current_pt_regs() \ ((struct pt_regs *) ((char *)current_thread_info() + 2*PAGE_SIZE) - 1) -#define force_successful_syscall_return() (current_pt_regs()->r0 = 0) +#define force_successful_syscall_return() \ + (current_thread_info()->syscall_meta \ + |= ALPHA_SYSCALL_META_FORCE_SUCCESS) static inline unsigned long regs_return_value(struct pt_regs *regs) { @@ -31,4 +33,12 @@ static inline unsigned long user_stack_pointer(struct pt_regs *regs) return regs->usp; } +static __always_inline bool regs_irqs_disabled(struct pt_regs *regs) +{ + return arch_irqs_disabled_flags(regs->ps); +} + +/* Syscall emulation defines */ +#define PTRACE_SYSEMU 0x1d +#define PTRACE_SYSEMU_SINGLESTEP 0x1e #endif diff --git a/arch/alpha/include/asm/stacktrace.h b/arch/alpha/include/asm/stacktrace.h new file mode 100644 index 000000000000..f006d6f00fd0 --- /dev/null +++ b/arch/alpha/include/asm/stacktrace.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_ALPHA_STACKTRACE_H +#define _ASM_ALPHA_STACKTRACE_H + +#include +#include + +#include +#include +#include + +static __always_inline bool on_thread_stack(void) +{ + unsigned long base = (unsigned long)current->stack; + unsigned long sp = (unsigned long)current_stack_pointer; + + return !((base ^ sp) & ~(THREAD_SIZE - 1)); +} + +#endif /* _ASM_ALPHA_STACKTRACE_H */ diff --git a/arch/alpha/include/asm/syscall.h b/arch/alpha/include/asm/syscall.h index 1e78cbd46faf..c1394910f584 100644 --- a/arch/alpha/include/asm/syscall.h +++ b/arch/alpha/include/asm/syscall.h @@ -8,6 +8,8 @@ #include #include +extern void *sys_call_table[]; + static inline int syscall_get_arch(struct task_struct *task) { return AUDIT_ARCH_ALPHA; @@ -104,10 +106,17 @@ static inline void syscall_set_return_value(struct task_struct *task, } /* Restore the original syscall nr after seccomp/ptrace modified regs->r1. */ + static inline void syscall_rollback(struct task_struct *task, struct pt_regs *regs) { - regs->r1 = regs->r2; + unsigned long nr = task_thread_info(task)->syscall_saved_nr; + + regs->r1 = nr; } +static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs) +{ + return false; +} #endif /* _ASM_ALPHA_SYSCALL_H */ diff --git a/arch/alpha/include/asm/thread_info.h b/arch/alpha/include/asm/thread_info.h index 1552ecca8520..d781ac79106d 100644 --- a/arch/alpha/include/asm/thread_info.h +++ b/arch/alpha/include/asm/thread_info.h @@ -12,16 +12,21 @@ #endif #ifndef __ASSEMBLER__ + struct thread_info { struct pcb_struct pcb; /* palcode state */ struct task_struct *task; /* main task structure */ - unsigned int flags; /* low level flags */ + unsigned long flags; /* low level flags */ + unsigned long syscall_work; /* SYSCALL_WORK_* flags */ unsigned int ieee_state; /* see fpu.h */ unsigned cpu; /* current CPU */ - int preempt_count; /* 0 => preemptable, <0 => BUG */ + int preempt_count; /* 0 => preemptable, <0 => BUG */ unsigned int status; /* thread-synchronous flags */ + unsigned long syscall_saved_r19; + unsigned long syscall_meta; + unsigned long syscall_saved_nr; int bpt_nsaved; unsigned long bpt_addr[2]; /* breakpoint handling */ @@ -50,6 +55,9 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define THREAD_SIZE_ORDER 1 #define THREAD_SIZE (2*PAGE_SIZE) +#define ALPHA_SYSCALL_META_SKIP 0x2 +#define ALPHA_SYSCALL_META_FORCE_SUCCESS 0x4 + /* * Thread information flags: * - these are process state flags and used from assembly @@ -68,6 +76,7 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define TIF_SECCOMP 6 /* seccomp syscall filtering active */ #define TIF_SYSCALL_TRACEPOINT 7 /* syscall tracepoint instrumentation */ #define TIF_DIE_IF_KERNEL 9 /* dik recursion lock */ +#define TIF_UPROBE 10 /* uprobe breakpoint or singlestep */ #define TIF_MEMDIE 13 /* is terminating due to OOM killer */ #define TIF_POLLING_NRFLAG 14 /* idle is polling for TIF_NEED_RESCHED */ @@ -80,22 +89,7 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define _TIF_SECCOMP (1< restart allowed - * $26 = 1 => restart NOT allowed - * $18 = preserved syscall nr (regs->r2) if restart allowed, else 0 - */ -.macro SYSCALL_SKIP_RETURN_RESTART_GATE - /* Fix up invalid "-1 success" return state. */ - ldq $19, 72($sp) /* a3 */ - bne $19, 1f /* already error => skip fixup */ - - ldq $20, 0($sp) /* r0 */ - lda $21, -1($31) - cmpeq $20, $21, $22 - beq $22, 1f /* r0 != -1 => skip fixup */ - - - lda $20, ENOSYS($31) - stq $20, 0($sp) /* r0 = ENOSYS */ - lda $19, 1($31) - stq $19, 72($sp) /* a3 = 1 */ -1: - /* Restart gating: success is never restartable here. */ - ldq $19, 72($sp) /* a3 */ - beq $19, 3f /* success => not restartable */ - - ldq $20, 0($sp) /* r0 (positive errno if a3==1) */ - lda $21, ERESTARTSYS($31) - cmpeq $20, $21, $22 - bne $22, 2f - lda $21, ERESTARTNOINTR($31) - cmpeq $20, $21, $22 - bne $22, 2f - lda $21, ERESTARTNOHAND($31) - cmpeq $20, $21, $22 - bne $22, 2f - lda $21, ERESTART_RESTARTBLOCK($31) - cmpeq $20, $21, $22 - bne $22, 2f - -3: /* Not a restart code (or success) => restart NOT allowed. */ - addq $31, 1, $26 /* $26=1 => restart NOT allowed */ - mov 0, $18 - br 4f - -2: /* Restart allowed. */ - ldq $18, 16($sp) /* preserved syscall nr (regs->r2) */ - mov $31, $26 /* $26=0 => restart allowed */ - br 4f -4: -.endm - -.macro LOCKDEP_HARDIRQS_ON_RESTORE +.macro LOCKDEP_HARDIRQS_ON_RESTORE psreg #ifdef CONFIG_PROVE_LOCKING /* a0 = saved PS */ - ldq $16, SP_OFF($sp) + ldq $16, \psreg /* a1 = callsite IP for lockdep */ lda $17, 1f @@ -248,7 +191,7 @@ CFI_START_OSF_FRAME entInt SAVE_ALL lda $8, 0x3fff - lda $26, ret_from_sys_call + lda $26, ret_from_exception bic $sp, $8, $8 mov $sp, $19 jsr $31, do_entInt @@ -257,7 +200,7 @@ CFI_END_OSF_FRAME entInt CFI_START_OSF_FRAME entArith SAVE_ALL lda $8, 0x3fff - lda $26, ret_from_sys_call + lda $26, ret_from_exception bic $sp, $8, $8 mov $sp, $18 jsr $31, do_entArith @@ -305,13 +248,13 @@ CFI_START_OSF_FRAME entMM .cfi_restore $15 .cfi_adjust_cfa_offset -64 /* finish up the syscall as normal. */ - br ret_from_sys_call + br ret_from_exception CFI_END_OSF_FRAME entMM CFI_START_OSF_FRAME entIF SAVE_ALL lda $8, 0x3fff - lda $26, ret_from_sys_call + lda $26, ret_from_exception bic $sp, $8, $8 mov $sp, $17 jsr $31, do_entIF @@ -440,7 +383,7 @@ CFI_START_OSF_FRAME entUna .cfi_restore $28 .cfi_restore $29 .cfi_adjust_cfa_offset -256 - LOCKDEP_HARDIRQS_ON_RESTORE + LOCKDEP_HARDIRQS_ON_RESTORE SP_OFF($sp) call_pal PAL_rti .align 4 @@ -487,18 +430,19 @@ entUnaUser: .cfi_restore $14 .cfi_restore $15 .cfi_adjust_cfa_offset -64 - br ret_from_sys_call + br ret_from_exception CFI_END_OSF_FRAME entUna CFI_START_OSF_FRAME entDbg SAVE_ALL lda $8, 0x3fff - lda $26, ret_from_sys_call + lda $26, ret_from_exception bic $sp, $8, $8 mov $sp, $16 jsr $31, do_entDbg CFI_END_OSF_FRAME entDbg + /* * The system call entry point is special. Most importantly, it looks * like a function call to userspace as far as clobbered registers. We @@ -516,9 +460,7 @@ CFI_END_OSF_FRAME entDbg * For seccomp/ptrace/generic syscall helpers we track the syscall * number separately: * - regs->r1: current (mutable) syscall number (may be changed or set to -1) - * - regs->r2: original syscall number for restart/rollback * - * On entry PAL provides the syscall number in r0; copy it into r1/r2. */ .align 4 @@ -531,146 +473,46 @@ CFI_END_OSF_FRAME entDbg .cfi_rel_offset $gp, 16 entSys: SAVE_ALL - ldq $1, 0($sp) /* syscall nr from saved r0 */ - stq $1, 8($sp) /* regs->r1 = shadow syscall nr */ - stq $1, 16($sp) /* regs->r2 = restart syscall nr */ - /* Syscalls always enter from user mode: snapshot USP into pt_regs->usp */ + ldq $1, 0($sp) /* syscall nr from saved r0 */ + stq $1, 8($sp) /* regs->r1 = shadow syscall nr */ + mov $0, $8 call_pal PAL_rdusp - stq $0, PT_REGS_USP($sp) + stq $0, PT_REGS_USP($sp) mov $8, $0 - lda $8, 0x3fff bic $sp, $8, $8 - lda $4, NR_syscalls($31) + stq $16, SP_OFF+24($sp) - lda $5, sys_call_table - lda $27, sys_ni_syscall - cmpult $0, $4, $4 - ldl $3, TI_FLAGS($8) stq $17, SP_OFF+32($sp) - s8addq $0, $5, $5 stq $18, SP_OFF+40($sp) - .cfi_rel_offset $16, SP_OFF+24 - .cfi_rel_offset $17, SP_OFF+32 - .cfi_rel_offset $18, SP_OFF+40 - lda $6, _TIF_SYSCALL_WORK - and $3, $6, $3 - bne $3, strace - beq $4, 1f - ldq $27, 0($5) -1: ldq $0, 8($sp) /* syscall nr shadow (regs->r1) */ - - jsr $26, ($27), sys_ni_syscall - ldgp $gp, 0($26) - blt $0, $syscall_error /* the call failed */ -$ret_success: - stq $0, 0($sp) - stq $31, 72($sp) /* a3=0 => no error */ - - .align 4 - .globl ret_from_sys_call -ret_from_sys_call: - cmovne $26, 0, $18 /* $18 = 0 => non-restartable */ - ldq $0, SP_OFF($sp) - and $0, 8, $0 - beq $0, ret_to_kernel -ret_to_user: - /* Make sure need_resched and sigpending don't change between - sampling and the rti. */ + mov $0, $1 lda $16, 7 call_pal PAL_swpipl - ldl $17, TI_FLAGS($8) - and $17, _TIF_WORK_MASK, $2 - bne $2, work_pending -restore_all: - ldl $2, TI_STATUS($8) - and $2, TS_SAVED_FP | TS_RESTORE_FP, $3 - bne $3, restore_fpu -restore_other: - .cfi_remember_state - LOCKDEP_HARDIRQS_ON_RESTORE - RESTORE_ALL - call_pal PAL_rti - -ret_to_kernel: - .cfi_restore_state - lda $16, 7 - call_pal PAL_swpipl - br restore_other - - .align 3 -$syscall_error: - /* Restart syscall nr comes from saved r2 (preserved even if r0 overwritten). */ - ldq $18, 16($sp) /* old syscall nr for restart */ - - ldq $19, 72($sp) /* .. and this a3 */ - subq $31, $0, $0 /* with error in v0 */ - addq $31, 1, $1 /* set a3 for errno return */ - stq $0, 0($sp) - mov $31, $26 /* tell "ret_from_sys_call" we can restart */ - stq $1, 72($sp) /* a3 for return */ - br ret_from_sys_call - -/* - * Do all cleanup when returning from all interrupts and system calls. - * - * Arguments: - * $8: current. - * $17: TI_FLAGS. - * $18: The old syscall number, or zero if this is not a return - * from a syscall that errored and is possibly restartable. - * $19: The old a3 value - */ - - .align 4 - .type work_pending, @function -work_pending: - and $17, _TIF_NOTIFY_RESUME | _TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL, $2 - bne $2, $work_notifysig - -$work_resched: - /* - * We can get here only if we returned from syscall without SIGPENDING - * or got through work_notifysig already. Either case means no syscall - * restarts for us, so let $18 and $19 burn. - */ - jsr $26, alpha_schedule_user_work - mov 0, $18 - br ret_to_user - -$work_notifysig: + mov $1, $0 mov $sp, $16 - DO_SWITCH_STACK - jsr $26, do_work_pending - UNDO_SWITCH_STACK - br restore_all + mov $0, $17 /* pv = selected syscall function */ -/* - * PTRACE syscall handler - */ - - .align 4 - .type strace, @function -strace: - /* set up signal stack, call syscall_trace */ - // NB: if anyone adds preemption, this block will need to be protected - ldl $1, TI_STATUS($8) - and $1, TS_SAVED_FP, $3 - or $1, TS_SAVED_FP, $2 - bne $3, 1f - stl $2, TI_STATUS($8) - bsr $26, __save_fpu -1: DO_SWITCH_STACK - jsr $26, syscall_trace_enter /* returns the syscall number */ + jsr $26, alpha_syscall_enter_select + ldgp $gp, 0($26) UNDO_SWITCH_STACK - stq $0, 8($sp) /* regs->r1 = shadow syscall nr */ + /* + * C returned syscall function pointer in $0. + * It also stored SKIP in TI_SYSCALL_META if dispatch is skipped. + */ + lda $8, 0x3fff + bic $sp, $8, $8 + ldq $3, TI_SYSCALL_META($8) + lda $6, ALPHA_SYSCALL_META_SKIP + and $3, $6, $6 + bne $6, skip_dispatch - /* get the arguments back.. */ + + mov $0, $27 ldq $16, SP_OFF+24($sp) ldq $17, SP_OFF+32($sp) ldq $18, SP_OFF+40($sp) @@ -678,56 +520,70 @@ strace: ldq $20, 80($sp) ldq $21, 88($sp) - /* nr == -1: internal skip-dispatch or userspace syscall(-1)? */ - lda $6, -1($31) - cmpeq $0, $6, $6 - bne $6, $strace_skip_call /* nr == -1 => dispatch */ - - /* get the system call pointer.. */ - lda $1, NR_syscalls($31) - lda $2, sys_call_table - lda $27, sys_ni_syscall - cmpult $0, $1, $1 - s8addq $0, $2, $2 - beq $1, 1f - ldq $27, 0($2) -1: jsr $26, ($27), sys_gettimeofday -ret_from_straced: + jsr $26, ($27), sys_ni_syscall ldgp $gp, 0($26) - /* check return.. */ - blt $0, $strace_error /* the call failed */ -$strace_success: - stq $31, 72($sp) /* a3=0 => no error */ - stq $0, 0($sp) /* save return value */ - -$strace_skip_call: - SYSCALL_SKIP_RETURN_RESTART_GATE - DO_SWITCH_STACK - jsr $26, syscall_trace_leave - UNDO_SWITCH_STACK - br $31, ret_from_sys_call - - .align 3 -$strace_error: - ldq $18, 16($sp) /* restart syscall nr */ - ldq $19, 72($sp) /* .. and this a3 */ - - subq $31, $0, $0 /* with error in v0 */ - addq $31, 1, $1 /* set a3 for errno return */ - stq $0, 0($sp) - stq $1, 72($sp) /* a3 for return */ +skip_dispatch: + mov $0, $17 /* raw ret; ignored if SKIP is set */ + mov $sp, $16 /* regs */ DO_SWITCH_STACK - mov $18, $9 /* save old syscall number */ - mov $19, $10 /* save old a3 */ - jsr $26, syscall_trace_leave - mov $9, $18 - mov $10, $19 + jsr $26, alpha_finish_syscall_to_user_mode + ldgp $gp, 0($26) UNDO_SWITCH_STACK - mov $31, $26 /* tell "ret_from_sys_call" we can restart */ - br ret_from_sys_call +restore_all: + lda $8, 0x3fff + bic $sp, $8, $8 + ldl $2, TI_STATUS($8) + and $2, TS_SAVED_FP | TS_RESTORE_FP, $3 + bne $3, restore_fpu +restore_other: + .cfi_remember_state + RESTORE_ALL + call_pal PAL_rti + +ret_to_user_from_syscall: + lda $8, 0x3fff + bic $sp, $8, $8 + + mov $sp, $16 + DO_SWITCH_STACK + jsr $26, alpha_syscall_exit_to_user_mode + ldgp $gp, 0($26) + UNDO_SWITCH_STACK + br restore_all + + .align 4 + .globl ret_from_sys_call +ret_from_sys_call: + ldq $0, SP_OFF($sp) + and $0, 8, $0 + beq $0, ret_to_kernel + br ret_to_user_from_syscall + +ret_from_exception: + ldq $0, SP_OFF($sp) + and $0, 8, $0 + beq $0, ret_to_kernel + br ret_to_user_from_exception + +ret_to_user_from_exception: + + mov $sp, $16 + DO_SWITCH_STACK + jsr $26, alpha_exit_to_user_mode + ldgp $gp, 0($26) + UNDO_SWITCH_STACK + br restore_all + +ret_to_kernel: + .cfi_restore_state + lda $16, 7 + call_pal PAL_swpipl + LOCKDEP_HARDIRQS_ON_RESTORE SP_OFF($sp) + br restore_other + CFI_END_OSF_FRAME entSys /* @@ -815,7 +671,6 @@ restore_fpu: br restore_other #undef V - /* * The meat of the context switch code. */ @@ -851,7 +706,7 @@ alpha_switch_to: .align 4 .ent ret_from_fork ret_from_fork: - lda $26, ret_to_user + lda $26, ret_to_user_from_exception mov $17, $16 jmp $31, schedule_tail .end ret_from_fork @@ -868,7 +723,7 @@ ret_from_kernel_thread: mov $9, $27 mov $10, $16 jsr $26, ($9) - br $31, ret_to_user + br $31, ret_to_user_from_exception .end ret_from_kernel_thread @@ -910,12 +765,9 @@ fork_like clone3 .ent sys_\name sys_\name: .prologue 0 - lda $9, ret_from_straced - cmpult $26, $9, $9 + mov $sp, $10 lda $sp, -SWITCH_STACK_SIZE($sp) jsr $26, do_\name - bne $9, 1f - jsr $26, syscall_trace_leave 1: br $1, undo_switch_stack br ret_from_sys_call .end sys_\name diff --git a/arch/alpha/kernel/irq_alpha.c b/arch/alpha/kernel/irq_alpha.c index 1b799aafb7dc..f90ff898a71a 100644 --- a/arch/alpha/kernel/irq_alpha.c +++ b/arch/alpha/kernel/irq_alpha.c @@ -116,20 +116,24 @@ void notrace lockdep_on_restore(unsigned long ps, unsigned long ip) { #ifdef CONFIG_PROVE_LOCKING - /* Restoring IPL==7 means interrupts remain disabled. */ + /* + * If PAL_rti will restore IPL == 7, IRQs remain disabled. + * There is no hardirqs-on transition to annotate. + */ if ((ps & 7) == 7) return; /* - * If hardware IRQs are already enabled here, then emitting a - * hardirqs-on transition is redundant. + * This helper is meant to run before PAL_rti, after entry.S has + * forced IPL to 7. If IRQs are already enabled, do not emit a + * fake transition. */ if (!irqs_disabled()) return; /* - * Only emit the transition if lockdep currently believes - * hardirqs are off. + * Only emit an ON transition if lockdep currently tracks hardirqs + * as off. */ if (lockdep_hardirqs_enabled()) return; diff --git a/arch/alpha/kernel/proto.h b/arch/alpha/kernel/proto.h index 9b262ef09a3a..f138bd494628 100644 --- a/arch/alpha/kernel/proto.h +++ b/arch/alpha/kernel/proto.h @@ -2,6 +2,7 @@ #include #include #include +#include /* Prototypes of functions used across modules here in this directory. */ @@ -164,16 +165,18 @@ extern void pcibios_claim_one_bus(struct pci_bus *); /* ptrace.c */ extern int ptrace_set_bpt (struct task_struct *child); extern int ptrace_cancel_bpt (struct task_struct *child); -extern void syscall_trace_leave(void); -extern unsigned long syscall_trace_enter(void); /* signal.c */ struct sigcontext; extern void do_sigreturn(struct sigcontext __user *); struct rt_sigframe; extern void do_rt_sigreturn(struct rt_sigframe __user *); -extern void do_work_pending(struct pt_regs *, unsigned long, unsigned long, unsigned long); extern void alpha_schedule_user_work(void); +extern void do_signal(struct pt_regs *regs, unsigned long r0, unsigned long r19); +extern void alpha_syscall_exit_to_user_mode(struct pt_regs *regs); +extern void alpha_exit_to_user_mode(struct pt_regs *regs); +extern void alpha_finish_syscall_to_user_mode(struct pt_regs *regs, long ret); +extern unsigned long alpha_syscall_enter_select(struct pt_regs *regs, long syscall); /* traps.c */ extern void dik_show_regs(struct pt_regs *regs, unsigned long *r9_15); diff --git a/arch/alpha/kernel/ptrace.c b/arch/alpha/kernel/ptrace.c index fc8f6cedbb28..e1d8c2f69688 100644 --- a/arch/alpha/kernel/ptrace.c +++ b/arch/alpha/kernel/ptrace.c @@ -134,18 +134,44 @@ get_reg_addr(struct task_struct * task, unsigned long regno) /* * Get contents of register REGNO in task TASK. */ -static unsigned long -get_reg(struct task_struct * task, unsigned long regno) + +static bool +valid_regno(unsigned long regno) { - /* Special hack for fpcr -- combine hardware and software bits. */ + return regno <= 65; +} + +static long +get_reg(struct task_struct *task, unsigned long regno) +{ + unsigned long *addr; + + if (!valid_regno(regno)) + return -EIO; + + /* + * Special hack for fpcr -- combine hardware and software bits. + */ if (regno == 63) { - unsigned long fpcr = *get_reg_addr(task, regno); - unsigned long swcr - = task_thread_info(task)->ieee_state & IEEE_SW_MASK; + unsigned long fpcr; + unsigned long swcr; + + addr = get_reg_addr(task, regno); + if (!addr) + return -EIO; + + fpcr = *addr; + swcr = task_thread_info(task)->ieee_state & IEEE_SW_MASK; swcr = swcr_update_status(swcr, fpcr); + return fpcr | swcr; } - return *get_reg_addr(task, regno); + + addr = get_reg_addr(task, regno); + if (!addr) + return -EIO; + + return *addr; } static void alpha_elf_fpregs_get(struct task_struct *target, @@ -271,14 +297,17 @@ static void alpha_elf_gregs_set(struct task_struct *child, pt->r19 = 1; } - -/* - * Write contents of register REGNO in task TASK. - */ static int put_reg(struct task_struct *task, unsigned long regno, unsigned long data) { struct pt_regs *regs = task_pt_regs(task); + unsigned long *addr; + unsigned long old_r0 = regs->r0; + + if (regno == 31) + return 0; + if (!valid_regno(regno)) + return -EIO; if (regno == 63) { task_thread_info(task)->ieee_state @@ -287,24 +316,30 @@ put_reg(struct task_struct *task, unsigned long regno, unsigned long data) data = (data & FPCR_DYN_MASK) | ieee_swcr_to_fpcr(data); } - *get_reg_addr(task, regno) = data; + addr = get_reg_addr(task, regno); + if (!addr) + return -EIO; + + *addr = data; /* * Alpha historically exposes r0/v0 as the syscall number at a * syscall-entry stop. The generic-entry conversion keeps the - * mutable syscall number in regs->r1, so old ptrace users such - * as strace that skip a syscall by poking r0 to -1 must also - * update the internal shadow syscall number. - * - * Do not mirror other r0 writes. strace later pokes r0 to the - * injected return value, e.g. 42, while r1 must remain -1. + * mutable syscall number in regs->r1. */ - if (regno == 0 && data == (unsigned long)-1) { + if (regno == 0 && regs->r1 == old_r0 && + (data == (unsigned long)-1 || + (regs->r19 == 0 && data < NR_syscalls))) { regs->r1 = data; - regs->r19 = 0; - } + /* + * Keep the skip path looking like a clean entry-side syscall + * rewrite. Do not touch r19 for ordinary syscall substitution. + */ + if (data == (unsigned long)-1) + regs->r19 = 0; + } return 0; } @@ -435,25 +470,24 @@ long arch_ptrace(struct task_struct *child, long request, switch (request) { /* When I and D space are separate, these will need to be fixed. */ - case PTRACE_PEEKTEXT: /* read word at location addr. */ + case PTRACE_PEEKTEXT: case PTRACE_PEEKDATA: copied = ptrace_access_vm(child, addr, &tmp, sizeof(tmp), FOLL_FORCE); ret = -EIO; if (copied != sizeof(tmp)) break; - force_successful_syscall_return(); ret = tmp; break; - /* Read register number ADDR. */ case PTRACE_PEEKUSR: - force_successful_syscall_return(); ret = get_reg(child, addr); - DBG(DBG_MEM, ("peek $%lu->%#lx\n", addr, ret)); - break; + if (ret == -EIO) + break; + force_successful_syscall_return(); + break; /* When I and D space are separate, this will have to be fixed. */ case PTRACE_POKETEXT: /* write the word at location addr. */ case PTRACE_POKEDATA: @@ -471,47 +505,6 @@ long arch_ptrace(struct task_struct *child, long request, return ret; } -asmlinkage unsigned long syscall_trace_enter(void) -{ - struct pt_regs *regs = current_pt_regs(); - - if (test_thread_flag(TIF_SYSCALL_TRACE) && - !ptrace_report_syscall_permit_entry(regs)) { - syscall_set_nr(current, regs, -1); - if (regs->r19 == 0 && regs->r0 == (unsigned long)-1) - syscall_set_return_value(current, regs, -ENOSYS, 0); - return -1UL; - } - - /* - * Do the secure computing after ptrace; failures should be fast. - * If this fails, seccomp may already have set up the return value - * (e.g. SECCOMP_RET_ERRNO / TRACE). - */ - if (!seccomp_permit_syscall()) { - if (regs->r19 == 0 && regs->r0 == (unsigned long)-1) - syscall_set_return_value(current, regs, -ENOSYS, 0); - syscall_set_nr(current, regs, -1); - return -1UL; - } - -#ifdef CONFIG_AUDITSYSCALL - audit_syscall_entry(syscall_get_nr(current, regs), - regs->r16, regs->r17, regs->r18, regs->r19); -#endif - return syscall_get_nr(current, regs); -} - - - -asmlinkage void -syscall_trace_leave(void) -{ - audit_syscall_exit(current_pt_regs()); - if (test_thread_flag(TIF_SYSCALL_TRACE)) - ptrace_report_syscall_exit(current_pt_regs(), 0); -} - /* * Minimal regset support for Alpha. * @@ -522,7 +515,6 @@ syscall_trace_leave(void) * regset_get should return 0 on success. So call dump_elf_thread() * directly and return membuf_write()'s result. */ - static int alpha_regset_set(struct task_struct *target, const struct user_regset *regset, unsigned int pos, unsigned int count, diff --git a/arch/alpha/kernel/signal.c b/arch/alpha/kernel/signal.c index ce40a49b8496..0c55a7554d63 100644 --- a/arch/alpha/kernel/signal.c +++ b/arch/alpha/kernel/signal.c @@ -27,10 +27,9 @@ #include #include #include - +#include #include "proto.h" - #define DEBUG_SIG 0 #define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) @@ -41,14 +40,6 @@ asmlinkage void ret_from_sys_call(void); * The OSF/1 sigprocmask calling sequence is different from the * C sigprocmask() sequence.. */ - -asmlinkage void alpha_schedule_user_work(void) -{ - local_irq_enable(); - schedule(); - local_irq_disable(); -} - SYSCALL_DEFINE2(osf_sigprocmask, int, how, unsigned long, newmask) { sigset_t oldmask; @@ -465,6 +456,7 @@ syscall_restart(unsigned long r0, unsigned long r19, fallthrough; case ERESTARTNOINTR: regs->r0 = r0; /* reset v0 and a3 and replay syscall */ + regs->r1 = r0; regs->r19 = r19; regs->pc -= 4; break; @@ -488,7 +480,7 @@ syscall_restart(unsigned long r0, unsigned long r19, * restart. "r0" is also used as an indicator whether we can restart at * all (if we get here from anything but a syscall return, it will be 0) */ -static void +void do_signal(struct pt_regs *regs, unsigned long r0, unsigned long r19) { unsigned long single_stepping = ptrace_cancel_bpt(current); @@ -511,12 +503,14 @@ do_signal(struct pt_regs *regs, unsigned long r0, unsigned long r19) case ERESTARTNOINTR: /* Reset v0 and a3 and replay syscall. */ regs->r0 = r0; + regs->r1 = r0; regs->r19 = r19; regs->pc -= 4; break; case ERESTART_RESTARTBLOCK: /* Set v0 to the restart_syscall and replay */ regs->r0 = __NR_restart_syscall; + regs->r1 = __NR_restart_syscall; regs->pc -= 4; break; } @@ -527,27 +521,133 @@ do_signal(struct pt_regs *regs, unsigned long r0, unsigned long r19) ptrace_set_bpt(current); /* re-set breakpoint */ } -void -do_work_pending(struct pt_regs *regs, unsigned long thread_flags, - unsigned long r0, unsigned long r19) +asmlinkage void alpha_exit_to_user_mode(struct pt_regs *regs) { - do { - if (thread_flags & _TIF_NEED_RESCHED) { - local_irq_enable(); - schedule(); - } else { - local_irq_enable(); - if (thread_flags & (_TIF_SIGPENDING|_TIF_NOTIFY_SIGNAL)) { - preempt_disable(); - save_fpu(); - preempt_enable(); - do_signal(regs, r0, r19); - r0 = 0; - } else { - resume_user_mode_work(regs); - } - } - local_irq_disable(); - thread_flags = read_thread_flags(); - } while (thread_flags & _TIF_WORK_MASK); + local_irq_disable(); + irqentry_exit_to_user_mode_prepare(regs); + exit_to_user_mode(); +} + +/* + * Syscall return reaches here after Alpha-specific r0/a3 result encoding. + * Delegate syscall-exit work and final exit-to-user handling to generic + * entry code; low-level PAL restore remains in assembly. + */ +asmlinkage void alpha_syscall_exit_to_user_mode(struct pt_regs *regs) +{ + syscall_exit_to_user_mode(regs); +} + +void arch_do_signal_or_restart(struct pt_regs *regs) +{ + struct thread_info *ti = current_thread_info(); + + preempt_disable(); + save_fpu(); + preempt_enable(); + do_signal(regs, ti->syscall_saved_nr, ti->syscall_saved_r19); +} + +asmlinkage unsigned long +alpha_syscall_enter_select(struct pt_regs *regs, long syscall) +{ + struct thread_info *ti = current_thread_info(); + unsigned long saved_r0 = regs->r0; + unsigned long saved_r19 = regs->r19; + unsigned long fn = (unsigned long)sys_ni_syscall; + long nr = syscall; + bool permit; + + ti->syscall_meta = 0; + ti->syscall_saved_nr = syscall; + + if (!(ti->status & TS_SAVED_FP)) { + ti->status |= TS_SAVED_FP; + __save_fpu(); + } + + permit = syscall_enter_from_user_mode_randomize_stack(regs, &nr); + + /* + * Alpha cannot preset the full -ENOSYS return state before generic + * entry handling, because r19 is still syscall argument 4 on the + * normal dispatch path. So provide the default -ENOSYS result only + * here, after we know no real syscall will be invoked, and only if + * entry work did not already change the return state. + */ + if (unlikely(!permit || (unsigned long)nr >= NR_syscalls)) { + if (regs->r0 == saved_r0 && regs->r19 == saved_r19) + syscall_set_return_value(current, regs, -ENOSYS, 0); + + ti->syscall_meta = ALPHA_SYSCALL_META_SKIP; + return fn; /* ignored by asm when SKIP is set */ + } + + instrumentation_begin(); + { + unsigned long index; + + index = array_index_nospec((unsigned long)nr, NR_syscalls); + fn = (unsigned long)sys_call_table[index]; + } + instrumentation_end(); + + return fn; +} + +asmlinkage noinstr void +alpha_finish_syscall_to_user_mode(struct pt_regs *regs, long ret) +{ + struct thread_info *ti = current_thread_info(); + unsigned long meta = ti->syscall_meta; + + ti->syscall_meta = 0; + ti->syscall_saved_r19 = regs->r19; + + instrumentation_begin(); + + if (meta & ALPHA_SYSCALL_META_SKIP) { + /* + * Skip-dispatch path: ptrace/seccomp may already have installed + * the return state in r0/r19. Preserve it unless the syscall was + * skipped with no explicit return value. + * + * Generic PTRACE_SET_SYSCALL_INFO changes only the syscall-number + * shadow, so r1 == -1 while r0 still contains the original Alpha + * syscall number. Legacy PTRACE_POKEUSR based skipping can leave + * r0 == -1 with a3/r19 still indicating success. Both represent + * an unhandled skipped syscall and should become ENOSYS/a3=1. + */ + if (regs->r1 == (unsigned long)-1 && + (regs->r0 == ti->syscall_saved_nr || + regs->r0 == (unsigned long)-1)) { + regs->r0 = ENOSYS; + regs->r19 = 1; + } + + instrumentation_end(); + + syscall_exit_to_user_mode(regs); + return; + } + + /* + * Some successful syscalls, notably legacy ptrace PEEK requests, + * return arbitrary data in r0. That data may have the bit pattern + * of a negative errno, so do not infer failure from ret < 0 when + * arch code explicitly requested a successful Alpha return. + */ + if (meta & ALPHA_SYSCALL_META_FORCE_SUCCESS) { + regs->r0 = ret; + regs->r19 = 0; + } else if (ret < 0) { + regs->r0 = -ret; + regs->r19 = 1; + } else { + regs->r0 = ret; + regs->r19 = 0; + } + + instrumentation_end(); + syscall_exit_to_user_mode(regs); } From 797a8e3cb5539a82cc0364c41a850aba940d436a Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:22 -0400 Subject: [PATCH 02/57] alpha: enter hardirq context before looking up the irq descriptor handle_irq() called irq_to_desc() before irq_enter(), so that lookup ran with the preempt count still saying task context and with RCU not yet watching. Generic code called from an interrupt handler should see hardirq context, and irq_to_desc() is more than an array index once SPARSE_IRQ is in use. Move irq_enter() to the top of the function and add the matching irq_exit() to the invalid interrupt path. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-1-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/irq.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/arch/alpha/kernel/irq.c b/arch/alpha/kernel/irq.c index 4a6a8b1d5a8b..5867a1655045 100644 --- a/arch/alpha/kernel/irq.c +++ b/arch/alpha/kernel/irq.c @@ -107,18 +107,21 @@ handle_irq(int irq) * handled by some other CPU. (or is disabled) */ static unsigned int illegal_count=0; - struct irq_desc *desc = irq_to_desc(irq); - + struct irq_desc *desc; + + irq_enter(); + + desc = irq_to_desc(irq); if (!desc || ((unsigned) irq > ACTUAL_NR_IRQS && illegal_count < MAX_ILLEGAL_IRQS)) { irq_err_count++; illegal_count++; printk(KERN_CRIT "device_interrupt: invalid interrupt %d\n", irq); + irq_exit(); return; } - irq_enter(); generic_handle_irq_desc(desc); irq_exit(); } From 63d97e41ec92d611644c6ad134f10a98d9534cb5 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:23 -0400 Subject: [PATCH 03/57] alpha: select ARCH_HAS_UBSAN GCC's undefined behavior sanitizer has no architecture-specific runtime requirements. Enable it for Alpha to allow UBSAN instrumentation of the kernel. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-2-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 51a7391869dd..713a0c1cc930 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -7,6 +7,7 @@ config ALPHA select ARCH_32BIT_USTAT_F_TINODE select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DMA_OPS if PCI + select ARCH_HAS_UBSAN select ARCH_MIGHT_HAVE_PC_PARPORT select ARCH_MIGHT_HAVE_PC_SERIO select ARCH_MODULE_NEEDS_WEAK_PER_CPU if SMP From 278de0f04816c97f701456b72af4f59d36440249 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:24 -0400 Subject: [PATCH 04/57] alpha: select ARCH_HAS_GCOV_PROFILE_ALL GCC's gcov instrumentation (-fprofile-arcs -ftest-coverage) has no architecture-specific runtime requirements. Enable GCOV_PROFILE_ALL support for Alpha to allow whole-kernel code coverage analysis. Also mark Alpha as "ok" in the gcov-profile-all feature matrix. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-3-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- Documentation/features/debug/gcov-profile-all/arch-support.txt | 2 +- arch/alpha/Kconfig | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/features/debug/gcov-profile-all/arch-support.txt b/Documentation/features/debug/gcov-profile-all/arch-support.txt index 63494bddc263..4701209b869d 100644 --- a/Documentation/features/debug/gcov-profile-all/arch-support.txt +++ b/Documentation/features/debug/gcov-profile-all/arch-support.txt @@ -6,7 +6,7 @@ ----------------------- | arch |status| ----------------------- - | alpha: | TODO | + | alpha: | ok | | arc: | TODO | | arm: | ok | | arm64: | ok | diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 713a0c1cc930..c4da16971652 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -7,6 +7,7 @@ config ALPHA select ARCH_32BIT_USTAT_F_TINODE select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DMA_OPS if PCI + select ARCH_HAS_GCOV_PROFILE_ALL select ARCH_HAS_UBSAN select ARCH_MIGHT_HAVE_PC_PARPORT select ARCH_MIGHT_HAVE_PC_SERIO From 2b41fbab67ef7ccdb48c7a5a69c38dfadba610e8 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:25 -0400 Subject: [PATCH 05/57] alpha: select HAVE_DEBUG_KMEMLEAK Kmemleak needs no architecture support beyond the standard linker script symbols for the data and bss sections, which Alpha already provides, and a scan of the kernel stacks, which is done generically. Also mark Alpha as "ok" in the kmemleak feature matrix. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-4-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- Documentation/features/debug/kmemleak/arch-support.txt | 2 +- arch/alpha/Kconfig | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/features/debug/kmemleak/arch-support.txt b/Documentation/features/debug/kmemleak/arch-support.txt index f45149cfa313..b65b3f8c0e89 100644 --- a/Documentation/features/debug/kmemleak/arch-support.txt +++ b/Documentation/features/debug/kmemleak/arch-support.txt @@ -6,7 +6,7 @@ ----------------------- | arch |status| ----------------------- - | alpha: | TODO | + | alpha: | ok | | arc: | ok | | arm: | ok | | arm64: | ok | diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index c4da16971652..f6d1d2469677 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -19,6 +19,7 @@ config ALPHA select PCI_DOMAINS if PCI select PCI_SYSCALL if PCI select HAVE_ASM_MODVERSIONS + select HAVE_DEBUG_KMEMLEAK select HAVE_PAGE_SIZE_8KB select HAVE_PCSPKR_PLATFORM select HAVE_PERF_EVENTS From 533993a3ac5db0542b5b781cad47115eae182486 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:26 -0400 Subject: [PATCH 06/57] alpha: select EDAC_SUPPORT The EDAC core builds and works on Alpha; it only needs the architecture to declare support. There is no Alpha memory controller driver yet, but the core is what a future driver, or the existing machine check error decoding in err_ev6.c and err_ev7.c, would report through. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-5-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index f6d1d2469677..032c23f979c4 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -32,6 +32,7 @@ config ALPHA select ARCH_WANT_IPC_PARSE_VERSION select ARCH_HAVE_NMI_SAFE_CMPXCHG select AUDIT_ARCH + select EDAC_SUPPORT select GENERIC_CPU_VULNERABILITIES select GENERIC_SMP_IDLE_THREAD select HAS_IOPORT From b345db9e12089e0221e4ac9e36124affdf879443 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:27 -0400 Subject: [PATCH 07/57] alpha: select ARCH_SUPPORTS_ATOMIC_RMW Alpha has native LL/SC instructions (LDL_L/STL_C, LDQ_L/STQ_C) that provide atomic read-modify-write capability, and already selects ARCH_HAVE_NMI_SAFE_CMPXCHG. Selecting ARCH_SUPPORTS_ATOMIC_RMW enables MUTEX_SPIN_ON_OWNER and RWSEM_SPIN_ON_OWNER on SMP, allowing optimistic spinning on contended locks rather than immediately sleeping. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-6-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 032c23f979c4..d039b7135d18 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -14,6 +14,7 @@ config ALPHA select ARCH_MODULE_NEEDS_WEAK_PER_CPU if SMP select ARCH_NO_PREEMPT select ARCH_NO_SG_CHAIN + select ARCH_SUPPORTS_ATOMIC_RMW select ARCH_USE_CMPXCHG_LOCKREF select FORCE_PCI select PCI_DOMAINS if PCI From d8df465bc46d1d6169a05dd55f82ec0dfa639a31 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:28 -0400 Subject: [PATCH 08/57] alpha: select ARCH_HAS_FAST_MULTIPLIER All Alpha CPUs have hardware integer multiply (MULL/MULQ). EV4 and EV5 implement it in microcode; EV56 and later have fast single-cycle multiply. Since no EV4 system is likely to run a modern kernel, select this unconditionally to enable multiply-based fast paths in hweight and string routines. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-7-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index d039b7135d18..2672971b83f3 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -7,6 +7,7 @@ config ALPHA select ARCH_32BIT_USTAT_F_TINODE select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DMA_OPS if PCI + select ARCH_HAS_FAST_MULTIPLIER select ARCH_HAS_GCOV_PROFILE_ALL select ARCH_HAS_UBSAN select ARCH_MIGHT_HAVE_PC_PARPORT From 3382beeda2e115f9f9a76019f294f328b7e75756 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 17 Oct 2026 13:40:07 -0400 Subject: [PATCH 09/57] alpha: select ARCH_SUPPORTS_INT128 gcc supports __int128 on Alpha, and the 64x64 to 128-bit multiply that the users in the scheduler and in lib/ care about maps onto mulq/umulh rather than an out-of-line libgcc helper. Variable 128-bit shifts are another matter. gcc open-codes them when optimizing for speed, but with CONFIG_CC_OPTIMIZE_FOR_SIZE=y it calls the libgcc routines __ashlti3, __ashrti3 and __lshrti3 instead. The kernel does not link against libgcc, so provide the three helpers in C, as s390 does, and export them for modules. They are built into the kernel proper rather than lib.a so the exports are present even when nothing built in references them. Like the s390 versions they are marked noinstr, since the compiler may emit calls to them from noinstr code. Gate the select on CC_HAS_INT128 as the other 64-bit architectures do. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261017174007.695344-1-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + arch/alpha/include/asm/asm-prototypes.h | 6 +++ arch/alpha/lib/Makefile | 2 + arch/alpha/lib/tishift.c | 70 +++++++++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 arch/alpha/lib/tishift.c diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 2672971b83f3..bed3f1c8d9d2 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -16,6 +16,7 @@ config ALPHA select ARCH_NO_PREEMPT select ARCH_NO_SG_CHAIN select ARCH_SUPPORTS_ATOMIC_RMW + select ARCH_SUPPORTS_INT128 if CC_HAS_INT128 select ARCH_USE_CMPXCHG_LOCKREF select FORCE_PCI select PCI_DOMAINS if PCI diff --git a/arch/alpha/include/asm/asm-prototypes.h b/arch/alpha/include/asm/asm-prototypes.h index c8ae46fc2e74..672fea15032f 100644 --- a/arch/alpha/include/asm/asm-prototypes.h +++ b/arch/alpha/include/asm/asm-prototypes.h @@ -17,3 +17,9 @@ extern void __remlu(void); extern void __divqu(void); extern void __remqu(void); extern unsigned long __udiv_qrnnd(unsigned long *, unsigned long, unsigned long , unsigned long); + +#ifdef CONFIG_ARCH_SUPPORTS_INT128 +extern __int128_t __ashlti3(__int128_t a, int shift); +extern __int128_t __ashrti3(__int128_t a, int shift); +extern __int128_t __lshrti3(__int128_t a, int shift); +#endif diff --git a/arch/alpha/lib/Makefile b/arch/alpha/lib/Makefile index 84046e730e6d..f77d4a46f2e6 100644 --- a/arch/alpha/lib/Makefile +++ b/arch/alpha/lib/Makefile @@ -35,6 +35,8 @@ lib-y = __divqu.o __remqu.o __divlu.o __remlu.o \ callback_srm.o srm_puts.o srm_printk.o \ fls.o +obj-$(CONFIG_ARCH_SUPPORTS_INT128) += tishift.o + # The division routines are built from single source, with different defines. AFLAGS___divqu.o = -DDIV AFLAGS___remqu.o = -DREM diff --git a/arch/alpha/lib/tishift.c b/arch/alpha/lib/tishift.c new file mode 100644 index 000000000000..8319907d8150 --- /dev/null +++ b/arch/alpha/lib/tishift.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 128-bit shift helpers. + * + * gcc open-codes a variable 128-bit shift when optimizing for speed, but + * calls these libgcc routines when optimizing for size. + */ + +#include +#include +#include + +union ti { + __int128_t val; + struct { + u64 low; + u64 high; + }; +}; + +noinstr __int128_t __ashlti3(__int128_t a, int shift) +{ + union ti ti = { .val = a }; + + if (!shift) + return ti.val; + if (shift < 64) { + ti.high = (ti.high << shift) | (ti.low >> (64 - shift)); + ti.low <<= shift; + } else { + ti.high = ti.low << (shift - 64); + ti.low = 0; + } + return ti.val; +} +EXPORT_SYMBOL(__ashlti3); + +noinstr __int128_t __ashrti3(__int128_t a, int shift) +{ + union ti ti = { .val = a }; + + if (!shift) + return ti.val; + if (shift < 64) { + ti.low = (ti.low >> shift) | (ti.high << (64 - shift)); + ti.high = (s64)ti.high >> shift; + } else { + ti.low = (s64)ti.high >> (shift - 64); + ti.high = (s64)ti.high >> 63; + } + return ti.val; +} +EXPORT_SYMBOL(__ashrti3); + +noinstr __int128_t __lshrti3(__int128_t a, int shift) +{ + union ti ti = { .val = a }; + + if (!shift) + return ti.val; + if (shift < 64) { + ti.low = (ti.low >> shift) | (ti.high << (64 - shift)); + ti.high >>= shift; + } else { + ti.low = ti.high >> (shift - 64); + ti.high = 0; + } + return ti.val; +} +EXPORT_SYMBOL(__lshrti3); From a30d7bef9c94ebdbf3cb97d09371811065deddc9 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:30 -0400 Subject: [PATCH 10/57] alpha: select HAVE_ARCH_COMPILER_H Alpha already provides asm/compiler.h (wrapping uapi/asm/compiler.h) which defines architecture-specific compiler intrinsics like __kernel_cmpbge, __kernel_cttz, __kernel_ctlz, and __kernel_ctpop. Selecting HAVE_ARCH_COMPILER_H causes linux/compiler_types.h to include asm/compiler.h, making these definitions available consistently. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-9-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index bed3f1c8d9d2..237ef0c214dd 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -21,6 +21,7 @@ config ALPHA select FORCE_PCI select PCI_DOMAINS if PCI select PCI_SYSCALL if PCI + select HAVE_ARCH_COMPILER_H select HAVE_ASM_MODVERSIONS select HAVE_DEBUG_KMEMLEAK select HAVE_PAGE_SIZE_8KB From 94e71e568683921de5b12e686a50d7f1d329e28d Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:31 -0400 Subject: [PATCH 11/57] alpha: select ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE Alpha has a clean split between user and kernel address spaces: user space occupies 0x0000000000000000-0x000003ffffffffff (TASK_SIZE), while kernel space uses KSEG and vmalloc regions near the top of the 64-bit address space. These are strictly non-overlapping. This enables optimizations in BPF and tracing that can skip address range checks when user and kernel pointers are distinguishable by address alone. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-10-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 237ef0c214dd..8f97d6e22b28 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -9,6 +9,7 @@ config ALPHA select ARCH_HAS_DMA_OPS if PCI select ARCH_HAS_FAST_MULTIPLIER select ARCH_HAS_GCOV_PROFILE_ALL + select ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE select ARCH_HAS_UBSAN select ARCH_MIGHT_HAVE_PC_PARPORT select ARCH_MIGHT_HAVE_PC_SERIO From 965341a655ef564b98b095b3e329063c34c796d3 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:32 -0400 Subject: [PATCH 12/57] alpha: add HAVE_ARCH_THREAD_STRUCT_WHITELIST support Alpha's thread_struct is empty (all per-thread state lives in thread_info), so the whitelist is trivially empty. This is more explicit than the generic fallback, which computes the full thread_struct size as copyable. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-11-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + arch/alpha/include/asm/processor.h | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 8f97d6e22b28..cc69f94a1c5b 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -44,6 +44,7 @@ config ALPHA select HAVE_ARCH_AUDITSYSCALL select HAVE_ARCH_SECCOMP select HAVE_ARCH_SECCOMP_FILTER + select HAVE_ARCH_THREAD_STRUCT_WHITELIST select HAVE_ARCH_TRACEHOOK select HAVE_MOD_ARCH_SPECIFIC select LOCK_MM_AND_FIND_VMA diff --git a/arch/alpha/include/asm/processor.h b/arch/alpha/include/asm/processor.h index 5dce5518a211..c461a26d47b3 100644 --- a/arch/alpha/include/asm/processor.h +++ b/arch/alpha/include/asm/processor.h @@ -26,6 +26,13 @@ struct thread_struct { }; #define INIT_THREAD { } +static inline void arch_thread_struct_whitelist(unsigned long *offset, + unsigned long *size) +{ + *offset = 0; + *size = 0; +} + /* Do necessary setup to start up a newly executed thread. */ struct pt_regs; extern void start_thread(struct pt_regs *, unsigned long, unsigned long); From 7ab5de838dfcb39322417253e69a7be67bae9e68 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:33 -0400 Subject: [PATCH 13/57] alpha: add ARCH_HAS_PTE_SPECIAL support Add _PAGE_SPECIAL using bit 19 (0x80000), which is unused in the Alpha PTE layout. This enables pte_special()/pte_mkspecial() for marking PTEs that are not backed by struct page (VDSO mappings, zero page, io_remap_pfn_range, etc.). Include _PAGE_SPECIAL in _PAGE_CHG_MASK so it is preserved across pte_modify() calls. Also mark Alpha as "ok" in the pte_special feature matrix. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-12-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- Documentation/features/vm/pte_special/arch-support.txt | 2 +- arch/alpha/Kconfig | 1 + arch/alpha/include/asm/pgtable.h | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Documentation/features/vm/pte_special/arch-support.txt b/Documentation/features/vm/pte_special/arch-support.txt index 3f777f8b67d5..1485bb7c73eb 100644 --- a/Documentation/features/vm/pte_special/arch-support.txt +++ b/Documentation/features/vm/pte_special/arch-support.txt @@ -6,7 +6,7 @@ ----------------------- | arch |status| ----------------------- - | alpha: | TODO | + | alpha: | ok | | arc: | ok | | arm: | ok | | arm64: | ok | diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index cc69f94a1c5b..1d28fa8d3b66 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -10,6 +10,7 @@ config ALPHA select ARCH_HAS_FAST_MULTIPLIER select ARCH_HAS_GCOV_PROFILE_ALL select ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE + select ARCH_HAS_PTE_SPECIAL select ARCH_HAS_UBSAN select ARCH_MIGHT_HAVE_PC_PARPORT select ARCH_MIGHT_HAVE_PC_SERIO diff --git a/arch/alpha/include/asm/pgtable.h b/arch/alpha/include/asm/pgtable.h index 8e00cf9dc39d..bdff33b505bc 100644 --- a/arch/alpha/include/asm/pgtable.h +++ b/arch/alpha/include/asm/pgtable.h @@ -73,6 +73,7 @@ struct vm_area_struct; /* .. and these are ours ... */ #define _PAGE_DIRTY 0x20000 #define _PAGE_ACCESSED 0x40000 +#define _PAGE_SPECIAL 0x80000 /* We borrow bit 39 to store the exclusive marker in swap PTEs. */ #define _PAGE_SWP_EXCLUSIVE 0x8000000000UL @@ -94,7 +95,7 @@ struct vm_area_struct; #define _PFN_MASK 0xFFFFFFFF00000000UL #define _PAGE_TABLE (_PAGE_VALID | __DIRTY_BITS | __ACCESS_BITS) -#define _PAGE_CHG_MASK (_PFN_MASK | __DIRTY_BITS | __ACCESS_BITS) +#define _PAGE_CHG_MASK (_PFN_MASK | __DIRTY_BITS | __ACCESS_BITS | _PAGE_SPECIAL) /* * All the normal masks have the "page accessed" bits on, as any time they are used, @@ -238,6 +239,8 @@ extern inline pte_t pte_mkold(pte_t pte) { pte_val(pte) &= ~(__ACCESS_BITS); ret extern inline pte_t pte_mkwrite_novma(pte_t pte){ pte_val(pte) &= ~_PAGE_FOW; return pte; } extern inline pte_t pte_mkdirty(pte_t pte) { pte_val(pte) |= __DIRTY_BITS; return pte; } extern inline pte_t pte_mkyoung(pte_t pte) { pte_val(pte) |= __ACCESS_BITS; return pte; } +extern inline int pte_special(pte_t pte) { return pte_val(pte) & _PAGE_SPECIAL; } +extern inline pte_t pte_mkspecial(pte_t pte) { pte_val(pte) |= _PAGE_SPECIAL; return pte; } /* * The smp_rmb() in the following functions are required to order the load of From df26a8f1e2c2733f563d9f921e4002596b170c13 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:34 -0400 Subject: [PATCH 14/57] alpha: add ARCH_SUPPORTS_PAGE_TABLE_CHECK support Add the page table check helpers needed for CONFIG_PAGE_TABLE_CHECK and select ARCH_SUPPORTS_PAGE_TABLE_CHECK in Kconfig. Alpha does not support huge pages, so pmd_user_accessible_page() and pud_user_accessible_page() always return false. pte_user_accessible_page() checks for present PTEs with user read access (_PAGE_URE). Update the helpers to the three-argument pxx_user_accessible_page(mm, addr, pxx) signature introduced by commit cf8771ca4cdb ("mm/page_table_check: Pass mm_struct to pxx_user_accessible_page()"), matching the other architectures. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-13-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + arch/alpha/include/asm/pgtable.h | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 1d28fa8d3b66..da4ec8399517 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -19,6 +19,7 @@ config ALPHA select ARCH_NO_SG_CHAIN select ARCH_SUPPORTS_ATOMIC_RMW select ARCH_SUPPORTS_INT128 if CC_HAS_INT128 + select ARCH_SUPPORTS_PAGE_TABLE_CHECK select ARCH_USE_CMPXCHG_LOCKREF select FORCE_PCI select PCI_DOMAINS if PCI diff --git a/arch/alpha/include/asm/pgtable.h b/arch/alpha/include/asm/pgtable.h index bdff33b505bc..6ebdd90f3035 100644 --- a/arch/alpha/include/asm/pgtable.h +++ b/arch/alpha/include/asm/pgtable.h @@ -232,6 +232,25 @@ extern inline void pud_clear(pud_t * pudp) { pud_val(*pudp) = 0; } extern inline int pte_write(pte_t pte) { return !(pte_val(pte) & _PAGE_FOW); } extern inline int pte_dirty(pte_t pte) { return pte_val(pte) & _PAGE_DIRTY; } extern inline int pte_young(pte_t pte) { return pte_val(pte) & _PAGE_ACCESSED; } +extern inline int pte_user(pte_t pte) { return pte_val(pte) & _PAGE_URE; } + +static inline bool pte_user_accessible_page(struct mm_struct *mm, + unsigned long addr, pte_t pte) +{ + return pte_present(pte) && pte_user(pte); +} + +static inline bool pmd_user_accessible_page(struct mm_struct *mm, + unsigned long addr, pmd_t pmd) +{ + return false; +} + +static inline bool pud_user_accessible_page(struct mm_struct *mm, + unsigned long addr, pud_t pud) +{ + return false; +} extern inline pte_t pte_wrprotect(pte_t pte) { pte_val(pte) |= _PAGE_FOW; return pte; } extern inline pte_t pte_mkclean(pte_t pte) { pte_val(pte) &= ~(__DIRTY_BITS); return pte; } From 533d4a1ef9da12dd820c7a04bb6024df528a54b0 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:35 -0400 Subject: [PATCH 15/57] alpha: select ARCH_HAS_DEBUG_VM_PGTABLE CONFIG_DEBUG_VM_PGTABLE runs a boot-time self test over the page table helpers: the protection bit accessors, pte_modify(), the swap entry encoding including the exclusive marker, and, where the architecture supports them, the huge page helpers. Alpha implements everything the test needs for a three-level page table without huge page support. Also mark Alpha as "ok" in the debug-vm-pgtable feature matrix. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-14-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- Documentation/features/debug/debug-vm-pgtable/arch-support.txt | 2 +- arch/alpha/Kconfig | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/features/debug/debug-vm-pgtable/arch-support.txt b/Documentation/features/debug/debug-vm-pgtable/arch-support.txt index 156687a7436d..b4e2374fe201 100644 --- a/Documentation/features/debug/debug-vm-pgtable/arch-support.txt +++ b/Documentation/features/debug/debug-vm-pgtable/arch-support.txt @@ -6,7 +6,7 @@ ----------------------- | arch |status| ----------------------- - | alpha: | TODO | + | alpha: | ok | | arc: | ok | | arm: | TODO | | arm64: | ok | diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index da4ec8399517..4bd5e913baaf 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -6,6 +6,7 @@ config ALPHA select HAVE_SYSCALL_TRACEPOINTS select ARCH_32BIT_USTAT_F_TINODE select ARCH_HAS_CURRENT_STACK_POINTER + select ARCH_HAS_DEBUG_VM_PGTABLE select ARCH_HAS_DMA_OPS if PCI select ARCH_HAS_FAST_MULTIPLIER select ARCH_HAS_GCOV_PROFILE_ALL From 4f0a9027b9fd7da2704ee863d8c7dc743cdeaf68 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:36 -0400 Subject: [PATCH 16/57] alpha: select HAVE_GUP_FAST The lockless GUP fast path needs the architecture to distinguish PTEs with no struct page behind them and to keep page table pages alive while a walker holds no lock. Alpha now has ARCH_HAS_PTE_SPECIAL, and it has selected MMU_GATHER_RCU_TABLE_FREE for a long time, so both requirements are met. PTEs are a single 64-bit word on Alpha, so the lockless reads that the generic walker performs cannot tear, and no arch-specific gup_fast_permitted() or ptep_get_lockless() is needed. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-15-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 4bd5e913baaf..1ba262812212 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -28,6 +28,7 @@ config ALPHA select HAVE_ARCH_COMPILER_H select HAVE_ASM_MODVERSIONS select HAVE_DEBUG_KMEMLEAK + select HAVE_GUP_FAST select HAVE_PAGE_SIZE_8KB select HAVE_PCSPKR_PLATFORM select HAVE_PERF_EVENTS From 4630bd788534290a7cbab26902fad7fe2d100ad8 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:37 -0400 Subject: [PATCH 17/57] alpha: select ARCH_USE_MEMTEST Wire up the generic early memtest from arch_mm_preinit(), which runs after parse_early_param() has picked up the memtest= command line option and before memblock_free_all() hands the memory to the buddy allocator. paging_init() would be the natural place, but Alpha calls it from setup_arch(), before the option is parsed, so the test would never run. The test range starts at the first DRAM address rather than physical zero because low memory on Alpha is owned by the console and PALcode. Tested under QEMU with memtest=4: all four patterns pass over the free memblock ranges, with no bad memory reserved. Also add ALPHA to the memtest= parameter's architecture list. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-16-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- Documentation/admin-guide/kernel-parameters.txt | 2 +- arch/alpha/Kconfig | 1 + arch/alpha/mm/init.c | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd2..58d057a678db 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4030,7 +4030,7 @@ Kernel parameters poison checking when freeing elements back to the pool. Useful for debugging mempool corruption. - memtest= [KNL,X86,ARM,M68K,PPC,RISCV,EARLY] Enable memtest + memtest= [KNL,ALPHA,X86,ARM,M68K,PPC,RISCV,EARLY] Enable memtest Format: default : 0 Specifies the number of memtest passes to be diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 1ba262812212..f2a5b8c8688a 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -22,6 +22,7 @@ config ALPHA select ARCH_SUPPORTS_INT128 if CC_HAS_INT128 select ARCH_SUPPORTS_PAGE_TABLE_CHECK select ARCH_USE_CMPXCHG_LOCKREF + select ARCH_USE_MEMTEST select FORCE_PCI select PCI_DOMAINS if PCI select PCI_SYSCALL if PCI diff --git a/arch/alpha/mm/init.c b/arch/alpha/mm/init.c index f4d65a60c869..6c28947d6e2d 100644 --- a/arch/alpha/mm/init.c +++ b/arch/alpha/mm/init.c @@ -226,6 +226,11 @@ void __init paging_init(void) memset(absolute_pointer(ZERO_PGE), 0, PAGE_SIZE); } +void __init arch_mm_preinit(void) +{ + early_memtest(memblock_start_of_DRAM(), PFN_PHYS(max_low_pfn)); +} + #if defined(CONFIG_ALPHA_GENERIC) || defined(CONFIG_ALPHA_SRM) void srm_paging_stop (void) From e517a172b77601839e1c52ac4303602e285804bb Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:38 -0400 Subject: [PATCH 18/57] alpha: select SYSCTL_EXCEPTION_TRACE Report unhandled user faults the way other architectures do, and hook up the debug.exception-trace sysctl that controls the reporting. The message gives the faulting address, the PC and the user stack pointer, and is rate limited. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-17-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + arch/alpha/mm/fault.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index f2a5b8c8688a..b9ff67370877 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -61,6 +61,7 @@ config ALPHA select MMU_GATHER_NO_RANGE select MMU_GATHER_RCU_TABLE_FREE select SPARSEMEM_EXTREME if SPARSEMEM + select SYSCTL_EXCEPTION_TRACE select ZONE_DMA select TRACE_IRQFLAGS_SUPPORT select ARCH_WANT_FRAME_POINTERS diff --git a/arch/alpha/mm/fault.c b/arch/alpha/mm/fault.c index a9816bbc9f34..dfe427d93072 100644 --- a/arch/alpha/mm/fault.c +++ b/arch/alpha/mm/fault.c @@ -27,8 +27,32 @@ #include #include +#include + extern void die_if_kernel(char *,struct pt_regs *,long, unsigned long *); +int show_unhandled_signals = 1; + +static void +show_signal_msg(struct pt_regs *regs, unsigned long address, int signo, + const char *desc) +{ + const char *level; + + if (!show_unhandled_signals || !unhandled_signal(current, signo)) + return; + if (!printk_ratelimit()) + return; + + /* Losing init is fatal, so shout about it. */ + level = task_pid_nr(current) > 1 ? KERN_INFO : KERN_EMERG; + + printk("%s%s[%d]: %s at %016lx pc %016lx sp %016lx\n", + level, current->comm, task_pid_nr(current), desc, + address, regs->pc, rdusp()); + print_vma_addr(KERN_CONT " in ", regs->pc); + printk(KERN_CONT "\n"); +} /* * Force a new ASN for a task. @@ -217,12 +241,17 @@ do_page_fault(unsigned long address, unsigned long mmcsr, mmap_read_unlock(mm); /* Send a sigbus, regardless of whether we were in kernel or user mode. */ + if (user_mode(regs)) + show_signal_msg(regs, address, SIGBUS, "bus error"); force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *) address); if (!user_mode(regs)) goto no_context; return; do_sigsegv: + show_signal_msg(regs, address, SIGSEGV, + si_code == SEGV_MAPERR ? "unmapped access" + : "access violation"); force_sig_fault(SIGSEGV, si_code, (void __user *) address); return; From 27f48a2c82917cb4c6fd771734671c118d2cf2af Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:39 -0400 Subject: [PATCH 19/57] alpha: discard .eh_frame and the relocation sections gcc emits .eh_frame for Alpha even though the kernel builds with -fno-asynchronous-unwind-tables, and nothing in the kernel consumes it: the stack trace code scans the stack for text addresses rather than unwinding DWARF. It was allocated in the loaded image, so dropping it shrinks vmlinux by about 1.1MB for a Marvel SMP configuration. The .rela* sections are equally useless, as the kernel image is not relocatable. The linker was already dropping their contents. Both were being placed as orphan sections, which is what drew attention to them. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-18-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/vmlinux.lds.S | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/arch/alpha/kernel/vmlinux.lds.S b/arch/alpha/kernel/vmlinux.lds.S index 95704e64b6a6..c44941d3b874 100644 --- a/arch/alpha/kernel/vmlinux.lds.S +++ b/arch/alpha/kernel/vmlinux.lds.S @@ -76,5 +76,16 @@ SECTIONS MODINFO ELF_DETAILS + /* + * gcc emits .eh_frame for Alpha even with + * -fno-asynchronous-unwind-tables, and the kernel has no DWARF + * unwinder to consume it. The relocation sections are likewise + * useless as the kernel image is not relocatable. + */ + /DISCARD/ : { + *(.eh_frame) + *(.rela .rela*) + } + DISCARDS } From 62fc4695be1b001049edf077452304bda74a7464 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:40 -0400 Subject: [PATCH 20/57] alpha: select ARCH_WANT_LD_ORPHAN_WARN With .eh_frame and the relocation sections now placed explicitly, an Alpha kernel links with no orphan sections left, so ask the linker to warn if new ones appear. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-19-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index b9ff67370877..33b51bef5139 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -40,6 +40,7 @@ config ALPHA select AUTO_IRQ_AFFINITY if SMP select GENERIC_IRQ_SHOW select ARCH_WANT_IPC_PARSE_VERSION + select ARCH_WANT_LD_ORPHAN_WARN select ARCH_HAVE_NMI_SAFE_CMPXCHG select AUDIT_ARCH select EDAC_SUPPORT From 04e57b9e71669e6e5f2a85d511a55fec41ca5c48 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:41 -0400 Subject: [PATCH 21/57] alpha: select ARCH_WANT_IRQS_OFF_ACTIVATE_MM activate_mm() on Alpha calls __load_new_mm_context() which assigns a new ASID and invokes PALcode swpctx. On SMP, an interrupt arriving between the ASID update and the page table switch could see inconsistent state. Ensure activate_mm() is called with interrupts disabled during exec. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-20-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 33b51bef5139..e5611766b6d6 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -39,6 +39,7 @@ config ALPHA select GENERIC_PCI_IOMAP select AUTO_IRQ_AFFINITY if SMP select GENERIC_IRQ_SHOW + select ARCH_WANT_IRQS_OFF_ACTIVATE_MM select ARCH_WANT_IPC_PARSE_VERSION select ARCH_WANT_LD_ORPHAN_WARN select ARCH_HAVE_NMI_SAFE_CMPXCHG From 0d4c9dd06c36484af44cea502813b4939788708d Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:43 -0400 Subject: [PATCH 22/57] alpha: select SPARSE_IRQ NR_IRQS on Alpha is sized for the largest configuration each platform supports, and for Marvel that is 32 PIDs, or 32784 interrupts. Without SPARSE_IRQ that becomes a static irq_desc[] array of 12.6 MB in .data, carried by every Marvel and every generic kernel regardless of how many interrupts the machine actually has. A real ES47 uses 87 of them. That is not just wasted memory, it constrains where the kernel can be loaded. The image links at a fixed 0xfffffc0001010000, just above the console's low memory reservation, and with memory striping enabled the console leaves only about 16 MB of usable low memory before the rest is relocated to an interleaved aperture. A 12.6 MB array of mostly unused interrupt descriptors is the difference between fitting and not. Alpha already goes through irq_to_desc() rather than indexing irq_desc[] directly, so nothing stands in the way. The platforms do wire up their interrupts by number, though, which needs the descriptors to exist by then, so provide arch_probe_nr_irqs() returning the machine vector's count instead of taking the NR_IRQS_LEGACY default. Without it every interrupt above 15 would have no descriptor and the handler setup would be silently dropped. Tested on an ES47 (Marvel, 2 EV7z): boots with all device interrupts working, and .data drops by 12.6 MB, from 15.7 MB to 3.1 MB. Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-22-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + arch/alpha/kernel/irq_alpha.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index e5611766b6d6..04511242506b 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -62,6 +62,7 @@ config ALPHA select CPU_NO_EFFICIENT_FFS if !ALPHA_EV67 select MMU_GATHER_NO_RANGE select MMU_GATHER_RCU_TABLE_FREE + select SPARSE_IRQ select SPARSEMEM_EXTREME if SPARSEMEM select SYSCTL_EXCEPTION_TRACE select ZONE_DMA diff --git a/arch/alpha/kernel/irq_alpha.c b/arch/alpha/kernel/irq_alpha.c index f90ff898a71a..8366d6de439d 100644 --- a/arch/alpha/kernel/irq_alpha.c +++ b/arch/alpha/kernel/irq_alpha.c @@ -152,6 +152,19 @@ common_init_isa_dma(void) outb(0, DMA2_CLR_MASK_REG); } +/* + * The platforms wire up their interrupts by calling into the generic irq + * code with fixed interrupt numbers, so the descriptors have to exist by + * then. Preallocate the machine's full complement rather than take the + * NR_IRQS_LEGACY default, which would leave everything above IRQ 15 + * without a descriptor and silently drop the handler setup. + */ +int __init +arch_probe_nr_irqs(void) +{ + return alpha_mv.nr_irqs; +} + void __init init_IRQ(void) { From 7d1b7e5670c0bfbe4534f61216027dee3aea6394 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 17 Oct 2026 12:53:33 -0400 Subject: [PATCH 23/57] xz: add arch-specific tuning for alpha Alpha has fixed-length, 4-byte-aligned instructions and no XZ BCJ filter, so it wants the same LZMA2 settings as the other 4-byte-aligned targets. Without an entry it hits the default case, which warns scripts/xz_wrap.sh: Arch-specific tuning is missing for 'alpha' and conservatively assumes 2-byte alignment. Alpha gains HAVE_KERNEL_XZ later in this series; the correct alignment also shrinks a UP1500 kernel's XZ image from 6469320 to 6448840 bytes. Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Reviewed-by: Lasse Collin Link: https://lore.kernel.org/r/20261017-alpha-kernel-compression-v2-1-c71e6b08dc63@gmail.com Signed-off-by: Magnus Lindholm --- scripts/xz_wrap.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/xz_wrap.sh b/scripts/xz_wrap.sh index f19369687030..5af0d9045cbc 100755 --- a/scripts/xz_wrap.sh +++ b/scripts/xz_wrap.sh @@ -10,6 +10,7 @@ # XZ-compressed kernel isn't currently supported on every listed arch. # # Arch Align Notes +# alpha 4 # arm 2/4 ARM and ARM-Thumb2 # arm64 4 # csky 2 @@ -44,6 +45,10 @@ BCJ= # Set the BCJ filter if one is available. # It must match the #ifdef usage in lib/decompress_unxz.c. case $SRCARCH in + alpha) + ALIGN=4 + ;; + arm) if is_enabled CONFIG_THUMB2_KERNEL; then ALIGN=2 From c437950dceb88a92ca526f5ce3afd327c4fce457 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 17 Oct 2026 12:53:34 -0400 Subject: [PATCH 24/57] alpha: add self-extracting compressed kernel boot stub Alpha only ever supported gzip for the kernel image, and did so in a way out of step with every other architecture: aboot, the disk boot loader, decompresses vmlinux.gz itself with its own bundled inflate code, so adding a compression format meant writing a new decompressor inside aboot as well as in the kernel. Build a proper self-extracting arch/alpha/boot/compressed/vmlinux instead, modeled on parisc: a small ELF stub with the real, compressed vmlinux linked in, which decompresses the kernel, relocates it into place and jumps to its entry point. aboot needs no changes. The stub has no sanitizer or coverage runtime and must run on any Alpha, so it builds private copies of the arch/alpha/lib/ memory and division routines it needs instead of linking lib.a, whose objects carry the kernel's instrumentation and -mcpu=. The build now emits arch/alpha/boot/vmlinuz rather than vmlinux.gz, which was never a name aboot cared about and would be a lie for an ELF stub: the bootpz path in the same directory still produces real gzip data as vmlinux.nh.gz. Existing aboot.conf files name the image as it was installed in /boot, so only the command that copies it out of the build tree changes. Unlike parisc and mips, the stub is linked at START_ADDR rather than at a separate low address; see compressed/vmlinux.lds.S. It has no console output, since bringing up the HWRPB console callbacks needs more than the CRB dispatch the kernel proper does much later, so error() just halts. The legacy SRM netboot path (bootpz.c) is left alone: it has its own boot protocol (PALcode reinitialization, self-populated ZERO_PGE, build-time-computed image sizes) that does not fit this design without a separate rewrite, and keeps using plain gzip via vmlinux.nh.gz. Co-developed-by: Magnus Lindholm Signed-off-by: Magnus Lindholm Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261017-alpha-kernel-compression-v2-2-c71e6b08dc63@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + arch/alpha/Makefile | 6 +- arch/alpha/boot/.gitignore | 2 + arch/alpha/boot/Makefile | 16 +- arch/alpha/boot/compressed/.gitignore | 3 + arch/alpha/boot/compressed/Makefile | 62 ++++++++ arch/alpha/boot/compressed/head.S | 36 +++++ arch/alpha/boot/compressed/misc.c | 178 +++++++++++++++++++++++ arch/alpha/boot/compressed/trampoline.S | 46 ++++++ arch/alpha/boot/compressed/vmlinux.lds.S | 83 +++++++++++ arch/alpha/boot/compressed/vmlinux.scr | 10 ++ 11 files changed, 435 insertions(+), 8 deletions(-) create mode 100644 arch/alpha/boot/.gitignore create mode 100644 arch/alpha/boot/compressed/.gitignore create mode 100644 arch/alpha/boot/compressed/Makefile create mode 100644 arch/alpha/boot/compressed/head.S create mode 100644 arch/alpha/boot/compressed/misc.c create mode 100644 arch/alpha/boot/compressed/trampoline.S create mode 100644 arch/alpha/boot/compressed/vmlinux.lds.S create mode 100644 arch/alpha/boot/compressed/vmlinux.scr diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 04511242506b..2688bb00f175 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -30,6 +30,7 @@ config ALPHA select HAVE_ASM_MODVERSIONS select HAVE_DEBUG_KMEMLEAK select HAVE_GUP_FAST + select HAVE_KERNEL_GZIP select HAVE_PAGE_SIZE_8KB select HAVE_PCSPKR_PLATFORM select HAVE_PERF_EVENTS diff --git a/arch/alpha/Makefile b/arch/alpha/Makefile index 7074602dca94..405d4bfd644c 100644 --- a/arch/alpha/Makefile +++ b/arch/alpha/Makefile @@ -47,9 +47,9 @@ export LIBS_Y boot := arch/alpha/boot #Default target when executing make with no arguments -all boot: $(boot)/vmlinux.gz +all boot: $(boot)/vmlinuz -$(boot)/vmlinux.gz: vmlinux +$(boot)/vmlinuz: vmlinux $(Q)$(MAKE) $(build)=$(boot) $@ bootimage bootpfile bootpzfile: vmlinux @@ -59,7 +59,7 @@ archheaders: $(Q)$(MAKE) $(build)=arch/alpha/kernel/syscalls all define archhelp - echo '* boot - Compressed kernel image (arch/alpha/boot/vmlinux.gz)' + echo '* boot - Compressed kernel image (arch/alpha/boot/vmlinuz)' echo ' bootimage - SRM bootable image (arch/alpha/boot/bootimage)' echo ' bootpfile - BOOTP bootable image (arch/alpha/boot/bootpfile)' echo ' bootpzfile - compressed kernel BOOTP image (arch/alpha/boot/bootpzfile)' diff --git a/arch/alpha/boot/.gitignore b/arch/alpha/boot/.gitignore new file mode 100644 index 000000000000..4125506521f6 --- /dev/null +++ b/arch/alpha/boot/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +vmlinuz diff --git a/arch/alpha/boot/Makefile b/arch/alpha/boot/Makefile index d8dba85e606c..7c2a41c1e2a0 100644 --- a/arch/alpha/boot/Makefile +++ b/arch/alpha/boot/Makefile @@ -9,9 +9,10 @@ # hostprogs := tools/mkbb tools/objstrip -targets := vmlinux.gz vmlinux \ +targets := vmlinuz vmlinux \ vmlinux.nh tools/lxboot tools/bootlx tools/bootph \ - tools/bootpzh bootloader bootpheader bootpzheader + tools/bootpzh bootloader bootpheader bootpzheader +subdir- := compressed OBJSTRIP := $(obj)/tools/objstrip KBUILD_HOSTCFLAGS := -Wall -I$(objtree)/usr/include @@ -38,9 +39,14 @@ ifdef INITRD cat $(INITRD) >> $@ endif -# Compressed kernel image -$(obj)/vmlinux.gz: $(obj)/vmlinux FORCE - $(call if_changed,gzip) +# Self-extracting compressed kernel image: a small ELF stub (see +# compressed/) with the real vmlinux linked in compressed, which aboot +# loads and enters like a plain vmlinux. +$(obj)/compressed/vmlinux: vmlinux FORCE + $(Q)$(MAKE) $(build)=$(obj)/compressed $@ + +$(obj)/vmlinuz: $(obj)/compressed/vmlinux FORCE + $(call if_changed,copy) @echo ' Kernel $@ is ready' $(obj)/main.o: $(obj)/ksize.h diff --git a/arch/alpha/boot/compressed/.gitignore b/arch/alpha/boot/compressed/.gitignore new file mode 100644 index 000000000000..765a08f1bd77 --- /dev/null +++ b/arch/alpha/boot/compressed/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +vmlinux +vmlinux.lds diff --git a/arch/alpha/boot/compressed/Makefile b/arch/alpha/boot/compressed/Makefile new file mode 100644 index 000000000000..fe7eac100617 --- /dev/null +++ b/arch/alpha/boot/compressed/Makefile @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Create a self-extracting compressed vmlinux image from the original +# vmlinux. aboot loads and enters it like a plain vmlinux. +# + +OBJECTS := head.o misc.o trampoline.o piggy.o +OBJECTS += memcpy.o memmove.o memset.o +OBJECTS += __divqu.o __remqu.o __divlu.o __remlu.o +targets := vmlinux.lds vmlinux vmlinux.bin vmlinux.bin.gz +targets += $(OBJECTS) + +# The stub runs before the kernel has identified the CPU it booted on, so +# build it for the base Alpha instruction set rather than inheriting the +# kernel's -mcpu=. +KBUILD_CFLAGS := -D__KERNEL__ -std=gnu11 -Os -fno-strict-aliasing +KBUILD_CFLAGS += -fno-PIE -ffreestanding -fno-stack-protector +KBUILD_CFLAGS += -fno-asynchronous-unwind-tables +KBUILD_CFLAGS += -pipe -mcpu=ev4 -mno-fp-regs -ffixed-8 +KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING -D__DISABLE_EXPORTS +KBUILD_CFLAGS += $(CC_FLAGS_DIALECT) +KBUILD_AFLAGS := -D__ASSEMBLY__ $(KBUILD_CFLAGS) + +# The stub has no sanitizer or coverage runtime. arch/alpha/lib/lib.a is +# built with the kernel's instrumentation and -mcpu=, so build private +# copies of the library routines the stub needs instead of linking it. +UBSAN_SANITIZE := n +GCOV_PROFILE := n + +$(obj)/memcpy.o: $(srctree)/arch/alpha/lib/memcpy.c FORCE + $(call if_changed_dep,cc_o_c) + +$(obj)/memmove.o: $(srctree)/arch/alpha/lib/memmove.S FORCE + $(call if_changed_dep,as_o_S) + +$(obj)/memset.o: $(srctree)/arch/alpha/lib/memset.S FORCE + $(call if_changed_dep,as_o_S) + +AFLAGS___divqu.o := -DDIV +AFLAGS___remqu.o := -DREM +AFLAGS___divlu.o := -DDIV -DINTSIZE +AFLAGS___remlu.o := -DREM -DINTSIZE + +$(addprefix $(obj)/,__divqu.o __remqu.o __divlu.o __remlu.o): $(srctree)/arch/alpha/lib/divide.S FORCE + $(call if_changed_dep,as_o_S) + +LDFLAGS_vmlinux := -X --orphan-handling=warn -T +$(obj)/vmlinux: $(obj)/vmlinux.lds $(addprefix $(obj)/, $(OBJECTS)) FORCE + $(call if_changed,ld) + +OBJCOPYFLAGS_vmlinux.bin := -R .comment -R .note -S +$(obj)/vmlinux.bin: vmlinux FORCE + $(call if_changed,objcopy) + +suffix-$(CONFIG_KERNEL_GZIP) := gz + +$(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE + $(call if_changed,gzip) + +LDFLAGS_piggy.o := -r --format binary --oformat elf64-alpha -T +$(obj)/piggy.o: $(obj)/vmlinux.scr $(obj)/vmlinux.bin.$(suffix-y) FORCE + $(call if_changed,ld) diff --git a/arch/alpha/boot/compressed/head.S b/arch/alpha/boot/compressed/head.S new file mode 100644 index 000000000000..c689ac439d15 --- /dev/null +++ b/arch/alpha/boot/compressed/head.S @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Entry point for the self-extracting compressed kernel stub. aboot + * jumps here with the OSF/1 parameter page (ZERO_PGE) already populated + * and $30 set to INIT_STACK, exactly as it would for an uncompressed + * vmlinux. + */ + +#include +#include + + .set noreorder + +__HEAD + .globl __start + .ent __start +__start: + br $29,1f +1: ldgp $29,0($29) + + /* + * Clear our own bss. The decompressors keep state there, and + * nothing guarantees that aboot zeroed the part of our PT_LOAD + * segment that has no file backing. + */ + lda $1,_bss + lda $2,_ebss +2: cmpult $1,$2,$3 + beq $3,3f + stq $31,0($1) + addq $1,8,$1 + br $31,2b + +3: jsr $26,decompress_and_boot + call_pal PAL_halt + .end __start diff --git a/arch/alpha/boot/compressed/misc.c b/arch/alpha/boot/compressed/misc.c new file mode 100644 index 000000000000..72998582be68 --- /dev/null +++ b/arch/alpha/boot/compressed/misc.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Decompression and self-relocation for the compressed kernel boot stub, + * based on arch/parisc/boot/compressed/misc.c. + * + * aboot loads the stub as an ordinary ELF at START_ADDR and jumps to it + * with the parameter page and stack already set up; vmlinux.lds.S + * explains why the stub has to be linked there. We decompress above our + * own bss and never write below START_ADDR, so what aboot prepared stays + * valid for the real kernel. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define STATIC static +#define memzero(s, n) memset((s), 0, (n)) + +extern char input_data[]; +extern int input_len; +/* output_len is inserted by the linker, possibly at an unaligned address */ +extern char output_len; + +extern char _ebss[]; +extern char trampoline_start[], trampoline_end[]; + +static unsigned long free_mem_ptr; +static unsigned long free_mem_end_ptr; +static unsigned long mem_end; + +/* The stub has no console, so the message is only for debuggers. */ +void __noreturn error(char *m) +{ + while (1) + __halt(); +} + +#ifdef CONFIG_KERNEL_GZIP +#include "../../../../lib/decompress_inflate.c" +#endif + +/* Scratch heap for the decompressors, larger than any of them needs. */ +#define STUB_HEAP_SIZE (4 * 1024 * 1024) + +/* + * Type punning through a packed struct, to avoid the warnings the regular + * get_unaligned_le32() produces when reading a char-typed symbol as an + * integer. + */ +static u32 punned_get_unaligned_le32(const void *p) +{ + const struct { __le32 x; } __packed *pp = p; + + return le32_to_cpu(pp->x); +} + +/* + * End of the usable memory cluster that holds the kernel, or 0 if the + * HWRPB does not describe one. Used to check that the decompressed image + * and the stub's scratch space fit before anything is written. + */ +static unsigned long usable_memory_end(void) +{ + struct hwrpb_struct *hwrpb; + struct memdesc_struct *memdesc; + unsigned long pfn = (START_ADDR - PAGE_OFFSET) >> PAGE_SHIFT; + unsigned long i; + + hwrpb = (struct hwrpb_struct *)(PAGE_OFFSET + INIT_HWRPB->phys_addr); + memdesc = (struct memdesc_struct *)((unsigned long)hwrpb + + hwrpb->mddt_offset); + + for (i = 0; i < memdesc->numclusters; i++) { + struct memclust_struct *cluster = &memdesc->cluster[i]; + unsigned long end_pfn = cluster->start_pfn + cluster->numpages; + + if (cluster->usage) + continue; + if (pfn >= cluster->start_pfn && pfn < end_pfn) + return PAGE_OFFSET + (end_pfn << PAGE_SHIFT); + } + + return 0; +} + +/* + * Move the decompressed kernel's single PT_LOAD segment down onto + * START_ADDR and jump to its entry point. The kernel is always linked + * with exactly one PT_LOAD whose p_vaddr equals its e_entry equals + * START_ADDR (see arch/alpha/kernel/vmlinux.lds.S); anything else we do + * not know how to boot. + * + * The copy cannot run from this stub, which is itself executing at + * START_ADDR; see trampoline.S. + */ +static void __noreturn relocate_and_jump(void *image) +{ + void (*trampoline)(unsigned long dest, unsigned long src, + unsigned long filesz, unsigned long memsz, + unsigned long entry); + unsigned long tramp_len = trampoline_end - trampoline_start; + Elf64_Phdr phdr, load_phdr = {}; + Elf64_Ehdr ehdr; + int i, found_load = 0; + unsigned long tramp; + + memcpy(&ehdr, image, sizeof(ehdr)); + + if (ehdr.e_ident[EI_MAG0] != ELFMAG0 || + ehdr.e_ident[EI_MAG1] != ELFMAG1 || + ehdr.e_ident[EI_MAG2] != ELFMAG2 || + ehdr.e_ident[EI_MAG3] != ELFMAG3 || + ehdr.e_ident[EI_CLASS] != ELFCLASS64 || + ehdr.e_ident[EI_DATA] != ELFDATA2LSB || + ehdr.e_machine != EM_ALPHA || + ehdr.e_type != ET_EXEC || + ehdr.e_entry != START_ADDR) + error("Not a bootable Alpha kernel image"); + + for (i = 0; i < ehdr.e_phnum; i++) { + memcpy(&phdr, image + ehdr.e_phoff + i * ehdr.e_phentsize, + sizeof(phdr)); + + if (phdr.p_type != PT_LOAD) + continue; + if (found_load || phdr.p_vaddr != START_ADDR) + error("Unexpected kernel segment layout"); + found_load = 1; + load_phdr = phdr; + } + if (!found_load) + error("Kernel image has no PT_LOAD segment"); + + /* + * The trampoline has to outlive both the scratch heap it is copied + * into and the region it is about to write, which extends past the + * kernel's file image to the end of its bss. + */ + tramp = ALIGN(max(free_mem_end_ptr, START_ADDR + load_phdr.p_memsz), 8); + if (mem_end && tramp + tramp_len > mem_end) + error("Not enough memory to relocate the kernel"); + + memcpy((void *)tramp, trampoline_start, tramp_len); + imb(); + + trampoline = (void *)tramp; + trampoline(load_phdr.p_vaddr, + (unsigned long)image + load_phdr.p_offset, + load_phdr.p_filesz, load_phdr.p_memsz, ehdr.e_entry); + unreachable(); +} + +void __noreturn decompress_and_boot(void) +{ + unsigned long image_len = punned_get_unaligned_le32(&output_len); + void *image = (void *)ALIGN((unsigned long)_ebss, PAGE_SIZE); + + free_mem_ptr = ALIGN((unsigned long)image + image_len, PAGE_SIZE); + free_mem_end_ptr = free_mem_ptr + STUB_HEAP_SIZE; + + mem_end = usable_memory_end(); + if (mem_end && free_mem_end_ptr > mem_end) + error("Not enough memory to decompress the kernel"); + + /* Some decompressors report failure only through the return value. */ + if (__decompress(input_data, input_len, NULL, NULL, image, image_len, + NULL, error)) + error("Kernel decompression failed"); + + relocate_and_jump(image); +} diff --git a/arch/alpha/boot/compressed/trampoline.S b/arch/alpha/boot/compressed/trampoline.S new file mode 100644 index 000000000000..bf57d3b0fd7f --- /dev/null +++ b/arch/alpha/boot/compressed/trampoline.S @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copy the decompressed kernel onto START_ADDR, clear its bss and jump to + * its entry point. See relocate_and_jump() in misc.c for why this cannot + * run from the stub itself. It is position independent (no data or + * rodata references, no gp) and clobbers only its argument registers and + * $1, so misc.c can copy it anywhere and call it there. + */ + + .set noreorder + + .globl trampoline_start + .globl trampoline_end + .ent trampoline_start +trampoline_start: + /* $16 = dest, $17 = src, $18 = p_filesz, $19 = p_memsz, $20 = entry */ + + /* + * dest and src are both page aligned, and everything above + * p_filesz is bss, so round both lengths up and work in quadwords + * only, using instructions every Alpha implements. + */ + addq $18,7,$18 + bic $18,7,$18 + addq $19,7,$19 + bic $19,7,$19 + subq $19,$18,$19 /* bytes to zero after the copy */ + +1: beq $18,2f + ldq $1,0($17) + stq $1,0($16) + addq $17,8,$17 + addq $16,8,$16 + subq $18,8,$18 + br $31,1b + +2: beq $19,3f + stq $31,0($16) + addq $16,8,$16 + subq $19,8,$19 + br $31,2b + +3: imb + jmp $31,($20) + .end trampoline_start +trampoline_end: diff --git a/arch/alpha/boot/compressed/vmlinux.lds.S b/arch/alpha/boot/compressed/vmlinux.lds.S new file mode 100644 index 000000000000..5328c1ccaccf --- /dev/null +++ b/arch/alpha/boot/compressed/vmlinux.lds.S @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include +#include + +OUTPUT_FORMAT("elf64-alpha") +OUTPUT_ARCH(alpha) +ENTRY(__start) + +SECTIONS +{ + /* + * Link the stub at the address the real, uncompressed vmlinux has + * always occupied. aboot derives the OSF/1 parameter-page + * (ZERO_PGE) and initial stack (INIT_STACK) addresses from the + * loaded image's own p_vaddr, so a stub linked anywhere else would + * hand the real kernel a parameter page it does not expect. + */ + . = START_ADDR; + + .head.text : { + _head = .; + HEAD_TEXT + _ehead = .; + } + + . = ALIGN(8); + .rodata.compressed : { + *(.rodata.compressed) + } + + . = ALIGN(PAGE_SIZE); + .text : { + _text = .; + *(.text) + *(.text.*) + _etext = .; + } + . = ALIGN(8); + .rodata : { + *(.rodata) + *(.rodata.*) + } + . = ALIGN(8); + .data : { + *(.data) + *(.data.*) + } + .got : { + *(.got) + } + .sdata : { + *(.sdata) + } + /* + * misc.c decompresses to _ebss, so nothing that the stub still + * needs may be placed after .bss. --orphan-handling=warn keeps a + * new section from silently landing there. + */ + . = ALIGN(8); + .bss : { + _bss = .; + *(.sbss) + *(.scommon) + *(.bss) + *(.bss.*) + *(COMMON) + . = ALIGN(8); + _ebss = .; + } + + STABS_DEBUG + DWARF_DEBUG + ELF_DETAILS + + DISCARDS + /DISCARD/ : { + *(.eh_frame) + *(.modinfo) + *(.note) + *(.note.*) + } +} diff --git a/arch/alpha/boot/compressed/vmlinux.scr b/arch/alpha/boot/compressed/vmlinux.scr new file mode 100644 index 000000000000..dac2d142bcfa --- /dev/null +++ b/arch/alpha/boot/compressed/vmlinux.scr @@ -0,0 +1,10 @@ +SECTIONS +{ + .rodata.compressed : { + input_len = .; + LONG(input_data_end - input_data) input_data = .; + *(.data) + output_len = . - 4; /* can be at unaligned address */ + input_data_end = .; + } +} From 7fa247f96315d00511615bd9a36dca3f3906c621 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 17 Oct 2026 12:53:35 -0400 Subject: [PATCH 25/57] alpha: support the remaining kernel compression formats Nothing about the boot stub is specific to gzip, so wire up bzip2, lz4, lzma, lzo, xz and zstd as well: select the matching HAVE_KERNEL_* symbol, add a suffix-y entry and a compression rule for the piggy-backed image, and include the decompressor into the stub's misc.c. Only gzip's trailer already carries the uncompressed size, so the other formats use the _with_size variants; output_len, which vmlinux.scr takes from the last 4 bytes of the payload, then works for all of them. The stub passes it to __decompress(), which zstd requires. Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261017-alpha-kernel-compression-v2-3-c71e6b08dc63@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 6 ++++++ arch/alpha/boot/compressed/Makefile | 31 +++++++++++++++++++++++++++-- arch/alpha/boot/compressed/misc.c | 31 +++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 2688bb00f175..92cc7bb8d29a 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -30,7 +30,13 @@ config ALPHA select HAVE_ASM_MODVERSIONS select HAVE_DEBUG_KMEMLEAK select HAVE_GUP_FAST + select HAVE_KERNEL_BZIP2 select HAVE_KERNEL_GZIP + select HAVE_KERNEL_LZ4 + select HAVE_KERNEL_LZMA + select HAVE_KERNEL_LZO + select HAVE_KERNEL_XZ + select HAVE_KERNEL_ZSTD select HAVE_PAGE_SIZE_8KB select HAVE_PCSPKR_PLATFORM select HAVE_PERF_EVENTS diff --git a/arch/alpha/boot/compressed/Makefile b/arch/alpha/boot/compressed/Makefile index fe7eac100617..692a29588aad 100644 --- a/arch/alpha/boot/compressed/Makefile +++ b/arch/alpha/boot/compressed/Makefile @@ -7,7 +7,10 @@ OBJECTS := head.o misc.o trampoline.o piggy.o OBJECTS += memcpy.o memmove.o memset.o OBJECTS += __divqu.o __remqu.o __divlu.o __remlu.o -targets := vmlinux.lds vmlinux vmlinux.bin vmlinux.bin.gz +targets := vmlinux.lds vmlinux vmlinux.bin +targets += vmlinux.bin.bz2 vmlinux.bin.gz vmlinux.bin.lz4 +targets += vmlinux.bin.lzma vmlinux.bin.lzo vmlinux.bin.xz +targets += vmlinux.bin.zst targets += $(OBJECTS) # The stub runs before the kernel has identified the CPU it booted on, so @@ -44,18 +47,42 @@ AFLAGS___remlu.o := -DREM -DINTSIZE $(addprefix $(obj)/,__divqu.o __remqu.o __divlu.o __remlu.o): $(srctree)/arch/alpha/lib/divide.S FORCE $(call if_changed_dep,as_o_S) +# zstd's decompressor uses __builtin_ctzll()/__builtin_clzll(). Those are +# inline code only with the CIX extension, which the base instruction set +# the stub is built for does not have, so they become calls to libgcc's +# __ctzdi2/__clzdi2. +LIBGCC := $(shell $(CC) -print-libgcc-file-name) + LDFLAGS_vmlinux := -X --orphan-handling=warn -T -$(obj)/vmlinux: $(obj)/vmlinux.lds $(addprefix $(obj)/, $(OBJECTS)) FORCE +$(obj)/vmlinux: $(obj)/vmlinux.lds $(addprefix $(obj)/, $(OBJECTS)) $(LIBGCC) FORCE $(call if_changed,ld) OBJCOPYFLAGS_vmlinux.bin := -R .comment -R .note -S $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) +suffix-$(CONFIG_KERNEL_BZIP2) := bz2 suffix-$(CONFIG_KERNEL_GZIP) := gz +suffix-$(CONFIG_KERNEL_LZ4) := lz4 +suffix-$(CONFIG_KERNEL_LZMA) := lzma +suffix-$(CONFIG_KERNEL_LZO) := lzo +suffix-$(CONFIG_KERNEL_XZ) := xz +suffix-$(CONFIG_KERNEL_ZSTD) := zst +$(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE + $(call if_changed,bzip2_with_size) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE $(call if_changed,gzip) +$(obj)/vmlinux.bin.lz4: $(obj)/vmlinux.bin FORCE + $(call if_changed,lz4_with_size) +$(obj)/vmlinux.bin.lzma: $(obj)/vmlinux.bin FORCE + $(call if_changed,lzma_with_size) +$(obj)/vmlinux.bin.lzo: $(obj)/vmlinux.bin FORCE + $(call if_changed,lzo_with_size) +$(obj)/vmlinux.bin.xz: $(obj)/vmlinux.bin FORCE + $(call if_changed,xzkern_with_size) +$(obj)/vmlinux.bin.zst: $(obj)/vmlinux.bin FORCE + $(call if_changed,zstd22_with_size) LDFLAGS_piggy.o := -r --format binary --oformat elf64-alpha -T $(obj)/piggy.o: $(obj)/vmlinux.scr $(obj)/vmlinux.bin.$(suffix-y) FORCE diff --git a/arch/alpha/boot/compressed/misc.c b/arch/alpha/boot/compressed/misc.c index 72998582be68..1b5b8e716613 100644 --- a/arch/alpha/boot/compressed/misc.c +++ b/arch/alpha/boot/compressed/misc.c @@ -23,6 +23,13 @@ #define STATIC static #define memzero(s, n) memset((s), 0, (n)) +/* + * decompress_unxz.c supplies its own memmove() unless the identifier is + * already a macro. We link our own build of arch/alpha/lib/memmove.S, + * which is better, so take that instead. + */ +#define memmove memmove + extern char input_data[]; extern int input_len; /* output_len is inserted by the linker, possibly at an unaligned address */ @@ -42,10 +49,34 @@ void __noreturn error(char *m) __halt(); } +#ifdef CONFIG_KERNEL_BZIP2 +#include "../../../../lib/decompress_bunzip2.c" +#endif + #ifdef CONFIG_KERNEL_GZIP #include "../../../../lib/decompress_inflate.c" #endif +#ifdef CONFIG_KERNEL_LZ4 +#include "../../../../lib/decompress_unlz4.c" +#endif + +#ifdef CONFIG_KERNEL_LZMA +#include "../../../../lib/decompress_unlzma.c" +#endif + +#ifdef CONFIG_KERNEL_LZO +#include "../../../../lib/decompress_unlzo.c" +#endif + +#ifdef CONFIG_KERNEL_XZ +#include "../../../../lib/decompress_unxz.c" +#endif + +#ifdef CONFIG_KERNEL_ZSTD +#include "../../../../lib/decompress_unzstd.c" +#endif + /* Scratch heap for the decompressors, larger than any of them needs. */ #define STUB_HEAP_SIZE (4 * 1024 * 1024) From 2ba84bf28f43cdb28b14e078ea9a6de9cc03181f Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Thu, 1 Oct 2026 12:50:15 -0400 Subject: [PATCH 26/57] alpha: replace dead links in Kconfig help text The Linux/Alpha FAQ, the unix-ag LX164 overview, the Rawhide install HOWTO and the Ruffian FAQ are all gone. Point the "Alpha system type" help at the system and motherboard lists on the alphalinux.org wiki instead, give LX164 a one-line description in place of its link, and drop the others. Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261001165018.415526-1-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 92cc7bb8d29a..7c1cc676ddfe 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -127,9 +127,10 @@ choice run on any supported Alpha system. However, if you configure a kernel for your specific system, it will be faster and smaller. - To find out what type of Alpha system you have, you may want to - check out the Linux/Alpha FAQ, accessible on the WWW from - . In summary: + To find out what type of Alpha system you have, see the system and + motherboard lists at + and + . In summary: Alcor/Alpha-XLT AS 600, AS 500, XL-300, XL-366 DP264 DP264 / DS20 / ES40 / DS10 / DS10L @@ -184,8 +185,8 @@ config ALPHA_EIGER config ALPHA_LX164 bool "LX164" help - A technical overview of this board is available at - . + AlphaPC164-LX, a 21164A-based single-board computer using the + Pyxis chipset. config ALPHA_MARVEL bool "Marvel" @@ -224,14 +225,11 @@ config ALPHA_RAWHIDE select HAVE_EISA help AlphaServer 1200, AlphaServer 4000 and AlphaServer 4100 machines. - See HOWTO at - . config ALPHA_RUFFIAN bool "Ruffian" help - Samsung APC164UX. There is a page on known problems and workarounds - at . + Samsung APC164UX. config ALPHA_RX164 bool "RX164" @@ -372,9 +370,7 @@ config ALPHA_SRM help There are two different types of booting firmware on Alphas: SRM, which is command line driven, and ARC, which uses menus and arrow - keys. Details about the Linux/Alpha booting process are contained in - the Linux/Alpha FAQ, accessible on the WWW from - . + keys. The usual way to load Linux on an Alpha machine is to use MILO (a bootloader that lets you pass command line parameters to the From bcd2e0a28d49c195423e4047e6d5f690da720651 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Thu, 1 Oct 2026 12:50:16 -0400 Subject: [PATCH 27/57] alpha: correct system descriptions in Kconfig help text Fix the system lists in the "Alpha system type" choice and the platform help texts, cross-checked against the alphalinux.org wiki and the kernel sources: - There is no AlphaServer GS40 or DS10E. Wildfire also shipped with EV68 (21264C) CPUs. - "AS" means AlphaServer elsewhere in the list, but the Alcor "AS 600, AS 500" are AlphaStations, as is the Noritake "AlphaServer 600A". - Takara is 21164-based, not "11164". - Linux 6.10 dropped support for CPUs before EV56, which took the AlphaStation 600 and Alpha XL 300 with it. Say once that EV56 or later is required rather than qualifying every entry. - Add systems missing from their entries: the UP1500 (Nautilus), DS15 and TS202c (Titan), Digital Server 3300 (Noritake), Digital Server 5300 and 7300 (Rawhide), and the Ruffian boards in place of "Samsung APC164UX". - Add Eiger, Nautilus and RX164 to the summary list and help text to PC164, RX164, SX164 and Shark, which had none. Note that the Ruffian and RX164 boards have no SRM console. Also drop the Jensen and EV4/EV5 references removed in 6.10, and the claim that SRM cannot boot from IDE, which is not true of SRM in general. NR_CPUS can be set to 32 on any platform, so say what the hardware supports instead of implying a per-platform kernel limit. Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261001165018.415526-2-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 85 ++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 7c1cc676ddfe..920dd10e7df0 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -126,28 +126,35 @@ choice This is the system type of your hardware. A "generic" kernel will run on any supported Alpha system. However, if you configure a kernel for your specific system, it will be faster and smaller. + Only EV56 (21164A) and later CPUs are supported. To find out what type of Alpha system you have, see the system and motherboard lists at and . In summary: - Alcor/Alpha-XLT AS 600, AS 500, XL-300, XL-366 - DP264 DP264 / DS20 / ES40 / DS10 / DS10L + Alcor/Alpha-XLT AlphaStation 500, Alpha XL 366, 433 + DP264 DP264, UP2000(+), DS10, DS10L, DS20, DS20E, + ES40, XP900, XP1000 + Eiger Eiger (OEM) LX164 AlphaPC164-LX + Marvel AlphaServer ES47, ES80, GS1280 Miata Personal Workstation 433/500/600 a/au - Marvel AlphaServer ES47 / ES80 / GS1280 - Mikasa AS 1000 - Noritake AS 1000A, AS 600A, AS 800 + Mikasa AlphaServer 1000 + Nautilus UP1000, UP1100, UP1500 + Noritake AlphaServer 1000A, 800, AlphaStation 600A, + Digital Server 3300 PC164 AlphaPC164 - Rawhide AS 1200, AS 4000, AS 4100 + Rawhide AlphaServer 1200, 4000, 4100, + Digital Server 5300, 7300 Ruffian RPX164-2, AlphaPC164-UX, AlphaPC164-BX + RX164 AlphaPC164-RX SX164 AlphaPC164-SX - Sable AS 2000, AS 2100 - Shark DS 20L + Sable AlphaServer 2000, 2100 + Shark AlphaServer DS20L, API NetWorks CS20 Takara Takara (OEM) - Titan AlphaServer ES45 / DS25 / DS15 - Wildfire AlphaServer GS 40/80/160/320 + Titan AlphaServer DS15, DS25, ES45, TS202c + Wildfire AlphaServer GS80, GS160, GS320 If you don't know what to do, choose "generic". @@ -168,13 +175,14 @@ config ALPHA_ALCOR controller (256-bit memory bus) and a PCI interface. It also does all the work required to support an external Bcache and to maintain memory coherence when a PCI device DMAs into (or out of) memory. + It is used by the AlphaStation 500 and the Alpha XL 366 and 433. config ALPHA_DP264 bool "DP264" help Various 21264 systems with the tsunami core logic chipset. - API Networks: 264DP, UP2000(+), CS20; - Compaq: DS10(E,L), XP900, XP1000, DS20(E), ES40. + API NetWorks: 264DP, UP2000(+); + Compaq: DS10, DS10L, XP900, XP1000, DS20, DS20E, ES40. config ALPHA_EIGER bool "Eiger" @@ -197,8 +205,8 @@ config ALPHA_MIATA bool "Miata" select HAVE_EISA help - The Digital PersonalWorkStation (PWS 433a, 433au, 500a, 500au, 600a, - or 600au). + The Digital Personal Workstation (PWS 433a, 433au, 500a, 500au, + 600a, or 600au). config ALPHA_MIKASA bool "Mikasa" @@ -208,34 +216,47 @@ config ALPHA_MIKASA config ALPHA_NAUTILUS bool "Nautilus" help - Alpha systems based on the AMD 751 & ALI 1543C chipsets. + UP1000, UP1100 and UP1500 systems based on the AMD 751 or 761 + chipset. config ALPHA_NORITAKE bool "Noritake" select HAVE_EISA help - AlphaServer 1000A, AlphaServer 600A, and AlphaServer 800-based - systems. + AlphaServer 1000A, AlphaServer 800, AlphaStation 600A and + Digital Server 3300 systems. config ALPHA_PC164 bool "PC164" + help + AlphaPC164, a 21164A-based single-board computer using the CIA + chipset. config ALPHA_RAWHIDE bool "Rawhide" select HAVE_EISA help - AlphaServer 1200, AlphaServer 4000 and AlphaServer 4100 machines. + AlphaServer 1200, 4000 and 4100 and Digital Server 5300 and 7300 + machines. config ALPHA_RUFFIAN bool "Ruffian" help - Samsung APC164UX. + DeskStation RPX164-2, AlphaPC164-UX and AlphaPC164-BX boards. + They have no SRM console and can only boot Linux through MILO. config ALPHA_RX164 bool "RX164" + help + AlphaPC164-RX, a 21164PC-based single-board computer using the + Polaris chipset. It has no SRM console and can only boot Linux + through MILO. config ALPHA_SX164 bool "SX164" + help + AlphaPC164-SX, a 21164PC-based single-board computer using the + Pyxis chipset. config ALPHA_SABLE bool "Sable" @@ -245,21 +266,26 @@ config ALPHA_SABLE config ALPHA_SHARK bool "Shark" + help + AlphaServer DS20L and API NetWorks CS20 systems based on the + Tsunami chipset. config ALPHA_TAKARA bool "Takara" help - Alpha 11164-based OEM single-board computer. + Alpha 21164-based OEM single-board computer. config ALPHA_TITAN bool "Titan" help - AlphaServer ES45/DS25 SMP based on EV68 and Titan chipset. + AlphaServer DS15, DS25, ES45 and TS202c based on EV68 and the + Titan chipset. config ALPHA_WILDFIRE bool "Wildfire" help - AlphaServer GS 40/80/160/320 SMP based on the EV67 core. + AlphaServer GS80, GS160 and GS320 SMP based on the EV67 or EV68 + core. endchoice @@ -378,10 +404,9 @@ config ALPHA_SRM loaded either from ARC or can be installed directly as a permanent firmware replacement from floppy (which requires changing a certain jumper on the motherboard). If you want to do either of these, say N - here. If MILO doesn't work on your system (true for Jensen - motherboards), you can bypass it altogether and boot Linux directly - from an SRM console; say Y here in order to do that. Note that you - won't be able to boot from an IDE disk using SRM. + here. If MILO doesn't work on your system, you can bypass it + altogether and boot Linux directly from an SRM console; say Y here + in order to do that. If unsure, say N. @@ -414,8 +439,8 @@ config NR_CPUS default "32" if ALPHA_GENERIC || ALPHA_MARVEL default "4" if !ALPHA_GENERIC && !ALPHA_MARVEL help - MARVEL support can handle a maximum of 32 CPUs, all the others - with working support have a maximum of 4 CPUs. + The kernel supports at most 32 CPUs. Only Marvel and Wildfire + systems have more than 4. config ARCH_SPARSEMEM_ENABLE bool "Sparse Memory Support" @@ -439,8 +464,8 @@ config ALPHA_WTINT For emulation under QEMU, definitely say Y here, as we have other mechanisms for measuring time than the cycle counter. - For EV4 (but not LCA), EV5 and EV56 systems, or for systems running - MILO, sleep mode is not supported so you might as well say N here. + For EV56 systems, or for systems running MILO, sleep mode is not + supported so you might as well say N here. For SMP systems we cannot use the cycle counter for timing anyway, so you might as well say Y here. From d3851e5745716b8f519d0986952eb7739a316bd0 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Thu, 1 Oct 2026 12:50:17 -0400 Subject: [PATCH 28/57] alpha: link platform help text to the alphalinux.org wiki The alphalinux.org wiki has a page for each system and motherboard covering its CPUs, firmware, Linux status and known issues. Link each platform's help text to the pages for the systems it supports. Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261001165018.415526-3-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 920dd10e7df0..0fc61d986a7d 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -177,6 +177,8 @@ config ALPHA_ALCOR memory coherence when a PCI device DMAs into (or out of) memory. It is used by the AlphaStation 500 and the Alpha XL 366 and 433. + See . + config ALPHA_DP264 bool "DP264" help @@ -184,23 +186,37 @@ config ALPHA_DP264 API NetWorks: 264DP, UP2000(+); Compaq: DS10, DS10L, XP900, XP1000, DS20, DS20E, ES40. + See: + + + + + + + config ALPHA_EIGER bool "Eiger" help Apparently an obscure OEM single-board computer based on the Typhoon/Tsunami chipset family. Information on it is scanty. + See . + config ALPHA_LX164 bool "LX164" help AlphaPC164-LX, a 21164A-based single-board computer using the Pyxis chipset. + See . + config ALPHA_MARVEL bool "Marvel" help AlphaServer ES47 / ES80 / GS1280 based on EV7. + See . + config ALPHA_MIATA bool "Miata" select HAVE_EISA @@ -208,17 +224,26 @@ config ALPHA_MIATA The Digital Personal Workstation (PWS 433a, 433au, 500a, 500au, 600a, or 600au). + See . + config ALPHA_MIKASA bool "Mikasa" help AlphaServer 1000-based Alpha systems. + See . + config ALPHA_NAUTILUS bool "Nautilus" help UP1000, UP1100 and UP1500 systems based on the AMD 751 or 761 chipset. + See: + + + + config ALPHA_NORITAKE bool "Noritake" select HAVE_EISA @@ -226,12 +251,16 @@ config ALPHA_NORITAKE AlphaServer 1000A, AlphaServer 800, AlphaStation 600A and Digital Server 3300 systems. + See . + config ALPHA_PC164 bool "PC164" help AlphaPC164, a 21164A-based single-board computer using the CIA chipset. + See . + config ALPHA_RAWHIDE bool "Rawhide" select HAVE_EISA @@ -239,12 +268,16 @@ config ALPHA_RAWHIDE AlphaServer 1200, 4000 and 4100 and Digital Server 5300 and 7300 machines. + See . + config ALPHA_RUFFIAN bool "Ruffian" help DeskStation RPX164-2, AlphaPC164-UX and AlphaPC164-BX boards. They have no SRM console and can only boot Linux through MILO. + See . + config ALPHA_RX164 bool "RX164" help @@ -252,41 +285,55 @@ config ALPHA_RX164 Polaris chipset. It has no SRM console and can only boot Linux through MILO. + See . + config ALPHA_SX164 bool "SX164" help AlphaPC164-SX, a 21164PC-based single-board computer using the Pyxis chipset. + See . + config ALPHA_SABLE bool "Sable" select HAVE_EISA help Digital AlphaServer 2000 and 2100-based systems. + See . + config ALPHA_SHARK bool "Shark" help AlphaServer DS20L and API NetWorks CS20 systems based on the Tsunami chipset. + See . + config ALPHA_TAKARA bool "Takara" help Alpha 21164-based OEM single-board computer. + See . + config ALPHA_TITAN bool "Titan" help AlphaServer DS15, DS25, ES45 and TS202c based on EV68 and the Titan chipset. + See . + config ALPHA_WILDFIRE bool "Wildfire" help AlphaServer GS80, GS160 and GS320 SMP based on the EV67 or EV68 core. + See . + endchoice # clear all implied options (don't want default values for those): From a2460bfc8ac90f008cad77af690eee8cc8222553 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Thu, 1 Oct 2026 12:50:18 -0400 Subject: [PATCH 29/57] alpha: default NR_CPUS to 32 for Wildfire The AlphaServer GS320 has up to 32 CPUs and the Wildfire code handles all eight QBBs, but a Wildfire-only kernel defaults to NR_CPUS=4 and ignores the rest unless the user raises it by hand. Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261001165018.415526-4-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 0fc61d986a7d..b5b02bb42aca 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -483,8 +483,8 @@ config NR_CPUS int "Maximum number of CPUs (2-32)" range 2 32 depends on SMP - default "32" if ALPHA_GENERIC || ALPHA_MARVEL - default "4" if !ALPHA_GENERIC && !ALPHA_MARVEL + default "32" if ALPHA_GENERIC || ALPHA_MARVEL || ALPHA_WILDFIRE + default "4" help The kernel supports at most 32 CPUs. Only Marvel and Wildfire systems have more than 4. From 7d2d545992c1dd57185759312a642f643053f02c Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Wed, 7 Oct 2026 23:24:04 -0400 Subject: [PATCH 30/57] alpha: add missing page_table_check_pte_clear() to ptep_get_and_clear() Alpha's CONFIG_COMPACTION-gated ptep_get_and_clear() overrides the generic version in include/linux/pgtable.h but omits its page_table_check_pte_clear() call. With CONFIG_PAGE_TABLE_CHECK=y the map count taken by set_ptes() is therefore never dropped when a PTE is cleared through this path. zap_pte_range() clears PTEs this way, so the first page freed after it hits the BUG_ON() in __page_table_check_zero(). The call was instead placed in ptep_clear_flush() by commit dd5712f3379c ("alpha: fix user-space corruption during memory compaction"), where it was harmless until alpha enabled page table check. Add the missing call, and drop the now-duplicate one in ptep_clear_flush(), which already goes through ptep_get_and_clear(). Fixes: e761b6fe4085 ("alpha: add ARCH_SUPPORTS_PAGE_TABLE_CHECK support") Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Signed-off-by: Matt Turner Link: https://lore.kernel.org/r/20261007-alpha-hugepages-v2-1-8e108af2d961@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/pgtable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/alpha/include/asm/pgtable.h b/arch/alpha/include/asm/pgtable.h index 6ebdd90f3035..8a175e0c2b42 100644 --- a/arch/alpha/include/asm/pgtable.h +++ b/arch/alpha/include/asm/pgtable.h @@ -305,6 +305,7 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm, pte_t pte = READ_ONCE(*ptep); pte_clear(mm, address, ptep); + page_table_check_pte_clear(mm, address, pte); return pte; } @@ -316,7 +317,6 @@ static inline pte_t ptep_clear_flush(struct vm_area_struct *vma, struct mm_struct *mm = vma->vm_mm; pte_t pte = ptep_get_and_clear(mm, addr, ptep); - page_table_check_pte_clear(mm, addr, pte); migrate_flush_tlb_page(vma, addr); return pte; } From 4bef20bbb9e02568a009efa4d404ce2621289be5 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Wed, 7 Oct 2026 23:24:05 -0400 Subject: [PATCH 31/57] alpha: handle VM_FAULT_HWPOISON in do_page_fault() do_page_fault() handles VM_FAULT_OOM, VM_FAULT_SIGSEGV and VM_FAULT_SIGBUS and then falls into BUG() for anything else in VM_FAULT_ERROR. VM_FAULT_HWPOISON and VM_FAULT_HWPOISON_LARGE are in that set, so a fault on a poisoned page takes down the kernel instead of delivering a signal. Alpha does not select ARCH_SUPPORTS_MEMORY_FAILURE, so the machine check paths cannot produce these. UFFDIO_POISON can: it installs a poison PTE marker without any memory failure support, and a subsequent access returns VM_FAULT_HWPOISON. Registering an anonymous range with userfaultfd, poisoning it and reading it back reliably hits the BUG(): Kernel bug at arch/alpha/mm/fault.c:188 poison(50809): Kernel Bug 1 pc is at do_page_fault+0x4e8/0x550 ra is at do_page_fault+0xfc/0x550 The BUG() fires with mmap_read_lock() still held, so the faulting task is left unkillable in D state holding the lock, and shutdown stalls behind it. A UFFD_USER_MODE_ONLY userfaultfd is always allowed, so any local user can do this. Deliver SIGBUS with BUS_MCEERR_AR instead, reporting the size of the poisoned area, as the other architectures do. That is the huge page size for VM_FAULT_HWPOISON_LARGE, which becomes reachable once alpha implements huge pages. Tested on an UP1500 (EV68AL): the reproducer above now takes a SIGBUS and the kernel logs poison[373]: hardware memory error at 0000020000030000 pc 00000200010007ec with no oops, no wedged task and no taint. A backport needs the show_signal_msg() call dropped, since alpha only has it since commit 9ca6af1aac40 ("alpha: select SYSCTL_EXCEPTION_TRACE"). Fixes: fc71884a5f59 ("mm: userfaultfd: add new UFFDIO_POISON ioctl") Cc: stable@vger.kernel.org # 6.6+ Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Signed-off-by: Matt Turner Link: https://lore.kernel.org/r/20261007-alpha-hugepages-v2-2-8e108af2d961@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/mm/fault.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/arch/alpha/mm/fault.c b/arch/alpha/mm/fault.c index dfe427d93072..24408b53197c 100644 --- a/arch/alpha/mm/fault.c +++ b/arch/alpha/mm/fault.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #define __EXTERN_INLINE inline @@ -113,6 +114,7 @@ do_page_fault(unsigned long address, unsigned long mmcsr, struct mm_struct *mm = current->mm; const struct exception_table_entry *fixup; int si_code = SEGV_MAPERR; + unsigned int lsb; vm_fault_t fault; unsigned int flags = FAULT_FLAG_DEFAULT; @@ -185,6 +187,8 @@ do_page_fault(unsigned long address, unsigned long mmcsr, goto bad_area; else if (fault & VM_FAULT_SIGBUS) goto do_sigbus; + else if (fault & (VM_FAULT_HWPOISON | VM_FAULT_HWPOISON_LARGE)) + goto do_sigbus_mceerr; BUG(); } @@ -248,6 +252,21 @@ do_page_fault(unsigned long address, unsigned long mmcsr, goto no_context; return; + do_sigbus_mceerr: + mmap_read_unlock(mm); + if (!user_mode(regs)) + goto no_context; + /* + * Report the size of the poisoned area, which for a hugetlb fault + * is the size of the huge page that could not be mapped. + */ + lsb = PAGE_SHIFT; + if (fault & VM_FAULT_HWPOISON_LARGE) + lsb = hstate_index_to_shift(VM_FAULT_GET_HINDEX(fault)); + show_signal_msg(regs, address, SIGBUS, "hardware memory error"); + force_sig_mceerr(BUS_MCEERR_AR, (void __user *) address, lsb); + return; + do_sigsegv: show_signal_msg(regs, address, SIGSEGV, si_code == SEGV_MAPERR ? "unmapped access" From 5cea79aacfcf677fd9e62e4efad0413be51665c9 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Wed, 7 Oct 2026 23:24:06 -0400 Subject: [PATCH 32/57] alpha: describe the PTE read and write enable bits The comments on _PAGE_KRE and _PAGE_URE said only "xxx". Describe the four enable bits as the Alpha Linux PTE defines them, in Table 22-3 of the Alpha Architecture Reference Manual: kernel and user read enable in bits 8 and 9, kernel and user write enable in bits 12 and 13, with bits <11:10> and <15:14> reserved. There are only two processor modes, user and kernel (section 22.5.1). Linux uses the read enables as its accessed bit and the write enables as its dirty bit, so say which of __ACCESS_BITS and __DIRTY_BITS each one belongs to. The names differ from what the hardware calls those bits on the 21264. Its PALcode loads the PTE unchanged into DTB_PTE (21264/EV67 Hardware Reference Manual, section 6.9), where bits 9 and 13 are the Executive read and write enables (Figure 5-27), Executive being mode 1 of the four the processor implements (Table 5-5). That is a detail below the PALcode interface, and it is also the layout of the OpenVMS PTE in Table 11-2, which is not the one Linux uses. No functional change. Suggested-by: Magnus Lindholm Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261007-alpha-hugepages-v2-3-8e108af2d961@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/pgtable.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/alpha/include/asm/pgtable.h b/arch/alpha/include/asm/pgtable.h index 8a175e0c2b42..d22e28b956fb 100644 --- a/arch/alpha/include/asm/pgtable.h +++ b/arch/alpha/include/asm/pgtable.h @@ -65,10 +65,10 @@ struct vm_area_struct; #define _PAGE_FOW 0x0004 /* used for page protection (fault on write) */ #define _PAGE_FOE 0x0008 /* used for page protection (fault on exec) */ #define _PAGE_ASM 0x0010 -#define _PAGE_KRE 0x0100 /* xxx - see below on the "accessed" bit */ -#define _PAGE_URE 0x0200 /* xxx */ -#define _PAGE_KWE 0x1000 /* used to do the dirty bit in software */ -#define _PAGE_UWE 0x2000 /* used to do the dirty bit in software */ +#define _PAGE_KRE 0x0100 /* kernel read enable, in __ACCESS_BITS */ +#define _PAGE_URE 0x0200 /* user read enable, in __ACCESS_BITS */ +#define _PAGE_KWE 0x1000 /* kernel write enable, in __DIRTY_BITS */ +#define _PAGE_UWE 0x2000 /* user write enable, in __DIRTY_BITS */ /* .. and these are ours ... */ #define _PAGE_DIRTY 0x20000 From 5072e72dd3b5b94586392bf003bdbff2c22e87f7 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Wed, 7 Oct 2026 23:24:07 -0400 Subject: [PATCH 33/57] alpha: define granularity hint PTE bits Bits <6:5> of the Alpha PTE are the granularity hint, described in Table 22-3 of the Alpha Architecture Reference Manual. A hint of N marks the PTE as one of a block of 8^N physically contiguous, naturally aligned pages that the translation buffer may map with a single entry. With 8KB pages that gives 64KB, 512KB and 4MB blocks. Define the field and the helpers to encode and decode it. Nothing sets a non-zero hint yet. Add the hint to _PAGE_CHG_MASK so that pte_modify() preserves it. Swap PTEs leave bits <31:0> clear, so pte_huge() is false on swap, migration and marker entries, and page table entries above the last level keep a zero hint where pmd_bad() would reject anything else. Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Signed-off-by: Matt Turner Link: https://lore.kernel.org/r/20261007-alpha-hugepages-v2-4-8e108af2d961@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/pgtable.h | 51 +++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/arch/alpha/include/asm/pgtable.h b/arch/alpha/include/asm/pgtable.h index d22e28b956fb..7cf685a694b6 100644 --- a/arch/alpha/include/asm/pgtable.h +++ b/arch/alpha/include/asm/pgtable.h @@ -65,6 +65,8 @@ struct vm_area_struct; #define _PAGE_FOW 0x0004 /* used for page protection (fault on write) */ #define _PAGE_FOE 0x0008 /* used for page protection (fault on exec) */ #define _PAGE_ASM 0x0010 +#define _PAGE_GH_MASK 0x0060 /* granularity hint, PTE<6:5> */ +#define _PAGE_GH_SHIFT 5 #define _PAGE_KRE 0x0100 /* kernel read enable, in __ACCESS_BITS */ #define _PAGE_URE 0x0200 /* user read enable, in __ACCESS_BITS */ #define _PAGE_KWE 0x1000 /* kernel write enable, in __DIRTY_BITS */ @@ -95,7 +97,36 @@ struct vm_area_struct; #define _PFN_MASK 0xFFFFFFFF00000000UL #define _PAGE_TABLE (_PAGE_VALID | __DIRTY_BITS | __ACCESS_BITS) -#define _PAGE_CHG_MASK (_PFN_MASK | __DIRTY_BITS | __ACCESS_BITS | _PAGE_SPECIAL) +/* + * pte_modify() must preserve the granularity hint, or + * hugetlb_change_protection() would turn a block into single-page PTEs. + */ +#define _PAGE_CHG_MASK (_PFN_MASK | __DIRTY_BITS | __ACCESS_BITS | \ + _PAGE_GH_MASK | _PAGE_SPECIAL) + +/* + * Granularity hints. A PTE with hint order N is one of a block of 8^N + * physically contiguous pages, naturally aligned both virtually and + * physically, that the TB may map with a single entry. With 8KB pages the + * four encodings give 8KB, 64KB, 512KB and 4MB. + * + * Every PTE of a block must still carry the PFN of its own page, and all of + * them must agree in bits <15:0>: the protection, fault, hint and valid bits. + * __ACCESS_BITS and __DIRTY_BITS reach into that range, so young and dirty + * updates have to be applied to the whole block too. + * + * The hint is advisory: an implementation that ignores it still translates + * correctly through the individual PTEs, so it is safe to set on any Alpha. + */ +#define GH_ORDER_MAX 4 /* hint orders 0..3 */ + +#define gh_cont_shift(order) (PAGE_SHIFT + 3 * (order)) +#define gh_cont_size(order) (1UL << gh_cont_shift(order)) +#define gh_cont_mask(order) (~(gh_cont_size(order) - 1)) +#define gh_pte_num(order) (1UL << (3 * (order))) + +#define for_each_gh_order(order) \ + for ((order) = 1; (order) < GH_ORDER_MAX; (order)++) /* * All the normal masks have the "page accessed" bits on, as any time they are used, @@ -261,6 +292,24 @@ extern inline pte_t pte_mkyoung(pte_t pte) { pte_val(pte) |= __ACCESS_BITS; retu extern inline int pte_special(pte_t pte) { return pte_val(pte) & _PAGE_SPECIAL; } extern inline pte_t pte_mkspecial(pte_t pte) { pte_val(pte) |= _PAGE_SPECIAL; return pte; } +extern inline unsigned int pte_gh_order(pte_t pte) +{ + return (pte_val(pte) & _PAGE_GH_MASK) >> _PAGE_GH_SHIFT; +} + +extern inline pte_t pte_mkgh(pte_t pte, unsigned int order) +{ + pte_val(pte) &= ~_PAGE_GH_MASK; + pte_val(pte) |= (unsigned long)order << _PAGE_GH_SHIFT; + return pte; +} + +/* + * Swap PTEs leave bits <31:0> clear, so this is false for every swap, + * migration and marker entry, not just for ordinary small pages. + */ +extern inline int pte_huge(pte_t pte) { return pte_val(pte) & _PAGE_GH_MASK; } + /* * The smp_rmb() in the following functions are required to order the load of * *dir (the pointer in the top level page table) with any subsequent load of From d25293ba796669664d3f4e2721ed8b67a1f96f4a Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Wed, 7 Oct 2026 23:24:08 -0400 Subject: [PATCH 34/57] alpha: align hugetlb mappings in arch_get_unmapped_area() hugetlb_get_unmapped_area() does not align the address itself; it delegates to mm_get_unmapped_area_vmaflags(), and the huge page alignment is applied by generic_get_unmapped_area() and generic_get_unmapped_area_topdown() via info.align_mask. Alpha defines HAVE_ARCH_UNMAPPED_AREA and supplies its own arch_get_unmapped_area(), which never sets align_mask. An mmap(MAP_HUGETLB) with no address hint would therefore return a merely page-aligned address. That has been the architecture's job since commit 7bd3f1e1a9ae ("mm: make hugetlb mappings go through mm_get_unmapped_area_vmflags"), whose series converted the generic code along with x86, s390, sparc and powerpc. LoongArch, which also has its own arch_get_unmapped_area(), was missed and hit a BUG in LTP's hugefork02 until commit 3109d5ff484b ("LoongArch: Set hugetlb mmap base address aligned with pmd size"). Pass the file down to arch_get_unmapped_area_1() and set align_mask for hugetlbfs mappings, as x86, s390, sparc and loongarch already do. This is a no-op until alpha gains hugetlb support. Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Signed-off-by: Matt Turner Link: https://lore.kernel.org/r/20261007-alpha-hugepages-v2-5-8e108af2d961@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/osf_sys.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/arch/alpha/kernel/osf_sys.c b/arch/alpha/kernel/osf_sys.c index 7b6543d2cca3..61d806176213 100644 --- a/arch/alpha/kernel/osf_sys.c +++ b/arch/alpha/kernel/osf_sys.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -1201,14 +1202,16 @@ SYSCALL_DEFINE1(old_adjtimex, struct timex32 __user *, txc_p) /* Get an address range which is currently unmapped. */ static unsigned long -arch_get_unmapped_area_1(unsigned long addr, unsigned long len, - unsigned long limit) +arch_get_unmapped_area_1(struct file *filp, unsigned long addr, + unsigned long len, unsigned long limit) { struct vm_unmapped_area_info info = {}; info.length = len; info.low_limit = addr; info.high_limit = limit; + if (filp && is_file_hugepages(filp)) + info.align_mask = huge_page_mask_align(filp); return vm_unmapped_area(&info); } @@ -1236,19 +1239,20 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, this feature should be incorporated into all ports? */ if (addr) { - addr = arch_get_unmapped_area_1 (PAGE_ALIGN(addr), len, limit); + addr = arch_get_unmapped_area_1 (filp, PAGE_ALIGN(addr), len, + limit); if (addr != (unsigned long) -ENOMEM) return addr; } /* Next, try allocating at TASK_UNMAPPED_BASE. */ - addr = arch_get_unmapped_area_1 (PAGE_ALIGN(TASK_UNMAPPED_BASE), + addr = arch_get_unmapped_area_1 (filp, PAGE_ALIGN(TASK_UNMAPPED_BASE), len, limit); if (addr != (unsigned long) -ENOMEM) return addr; /* Finally, try allocating in low memory. */ - addr = arch_get_unmapped_area_1 (PAGE_SIZE, len, limit); + addr = arch_get_unmapped_area_1 (filp, PAGE_SIZE, len, limit); return addr; } From 8ba8994e76e45a47e749a7f92fdfe0ee701362e3 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Wed, 7 Oct 2026 23:24:09 -0400 Subject: [PATCH 35/57] alpha: implement hugetlb support Alpha has no leaf entry above the last page table level, so the only huge pages it can offer are granularity hint blocks: 64KB, 512KB and 4MB with 8KB pages. Register all three as hstates, with 4MB as the default. There is no PMD sized huge page, hence no transparent huge pages, no PMD page table sharing and no gigantic pages. As with arm64's contiguous PTEs, each PTE of a block keeps the frame number of its own page, so a block is written with set_ptes(). Each hstate's buddy order is its hint order, so a hugetlb folio is always naturally aligned physically, as the hint requires, including after a demote. All PTEs of a block must agree in bits <15:0> (note 2 of Table 22-3 of the Alpha Architecture Reference Manual), and both __ACCESS_BITS and __DIRTY_BITS reach into that range, so every update rewrites the whole block and huge_ptep_get() merges the young and dirty state back together. Rewriting a valid block in place would leave its PTEs disagreeing part way through, so changes to a valid block go through break before make, following the procedure in section 11.6.1. The hint is advisory, so this is safe on implementations that ignore it, and gup_fast needs no changes since it walks the individual PTEs. Enabling CONFIG_HUGETLB_PAGE derives pageblock_order from HUGETLB_PAGE_ORDER, which lowers it from 10 to 9 and so halves the anti-fragmentation and compaction granularity from 8MB to 4MB. Hugepage migration is left disabled, since it has not been tested. Co-developed-by: Magnus Lindholm Signed-off-by: Magnus Lindholm Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261007-alpha-hugepages-v2-6-8e108af2d961@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + arch/alpha/include/asm/hugetlb.h | 43 +++++ arch/alpha/include/asm/page.h | 13 ++ arch/alpha/mm/Makefile | 2 + arch/alpha/mm/hugetlbpage.c | 308 +++++++++++++++++++++++++++++++ 5 files changed, 367 insertions(+) create mode 100644 arch/alpha/include/asm/hugetlb.h create mode 100644 arch/alpha/mm/hugetlbpage.c diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index b5b02bb42aca..b2c359b947f6 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -19,6 +19,7 @@ config ALPHA select ARCH_NO_PREEMPT select ARCH_NO_SG_CHAIN select ARCH_SUPPORTS_ATOMIC_RMW + select ARCH_SUPPORTS_HUGETLBFS select ARCH_SUPPORTS_INT128 if CC_HAS_INT128 select ARCH_SUPPORTS_PAGE_TABLE_CHECK select ARCH_USE_CMPXCHG_LOCKREF diff --git a/arch/alpha/include/asm/hugetlb.h b/arch/alpha/include/asm/hugetlb.h new file mode 100644 index 000000000000..ff69e24c775d --- /dev/null +++ b/arch/alpha/include/asm/hugetlb.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ALPHA_HUGETLB_H +#define _ALPHA_HUGETLB_H + +#include + +pte_t arch_make_huge_pte(pte_t entry, unsigned int shift, vm_flags_t flags); +#define arch_make_huge_pte arch_make_huge_pte + +#define __HAVE_ARCH_HUGE_PTEP_GET +extern pte_t huge_ptep_get(struct mm_struct *mm, unsigned long addr, + pte_t *ptep); +#define __HAVE_ARCH_HUGE_SET_HUGE_PTE_AT +extern void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, pte_t pte, unsigned long sz); +#define __HAVE_ARCH_HUGE_PTEP_GET_AND_CLEAR +extern pte_t huge_ptep_get_and_clear(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, unsigned long sz); +#define __HAVE_ARCH_HUGE_PTEP_CLEAR_FLUSH +extern pte_t huge_ptep_clear_flush(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep); +#define __HAVE_ARCH_HUGE_PTEP_SET_WRPROTECT +extern void huge_ptep_set_wrprotect(struct mm_struct *mm, unsigned long addr, + pte_t *ptep); +#define __HAVE_ARCH_HUGE_PTEP_SET_ACCESS_FLAGS +extern int huge_ptep_set_access_flags(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep, + pte_t pte, int dirty); +#define __HAVE_ARCH_HUGE_PTE_CLEAR +extern void huge_pte_clear(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, unsigned long sz); + +/* + * The generic version does not flush, which would leave the old translation + * live across a restrictive change to PTE bits <15:0>. + */ +#define huge_ptep_modify_prot_start huge_ptep_modify_prot_start +extern pte_t huge_ptep_modify_prot_start(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep); + +#include + +#endif /* _ALPHA_HUGETLB_H */ diff --git a/arch/alpha/include/asm/page.h b/arch/alpha/include/asm/page.h index 59d01f9b77f6..67053c52953f 100644 --- a/arch/alpha/include/asm/page.h +++ b/arch/alpha/include/asm/page.h @@ -6,6 +6,19 @@ #include #include +#ifdef CONFIG_HUGETLB_PAGE +/* + * The default huge page is the largest granularity hint block, 4MB. All + * three hint sizes are registered as hstates, hence HUGE_MAX_HSTATE, which + * linux/hugetlb.h needs before it includes asm/hugetlb.h. + */ +#define HPAGE_SHIFT 22 +#define HPAGE_SIZE (_AC(1, UL) << HPAGE_SHIFT) +#define HPAGE_MASK (~(HPAGE_SIZE - 1)) +#define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT) +#define HUGE_MAX_HSTATE 3 +#endif + #ifndef __ASSEMBLER__ #define STRICT_MM_TYPECHECKS diff --git a/arch/alpha/mm/Makefile b/arch/alpha/mm/Makefile index 2d05664058f6..c022e55f231a 100644 --- a/arch/alpha/mm/Makefile +++ b/arch/alpha/mm/Makefile @@ -4,3 +4,5 @@ # obj-y := init.o fault.o tlbflush.o + +obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o diff --git a/arch/alpha/mm/hugetlbpage.c b/arch/alpha/mm/hugetlbpage.c new file mode 100644 index 000000000000..ca36af4667dc --- /dev/null +++ b/arch/alpha/mm/hugetlbpage.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Alpha huge page support, built on the page table entry granularity hint + * described in asm/pgtable.h. All PTEs of a block have to agree in bits + * <15:0>, so every update below rewrites the whole block. + */ + +#include +#include +#include + +#include + +/* + * Every hint size is smaller than PMD_SIZE, so a block always fits inside a + * single last level page table and its entry count is just its page count. + */ +static inline unsigned long num_contig_ptes(unsigned long sz) +{ + return sz >> PAGE_SHIFT; +} + +/* Hint order that maps @sz, or 0 if @sz is not a hint size. */ +static unsigned int gh_order_from_size(unsigned long sz) +{ + unsigned int order; + + for_each_gh_order(order) + if (gh_cont_size(order) == sz) + return order; + + return 0; +} + +pte_t *huge_pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma, + unsigned long addr, unsigned long sz) +{ + unsigned int order = gh_order_from_size(sz); + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; + pmd_t *pmd; + + if (!order) + return NULL; + + pgd = pgd_offset(mm, addr); + p4d = p4d_alloc(mm, pgd, addr); + if (!p4d) + return NULL; + + pud = pud_alloc(mm, p4d, addr); + if (!pud) + return NULL; + + pmd = pmd_alloc(mm, pud, addr); + if (!pmd) + return NULL; + + return pte_alloc_huge(mm, pmd, addr & gh_cont_mask(order)); +} + +pte_t *huge_pte_offset(struct mm_struct *mm, unsigned long addr, + unsigned long sz) +{ + unsigned int order = gh_order_from_size(sz); + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; + pmd_t *pmd; + + if (!order) + return NULL; + + pgd = pgd_offset(mm, addr); + if (!pgd_present(pgdp_get(pgd))) + return NULL; + + p4d = p4d_offset(pgd, addr); + if (!p4d_present(p4dp_get(p4d))) + return NULL; + + pud = pud_offset(p4d, addr); + if (!pud_present(pudp_get(pud))) + return NULL; + + pmd = pmd_offset(pud, addr); + if (!pmd_present(pmdp_get(pmd))) + return NULL; + + return pte_offset_huge(pmd, addr & gh_cont_mask(order)); +} + +/* + * A block is only as large as its own hint, so the walk can skip to the end of + * the containing last level page table but no further. + */ +unsigned long hugetlb_mask_last_page(struct hstate *h) +{ + return PMD_SIZE - huge_page_size(h); +} + +pte_t huge_ptep_get(struct mm_struct *mm, unsigned long addr, pte_t *ptep) +{ + pte_t orig_pte = ptep_get(ptep); + unsigned long i, ncontig; + + /* Swap, migration and marker entries carry no hint. */ + if (!pte_huge(orig_pte)) + return orig_pte; + + ncontig = gh_pte_num(pte_gh_order(orig_pte)); + + for (i = 0; i < ncontig; i++, ptep++) { + pte_t pte = ptep_get(ptep); + + if (pte_dirty(pte)) + orig_pte = pte_mkdirty(orig_pte); + if (pte_young(pte)) + orig_pte = pte_mkyoung(orig_pte); + } + + return orig_pte; +} + +static pte_t get_clear_contig(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, unsigned long ncontig) +{ + pte_t pte = ptep_get_and_clear(mm, addr, ptep); + bool present = pte_present(pte); + + while (--ncontig) { + pte_t tmp_pte; + + ptep++; + addr += PAGE_SIZE; + tmp_pte = ptep_get_and_clear(mm, addr, ptep); + if (present) { + if (pte_dirty(tmp_pte)) + pte = pte_mkdirty(pte); + if (pte_young(tmp_pte)) + pte = pte_mkyoung(pte); + } + } + + return pte; +} + +static pte_t get_clear_contig_flush(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, unsigned long ncontig) +{ + pte_t orig_pte = get_clear_contig(mm, addr, ptep, ncontig); + struct vm_area_struct vma = TLB_FLUSH_VMA(mm, 0); + + if (!pte_none(orig_pte)) + flush_tlb_range(&vma, addr, addr + ncontig * PAGE_SIZE); + + return orig_pte; +} + +pte_t arch_make_huge_pte(pte_t entry, unsigned int shift, vm_flags_t flags) +{ + unsigned int order = gh_order_from_size(1UL << shift); + + /* + * Not every caller asks for a size Alpha can hint; mm/debug_vm_pgtable.c + * probes with PMD_SHIFT. Leave the entry alone rather than complaining. + */ + if (order) + entry = pte_mkgh(entry, order); + + return entry; +} + +void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep, + pte_t pte, unsigned long sz) +{ + unsigned long i, ncontig = num_contig_ptes(sz); + + /* The hint requires the block to be naturally aligned in the VA too. */ + VM_WARN_ON_ONCE(addr & (sz - 1)); + + if (!pte_present(pte)) { + /* + * Swap, migration and marker entries hold no frame number, so + * every slot gets the same value. + */ + for (i = 0; i < ncontig; i++, ptep++, addr += PAGE_SIZE) + set_ptes(mm, addr, ptep, pte, 1); + return; + } + + /* + * All PTEs of a block must agree in bits <15:0> (ARM Table 22-3, note + * 2), which rewriting a valid block in place would violate part way + * through. Invalidate it everywhere first, following ARM 11.6.1: the + * invalidate has to assume the hint is zero, so it must cover every + * page of the block. + */ + if (pte_present(ptep_get(ptep))) + get_clear_contig_flush(mm, addr, ptep, ncontig); + + /* set_ptes() advances the frame number for each entry. */ + set_ptes(mm, addr, ptep, pte, ncontig); +} + +pte_t huge_ptep_get_and_clear(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, unsigned long sz) +{ + return get_clear_contig(mm, addr, ptep, num_contig_ptes(sz)); +} + +/* + * The block size comes from the hstate rather than from the entry, because + * this is also reached for non-present entries, which carry no hint. + */ +pte_t huge_ptep_clear_flush(struct vm_area_struct *vma, unsigned long addr, + pte_t *ptep) +{ + unsigned long sz = huge_page_size(hstate_vma(vma)); + + return get_clear_contig_flush(vma->vm_mm, addr, ptep, + num_contig_ptes(sz)); +} + +pte_t huge_ptep_modify_prot_start(struct vm_area_struct *vma, unsigned long addr, + pte_t *ptep) +{ + return huge_ptep_clear_flush(vma, addr, ptep); +} + +/* True if any entry of the block differs from @pte outside the frame number. */ +static bool gh_access_flags_changed(pte_t *ptep, pte_t pte, + unsigned long ncontig) +{ + unsigned long i; + + for (i = 0; i < ncontig; i++) + if ((pte_val(ptep_get(ptep + i)) ^ pte_val(pte)) & ~_PFN_MASK) + return true; + + return false; +} + +int huge_ptep_set_access_flags(struct vm_area_struct *vma, unsigned long addr, + pte_t *ptep, pte_t pte, int dirty) +{ + struct mm_struct *mm = vma->vm_mm; + unsigned long sz = huge_page_size(hstate_vma(vma)); + unsigned long ncontig = num_contig_ptes(sz); + pte_t orig_pte = huge_ptep_get(mm, addr, ptep); + + /* Keep the young and dirty state the block already has. */ + if (pte_dirty(orig_pte)) + pte = pte_mkdirty(pte); + if (pte_young(orig_pte)) + pte = pte_mkyoung(pte); + + /* Breaking an unchanged block only makes concurrent touchers refault. */ + if (!gh_access_flags_changed(ptep, pte, ncontig)) + return 0; + + get_clear_contig_flush(mm, addr, ptep, ncontig); + set_ptes(mm, addr, ptep, pte, ncontig); + + return 1; +} + +void huge_ptep_set_wrprotect(struct mm_struct *mm, unsigned long addr, + pte_t *ptep) +{ + pte_t pte = ptep_get(ptep); + unsigned long ncontig = gh_pte_num(pte_gh_order(pte)); + + VM_WARN_ON_ONCE(!pte_huge(pte)); + + /* + * pte_wrprotect() sets _PAGE_FOW, which lives in the bits the whole + * block must agree on, so this takes the break before make path. + */ + pte = pte_wrprotect(get_clear_contig_flush(mm, addr, ptep, ncontig)); + set_ptes(mm, addr, ptep, pte, ncontig); +} + +void huge_pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *ptep, + unsigned long sz) +{ + unsigned long i, ncontig = num_contig_ptes(sz); + + for (i = 0; i < ncontig; i++, addr += PAGE_SIZE, ptep++) + pte_clear(mm, addr, ptep); +} + +bool __init arch_hugetlb_valid_size(unsigned long size) +{ + return gh_order_from_size(size) != 0; +} + +static __init int alpha_hugetlbpage_init(void) +{ + unsigned int order; + + for_each_gh_order(order) + hugetlb_add_hstate(gh_cont_shift(order) - PAGE_SHIFT); + + return 0; +} +arch_initcall(alpha_hugetlbpage_init); From 9b8be19d5dc9525a408942e2e8012873199e7455 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:24:27 +0200 Subject: [PATCH 36/57] alpha: load the MMU context when switch_mm() switches the current task ev5_switch_mm() only prepares the incoming PCB. The context is installed by PAL_swpctx, which alpha_switch_to() issues against that PCB on the way out of the scheduler. Direct callers reach switch_mm() without going through alpha_switch_to(): kthread_use_mm(), which borrows an mm for the current kernel thread, sched_force_init_mm() on the CPU-hotplug teardown path, and do_shoot_lazy_tlb(), which switches to init_mm from an IPI. None explicitly loads the context, so the task can carry on running under whatever was loaded before. For kthread_use_mm(), current->mm then names the borrowed mm even though its context has not been loaded. The other two callers change active_mm while leaving current->mm NULL. The stale page-table root need not be swapper_pg_dir; it may belong to a user process that ran on the CPU earlier, and the kthread's user accesses can then read and write that process's memory wherever the stale mappings allow. Translations taken that way can also end up tagged with the borrowed mm's ASN: ev5_switch_mm() writes that ASN into the PCB, which the next PAL_swpctx installs against the stale ptbr. An address the stale tables do not map faults instead, and since do_page_fault() resolves faults against current->mm without reloading the context, the same access can fault again on return. sched_force_init_mm() needs CONFIG_HOTPLUG_CPU and do_shoot_lazy_tlb() needs CONFIG_MMU_LAZY_TLB_SHOOTDOWN. Alpha selects neither, so only kthread_use_mm() is reachable in practice. Test the caller's identity rather than special-casing individual callers. The scheduler passes the incoming task, which is not current until alpha_switch_to() runs; all three direct callers pass current. Test for that and load the context the way activate_mm() does. All three run with interrupts disabled across the switch, so this completes before any shootdown can be taken and needs no asn_lock handshake. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Tested-by: Matt Turner Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010142533.974797-2-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/mmu_context.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/arch/alpha/include/asm/mmu_context.h b/arch/alpha/include/asm/mmu_context.h index eee8fe836a59..d366f89fee58 100644 --- a/arch/alpha/include/asm/mmu_context.h +++ b/arch/alpha/include/asm/mmu_context.h @@ -130,6 +130,8 @@ __get_new_mm_context(struct mm_struct *mm, long cpu) return next; } +extern void __load_new_mm_context(struct mm_struct *); + __EXTERN_INLINE void ev5_switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm, struct task_struct *next) @@ -139,6 +141,16 @@ ev5_switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm, unsigned long mmc; long cpu = smp_processor_id(); + /* + * kthread_use_mm(), sched_force_init_mm() and do_shoot_lazy_tlb() + * switch current's mm without alpha_switch_to(), which loads the + * context. The latter two require options Alpha does not select. + */ + if (next == current) { + __load_new_mm_context(next_mm); + return; + } + #ifdef CONFIG_SMP cpu_data[cpu].asn_lock = 1; barrier(); @@ -160,7 +172,6 @@ ev5_switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm, task_thread_info(next)->pcb.asn = mmc & HARDWARE_ASN_MASK; } -extern void __load_new_mm_context(struct mm_struct *); asmlinkage void do_page_fault(unsigned long address, unsigned long mmcsr, long cause, struct pt_regs *regs); From 020d2e70b7538b66eee3c3480d2b8921fb182f0c Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:24:28 +0200 Subject: [PATCH 37/57] alpha: run check_mmu_context() from finish_arch_post_lock_switch() check_mmu_context() clears asn_lock and acts on need_new_asn, but it runs only as the tail of switch_to(), after alpha_switch_to() returns. A newly forked user task never gets there: its first context switch resumes at ret_from_fork and reaches schedule_tail() without returning to the code following alpha_switch_to(). asn_lock is left set on that CPU, so the new user task can run with interrupts enabled while shootdown IPIs keep taking the deferred path. The handshake meant to complete those invalidations never runs. New kernel threads also reach schedule_tail(), but context_switch() does not call switch_mm() for a task without an mm. They therefore do not set asn_lock in the first place and are not affected by this bug. Move check_mmu_context() to finish_arch_post_lock_switch(), which is called by finish_task_switch() for both existing and newly created tasks. Drop the now redundant call from switch_to(). The hook runs after finish_lock_switch() releases the rq lock and enables interrupts. An IPI arriving before asn_lock is cleared retires the slot through flush_tlb_other(); an IPI arriving afterwards can flush directly, since PAL_swpctx has already installed the incoming context. Set need_new_asn whether ev5_switch_mm() reuses or allocates an ASN. An IPI in that interval can zero either slot. Without this, the allocated case can return to user space with a live ASN but a zero context slot, which a later context-based shootdown shortcut could mistake for an inactive CPU. check_mmu_context() reloads only when the slot is zero. Call the hook unconditionally. Alpha selects ARCH_NO_PREEMPT, and neither kthread_use_mm() nor sched_force_init_mm() sleeps between the direct switch and this hook. RCU_STRICT_GRACE_PERIOD can enable PREEMPT_COUNT, but cannot make those callers migrate there. Testing preemptible() would only leave asn_lock set in that configuration. This requires the preceding direct-switch fix, "alpha: load the MMU context when switch_mm() switches the current task". It installs the borrowed context before kthread_use_mm() reaches this hook, so clearing asn_lock cannot expose a targeted invalidate against the wrong context. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010142533.974797-3-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/mmu_context.h | 15 +++++++++++++-- arch/alpha/include/asm/switch_to.h | 1 - 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/arch/alpha/include/asm/mmu_context.h b/arch/alpha/include/asm/mmu_context.h index d366f89fee58..f41d7c73fdd6 100644 --- a/arch/alpha/include/asm/mmu_context.h +++ b/arch/alpha/include/asm/mmu_context.h @@ -162,8 +162,8 @@ ev5_switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm, next_mm->context[cpu] = mmc; } #ifdef CONFIG_SMP - else - cpu_data[cpu].need_new_asn = 1; + /* A deferred shootdown can also invalidate a newly allocated ASN. */ + cpu_data[cpu].need_new_asn = 1; #endif /* Always update the PCB ASN. Another thread may have allocated @@ -192,6 +192,17 @@ do { \ #define check_mmu_context() do { } while(0) #endif +/* + * Also called from kthread_use_mm() with preemption enabled. Alpha has + * no kernel preemption, so the task is still on the CPU that ran + * switch_mm(). + */ +#define finish_arch_post_lock_switch finish_arch_post_lock_switch +static inline void finish_arch_post_lock_switch(void) +{ + check_mmu_context(); +} + __EXTERN_INLINE void ev5_activate_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm) { diff --git a/arch/alpha/include/asm/switch_to.h b/arch/alpha/include/asm/switch_to.h index 762b7f975310..35c4b2c9d992 100644 --- a/arch/alpha/include/asm/switch_to.h +++ b/arch/alpha/include/asm/switch_to.h @@ -9,7 +9,6 @@ extern struct task_struct *alpha_switch_to(unsigned long, struct task_struct *); #define switch_to(P,N,L) \ do { \ (L) = alpha_switch_to(virt_to_phys(&task_thread_info(N)->pcb), (P)); \ - check_mmu_context(); \ } while (0) #endif /* __ALPHA_SWITCH_TO_H */ From 0d92845ac5dbc79e4ac4810b33c673f4c1c227e3 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:24:29 +0200 Subject: [PATCH 38/57] alpha: only use a targeted tbi() when the target mm is really current ipi_flush_tlb_page() gates a targeted tbi() on current->active_mm. tbi() acts on the address space context currently loaded on the CPU, so it is only guaranteed to reach an mm's translations when a thread of that mm is running there. current->active_mm is not sufficient: under lazy TLB an idle or kernel task keeps an mm as its active_mm while a different ASN is loaded, so the invalidate is issued against the wrong context and the stale entry can survive. Nothing retires the old ASN afterwards either, mm->context[cpu] still being valid, so the resuming thread can reuse it. Test current->mm instead and otherwise fall back to flush_tlb_other(), which clears mm->context[cpu] and forces a fresh ASN at the next switch whatever is loaded now. For an mm borrowed through kthread_use_mm(), patch 1 of this series installs the context before the task can take a shootdown IPI. The current->mm test therefore covers that case as well. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010142533.974797-4-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/smp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/alpha/kernel/smp.c b/arch/alpha/kernel/smp.c index ed06367ece57..1ad448105201 100644 --- a/arch/alpha/kernel/smp.c +++ b/arch/alpha/kernel/smp.c @@ -669,7 +669,8 @@ ipi_flush_tlb_page(void *x) struct flush_tlb_page_struct *data = x; struct mm_struct * mm = data->mm; - if (mm == current->active_mm && !asn_locked()) + /* A targeted tbi() needs a thread of MM to be current. */ + if (mm == current->mm && !asn_locked()) flush_tlb_current_page(mm, data->vma, data->addr); else flush_tlb_other(mm); From c0db730bf05eb3c10c1fae64b19f6913dce129fc Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:24:30 +0200 Subject: [PATCH 39/57] alpha: invalidate the local context in flush_tlb_page() When the target mm is not the calling CPU's active_mm, flush_tlb_page() does nothing locally, and smp_call_function() reaches only the other CPUs. This CPU may still hold translations for the mm and can later reuse the old ASN together with them. Add the missing else branch. flush_tlb_other() clears mm->context[cpu] so a fresh ASN is taken at the next switch. Add this before changing the current->active_mm test to current->mm: that change will also send lazy callers here, so their local invalidate must already be in place. The uniprocessor implementations of flush_tlb_mm() and flush_icache_user_page() in asm/tlbflush.h and asm/cacheflush.h already contain exactly this branch. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010142533.974797-5-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/smp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/alpha/kernel/smp.c b/arch/alpha/kernel/smp.c index 1ad448105201..e4ba0aab8a66 100644 --- a/arch/alpha/kernel/smp.c +++ b/arch/alpha/kernel/smp.c @@ -697,6 +697,9 @@ flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) preempt_enable(); return; } + } else { + /* smp_call_function() does not call back into this CPU. */ + flush_tlb_other(mm); } data.vma = vma; From 32f4dd420d626f96128fcfeea2ab24760b4f21a6 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:24:31 +0200 Subject: [PATCH 40/57] alpha: fix the local TLB invalidate in flush_tlb_page() flush_tlb_page() invalidates the calling CPU itself before asking the others, and gates that on current->active_mm. For a non-executable vma that means a targeted tbi(2, addr), which acts on the context currently loaded and is only guaranteed to invalidate the intended translations when the target mm's context is the loaded one. A lazy active_mm does not establish that, so as in ipi_flush_tlb_page() the target mm's stale translations can survive, and nothing forces the old ASN to be retired afterwards. Test current->mm instead. A lazy caller then takes the flush_tlb_other() branch added by the previous patch, retiring its local context. Adding that branch first preserves the local invalidate at every step, including on EV7 where a targeted tbi() against a foreign ASN appeared to work. Which callers reach here with a foreign mm depends on the configuration. folio_mkclean(), from the writeback flusher kworker that has no mm of its own, accounted for about half the calls during writeback of a shared mapping and none at all on anonymous memory. Those counts were measured with CONFIG_COMPACTION=n: with COMPACTION=y, asm/pgtable.h overrides ptep_clear_flush() to call migrate_flush_tlb_page(), which rendezvouses with every CPU and handles the context itself, so folio_mkclean() does not reach this function. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010142533.974797-6-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/smp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/alpha/kernel/smp.c b/arch/alpha/kernel/smp.c index e4ba0aab8a66..a5a42ae4a7d8 100644 --- a/arch/alpha/kernel/smp.c +++ b/arch/alpha/kernel/smp.c @@ -684,7 +684,8 @@ flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) preempt_disable(); - if (mm == current->active_mm) { + /* As in ipi_flush_tlb_page(): a targeted tbi() needs MM current. */ + if (mm == current->mm) { flush_tlb_current_page(mm, vma, addr); if (atomic_read(&mm->mm_users) <= 1) { int cpu, this_cpu = smp_processor_id(); From c825bf802a7a43fd302c879ef25c975e067a2e94 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:24:32 +0200 Subject: [PATCH 41/57] alpha: fix the local TLB invalidate in the UP flush_tlb_page() The uniprocessor flush_tlb_page() in asm/tlbflush.h has the same wrong test as the SMP one: a targeted tbi() is only guaranteed to invalidate the intended translations when the target mm's context is loaded, but the gate is current->active_mm, which a lazily borrowed mm also satisfies. Test current->mm instead. The else branch it falls to already exists here, so unlike the SMP side this is the whole fix. arch/alpha/kernel/smp.c is not built with CONFIG_SMP=n, so this is how the same defect reaches a uniprocessor, where the flusher kworker necessarily shares the only CPU with the writer. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010142533.974797-7-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/tlbflush.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/alpha/include/asm/tlbflush.h b/arch/alpha/include/asm/tlbflush.h index 0c8529997f54..9ae1902faf8e 100644 --- a/arch/alpha/include/asm/tlbflush.h +++ b/arch/alpha/include/asm/tlbflush.h @@ -87,7 +87,8 @@ flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) { struct mm_struct *mm = vma->vm_mm; - if (mm == current->active_mm) + /* A targeted tbi() needs a thread of MM to be current. */ + if (mm == current->mm) flush_tlb_current_page(mm, vma, addr); else flush_tlb_other(mm); From 721bb7a129771f0ff9cef1dd5abfc106ef65feba Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:24:33 +0200 Subject: [PATCH 42/57] alpha: invalidate the local context in flush_tlb_mm() flush_tlb_mm() has the same caller-CPU omission that was fixed in flush_tlb_page(): when the target mm is not the calling CPU's active_mm nothing happens locally, and smp_call_function() handles only the other CPUs, so this CPU may later reuse the old ASN together with the translations it still holds. The active_mm test itself is left alone here. That path calls flush_tlb_current(), which loads a fresh context through __load_new_mm_context() rather than issuing a targeted tbi() against whatever ASN happens to be loaded, so it does not depend on which context is current. The uniprocessor implementation in asm/tlbflush.h already has the missing branch. Counted over a fork-heavy run, flush_tlb_mm() was entered with the mm not this CPU's active_mm 2934 times, and 632 times while otherwise idle. The proposed Alpha hugetlb support routes hugetlb invalidations through flush_tlb_range(), which calls this function. Unmapping or migrating a hugetlb folio from another context will also need this local invalidate. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010142533.974797-8-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/smp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/alpha/kernel/smp.c b/arch/alpha/kernel/smp.c index a5a42ae4a7d8..988e397b0b8a 100644 --- a/arch/alpha/kernel/smp.c +++ b/arch/alpha/kernel/smp.c @@ -649,6 +649,9 @@ flush_tlb_mm(struct mm_struct *mm) preempt_enable(); return; } + } else { + /* smp_call_function() does not call back into this CPU. */ + flush_tlb_other(mm); } smp_call_function(ipi_flush_tlb_mm, mm, 1); From 4244c555ac440a445cc168f860b2677099d761f9 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:24:34 +0200 Subject: [PATCH 43/57] alpha: invalidate the local context in flush_icache_user_page() flush_icache_user_page() has the same caller-CPU omission that the previous patch fixed in flush_tlb_mm(): when the target mm is not the calling CPU's active_mm nothing happens locally, and smp_call_function() handles only the other CPUs, so this CPU may later reuse the old ASN together with the translations it still holds. This matters here in particular because the function exists for operating on another process's mappings: the comment above it describes setting breakpoints through ptrace, and access_remote_vm() reaches it through copy_to_user_page(). The calling CPU is therefore often running something other than the target mm. As in flush_tlb_mm(), the uniprocessor implementation in asm/cacheflush.h already has the missing case. No imb() is needed, here or in ipi_flush_icache_page(). Alpha's user-space I-cache flush works by allocating a new ASN rather than by invalidating the I-cache: the entries stay, but they are tagged with the old ASN and can no longer match. An imb() is only required when the ASN space wraps and numbers are reused, and __get_new_mm_context() already does one in that case. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010142533.974797-9-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/smp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/alpha/kernel/smp.c b/arch/alpha/kernel/smp.c index 988e397b0b8a..e21bc3920bec 100644 --- a/arch/alpha/kernel/smp.c +++ b/arch/alpha/kernel/smp.c @@ -758,6 +758,9 @@ flush_icache_user_page(struct vm_area_struct *vma, struct page *page, preempt_enable(); return; } + } else { + /* smp_call_function() does not call back into this CPU. */ + flush_tlb_other(mm); } smp_call_function(ipi_flush_icache_page, mm, 1); From 16266d50c51eb5eb98f1c345c5aa9ff359a305aa Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:33:10 +0200 Subject: [PATCH 44/57] alpha: do not clear remote MMU contexts in migrate_flush_tlb_page() After the on_each_cpu() rendezvous, migrate_flush_tlb_page() walks the other CPUs and zeroes their mm->context[cpu] when mm_users is at most one, described as mimicking flush_tlb_mm()'s mm_users<=1 optimization. It is not one. flush_tlb_mm() tests mm_users before deciding whether to send the IPIs; here every CPU has already been visited and waited for, so nothing is saved. The callback has also just set each CPU's own slot correctly, so the loop only overwrites it, and does so while that CPU may still be running in the address space. Remove it. The shootdown shortcuts in flush_tlb_mm(), flush_tlb_page() and flush_icache_user_page() clear remote slots too; once the next patch removes those as well, every runtime update of mm->context[cpu] is made by CPU cpu itself. That patch relies on the invariant when it reads those slots to decide whether a shootdown can be skipped, so this one is tagged for stable as its prerequisite. Fixes: dd5712f3379c ("alpha: fix user-space corruption during memory compaction") Cc: stable@vger.kernel.org Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010144621.974850-2-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/mm/tlbflush.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/arch/alpha/mm/tlbflush.c b/arch/alpha/mm/tlbflush.c index ccbc317b9a34..239c72b8a741 100644 --- a/arch/alpha/mm/tlbflush.c +++ b/arch/alpha/mm/tlbflush.c @@ -90,22 +90,6 @@ void migrate_flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) */ preempt_disable(); on_each_cpu(ipi_flush_mm_and_page, &d, 1); - - /* - * mimic flush_tlb_mm()'s mm_users<=1 optimization. - */ - if (atomic_read(&mm->mm_users) <= 1) { - - int cpu, this_cpu; - this_cpu = smp_processor_id(); - - for (cpu = 0; cpu < NR_CPUS; cpu++) { - if (!cpu_online(cpu) || cpu == this_cpu) - continue; - if (READ_ONCE(mm->context[cpu])) - WRITE_ONCE(mm->context[cpu], 0); - } - } preempt_enable(); } From fe221b7b2d3ecd042dd391f74ff6a6fdeb7c2038 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 16:33:11 +0200 Subject: [PATCH 45/57] alpha: do not skip TLB shootdown IPIs for kthread-borrowed mms flush_tlb_mm(), flush_tlb_page() and flush_icache_user_page() skip the shootdown IPI when mm_users <= 1, on the assumption that no other CPU can be running the mm. A task that borrows an mm through kthread_use_mm() takes mmgrab() rather than mmget(), so it never appears in mm_users, and kthread_use_mm() may be handed an mm that is already the caller's active_mm and loaded on that CPU. Such a CPU was not only left without an IPI, its mm->context[cpu] was cleared from under it. mm->context[cpu] is already the record of which CPUs hold an ASN for the mm. Test it rather than clearing it: take the shortcut only when no other CPU has one, and otherwise fall through to the IPI, which invalidates those CPUs properly. A CPU that merely ran the mm in the past also holds a context and now costs an IPI, which errs in the safe direction. Change ipi_flush_tlb_mm(), ipi_flush_icache_page() and the migration callback ipi_flush_mm_and_page() to test current->mm, like ipi_flush_tlb_page(). A CPU that only retains the mm lazily then clears its own slot through flush_tlb_other(), rather than publishing a new ASN on every IPI or migration rendezvous. The migration callback still does its per-VA tbi() afterwards. Returning to the mm allocates a fresh ASN through ev5_switch_mm(), or through the direct-load path when kthread_use_mm() switches current's mm. Once these historical slots are gone, the shortcut is available again if mm_users remains at most one and no other CPU has taken or kept a context. An explicit kthread_use_mm() borrower has current->mm set and still receives the invalidate it needs. The caller-side tests in flush_tlb_mm() and flush_icache_user_page() remain current->active_mm. A kernel thread that flushes its lazily held mm can therefore publish its own context slot. Those branches also perform the shortcut check, which excludes the calling CPU's slot, so leave them unchanged. This is distinct from a remote lazy CPU keeping its slot populated through repeated callbacks. Together with the preceding patch, dropping these clearing loops leaves every runtime write to mm->context[] targeting the writing CPU's own slot, so the array becomes a lockless publication of which CPUs may be using the mm, with a single writer per slot. Mark the two stores that are now observed from other CPUs; flush_tlb_other() already uses WRITE_ONCE(). The uniprocessor flush_icache_user_page() is left alone, as nothing reads another CPU's slot there, and init_new_context() runs before the mm is shared. Order the slot reads after the page-table changes with smp_mb(). On the publishing CPU, the store in ev5_switch_mm() precedes the mb() in Alpha's arch_spin_unlock(), when finish_lock_switch() drops the rq lock. This relies on Alpha's unlock implementation, not a generic scheduler promise of a full barrier on unlock; document that reliance at the store. Also issue smp_mb() after publishing in __load_new_mm_context(), before loading and using the mm. This covers every caller, including check_mmu_context(), which can publish a replacement after finish_lock_switch() has already run. This requires the preceding removal of remote slot clearing from migrate_flush_tlb_page(). It also requires the stale-TLB series to set need_new_asn on both the reused and newly allocated ASN paths. Otherwise an IPI between finish_lock_switch() and check_mmu_context() can zero a new slot while asn_lock is set, and the task can return to user space with a live ASN that the shortcut cannot see. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: # 53ab52cfe5ce: alpha: load the MMU context when switch_mm() switches the current task Cc: # 01df8f401888: alpha: run check_mmu_context() from finish_arch_post_lock_switch() Cc: # ef43cfc28c42: alpha: only use a targeted tbi() when the target mm is really current Cc: # 738cf8f7a78f: alpha: invalidate the local context in flush_tlb_page() Cc: # 62c92086f30e: alpha: fix the local TLB invalidate in flush_tlb_page() Cc: # 3faee097b939: alpha: fix the local TLB invalidate in the UP flush_tlb_page() Cc: # b2c3b654bb64: alpha: invalidate the local context in flush_tlb_mm() Cc: # f19ae6f51a38: alpha: invalidate the local context in flush_icache_user_page() Cc: # 471e7f9c890b: alpha: do not clear remote MMU contexts in migrate_flush_tlb_page() Cc: stable@vger.kernel.org Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010144621.974850-3-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/mmu_context.h | 7 +++- arch/alpha/kernel/smp.c | 52 ++++++++++++++-------------- arch/alpha/mm/fault.c | 4 ++- arch/alpha/mm/tlbflush.c | 2 +- 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/arch/alpha/include/asm/mmu_context.h b/arch/alpha/include/asm/mmu_context.h index f41d7c73fdd6..09f356934b90 100644 --- a/arch/alpha/include/asm/mmu_context.h +++ b/arch/alpha/include/asm/mmu_context.h @@ -159,7 +159,12 @@ ev5_switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm, mmc = next_mm->context[cpu]; if ((mmc ^ asn) & ~HARDWARE_ASN_MASK) { mmc = __get_new_mm_context(next_mm, cpu); - next_mm->context[cpu] = mmc; + /* + * Ordered before any user access through the mm by the + * mb() in arch_spin_unlock(), when finish_lock_switch() + * drops the rq lock. + */ + WRITE_ONCE(next_mm->context[cpu], mmc); } #ifdef CONFIG_SMP /* A deferred shootdown can also invalidate a newly allocated ASN. */ diff --git a/arch/alpha/kernel/smp.c b/arch/alpha/kernel/smp.c index e21bc3920bec..11768aa292a4 100644 --- a/arch/alpha/kernel/smp.c +++ b/arch/alpha/kernel/smp.c @@ -625,12 +625,30 @@ static void ipi_flush_tlb_mm(void *x) { struct mm_struct *mm = x; - if (mm == current->active_mm && !asn_locked()) + if (mm == current->mm && !asn_locked()) flush_tlb_current(mm); else flush_tlb_other(mm); } +/* True if a CPU other than this one holds an ASN for MM. */ +static bool +mm_context_elsewhere(struct mm_struct *mm) +{ + int cpu, this_cpu = smp_processor_id(); + + /* Pairs with the barrier the publishing CPU issues before using MM. */ + smp_mb(); + + for_each_online_cpu(cpu) { + if (cpu == this_cpu) + continue; + if (READ_ONCE(mm->context[cpu])) + return true; + } + return false; +} + void flush_tlb_mm(struct mm_struct *mm) { @@ -638,14 +656,8 @@ flush_tlb_mm(struct mm_struct *mm) if (mm == current->active_mm) { flush_tlb_current(mm); - if (atomic_read(&mm->mm_users) <= 1) { - int cpu, this_cpu = smp_processor_id(); - for (cpu = 0; cpu < NR_CPUS; cpu++) { - if (!cpu_online(cpu) || cpu == this_cpu) - continue; - if (mm->context[cpu]) - mm->context[cpu] = 0; - } + if (atomic_read(&mm->mm_users) <= 1 && + !mm_context_elsewhere(mm)) { preempt_enable(); return; } @@ -690,14 +702,8 @@ flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) /* As in ipi_flush_tlb_page(): a targeted tbi() needs MM current. */ if (mm == current->mm) { flush_tlb_current_page(mm, vma, addr); - if (atomic_read(&mm->mm_users) <= 1) { - int cpu, this_cpu = smp_processor_id(); - for (cpu = 0; cpu < NR_CPUS; cpu++) { - if (!cpu_online(cpu) || cpu == this_cpu) - continue; - if (mm->context[cpu]) - mm->context[cpu] = 0; - } + if (atomic_read(&mm->mm_users) <= 1 && + !mm_context_elsewhere(mm)) { preempt_enable(); return; } @@ -728,7 +734,7 @@ static void ipi_flush_icache_page(void *x) { struct mm_struct *mm = (struct mm_struct *) x; - if (mm == current->active_mm && !asn_locked()) + if (mm == current->mm && !asn_locked()) __load_new_mm_context(mm); else flush_tlb_other(mm); @@ -747,14 +753,8 @@ flush_icache_user_page(struct vm_area_struct *vma, struct page *page, if (mm == current->active_mm) { __load_new_mm_context(mm); - if (atomic_read(&mm->mm_users) <= 1) { - int cpu, this_cpu = smp_processor_id(); - for (cpu = 0; cpu < NR_CPUS; cpu++) { - if (!cpu_online(cpu) || cpu == this_cpu) - continue; - if (mm->context[cpu]) - mm->context[cpu] = 0; - } + if (atomic_read(&mm->mm_users) <= 1 && + !mm_context_elsewhere(mm)) { preempt_enable(); return; } diff --git a/arch/alpha/mm/fault.c b/arch/alpha/mm/fault.c index 24408b53197c..63a24afa7a15 100644 --- a/arch/alpha/mm/fault.c +++ b/arch/alpha/mm/fault.c @@ -70,7 +70,9 @@ __load_new_mm_context(struct mm_struct *next_mm) struct pcb_struct *pcb; mmc = __get_new_mm_context(next_mm, smp_processor_id()); - next_mm->context[smp_processor_id()] = mmc; + WRITE_ONCE(next_mm->context[smp_processor_id()], mmc); + /* Publish the context before this CPU can access the mm. */ + smp_mb(); pcb = ¤t_thread_info()->pcb; pcb->asn = mmc & HARDWARE_ASN_MASK; diff --git a/arch/alpha/mm/tlbflush.c b/arch/alpha/mm/tlbflush.c index 239c72b8a741..a2aef90e6234 100644 --- a/arch/alpha/mm/tlbflush.c +++ b/arch/alpha/mm/tlbflush.c @@ -66,7 +66,7 @@ static void ipi_flush_mm_and_page(void *x) struct tlb_mm_and_addr *d = x; /* Part 1: mm context side (Alpha uses ASN/context as a key mechanism). */ - if (d->mm == current->active_mm && !asn_locked()) + if (d->mm == current->mm && !asn_locked()) __load_new_mm_context(d->mm); else flush_tlb_other(d->mm); From b9fad53a62776359d3bb607bd0ba66e37d198260 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Sat, 10 Oct 2026 15:42:00 +0200 Subject: [PATCH 46/57] selftests/seccomp: add Alpha support to seccomp_bpf Enable the seccomp BPF selftests on Alpha with a fallback seccomp syscall number and architecture-specific syscall register accessors. Use the NT_PRSTATUS register layout rather than struct pt_regs. The register set contains r0-r30 followed by pc and unique. Read and update the syscall number in r1, and set both r0 and the r19/a3 error flag when synthesizing syscall results. At syscall entry, r19 still contains the fourth syscall argument, so callers setting a synthetic return value must also set the syscall number to -1 to skip execution. Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261010134214.974565-1-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- tools/testing/selftests/seccomp/seccomp_bpf.c | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c index 0622bc2acad4..79e8eb374cb2 100644 --- a/tools/testing/selftests/seccomp/seccomp_bpf.c +++ b/tools/testing/selftests/seccomp/seccomp_bpf.c @@ -136,6 +136,8 @@ struct seccomp_data { # define __NR_seccomp 354 # elif defined(__x86_64__) # define __NR_seccomp 317 +# elif defined(__alpha__) +# define __NR_seccomp 514 # elif defined(__arm__) # define __NR_seccomp 383 # elif defined(__aarch64__) @@ -1754,6 +1756,30 @@ TEST_F(TRACE_poke, getpid_runs_normally) # define ARCH_REGS struct user_regs_struct # define SYSCALL_NUM(_regs) (_regs).orig_eax # define SYSCALL_RET(_regs) (_regs).eax +#elif defined(__alpha__) +/* NT_PRSTATUS contains r0-r30, pc and unique, not struct pt_regs. */ +struct alpha_regs { + unsigned long regs[33]; +}; + +# define ARCH_REGS struct alpha_regs +/* The kernel keeps the mutable syscall number in r1. */ +# define SYSCALL_NUM(_regs) ((_regs).regs[1]) +# define SYSCALL_RET(_regs) ((_regs).regs[0]) +/* + * Alpha returns positive errno in r0 with the r19/a3 error flag set. + * At a syscall-entry stop, r19 is still the fourth syscall argument. + * Callers must also set the syscall number to -1 to skip the syscall + * before replacing r19 with the error flag. + */ +# define SYSCALL_RET_SET(_regs, _val) \ + do { \ + struct alpha_regs *__regs = &(_regs); \ + long __ret = (_val); \ + \ + __regs->regs[0] = __ret < 0 ? -__ret : __ret; \ + __regs->regs[19] = __ret < 0; \ + } while (0) #elif defined(__arm__) # define ARCH_REGS struct pt_regs # define SYSCALL_NUM(_regs) (_regs).ARM_r7 From bceba446fa2bc66a905ec0bbf6fd80e98701c1d1 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sun, 18 Oct 2026 10:22:01 -0400 Subject: [PATCH 47/57] alpha: fix stale user_stack_pointer() PALcode keeps the user stack pointer in the PCB, not the trap frame, so pt_regs->usp is only valid where entry code stores it: entSys, do_entArith() and do_entIF(). Interrupts, page faults and unaligned traps leave it stale, and ptrace and /proc//syscall read it through user_stack_pointer(). A user-mode pt_regs is at the top of its task's stack, so find the thread_info from it and use rdusp() for current or pcb.usp otherwise, as KSTK_ESP() and the regset code already do. Fixes: 8a2857bc2b30 ("alpha: enable regset-based ptrace and core dumps") Assisted-by: Claude:claude-opus-5-5 Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Acked-by: Ian Rogers Signed-off-by: Matt Turner Link: https://lore.kernel.org/r/20261018-alpha-perf-regs-v3-v3-1-52be85332c57@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/ptrace.h | 7 +------ arch/alpha/kernel/ptrace.c | 12 ++++++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/arch/alpha/include/asm/ptrace.h b/arch/alpha/include/asm/ptrace.h index 430e8dc27ff7..45d1229344b6 100644 --- a/arch/alpha/include/asm/ptrace.h +++ b/arch/alpha/include/asm/ptrace.h @@ -26,12 +26,7 @@ static inline unsigned long regs_return_value(struct pt_regs *regs) return regs->r0; } -/* Helpers for working with the user stack pointer */ -static inline unsigned long user_stack_pointer(struct pt_regs *regs) -{ - /* Valid for user-mode regs */ - return regs->usp; -} +unsigned long user_stack_pointer(struct pt_regs *regs); static __always_inline bool regs_irqs_disabled(struct pt_regs *regs) { diff --git a/arch/alpha/kernel/ptrace.c b/arch/alpha/kernel/ptrace.c index e1d8c2f69688..dba7e9905cfa 100644 --- a/arch/alpha/kernel/ptrace.c +++ b/arch/alpha/kernel/ptrace.c @@ -440,6 +440,18 @@ ptrace_cancel_bpt(struct task_struct * child) return (nsaved != 0); } +/* + * PALcode keeps the user stack pointer in the PCB, not the trap frame. + * A user-mode pt_regs sits at the top of its task's stack, so the + * thread_info is at the base. + */ +unsigned long user_stack_pointer(struct pt_regs *regs) +{ + struct thread_info *ti = (void *)((unsigned long)regs & -THREAD_SIZE); + + return ti == current_thread_info() ? rdusp() : ti->pcb.usp; +} + void user_enable_single_step(struct task_struct *child) { /* Mark single stepping. */ From 501bcb5953cc4d56537a321c00f9193e9fd68718 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sun, 18 Oct 2026 10:22:02 -0400 Subject: [PATCH 48/57] alpha: stop storing the user stack pointer in pt_regs Nothing reads pt_regs->usp now that user_stack_pointer() uses the PCB. Drop the stores, which saves a PAL_rdusp call on every syscall, and give the field back its old name. Assisted-by: Claude:claude-opus-5-5 Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Acked-by: Ian Rogers Signed-off-by: Matt Turner Link: https://lore.kernel.org/r/20261018-alpha-perf-regs-v3-v3-2-52be85332c57@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/uapi/asm/ptrace.h | 2 +- arch/alpha/kernel/asm-offsets.c | 1 - arch/alpha/kernel/entry.S | 5 ----- arch/alpha/kernel/traps.c | 8 -------- 4 files changed, 1 insertion(+), 15 deletions(-) diff --git a/arch/alpha/include/uapi/asm/ptrace.h b/arch/alpha/include/uapi/asm/ptrace.h index 9d86b2a1526e..72ed913a910f 100644 --- a/arch/alpha/include/uapi/asm/ptrace.h +++ b/arch/alpha/include/uapi/asm/ptrace.h @@ -43,7 +43,7 @@ struct pt_regs { unsigned long trap_a1; unsigned long trap_a2; /* This makes the stack 16-byte aligned as GCC expects */ - unsigned long usp; + unsigned long __pad0; /* These are saved by PAL-code: */ unsigned long ps; unsigned long pc; diff --git a/arch/alpha/kernel/asm-offsets.c b/arch/alpha/kernel/asm-offsets.c index c89792c86044..22ed3c975b1c 100644 --- a/arch/alpha/kernel/asm-offsets.c +++ b/arch/alpha/kernel/asm-offsets.c @@ -30,7 +30,6 @@ static void __used foo(void) DEFINE(HAE_CACHE, offsetof(struct alpha_machine_vector, hae_cache)); DEFINE(HAE_REG, offsetof(struct alpha_machine_vector, hae_register)); - DEFINE(PT_REGS_USP, offsetof(struct pt_regs, usp)); DEFINE(TI_FLAGS, offsetof(struct thread_info, flags)); DEFINE(TI_SYSCALL_WORK, offsetof(struct thread_info, syscall_work)); diff --git a/arch/alpha/kernel/entry.S b/arch/alpha/kernel/entry.S index bfa116d455fb..790389a15c3c 100644 --- a/arch/alpha/kernel/entry.S +++ b/arch/alpha/kernel/entry.S @@ -476,11 +476,6 @@ entSys: ldq $1, 0($sp) /* syscall nr from saved r0 */ stq $1, 8($sp) /* regs->r1 = shadow syscall nr */ - mov $0, $8 - call_pal PAL_rdusp - stq $0, PT_REGS_USP($sp) - mov $8, $0 - lda $8, 0x3fff bic $sp, $8, $8 diff --git a/arch/alpha/kernel/traps.c b/arch/alpha/kernel/traps.c index cfaef96c4ce8..5882c65b7ef1 100644 --- a/arch/alpha/kernel/traps.c +++ b/arch/alpha/kernel/traps.c @@ -30,12 +30,6 @@ #include "proto.h" -static __always_inline void alpha_snapshot_usp(struct pt_regs *regs) -{ - if (user_mode(regs)) - regs->usp = rdusp(); -} - void dik_show_regs(struct pt_regs *regs, unsigned long *r9_15) { @@ -186,7 +180,6 @@ do_entArith(unsigned long summary, unsigned long write_mask, { long si_code = FPE_FLTINV; - alpha_snapshot_usp(regs); if (summary & 1) { /* Software-completion summary bit is set, so try to emulate the instruction. If the processor supports @@ -208,7 +201,6 @@ do_entIF(unsigned long type, struct pt_regs *regs) { int signo, code; - alpha_snapshot_usp(regs); if (type == 3) { /* FEN fault */ /* Irritating users can call PAL_clrfen to disable the FPU for the process. The kernel will then trap in From c5280a56dc4cbcbbfd3256a0fd58071587e928b0 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sun, 18 Oct 2026 10:22:03 -0400 Subject: [PATCH 49/57] alpha: use user_stack_pointer() in KSTK_ESP() and dump_elf_thread() Both open-code the same PCB lookup. Assisted-by: Claude:claude-opus-5-5 Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Acked-by: Ian Rogers Signed-off-by: Matt Turner Link: https://lore.kernel.org/r/20261018-alpha-perf-regs-v3-v3-3-52be85332c57@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/processor.h | 3 +-- arch/alpha/kernel/process.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/alpha/include/asm/processor.h b/arch/alpha/include/asm/processor.h index c461a26d47b3..73251cd671e0 100644 --- a/arch/alpha/include/asm/processor.h +++ b/arch/alpha/include/asm/processor.h @@ -43,8 +43,7 @@ unsigned long __get_wchan(struct task_struct *p); #define KSTK_EIP(tsk) (task_pt_regs(tsk)->pc) -#define KSTK_ESP(tsk) \ - ((tsk) == current ? rdusp() : task_thread_info(tsk)->pcb.usp) +#define KSTK_ESP(tsk) user_stack_pointer(task_pt_regs(tsk)) #define cpu_relax() barrier() diff --git a/arch/alpha/kernel/process.c b/arch/alpha/kernel/process.c index 06522451f018..7861c9e767b7 100644 --- a/arch/alpha/kernel/process.c +++ b/arch/alpha/kernel/process.c @@ -318,7 +318,7 @@ dump_elf_thread(elf_greg_t *dest, struct pt_regs *pt, struct thread_info *ti) dest[27] = pt->r27; dest[28] = pt->r28; dest[29] = pt->gp; - dest[30] = ti == current_thread_info() ? rdusp() : ti->pcb.usp; + dest[30] = user_stack_pointer(pt); dest[31] = pt->pc; /* Once upon a time this was the PS value. Which is stupid From 9280e99d87ddd8c6c7fd83f9ea1585967b3dcd60 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sun, 18 Oct 2026 10:22:04 -0400 Subject: [PATCH 50/57] alpha: add HAVE_PERF_REGS and HAVE_PERF_USER_STACK_DUMP support Let perf sample registers and dump the user stack, which is what the perf tool needs for --call-graph dwarf. The register indices follow the hardware numbering, so r0-r30 map directly to DWARF register numbers, with pc after them. r9-r15 are callee-saved and live in switch_stack rather than pt_regs, so they cannot be sampled. Without r15 an unwinder cannot recover the CFA of a first frame that uses $15 as its frame pointer. sp is not in pt_regs either: for user frames it comes from user_stack_pointer(), and for kernel frames (PERF_SAMPLE_REGS_INTR) it is the end of the trap frame, as in stacktrace.c. Software events such as context switches and tracepoints hand perf a per-CPU pt_regs filled in by perf_fetch_caller_regs() instead of a trap frame. It is not on the stack and has no room for sp, so report sp as 0 there rather than the address of the per-CPU buffer. Assisted-by: Claude:claude-opus-5-5 Acked-by: Ian Rogers Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Signed-off-by: Matt Turner Link: https://lore.kernel.org/r/20261018-alpha-perf-regs-v3-v3-4-52be85332c57@gmail.com Signed-off-by: Magnus Lindholm --- .../features/perf/perf-regs/arch-support.txt | 2 +- .../perf/perf-stackdump/arch-support.txt | 2 +- arch/alpha/Kconfig | 2 + arch/alpha/include/uapi/asm/perf_regs.h | 45 +++++++++++++ arch/alpha/kernel/Makefile | 1 + arch/alpha/kernel/perf_regs.c | 64 +++++++++++++++++++ 6 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 arch/alpha/include/uapi/asm/perf_regs.h create mode 100644 arch/alpha/kernel/perf_regs.c diff --git a/Documentation/features/perf/perf-regs/arch-support.txt b/Documentation/features/perf/perf-regs/arch-support.txt index 4c9a5a012075..7d736d678fd4 100644 --- a/Documentation/features/perf/perf-regs/arch-support.txt +++ b/Documentation/features/perf/perf-regs/arch-support.txt @@ -6,7 +6,7 @@ ----------------------- | arch |status| ----------------------- - | alpha: | TODO | + | alpha: | ok | | arc: | TODO | | arm: | ok | | arm64: | ok | diff --git a/Documentation/features/perf/perf-stackdump/arch-support.txt b/Documentation/features/perf/perf-stackdump/arch-support.txt index dd362b5cb638..17b84ea7769d 100644 --- a/Documentation/features/perf/perf-stackdump/arch-support.txt +++ b/Documentation/features/perf/perf-stackdump/arch-support.txt @@ -6,7 +6,7 @@ ----------------------- | arch |status| ----------------------- - | alpha: | TODO | + | alpha: | ok | | arc: | TODO | | arm: | ok | | arm64: | ok | diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index b2c359b947f6..8fafbfabd1e5 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -41,6 +41,8 @@ config ALPHA select HAVE_PAGE_SIZE_8KB select HAVE_PCSPKR_PLATFORM select HAVE_PERF_EVENTS + select HAVE_PERF_REGS + select HAVE_PERF_USER_STACK_DUMP select NEED_DMA_MAP_STATE select NEED_SG_DMA_LENGTH select GENERIC_IRQ_PROBE diff --git a/arch/alpha/include/uapi/asm/perf_regs.h b/arch/alpha/include/uapi/asm/perf_regs.h new file mode 100644 index 000000000000..b6d2dcf6e184 --- /dev/null +++ b/arch/alpha/include/uapi/asm/perf_regs.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _ASM_ALPHA_PERF_REGS_H +#define _ASM_ALPHA_PERF_REGS_H + +/* + * Indices 0-30 match the hardware (and DWARF) register numbers. + * r9-r15 are not saved in pt_regs and cannot be sampled. + */ +enum perf_event_alpha_regs { + PERF_REG_ALPHA_R0, + PERF_REG_ALPHA_R1, + PERF_REG_ALPHA_R2, + PERF_REG_ALPHA_R3, + PERF_REG_ALPHA_R4, + PERF_REG_ALPHA_R5, + PERF_REG_ALPHA_R6, + PERF_REG_ALPHA_R7, + PERF_REG_ALPHA_R8, + PERF_REG_ALPHA_R9, + PERF_REG_ALPHA_R10, + PERF_REG_ALPHA_R11, + PERF_REG_ALPHA_R12, + PERF_REG_ALPHA_R13, + PERF_REG_ALPHA_R14, + PERF_REG_ALPHA_R15, + PERF_REG_ALPHA_R16, + PERF_REG_ALPHA_R17, + PERF_REG_ALPHA_R18, + PERF_REG_ALPHA_R19, + PERF_REG_ALPHA_R20, + PERF_REG_ALPHA_R21, + PERF_REG_ALPHA_R22, + PERF_REG_ALPHA_R23, + PERF_REG_ALPHA_R24, + PERF_REG_ALPHA_R25, + PERF_REG_ALPHA_R26, + PERF_REG_ALPHA_R27, + PERF_REG_ALPHA_R28, + PERF_REG_ALPHA_GP, + PERF_REG_ALPHA_SP, + PERF_REG_ALPHA_PC, + PERF_REG_ALPHA_MAX, +}; + +#endif /* _ASM_ALPHA_PERF_REGS_H */ diff --git a/arch/alpha/kernel/Makefile b/arch/alpha/kernel/Makefile index 4ea5c189e60e..0c04b89676ed 100644 --- a/arch/alpha/kernel/Makefile +++ b/arch/alpha/kernel/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_PCI) += pci.o pci_iommu.o pci-sysfs.o obj-$(CONFIG_SRM_ENV) += srm_env.o obj-$(CONFIG_MODULES) += module.o obj-$(CONFIG_PERF_EVENTS) += perf_event.o +obj-$(CONFIG_HAVE_PERF_REGS) += perf_regs.o obj-$(CONFIG_RTC_DRV_ALPHA) += rtc.o obj-$(CONFIG_AUDIT) += audit.o diff --git a/arch/alpha/kernel/perf_regs.c b/arch/alpha/kernel/perf_regs.c new file mode 100644 index 000000000000..33ea2e831ba0 --- /dev/null +++ b/arch/alpha/kernel/perf_regs.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +#define REG(idx, field) \ + [PERF_REG_ALPHA_##idx] = offsetof(struct pt_regs, field) + +#define REG_RESERVED (~GENMASK_ULL(PERF_REG_ALPHA_MAX - 1, 0) | \ + GENMASK_ULL(PERF_REG_ALPHA_R15, PERF_REG_ALPHA_R9)) + +static const unsigned int pt_regs_offset[PERF_REG_ALPHA_MAX] = { + REG(R0, r0), REG(R1, r1), REG(R2, r2), REG(R3, r3), + REG(R4, r4), REG(R5, r5), REG(R6, r6), REG(R7, r7), + REG(R8, r8), REG(R16, r16), REG(R17, r17), REG(R18, r18), + REG(R19, r19), REG(R20, r20), REG(R21, r21), REG(R22, r22), + REG(R23, r23), REG(R24, r24), REG(R25, r25), REG(R26, r26), + REG(R27, r27), REG(R28, r28), REG(GP, gp), REG(PC, pc), +}; + +u64 perf_reg_value(struct pt_regs *regs, int idx) +{ + if (idx == PERF_REG_ALPHA_SP) { + if (user_mode(regs)) + return user_stack_pointer(regs); + /* + * Software events built by perf_fetch_caller_regs() use a + * per-CPU pt_regs rather than a trap frame, and pt_regs has + * nowhere to record sp, so report none. + */ + if (!object_is_on_stack(regs)) + return 0; + /* Same kernel sp approximation as stacktrace.c. */ + return (unsigned long)(regs + 1); + } + + if (WARN_ON_ONCE((u32)idx >= PERF_REG_ALPHA_MAX || + BIT_ULL(idx) & REG_RESERVED)) + return 0; + + return *(unsigned long *)((void *)regs + pt_regs_offset[idx]); +} + +int perf_reg_validate(u64 mask) +{ + if (!mask || mask & REG_RESERVED) + return -EINVAL; + + return 0; +} + +u64 perf_reg_abi(struct task_struct *task) +{ + return PERF_SAMPLE_REGS_ABI_64; +} + +void perf_get_regs_user(struct perf_regs *regs_user, + struct pt_regs *regs) +{ + regs_user->regs = task_pt_regs(current); + regs_user->abi = perf_reg_abi(current); +} From 1a85a03fc2fe180c1ddc91b4a801d313321d9e53 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sun, 18 Oct 2026 10:22:05 -0400 Subject: [PATCH 51/57] alpha: fill in pc for software event samples Context switch and tracepoint events have no trap frame, so perf builds one with perf_fetch_caller_regs(). Alpha does not define perf_arch_fetch_caller_regs(), which leaves that pt_regs zeroed, and these samples report pc as 0. Record the caller's address as pc. The zero ps already marks the frame as kernel mode, and pt_regs has nowhere to keep sp, so pc is all that can be filled in. perf_fetch_caller_regs() gets that address from CALLER_ADDR0, which alpha defines as 0 when CONFIG_FRAME_POINTER is disabled. Finding the level zero return address does not need a frame pointer, so make alpha_ftrace_return_address0() available in both configurations. Only ftrace_return_address(n) still depends on frame pointers. Reported-by: Magnus Lindholm Closes: https://lore.kernel.org/all/CA%2B%3DFv5S8-Zk8-LUpZnWiBfTGSQXthepmRc_-a9O%2BpEPmVW0mRA@mail.gmail.com/ Suggested-by: Magnus Lindholm Assisted-by: Claude:claude-opus-5-5 Reviewed-by: Magnus Lindholm Acked-by: Ian Rogers Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Link: https://lore.kernel.org/r/20261018-alpha-perf-regs-v3-v3-5-52be85332c57@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/ftrace.h | 5 ++--- arch/alpha/include/asm/perf_event.h | 7 +++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/arch/alpha/include/asm/ftrace.h b/arch/alpha/include/asm/ftrace.h index 7ec44134c804..5b75a294a7c9 100644 --- a/arch/alpha/include/asm/ftrace.h +++ b/arch/alpha/include/asm/ftrace.h @@ -2,8 +2,6 @@ #ifndef _ASM_ALPHA_FTRACE_H #define _ASM_ALPHA_FTRACE_H -#ifdef CONFIG_FRAME_POINTER - static void *alpha_ftrace_return_address0(void) noinline notrace; static void *alpha_ftrace_return_address0(void) @@ -13,6 +11,8 @@ static void *alpha_ftrace_return_address0(void) #define ftrace_return_address0 alpha_ftrace_return_address0() +#ifdef CONFIG_FRAME_POINTER + /* * __builtin_return_address() requires a constant integer argument. * Keep this as a macro so the value is seen at the callsite. @@ -21,7 +21,6 @@ static void *alpha_ftrace_return_address0(void) #else /* !CONFIG_FRAME_POINTER */ -#define ftrace_return_address0 0UL #define ftrace_return_address(n) ((void)(n), 0UL) #endif /* CONFIG_FRAME_POINTER */ diff --git a/arch/alpha/include/asm/perf_event.h b/arch/alpha/include/asm/perf_event.h index 5996e7a6757e..72536566014a 100644 --- a/arch/alpha/include/asm/perf_event.h +++ b/arch/alpha/include/asm/perf_event.h @@ -1,4 +1,11 @@ #ifndef __ASM_ALPHA_PERF_EVENT_H #define __ASM_ALPHA_PERF_EVENT_H +/* + * Software events have no trap frame, so perf_fetch_caller_regs() fills + * in a zeroed pt_regs. A zero ps already means kernel mode; only pc is + * missing. + */ +#define perf_arch_fetch_caller_regs(regs, __ip) ((regs)->pc = (__ip)) + #endif /* __ASM_ALPHA_PERF_EVENT_H */ From 2c8792df90e565d5a280549f37f5c5563f76ebe3 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Thu, 8 Oct 2026 23:26:54 +0200 Subject: [PATCH 52/57] alpha: move Tsunami machine check handling to err_tsunami.c Move the existing handler and PCI error clearing helpers without changing their behavior. Build the error objects for ALPHA_TSUNAMI so that all Tsunami configurations, including non-generic Shark, can use them. Signed-off-by: Magnus Lindholm Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/Makefile | 4 ++- arch/alpha/kernel/core_tsunami.c | 34 ----------------------- arch/alpha/kernel/err_tsunami.c | 47 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 35 deletions(-) create mode 100644 arch/alpha/kernel/err_tsunami.c diff --git a/arch/alpha/kernel/Makefile b/arch/alpha/kernel/Makefile index 0c04b89676ed..d56b0ffec96b 100644 --- a/arch/alpha/kernel/Makefile +++ b/arch/alpha/kernel/Makefile @@ -40,7 +40,7 @@ obj-y += err_ev7.o err_titan.o err_marvel.o endif obj-y += irq_pyxis.o irq_i8259.o irq_srm.o -obj-y += err_ev6.o +obj-y += err_ev6.o err_tsunami.o obj-y += es1888.o smc37c669.o smc37c93x.o pc873xx.o gct.o obj-y += srmcons.o @@ -90,4 +90,6 @@ obj-$(CONFIG_ALPHA_MARVEL) += err_ev7.o err_marvel.o obj-$(CONFIG_ALPHA_NAUTILUS) += err_ev6.o obj-$(CONFIG_ALPHA_TITAN) += err_ev6.o err_titan.o +obj-$(CONFIG_ALPHA_TSUNAMI) += err_tsunami.o + endif # GENERIC diff --git a/arch/alpha/kernel/core_tsunami.c b/arch/alpha/kernel/core_tsunami.c index fc1ab73f23de..d435ec9fb828 100644 --- a/arch/alpha/kernel/core_tsunami.c +++ b/arch/alpha/kernel/core_tsunami.c @@ -448,37 +448,3 @@ tsunami_kill_arch(int mode) if (TSUNAMI_cchip->csc.csr & 1L<<14) tsunami_kill_one_pchip(TSUNAMI_pchip1, 1); } - -static inline void -tsunami_pci_clr_err_1(tsunami_pchip *pchip) -{ - pchip->perror.csr; - pchip->perror.csr = 0x040; - mb(); - pchip->perror.csr; -} - -static inline void -tsunami_pci_clr_err(void) -{ - tsunami_pci_clr_err_1(TSUNAMI_pchip0); - - /* TSUNAMI and TYPHOON can have 2, but might only have 1 (DS10) */ - if (TSUNAMI_cchip->csc.csr & 1L<<14) - tsunami_pci_clr_err_1(TSUNAMI_pchip1); -} - -void -tsunami_machine_check(unsigned long vector, unsigned long la_ptr) -{ - /* Clear error before any reporting. */ - mb(); - mb(); /* magic */ - draina(); - tsunami_pci_clr_err(); - wrmces(0x7); - mb(); - - process_mcheck_info(vector, la_ptr, "TSUNAMI", - mcheck_expected(smp_processor_id())); -} diff --git a/arch/alpha/kernel/err_tsunami.c b/arch/alpha/kernel/err_tsunami.c new file mode 100644 index 000000000000..1045b920fa6b --- /dev/null +++ b/arch/alpha/kernel/err_tsunami.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Machine check handling for Tsunami/Typhoon systems. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "err_impl.h" +#include "proto.h" +#include "pci_impl.h" + +static inline void +tsunami_pci_clr_err_1(tsunami_pchip *pchip) +{ + pchip->perror.csr; + pchip->perror.csr = 0x040; + mb(); + pchip->perror.csr; +} + +static inline void +tsunami_pci_clr_err(void) +{ + tsunami_pci_clr_err_1(TSUNAMI_pchip0); + + /* TSUNAMI and TYPHOON can have 2, but might only have 1 (DS10) */ + if (TSUNAMI_cchip->csc.csr & 1L<<14) + tsunami_pci_clr_err_1(TSUNAMI_pchip1); +} + +void +tsunami_machine_check(unsigned long vector, unsigned long la_ptr) +{ + /* Clear error before any reporting. */ + mb(); + mb(); /* magic */ + draina(); + tsunami_pci_clr_err(); + wrmces(0x7); + mb(); + + process_mcheck_info(vector, la_ptr, "TSUNAMI", + mcheck_expected(smp_processor_id())); +} From 6634cb1490ba8d3d7fccfbbf4028e702da4e2e8f Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Thu, 8 Oct 2026 23:26:54 +0200 Subject: [PATCH 53/57] alpha: describe the Tsunami system machine check frame Define the five system-specific quadwords in the SRM logout frame, as documented in the AlphaServer ES40 Service Guide, Table D-19. Signed-off-by: Magnus Lindholm Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/core_tsunami.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/alpha/include/asm/core_tsunami.h b/arch/alpha/include/asm/core_tsunami.h index 3391e95754f2..fe3fd4570fac 100644 --- a/arch/alpha/include/asm/core_tsunami.h +++ b/arch/alpha/include/asm/core_tsunami.h @@ -283,6 +283,11 @@ union TPchipPERRMASK { * Data structure for handling TSUNAMI machine checks: */ struct el_TSUNAMI_sysdata_mcheck { + u64 sesf; + u64 dir; + u64 misc; + u64 p0_perror; + u64 p1_perror; }; From a182df7ceae5d75a7fdc5dc211ccebdd821697bf Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Fri, 9 Oct 2026 01:12:59 +0200 Subject: [PATCH 54/57] alpha: bound EV6 logout decoding by the processor area The ES40 Service Guide, Table D-19, documents a correctable frame with its system area at 0x58 and total size 0x80. The existing EV6 decoder interprets system words as extended CPU registers and reads past its end. This establishes the layout for ES40; whether Titan and Nautilus PALcode also emits this short frame has not been verified. Validate the processor area before decoding it and only print extended registers when present. Preserve rejected frames with a raw dump rather than trusting their invalid offsets. Cap the dump at 1 KiB and round down to complete quadwords. This still relies on firmware supplying an accessible frame of its advertised size; the cap is not pointer validation. Signed-off-by: Magnus Lindholm Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/err_common.c | 13 ++++++++++++ arch/alpha/kernel/err_ev6.c | 38 +++++++++++++++++++++++++--------- arch/alpha/kernel/err_impl.h | 1 + 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/arch/alpha/kernel/err_common.c b/arch/alpha/kernel/err_common.c index 94e1b3dcf6d4..9271ea425a7b 100644 --- a/arch/alpha/kernel/err_common.c +++ b/arch/alpha/kernel/err_common.c @@ -44,6 +44,19 @@ mchk_dump_mem(void *data, size_t length, char **annotation) } } +/* Preserve rejected frames without trusting their internal offsets. */ +void +mchk_dump_logout_frame_raw(struct el_common *header) +{ + size_t length = header->size; + + /* Bound diagnostic output and never read a partial final quadword. */ + if (length > 1024) + length = 1024; + length &= ~7UL; + mchk_dump_mem(header, length, NULL); +} + void mchk_dump_logout_frame(struct el_common *mchk_header) { diff --git a/arch/alpha/kernel/err_ev6.c b/arch/alpha/kernel/err_ev6.c index 8144f2045b5b..f12299ae1f24 100644 --- a/arch/alpha/kernel/err_ev6.c +++ b/arch/alpha/kernel/err_ev6.c @@ -190,6 +190,25 @@ ev6_process_logout_frame(struct el_common *mchk_header, int print) (struct el_common_EV6_mcheck *)mchk_header; int status = MCHK_DISPOSITION_UNKNOWN_ERROR; + /* + * ES40 correctable frames end the CPU area before EXC_ADDR (Table + * D-19). Validate both regions before decoding or dumping any data. + */ + if (mchk_header->proc_offset != offsetof(struct el_common_EV6_mcheck, + I_STAT) || + mchk_header->sys_offset < offsetof(struct el_common_EV6_mcheck, + EXC_ADDR) || + mchk_header->sys_offset > mchk_header->size || + ((mchk_header->sys_offset | mchk_header->size) & 7)) { + if (print) { + printk("%s Invalid EV6 logout frame: size %x, CPU %x, system %x\n", + err_print_prefix, mchk_header->size, + mchk_header->proc_offset, mchk_header->sys_offset); + mchk_dump_logout_frame_raw(mchk_header); + } + return MCHK_DISPOSITION_UNKNOWN_ERROR; + } + status |= ev6_parse_ibox(ev6mchk->I_STAT, print); status |= ev6_parse_mbox(ev6mchk->MM_STAT, ev6mchk->DC_STAT, ev6mchk->C_STAT, print); @@ -203,16 +222,15 @@ ev6_process_logout_frame(struct el_common *mchk_header, int print) if (status != MCHK_DISPOSITION_DISMISS) { char *saved_err_prefix = err_print_prefix; - /* - * Dump some additional information from the frame - */ - printk("%s EXC_ADDR: 0x%016lx IER_CM: 0x%016lx" - " ISUM: 0x%016lx\n" - " PAL_BASE: 0x%016lx I_CTL: 0x%016lx" - " PCTX: 0x%016lx\n", - err_print_prefix, - ev6mchk->EXC_ADDR, ev6mchk->IER_CM, ev6mchk->ISUM, - ev6mchk->PAL_BASE, ev6mchk->I_CTL, ev6mchk->PCTX); + /* These registers are absent from short correctable frames. */ + if (mchk_header->sys_offset >= sizeof(*ev6mchk)) + printk("%s EXC_ADDR: 0x%016lx IER_CM: 0x%016lx" + " ISUM: 0x%016lx\n" + " PAL_BASE: 0x%016lx I_CTL: 0x%016lx" + " PCTX: 0x%016lx\n", + err_print_prefix, + ev6mchk->EXC_ADDR, ev6mchk->IER_CM, ev6mchk->ISUM, + ev6mchk->PAL_BASE, ev6mchk->I_CTL, ev6mchk->PCTX); if (status == MCHK_DISPOSITION_UNKNOWN_ERROR) { printk("%s UNKNOWN error, frame follows:\n", diff --git a/arch/alpha/kernel/err_impl.h b/arch/alpha/kernel/err_impl.h index 737b958a586d..5bef23a00942 100644 --- a/arch/alpha/kernel/err_impl.h +++ b/arch/alpha/kernel/err_impl.h @@ -47,6 +47,7 @@ struct el_subpacket_handler { extern char *err_print_prefix; extern void mchk_dump_mem(void *, size_t, char **); +extern void mchk_dump_logout_frame_raw(struct el_common *header); extern void mchk_dump_logout_frame(struct el_common *); extern void el_print_timestamp(union el_timestamp *); extern void el_process_subpackets(struct el_subpacket *, int); From ea2b073b4f31f334354479b993160788610c779d Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Fri, 9 Oct 2026 01:14:29 +0200 Subject: [PATCH 55/57] alpha: decode and acknowledge Tsunami system machine checks Decode PCI and ECC transactions separately, honor invalid transaction information, and select Pchip errors using the logout summary. Report master aborts without importing Titan-specific dismissal policy. Delegate processor vectors to the bounded EV6 decoder. On system vectors 660/620, capture live PERROR and MISC before acknowledging their defined W1C bits and report live status absent from the saved frame. Leave live chipset registers alone on CPU and environmental vectors: HRM 10.2.5.6 warns that PERROR can have INV set before the error reaches DRIR. Consuming that state from a CPU vector could lose the transaction and its pending system-error notification. Leave PERRMASK as configured by firmware. HRM 10.2.5.7 says a cleared mask bit prevents the corresponding PERROR bit from being set, including by PERRSET. Table 10-43 gives a reset value of zero, so this reporting depends on firmware enabling the errors; changing masks does not establish that previously latched state is cleared. Table 10-42 marks PERROR bit 9 reserved (MBZ/RAZ); remove the obsolete RTO definition and reserve its bitfield. Use the system vector and frame bounds without restricting the reason code to the ES40 values. Preserve invalid frames and unknown environmental layouts in bounded raw dumps. Print CPU registers for uncorrectable system errors, including the PC needed to diagnose NXM. Gate correctable system register dumps on CONFIG_VERBOSE_MCHECK and alpha_verbose_mcheck while retaining their error decode. Keep transaction information unavailable whenever SERR is set, as required by HRM 8.8.2.6. Table 10-42 selects ECC fields for CRE/UECC. The first error freezes PERROR and only LOST can be set afterwards, so mixed error bits were detected together. The expected-probe path has no active Tsunami writer. Reporting and acknowledgment do not establish recovery; panic policy remains separate. Signed-off-by: Magnus Lindholm Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/core_tsunami.h | 3 +- arch/alpha/kernel/Makefile | 2 +- arch/alpha/kernel/err_tsunami.c | 264 ++++++++++++++++++++++++-- 3 files changed, 246 insertions(+), 23 deletions(-) diff --git a/arch/alpha/include/asm/core_tsunami.h b/arch/alpha/include/asm/core_tsunami.h index fe3fd4570fac..5c5d3ff849ed 100644 --- a/arch/alpha/include/asm/core_tsunami.h +++ b/arch/alpha/include/asm/core_tsunami.h @@ -105,7 +105,6 @@ extern int TSUNAMI_bootcpu; #define perror_m_ta 0x40 #define perror_m_rdpe 0x80 #define perror_m_nds 0x100 -#define perror_m_rto 0x200 #define perror_m_uecc 0x400 #define perror_m_cre 0x800 #define perror_m_addrl 0xFFFFFFFF0000UL @@ -123,7 +122,7 @@ union TPchipPERROR { unsigned perror_v_ta : 1; unsigned perror_v_rdpe : 1; unsigned perror_v_nds : 1; - unsigned perror_v_rto : 1; + unsigned perror_v_reserved : 1; /* PERROR<9>: MBZ, RAZ */ unsigned perror_v_uecc : 1; unsigned perror_v_cre : 1; unsigned perror_v_rsvd1 : 4; diff --git a/arch/alpha/kernel/Makefile b/arch/alpha/kernel/Makefile index d56b0ffec96b..9d35715bd440 100644 --- a/arch/alpha/kernel/Makefile +++ b/arch/alpha/kernel/Makefile @@ -90,6 +90,6 @@ obj-$(CONFIG_ALPHA_MARVEL) += err_ev7.o err_marvel.o obj-$(CONFIG_ALPHA_NAUTILUS) += err_ev6.o obj-$(CONFIG_ALPHA_TITAN) += err_ev6.o err_titan.o -obj-$(CONFIG_ALPHA_TSUNAMI) += err_tsunami.o +obj-$(CONFIG_ALPHA_TSUNAMI) += err_ev6.o err_tsunami.o endif # GENERIC diff --git a/arch/alpha/kernel/err_tsunami.c b/arch/alpha/kernel/err_tsunami.c index 1045b920fa6b..c413acd45bff 100644 --- a/arch/alpha/kernel/err_tsunami.c +++ b/arch/alpha/kernel/err_tsunami.c @@ -1,5 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 -/* Machine check handling for Tsunami/Typhoon systems. */ +/* + * Machine check handling for Tsunami/Typhoon systems. + * Author: Magnus Lindholm + * Based on Titan error handling by Jeff Wiedemeier (Compaq). + * Register definitions: 21272 Hardware Reference Manual, revision 4.0. + */ #include #include #include @@ -12,36 +17,255 @@ #include "proto.h" #include "pci_impl.h" -static inline void -tsunami_pci_clr_err_1(tsunami_pchip *pchip) +#define TSUNAMI_PERROR_ERRMASK 0xdffUL +#define TSUNAMI_PERROR_ECCMASK (perror_m_uecc | perror_m_cre) +#define TSUNAMI_PERROR_INV (1UL << 51) +#define TSUNAMI_MISC_NXM (1UL << 28) + +static int +tsunami_frame_valid(struct el_common *header, size_t size) { - pchip->perror.csr; - pchip->perror.csr = 0x040; + return !((header->sys_offset | header->size) & 7) && + header->sys_offset >= sizeof(*header) && + header->sys_offset <= header->size && + size <= header->size - header->sys_offset; +} + +static int +tsunami_parse_p_perror(int which, u64 perror, int print) +{ + static const char * const errors[] = { + "Lost error", "SERR asserted", "PERR asserted", + "Delayed-completion retry timeout", "Invalid scatter-gather PTE", + "Address parity error", "Target abort", "Read data parity error", + "No DEVSEL (master abort)", NULL, + "Uncorrectable ECC error", "Correctable ECC error" + }; + static const char * const pci_cmd[] = { + "Interrupt acknowledge", "Special cycle", "I/O read", "I/O write", + "Reserved", "Reserved", "Memory read", "Memory write", + "Reserved", "Reserved", "Configuration read", "Configuration write", + "Memory read multiple", "Dual address cycle", + "Memory read line", "Memory write and invalidate" + }; + unsigned long addr; + unsigned int cmd; + int i; + + if (!(perror & TSUNAMI_PERROR_ERRMASK)) + return MCHK_DISPOSITION_UNKNOWN_ERROR; + /* Do not import Titan's BIOS master-abort dismissal policy. */ + if (!print) + return MCHK_DISPOSITION_REPORT; + + printk("%s Pchip %d PERROR: %016llx\n", err_print_prefix, which, perror); + for (i = 0; i < ARRAY_SIZE(errors); i++) + if ((perror & TSUNAMI_PERROR_ERRMASK & (1UL << i)) && errors[i]) + printk("%s %s\n", err_print_prefix, errors[i]); + + /* + * INV invalidates SYN, CMD and ADDR. HRM 8.8.2.6 leaves command/address + * undefined whenever SERR is set, including with other error bits. + */ + if (perror & (TSUNAMI_PERROR_INV | perror_m_serr)) { + printk("%s Transaction information unavailable\n", err_print_prefix); + return MCHK_DISPOSITION_REPORT; + } + cmd = (perror & perror_m_cmd) >> 52; + /* + * Table 10-42 selects the ECC fields whenever CRE or UECC is set. + * The first error freezes PERROR (10.2.5.6); mixed error bits were + * detected together. Only LOST can be set afterwards. + */ + if (perror & TSUNAMI_PERROR_ECCMASK) { + const char *command = "Reserved"; + + if (cmd == 0) + command = "DMA read"; + else if (cmd == 1) + command = "DMA RMW"; + else if (cmd == 3) + command = "SGTE read"; + addr = ((perror >> 19) & 0xffffffffUL) << 3; + printk("%s Command: %s (%x), syndrome: %02llx\n", + err_print_prefix, command, cmd, (perror & perror_m_syn) >> 56); + printk("%s System address: %lx\n", err_print_prefix, addr); + } else { + addr = ((perror >> 18) & 0x3fffffffUL) << 2; + /* SGE captures only PCI address bits <31:13> (8KB page). */ + if (perror & perror_m_sge) + addr &= ~0x1fffUL; + printk("%s Command: %s (%x), PCI address: %lx%s\n", + err_print_prefix, pci_cmd[cmd], cmd, addr, + (perror & perror_m_sge) ? " (8KB page)" : ""); + if (perror & (1UL << 17)) + printk("%s Monster window\n", err_print_prefix); + else if (perror & (1UL << 16)) + printk("%s DAC scatter-gather window 3\n", err_print_prefix); + } + return MCHK_DISPOSITION_REPORT; +} + +/* SESF bits 0/1 identify PCI errors, bit 2 covers ECC on either Pchip/CPU. */ +static u64 +tsunami_pchip_error(struct el_TSUNAMI_sysdata_mcheck *frame, int which) +{ + u64 perror = which ? frame->p1_perror : frame->p0_perror; + + if (frame->sesf & (1UL << which)) + return perror; + if ((frame->sesf & 4) && (perror & TSUNAMI_PERROR_ECCMASK)) + return perror; + return 0; +} + +static struct el_TSUNAMI_sysdata_mcheck * +tsunami_system_frame(struct el_common *header) +{ + if (!tsunami_frame_valid(header, sizeof(struct el_TSUNAMI_sysdata_mcheck))) + return NULL; + return (void *)header + header->sys_offset; +} + +static int +tsunami_process_logout_frame(struct el_common *header, int print) +{ + struct el_TSUNAMI_sysdata_mcheck *frame = tsunami_system_frame(header); + int status = MCHK_DISPOSITION_UNKNOWN_ERROR; + int i; + + if (!frame) { + if (print) { + printk("%s Invalid Tsunami system frame; raw frame follows\n", + err_print_prefix); + mchk_dump_logout_frame_raw(header); + } + return MCHK_DISPOSITION_UNKNOWN_ERROR; + } + if (print) + printk("%s SESF: %016llx DIR: %016llx MISC: %016llx\n", + err_print_prefix, frame->sesf, frame->dir, frame->misc); + if (frame->misc & TSUNAMI_MISC_NXM) { + status = MCHK_DISPOSITION_REPORT; + if (print) + printk("%s Non-existent memory, NXS: %llu\n", + err_print_prefix, (frame->misc >> 29) & 7); + } + for (i = 0; i < 2; i++) + status |= tsunami_parse_p_perror(i, tsunami_pchip_error(frame, i), print); + return status; +} + +struct tsunami_error_state { + u64 perror[2]; + u64 misc; +}; + +static void +tsunami_ack_pchip(tsunami_pchip *pchip, u64 perror) +{ + if (!(perror & TSUNAMI_PERROR_ERRMASK)) + return; + pchip->perror.csr = perror & TSUNAMI_PERROR_ERRMASK; + /* Complete the W1C write before releasing the logout frame. */ mb(); pchip->perror.csr; } -static inline void -tsunami_pci_clr_err(void) +static void +tsunami_clear_errors(struct tsunami_error_state *state) { - tsunami_pci_clr_err_1(TSUNAMI_pchip0); + bool pchip1 = TSUNAMI_cchip->csc.csr & (1UL << 14); - /* TSUNAMI and TYPHOON can have 2, but might only have 1 (DS10) */ - if (TSUNAMI_cchip->csc.csr & 1L<<14) - tsunami_pci_clr_err_1(TSUNAMI_pchip1); + /* Preserve each chip's transaction and the NXM source before clearing. */ + state->perror[0] = TSUNAMI_pchip0->perror.csr; + state->perror[1] = pchip1 ? TSUNAMI_pchip1->perror.csr : 0; + state->misc = TSUNAMI_cchip->misc.csr; + + /* + * Acknowledge both present Pchips: the saved frame may predate + * live errors. This can consume another chip's pending error + * with INV set before it reaches DRIR, losing its transaction + * details and subsequent system-error notification. + */ + tsunami_ack_pchip(TSUNAMI_pchip0, state->perror[0]); + /* Do not access the second Pchip on single-Pchip systems. */ + if (pchip1) + tsunami_ack_pchip(TSUNAMI_pchip1, state->perror[1]); + if (state->misc & TSUNAMI_MISC_NXM) { + /* Zero writes leave the other MISC fields unchanged. */ + TSUNAMI_cchip->misc.csr = TSUNAMI_MISC_NXM; + /* Complete the NXM acknowledgment before releasing the frame. */ + mb(); + TSUNAMI_cchip->misc.csr; + } +} + +static void +tsunami_report_live_errors(struct tsunami_error_state *state, + struct el_TSUNAMI_sysdata_mcheck *frame) +{ + int i; + + /* PALcode's frame may predate these errors, or describe another chip. */ + for (i = 0; i < 2; i++) { + if (!(state->perror[i] & TSUNAMI_PERROR_ERRMASK) || + (frame && state->perror[i] == tsunami_pchip_error(frame, i))) + continue; + printk("%s Live Pchip status before acknowledgment:\n", err_print_prefix); + tsunami_parse_p_perror(i, state->perror[i], 1); + } + if ((state->misc & TSUNAMI_MISC_NXM) && + (!frame || ((state->misc ^ frame->misc) & (0xfUL << 28)))) + printk("%s Live MISC: %016llx, non-existent memory, NXS: %llu\n", + err_print_prefix, state->misc, (state->misc >> 29) & 7); } void tsunami_machine_check(unsigned long vector, unsigned long la_ptr) { - /* Clear error before any reporting. */ - mb(); - mb(); /* magic */ - draina(); - tsunami_pci_clr_err(); - wrmces(0x7); - mb(); + struct el_common *header = (void *)la_ptr; + struct el_TSUNAMI_sysdata_mcheck *frame = NULL; + struct tsunami_error_state live = {}; + char *saved_err_prefix = err_print_prefix; + bool show_regs = vector == SCB_Q_SYSMCHK; - process_mcheck_info(vector, la_ptr, "TSUNAMI", - mcheck_expected(smp_processor_id())); + /* Synchronize pending accesses before inspecting the chipset. */ + mb(); + mb(); + draina(); + /* CPU/event vectors must not consume a pending Pchip system error. */ + if (vector == SCB_Q_SYSMCHK || vector == SCB_Q_SYSERR) { + tsunami_clear_errors(&live); + frame = tsunami_system_frame(header); + } + +#ifdef CONFIG_VERBOSE_MCHECK + show_regs |= alpha_verbose_mcheck != 0; +#endif + err_print_prefix = KERN_CRIT; + if (vector == SCB_Q_SYSMCHK || vector == SCB_Q_SYSERR) { + printk("%sSystem %s error (vector %lx, code %x) on CPU %d:\n", + err_print_prefix, vector == SCB_Q_SYSERR ? "correctable" : + "uncorrectable", vector, header->code, smp_processor_id()); + tsunami_process_logout_frame(header, 1); + if (show_regs) + dik_show_regs(get_irq_regs(), NULL); + tsunami_report_live_errors(&live, frame); + } else if (vector == SCB_Q_SYSEVENT) { + printk("%sTsunami environmental event (code %x): layout unknown\n", + err_print_prefix, header->code); + mchk_dump_logout_frame_raw(header); + } + err_print_prefix = saved_err_prefix; + + if (vector != SCB_Q_SYSMCHK && vector != SCB_Q_SYSERR && + vector != SCB_Q_SYSEVENT) { + /* EV6 decoding also releases the logout frame. */ + ev6_machine_check(vector, la_ptr); + } else { + wrmces(0x7); + /* Complete acknowledgment before resuming. */ + mb(); + } } From 73b8c00851e27ac3497d50d4ecc59892b0cc5577 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Wed, 14 Oct 2026 07:45:59 +0200 Subject: [PATCH 56/57] alpha: decode Clipper environmental events Restrict ES40 live environmental decoding to Clipper. Validate frame bounds and string-table masks, and separate fault bits from status context. Report valid notifications even when no recognized fault bits are set, so door-closed and supply-return notifications remain visible. A single status snapshot does not establish a recovery transition. Keep SMIR bit 0 and inverted halt/reset bits as raw context. During the ES40 PSU cable test, bit 0 remained set after the individual PSU failure indication cleared. Its polarity/latching is unverified, so do not call it a current PSU failure. Use mask 0x14 for the remaining SMIR faults. Decode only QW1 through QW7 as live environmental status, as documented in the ES40 Service Guide, Tables D-20 and D-21. Use plain door-status messages. Preserve unknown or truncated frames in bounded raw dumps. Firmware console-log scanning is independent of the live environmental handler. Signed-off-by: Magnus Lindholm Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/err_tsunami.c | 221 ++++++++++++++++++++++++++++++++ arch/alpha/kernel/proto.h | 1 + arch/alpha/kernel/sys_dp264.c | 2 +- 3 files changed, 223 insertions(+), 1 deletion(-) diff --git a/arch/alpha/kernel/err_tsunami.c b/arch/alpha/kernel/err_tsunami.c index c413acd45bff..c69fea2f4cbd 100644 --- a/arch/alpha/kernel/err_tsunami.c +++ b/arch/alpha/kernel/err_tsunami.c @@ -269,3 +269,224 @@ tsunami_machine_check(unsigned long vector, unsigned long la_ptr) mb(); } } + +static const char * const CLIPPER_EnvQW1SMIR[] = { + "", /* SMIR<0>: retain as raw context until its semantics are known. */ + "OCP or RMC halt detected", + "Sys_DC_Notok failure detected", + "", + "System temperature over 50 degrees C failure", + "PCI Bus #0 is in reset", + "PCI Bus #1 is in reset", + "System is being reset" +}; + +static const char * const CLIPPER_EnvQW2CPUIR[] = { + "CPU0 regulator is enabled", + "CPU1 regulator is enabled", + "CPU2 regulator is enabled", + "CPU3 regulator is enabled", + "CPU0 regulator or configuration sequence fail", + "CPU1 regulator or configuration sequence fail", + "CPU2 regulator or configuration sequence fail", + "CPU3 regulator or configuration sequence fail" +}; + +static const char * const CLIPPER_EnvQW3PSIR[] = { + "Power Supply 0 is enabled", + "Power Supply 1 is enabled", + "Power Supply 2 is enabled", + "", + "Power Supply 0 was enabled but failed", + "Power Supply 1 was enabled but failed", + "Power Supply 2 was enabled but failed" +}; + +static const char * const CLIPPER_EnvQW4LM78ISR[] = { + "PS +3.3V out of tolerance", + "PS +5V out of tolerance", + "PS +12V out of tolerance", + "VTERM out of tolerance", + "Temperature zone 0 (PCI Backplane slots 1-3 area) over limit failure", + "LM75 CPU0-3 Temperature over limit failure", + "System Fan 1 failure", + "System Fan 2 failure", + "CTERM out of tolerance", + "", + "-12V out of tolerance", + "", + "", + "", + "", + "", + "CPU0_VCORE +2V out of tolerance", + "CPU0_VIO +1.5V out of tolerance", + "CPU1_VCORE +2V out of tolerance", + "CPU1_VIO +1.5V out of tolerance", + "Temperature zone 1 (PCI Backplane slots 7-10 area) over limit failure", + "", + "System Fan 4 failure", + "System Fan 5 failure", + "", + "", + "", + "", + "", + "", + "", + "", + "CPU2_VCORE +2V out of tolerance", + "CPU2_VIO +1.5V out of tolerance", + "CPU3_VCORE +2V out of tolerance", + "CPU3_VIO +1.5V out of tolerance", + "Temperature zone 2 (PCI Backplane slots 4-6 area) over limit failure", + "", + "System Fan 3 failure", + "System Fan 6 failure", + "", + "", + "Power supply 3.3V rail above high amperage warning", + "Power supply 5.0V rail above high amperage warning", + "Power supply 12V rail above high amperage warning", + "Power supply high temperature warning", + "Power supply AC input low limit warning", + "Power supply AC input high limit warning" +}; + +static const char * const CLIPPER_EnvQW5DOOR[] = { + "", + "System CPU door is open", + "System Fan door is open", + "System PCI door is open", + "", + "System CPU door is closed", + "System Fan door is closed", + "System PCI door is closed" +}; + +static const char * const CLIPPER_EnvQW6TEMP[] = { + "CPU0 temperature warning fault has occurred", + "CPU1 temperature warning fault has occurred", + "CPU2 temperature warning fault has occurred", + "CPU3 temperature warning fault has occurred", + "System temperature zone 0 warning fault has occurred", + "System temperature zone 1 warning fault has occurred", + "System temperature zone 2 warning fault has occurred" +}; + +static const char * const CLIPPER_EnvQW7FAN[] = { + "System Fan 1 is not responding to RMC Commands", + "System Fan 2 is not responding to RMC Commands", + "System Fan 3 is not responding to RMC Commands", + "System Fan 4 is not responding to RMC Commands", + "System Fan 5 is not responding to RMC Commands", + "System Fan 6 is not responding to RMC Commands", + "", + "", + "CPU fans 5/6 at maximum speed", + "CPU fans 5/6 reduced speed from maximum", + "PCI fans 1-4 at maximum speed", + "PCI fans 1-4 reduced speed from maximum" +}; + +#define CLIPPER_ENV_LM78ISR_MASK 0xfcdf00df05ffUL + +/* ES40 Service Guide EK-ES240-SV A01, live frame: Tables D-20 and D-21. */ +struct el_clipper_envdata { + u64 summary; + u64 dir; + u64 smir; + u64 cpuir; + u64 psir; + u64 fault; + u64 doors; + u64 temp_warn; + u64 fan_ctrl; + u64 fatal_power_down; + u64 reserved; +}; + +static bool +clipper_print_bits(const char * const *messages, size_t count, + u64 value, u64 mask) +{ + size_t i; + + if (!(value & mask)) + return false; + for (i = 0; i < count; i++) + if (value & mask & (1UL << i)) + printk("%s %s\n", err_print_prefix, messages[i]); + return true; +} + +#define CLIPPER_BITS(table, value, mask) \ + clipper_print_bits(table, ARRAY_SIZE(table), value, mask) + +static void +clipper_process_environment(struct el_common *header) +{ + struct el_clipper_envdata *frame; + bool fault = false; + + if (!tsunami_frame_valid(header, sizeof(*frame))) { + printk("%s Truncated Clipper environmental frame\n", err_print_prefix); + mchk_dump_logout_frame_raw(header); + return; + } + frame = (void *)header + header->sys_offset; + /* Only QW1 through QW7 carry error state in the live 680 frame. */ + /* PSU summary and inverted halt/reset signals remain raw context. */ + fault |= CLIPPER_BITS(CLIPPER_EnvQW1SMIR, frame->smir, 0x14); + fault |= CLIPPER_BITS(CLIPPER_EnvQW2CPUIR, frame->cpuir, 0xf0); + fault |= CLIPPER_BITS(CLIPPER_EnvQW3PSIR, frame->psir, 0x70); + fault |= CLIPPER_BITS(CLIPPER_EnvQW4LM78ISR, + frame->fault, CLIPPER_ENV_LM78ISR_MASK); + fault |= CLIPPER_BITS(CLIPPER_EnvQW5DOOR, frame->doors, 0x0e); + fault |= CLIPPER_BITS(CLIPPER_EnvQW6TEMP, frame->temp_warn, 0x7f); + fault |= CLIPPER_BITS(CLIPPER_EnvQW7FAN, frame->fan_ctrl, 0x3f); + /* A notification with no recognized fault can describe recovery. */ + if (!fault) + printk("%s No recognized environmental fault bits set\n", + err_print_prefix); + printk("%s Environmental summary: %016llx DIR: %016llx\n", + err_print_prefix, frame->summary, frame->dir); + printk("%s SMIR: %016llx (PSU summary/halt/reset bits undecoded)\n", + err_print_prefix, frame->smir); + CLIPPER_BITS(CLIPPER_EnvQW2CPUIR, frame->cpuir, 0x0f); + CLIPPER_BITS(CLIPPER_EnvQW3PSIR, frame->psir, 0x07); + CLIPPER_BITS(CLIPPER_EnvQW5DOOR, frame->doors, 0xe0); + CLIPPER_BITS(CLIPPER_EnvQW7FAN, frame->fan_ctrl, 0xf00); + if (frame->fault & (0x3fUL << 42)) + printk("%s Power supply warning source: %llu (3 = reserved)\n", + err_print_prefix, (frame->fault >> 40) & 3); +} + +void +clipper_machine_check(unsigned long vector, unsigned long la_ptr) +{ + struct el_common *header = (void *)la_ptr; + char *saved_err_prefix = err_print_prefix; + + if (vector != SCB_Q_SYSEVENT) { + tsunami_machine_check(vector, la_ptr); + return; + } + /* Drain pending accesses before inspecting the environmental frame. */ + mb(); + draina(); + err_print_prefix = KERN_CRIT; + printk("%sSystem correctable environmental event (code %x) on CPU %d:\n", + err_print_prefix, header->code, smp_processor_id()); + if (header->code == 0x206) + clipper_process_environment(header); + else { + printk("%s Unrecognized environmental event; raw frame follows\n", + err_print_prefix); + mchk_dump_logout_frame_raw(header); + } + err_print_prefix = saved_err_prefix; + wrmces(0x7); + /* Complete acknowledgment before returning from the event. */ + mb(); +} diff --git a/arch/alpha/kernel/proto.h b/arch/alpha/kernel/proto.h index f138bd494628..8cdf72f4ab8b 100644 --- a/arch/alpha/kernel/proto.h +++ b/arch/alpha/kernel/proto.h @@ -78,6 +78,7 @@ extern struct _alpha_agp_info *titan_agp_info(void); extern struct pci_ops tsunami_pci_ops; extern void tsunami_init_arch(void); extern void tsunami_kill_arch(int); +extern void clipper_machine_check(unsigned long vector, unsigned long la_ptr); extern void tsunami_machine_check(unsigned long vector, unsigned long la_ptr); extern void tsunami_pci_tbi(struct pci_controller *, dma_addr_t, dma_addr_t); diff --git a/arch/alpha/kernel/sys_dp264.c b/arch/alpha/kernel/sys_dp264.c index 0a2d319bb1c8..0b5687ab45f2 100644 --- a/arch/alpha/kernel/sys_dp264.c +++ b/arch/alpha/kernel/sys_dp264.c @@ -627,7 +627,7 @@ struct alpha_machine_vector clipper_mv __initmv = { DO_EV6_MMU, DO_DEFAULT_RTC, DO_TSUNAMI_IO, - .machine_check = tsunami_machine_check, + .machine_check = clipper_machine_check, .max_isa_dma_address = ALPHA_MAX_ISA_DMA_ADDRESS, .min_io_address = DEFAULT_IO_BASE, .min_mem_address = DEFAULT_MEM_BASE, From 71cf9644bcb35e686c3e12a703325335a0925946 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Wed, 14 Oct 2026 07:45:59 +0200 Subject: [PATCH 57/57] alpha: add bounded Clipper console-log scanning Clipper console logs have an HWRPB byte count. Bound wrapper and child traversal by that count before decoding embedded processor and system logout frames. Print the wrapper name and event/halt timestamp, halt code, and recorded CPU ID for processor packets. Acknowledge the log only after a complete termination packet. Retain the firmware pointer on unknown or incomplete wrappers and print at most 1 KiB of raw bytes using print_hex_dump(). This avoids the generic scanner's unconditional acknowledgment after a parsing failure. The ES40 record observed during testing is class 5, type 4. Its wrapper layout is unknown and remains raw and unacknowledged; it may therefore be printed again at each boot. Successful decoding of a nonempty hardware log remains unverified. Keep console scanning independent of the live machine-check and environmental handlers and restrict it to Clipper. Console environmental payloads also remain raw: ES40 Service Guide Table D-18's sys_offset conflicts with its field offsets. No register layout is assumed for unverified extended subpacket types 6 and 7. The scanner invokes the payload decoder directly during PCI init; no generic subpacket-handler registration is needed. Mark its helpers __init and retain the PCI init hook in sys_dp264.c. Signed-off-by: Magnus Lindholm Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/err_tsunami.c | 196 ++++++++++++++++++++++++++++++++ arch/alpha/kernel/proto.h | 1 + arch/alpha/kernel/sys_dp264.c | 1 + 3 files changed, 198 insertions(+) diff --git a/arch/alpha/kernel/err_tsunami.c b/arch/alpha/kernel/err_tsunami.c index c69fea2f4cbd..dacf91d952fe 100644 --- a/arch/alpha/kernel/err_tsunami.c +++ b/arch/alpha/kernel/err_tsunami.c @@ -6,9 +6,11 @@ * Register definitions: 21272 Hardware Reference Manual, revision 4.0. */ #include +#include #include #include #include +#include #include #include #include @@ -490,3 +492,197 @@ clipper_machine_check(unsigned long vector, unsigned long la_ptr) /* Complete acknowledgment before returning from the event. */ mb(); } + +static struct el_subpacket * __init +tsunami_process_subpacket(struct el_subpacket *packet) +{ + struct el_common *frame; + size_t offset = offsetof(struct el_subpacket, by_type.regatta_frame.data_start); + int status; + + /* Raw dumps consume whole quadwords, and the next packet must advance. */ + if (packet->length < offsetof(struct el_subpacket, by_type) || + (packet->length & 7)) + return NULL; + if (packet->revision != 1) { + el_annotate_subpacket(packet); + return (void *)packet + packet->length; + } + + switch (packet->type) { + case EL_TYPE__REGATTA__PROCESSOR_ERROR_FRAME: + case EL_TYPE__REGATTA__PROCESSOR_DBL_ERROR_HALT: + case EL_TYPE__REGATTA__SYSTEM_ERROR_FRAME: + case EL_TYPE__REGATTA__SYSTEM_DBL_ERROR_HALT: + if (packet->length < offset + sizeof(*frame)) + return NULL; + frame = (void *)packet + offset; + if (frame->size > packet->length - offset || frame->size < sizeof(*frame)) + return NULL; + if (packet->type == EL_TYPE__REGATTA__PROCESSOR_ERROR_FRAME || + packet->type == EL_TYPE__REGATTA__PROCESSOR_DBL_ERROR_HALT) { + printk("%s ** Occurred on CPU %d:\n", err_print_prefix, + (int)packet->by_type.regatta_frame.cpuid); + status = ev6_process_logout_frame(frame, 0); + if (status != MCHK_DISPOSITION_DISMISS) + ev6_process_logout_frame(frame, 1); + } else { + tsunami_process_logout_frame(frame, 1); + } + break; + case EL_TYPE__REGATTA__ENVIRONMENTAL_FRAME: + /* + * ES40 Table D-18 gives sys_offset=0x20, but places the + * summary at frame+0x18. Preserve the complete packet until + * the console layout is verified; do not apply the live layout. + */ + printk("%s Console environmental layout unverified; raw packet follows\n", + err_print_prefix); + el_annotate_subpacket(packet); + break; + default: + el_annotate_subpacket(packet); + break; + } + return (void *)packet + packet->length; +} + +/* + * Clipper console logs have an HWRPB byte count. Do not use the generic + * unbounded dispatcher: a rejected record must not acknowledge the log. + */ +static bool __init +tsunami_console_log(void *data, size_t remaining) +{ + struct el_subpacket *packet; + unsigned char *pos = data; + size_t size, length; + u32 count; + union el_timestamp timestamp; + const char *name; + + while (remaining >= offsetof(struct el_subpacket, by_type)) { + packet = (void *)pos; + size = packet->length; + if (size < offsetof(struct el_subpacket, by_type) || + size > remaining || (size & 7)) + return false; + if (packet->class == EL_CLASS__TERMINATION) + return packet->type == EL_TYPE__TERMINATION__TERMINATION; + if (packet->class != EL_CLASS__HEADER || packet->revision != 1) + return false; + + timestamp.as_int = 0; + /* Check each header's extent before accessing its union member. */ +#define CONSOLE_HEADER_SIZE(member) \ + (offsetof(struct el_subpacket, by_type.member) + \ + sizeof(packet->by_type.member)) + switch (packet->type) { + case EL_TYPE__HEADER__SYSTEM_ERROR_FRAME: + if (size < CONSOLE_HEADER_SIZE(sys_err)) + return false; + name = "SYSTEM ERROR"; + length = packet->by_type.sys_err.frame_length; + count = packet->by_type.sys_err.frame_packet_count; + break; + case EL_TYPE__HEADER__SYSTEM_EVENT_FRAME: + if (size < CONSOLE_HEADER_SIZE(sys_event)) + return false; + name = "SYSTEM EVENT"; + timestamp = packet->by_type.sys_event.timestamp; + length = packet->by_type.sys_event.frame_length; + count = packet->by_type.sys_event.frame_packet_count; + break; + case EL_TYPE__HEADER__HALT_FRAME: + if (size < CONSOLE_HEADER_SIZE(err_halt)) + return false; + name = "ERROR HALT"; + timestamp = packet->by_type.err_halt.timestamp; + length = packet->by_type.err_halt.frame_length; + count = packet->by_type.err_halt.frame_packet_count; + break; + case EL_TYPE__HEADER__LOGOUT_FRAME: + if (size < CONSOLE_HEADER_SIZE(logout_header)) + return false; + name = "LOGOUT FRAME"; + length = packet->by_type.logout_header.frame_length; + count = 1; + break; + default: + return false; + } +#undef CONSOLE_HEADER_SIZE + if (length > remaining - size || (length & 7) || + count > length / offsetof(struct el_subpacket, by_type)) + return false; + printk("%s*** %s: CLASS %u, TYPE %u\n", err_print_prefix, + name, packet->class, packet->type); + el_print_timestamp(×tamp); + if (packet->type == EL_TYPE__HEADER__HALT_FRAME) + printk("%s HALT CODE: %04x\n", err_print_prefix, + packet->by_type.err_halt.halt_code); + pos += size; + remaining -= size + length; + while (count--) { + if (length < offsetof(struct el_subpacket, by_type)) + return false; + packet = (void *)pos; + size = packet->length; + if (size < offsetof(struct el_subpacket, by_type) || + size > length || (size & 7) || + packet->class != EL_CLASS__REGATTA_FAMILY) + return false; + /* This handler checks embedded logout-frame bounds. */ + if (tsunami_process_subpacket(packet) != (void *)pos + size) + return false; + pos += size; + length -= size; + } + if (length) + return false; + } + /* Exhaustion without a complete termination packet is incomplete. */ + return false; +} + +static void __init +tsunami_console_data_log(int cpu, struct percpu_struct *pcpu) +{ + unsigned char *data = (void *)(IDENT_ADDR | pcpu->console_data_log_pa); + size_t length = pcpu->console_data_log_length; + size_t dump = length < 1024 ? length : 1024; + + printk("%sConsole data log for CPU %d (%zu bytes):\n", + err_print_prefix, cpu, length); + if (!(pcpu->console_data_log_pa & 7) && + length <= ~0UL - (unsigned long)data && + tsunami_console_log(data, length)) { + /* Acknowledge only after reaching a valid termination packet. */ + pcpu->console_data_log_pa = 0; + return; + } + printk("%s Incomplete console log; retaining firmware pointer\n", + err_print_prefix); + /* Byte reads preserve partial headers without rounding beyond length. */ + if (dump > ~0UL - (unsigned long)data) + return; + print_hex_dump(err_print_prefix, " ", DUMP_PREFIX_OFFSET, 16, 1, + data, dump, false); + if (dump < length) + printk("%s Raw console log truncated to %zu bytes\n", + err_print_prefix, dump); +} + +void __init +tsunami_check_console_data_log(void) +{ + struct percpu_struct *pcpu; + unsigned long cpu; + + for (cpu = 0; cpu < hwrpb->nr_processors; cpu++) { + pcpu = (void *)hwrpb + hwrpb->processor_offset + + cpu * hwrpb->processor_size; + if (pcpu->console_data_log_pa) + tsunami_console_data_log(cpu, pcpu); + } +} diff --git a/arch/alpha/kernel/proto.h b/arch/alpha/kernel/proto.h index 8cdf72f4ab8b..7ece3b635e27 100644 --- a/arch/alpha/kernel/proto.h +++ b/arch/alpha/kernel/proto.h @@ -78,6 +78,7 @@ extern struct _alpha_agp_info *titan_agp_info(void); extern struct pci_ops tsunami_pci_ops; extern void tsunami_init_arch(void); extern void tsunami_kill_arch(int); +extern void tsunami_check_console_data_log(void); extern void clipper_machine_check(unsigned long vector, unsigned long la_ptr); extern void tsunami_machine_check(unsigned long vector, unsigned long la_ptr); extern void tsunami_pci_tbi(struct pci_controller *, dma_addr_t, dma_addr_t); diff --git a/arch/alpha/kernel/sys_dp264.c b/arch/alpha/kernel/sys_dp264.c index 0b5687ab45f2..f844d8141553 100644 --- a/arch/alpha/kernel/sys_dp264.c +++ b/arch/alpha/kernel/sys_dp264.c @@ -533,6 +533,7 @@ monet_init_pci(void) static void __init clipper_init_pci(void) { + tsunami_check_console_data_log(); common_init_pci(); locate_and_init_vga(NULL); }