From 8884b9b5c8170424da4929e096cda64632207f4f Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sun, 17 May 2026 16:32:40 -0700 Subject: [PATCH 01/15] alpha: remove unnecessary architecture-specific arch/alpha/include/asm/device.h simply includes , and therefore the Alpha-specific version is unnecessary. Remove it. Signed-off-by: Ethan Nelson-Moore Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260517233246.23915-1-enelsonmoore@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/device.h | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 arch/alpha/include/asm/device.h diff --git a/arch/alpha/include/asm/device.h b/arch/alpha/include/asm/device.h deleted file mode 100644 index 9ca75a7db23e..000000000000 --- a/arch/alpha/include/asm/device.h +++ /dev/null @@ -1,6 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Arch specific extensions to struct device - */ -#include - From 3a3ac1f6c6a67b3803f2643584310f78301e58a8 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Thu, 28 May 2026 19:05:15 -0400 Subject: [PATCH 02/15] alpha: marvel: Fix irq_set_status_flags to use correct IRQ number Pass base + i to irq_set_status_flags() to match the IRQ number used in irq_set_chip_and_handler(). Previously, IRQ_LEVEL was set on the wrong (low-numbered) IRQ descriptors rather than the IO7 IRQs at base + i. Cc: stable@vger.kernel.org Fixes: 08876fe8519c ("alpha: marvel: Convert irq_chip functions") Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260528230516.1839694-1-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/sys_marvel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/alpha/kernel/sys_marvel.c b/arch/alpha/kernel/sys_marvel.c index 1f99b03effc2..bebeea3c286d 100644 --- a/arch/alpha/kernel/sys_marvel.c +++ b/arch/alpha/kernel/sys_marvel.c @@ -275,7 +275,7 @@ init_io7_irqs(struct io7 *io7, /* Set up the lsi irqs. */ for (i = 0; i < 128; ++i) { irq_set_chip_and_handler(base + i, lsi_ops, handle_level_irq); - irq_set_status_flags(i, IRQ_LEVEL); + irq_set_status_flags(base + i, IRQ_LEVEL); } /* Disable the implemented irqs in hardware. */ @@ -289,7 +289,7 @@ init_io7_irqs(struct io7 *io7, /* Set up the msi irqs. */ for (i = 128; i < (128 + 512); ++i) { irq_set_chip_and_handler(base + i, msi_ops, handle_level_irq); - irq_set_status_flags(i, IRQ_LEVEL); + irq_set_status_flags(base + i, IRQ_LEVEL); } for (i = 0; i < 16; ++i) From 24d68db713d63dfe3660c56b50e887784844baea Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Thu, 28 May 2026 19:05:16 -0400 Subject: [PATCH 03/15] alpha: marvel: Fix lock ordering in init_io7_irqs() Move irq_set_chip_and_handler() and irq_set_status_flags() calls outside the io7->irq_lock raw spinlock. These functions take sparse_irq_lock, which is a mutex, and taking a sleeping lock while holding a raw spinlock is invalid. The raw spinlock only needs to protect the hardware CSR accesses. This fixes the following lockdep splat during boot: [ BUG: Invalid wait context ] swapper/0/0 is trying to lock: sparse_irq_lock{....}-{4:4}, at: irq_mark_irq other info that might help us debug this: context-{5:5} 1 lock held by swapper/0/0: #0: &io7->irq_lock{....}-{2:2}, at: init_io7_irqs.constprop.0 Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260528230516.1839694-2-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/sys_marvel.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/arch/alpha/kernel/sys_marvel.c b/arch/alpha/kernel/sys_marvel.c index bebeea3c286d..a37707e05e34 100644 --- a/arch/alpha/kernel/sys_marvel.c +++ b/arch/alpha/kernel/sys_marvel.c @@ -263,6 +263,18 @@ init_io7_irqs(struct io7 *io7, */ printk(" Interrupts reported to CPU at PE %u\n", boot_cpuid); + /* Set up the lsi irqs. */ + for (i = 0; i < 128; ++i) { + irq_set_chip_and_handler(base + i, lsi_ops, handle_level_irq); + irq_set_status_flags(base + i, IRQ_LEVEL); + } + + /* Set up the msi irqs. */ + for (i = 128; i < (128 + 512); ++i) { + irq_set_chip_and_handler(base + i, msi_ops, handle_level_irq); + irq_set_status_flags(base + i, IRQ_LEVEL); + } + raw_spin_lock(&io7->irq_lock); /* set up the error irqs */ @@ -272,12 +284,6 @@ init_io7_irqs(struct io7 *io7, io7_redirect_irq(io7, &io7->csrs->STV_CTL.csr, boot_cpuid); io7_redirect_irq(io7, &io7->csrs->HEI_CTL.csr, boot_cpuid); - /* Set up the lsi irqs. */ - for (i = 0; i < 128; ++i) { - irq_set_chip_and_handler(base + i, lsi_ops, handle_level_irq); - irq_set_status_flags(base + i, IRQ_LEVEL); - } - /* Disable the implemented irqs in hardware. */ for (i = 0; i < 0x60; ++i) init_one_io7_lsi(io7, i, boot_cpuid); @@ -285,13 +291,6 @@ init_io7_irqs(struct io7 *io7, init_one_io7_lsi(io7, 0x74, boot_cpuid); init_one_io7_lsi(io7, 0x75, boot_cpuid); - - /* Set up the msi irqs. */ - for (i = 128; i < (128 + 512); ++i) { - irq_set_chip_and_handler(base + i, msi_ops, handle_level_irq); - irq_set_status_flags(base + i, IRQ_LEVEL); - } - for (i = 0; i < 16; ++i) init_one_io7_msi(io7, i, boot_cpuid); From 8a2857bc2b3039eef6c15900b00424417a2293e8 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Mon, 6 Jul 2026 18:56:42 +0200 Subject: [PATCH 04/15] alpha: enable regset-based ptrace and core dumps Add a user_regset_view for Alpha and switch ELF core dumping to CORE_DUMP_USE_REGSET. General-purpose registers are exported in ELF gregs layout, including callee-saved registers and a correct user stack pointer. The user stack pointer is not preserved in pt_regs on Alpha, so expose it from the PCB, or via rdusp() for the current task, when building the ELF register image. This makes the user stack pointer consistent for core dumps, ptrace regsets, and PTRACE_GET_SYSCALL_INFO. Implement regset get/set callbacks for both NT_PRSTATUS and NT_PRFPREG. The callbacks translate between Alpha's pt_regs/thread state and the ELF-visible register layouts, while the common ptrace regset code handles PTRACE_GETREGSET and PTRACE_SETREGSET iovec semantics. This avoids duplicating subtle short-buffer and oversized-buffer behavior in arch_ptrace(). With these changes Alpha satisfies the requirements for HAVE_ARCH_TRACEHOOK and selects it, enabling generic tracehook and ptrace syscall-info code paths without changing the existing syscall entry ABI. Reviewed-by: Matt Turner Tested-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260706170019.2941459-2-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- .../features/core/tracehook/arch-support.txt | 2 +- arch/alpha/Kconfig | 1 + arch/alpha/include/asm/elf.h | 1 + arch/alpha/include/asm/ptrace.h | 7 + arch/alpha/include/asm/syscall.h | 7 + arch/alpha/include/asm/thread_info.h | 7 +- arch/alpha/include/uapi/asm/ptrace.h | 2 +- arch/alpha/kernel/asm-offsets.c | 1 + arch/alpha/kernel/entry.S | 15 +- arch/alpha/kernel/ptrace.c | 320 +++++++++++++++--- arch/alpha/kernel/traps.c | 8 + 11 files changed, 312 insertions(+), 59 deletions(-) diff --git a/Documentation/features/core/tracehook/arch-support.txt b/Documentation/features/core/tracehook/arch-support.txt index 4f36fcbfb6d5..654f38413d16 100644 --- a/Documentation/features/core/tracehook/arch-support.txt +++ b/Documentation/features/core/tracehook/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 7b7dafe7d9df..f3b882835617 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -33,6 +33,7 @@ config ALPHA select HAVE_ARCH_AUDITSYSCALL select HAVE_ARCH_SECCOMP select HAVE_ARCH_SECCOMP_FILTER + select HAVE_ARCH_TRACEHOOK select HAVE_MOD_ARCH_SPECIFIC select LOCK_MM_AND_FIND_VMA select MODULES_USE_ELF_RELA diff --git a/arch/alpha/include/asm/elf.h b/arch/alpha/include/asm/elf.h index 50c82187e60e..b15946621d57 100644 --- a/arch/alpha/include/asm/elf.h +++ b/arch/alpha/include/asm/elf.h @@ -53,6 +53,7 @@ #define EF_ALPHA_32BIT 1 /* All addresses are below 2GB */ +#define CORE_DUMP_USE_REGSET 1 /* * ELF register definitions.. */ diff --git a/arch/alpha/include/asm/ptrace.h b/arch/alpha/include/asm/ptrace.h index 3557ce64ed21..8e0a589e2d15 100644 --- a/arch/alpha/include/asm/ptrace.h +++ b/arch/alpha/include/asm/ptrace.h @@ -24,4 +24,11 @@ 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; +} + #endif diff --git a/arch/alpha/include/asm/syscall.h b/arch/alpha/include/asm/syscall.h index 584b1ab2e325..1e78cbd46faf 100644 --- a/arch/alpha/include/asm/syscall.h +++ b/arch/alpha/include/asm/syscall.h @@ -19,6 +19,13 @@ static inline long syscall_get_return_value(struct task_struct *task, return regs->r19 ? -(long)regs->r0 : (long)regs->r0; } +static inline long syscall_get_error(struct task_struct *task, + struct pt_regs *regs) +{ + return regs->r19 ? -(long)regs->r0 : 0; +} + + /* * Alpha syscall ABI / kernel conventions: * - PAL provides syscall number in r0 on entry. diff --git a/arch/alpha/include/asm/thread_info.h b/arch/alpha/include/asm/thread_info.h index 94ef9cfa30f5..1552ecca8520 100644 --- a/arch/alpha/include/asm/thread_info.h +++ b/arch/alpha/include/asm/thread_info.h @@ -66,6 +66,7 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define TIF_SYSCALL_AUDIT 4 /* syscall audit active */ #define TIF_NOTIFY_SIGNAL 5 /* signal notifications exist */ #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_MEMDIE 13 /* is terminating due to OOM killer */ #define TIF_POLLING_NRFLAG 14 /* idle is polling for TIF_NEED_RESCHED */ @@ -78,6 +79,7 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define _TIF_NOTIFY_SIGNAL (1<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 */ + mov $0, $8 + call_pal PAL_rdusp + stq $0, PT_REGS_USP($sp) + mov $8, $0 + lda $8, 0x3fff bic $sp, $8, $8 @@ -535,15 +541,10 @@ entSys: .cfi_rel_offset $16, SP_OFF+24 .cfi_rel_offset $17, SP_OFF+32 .cfi_rel_offset $18, SP_OFF+40 -#ifdef CONFIG_AUDITSYSCALL - lda $6, _TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | _TIF_SECCOMP + lda $6, _TIF_SYSCALL_WORK and $3, $6, $3 bne $3, strace -#else - lda $6, _TIF_SYSCALL_TRACE | _TIF_SECCOMP - and $3, $6, $3 - bne $3, strace -#endif + beq $4, 1f ldq $27, 0($5) 1: ldq $0, 8($sp) /* syscall nr shadow (regs->r1) */ diff --git a/arch/alpha/kernel/ptrace.c b/arch/alpha/kernel/ptrace.c index 0687760ea466..69eb337347df 100644 --- a/arch/alpha/kernel/ptrace.c +++ b/arch/alpha/kernel/ptrace.c @@ -24,10 +24,15 @@ #include "proto.h" #include +#include #define DEBUG DBG_MEM #undef DEBUG +#ifndef NT_FPREGSET +#define NT_FPREGSET NT_PRFPREG +#endif + #ifdef DEBUG enum { DBG_MEM = (1<<0), @@ -143,19 +148,163 @@ get_reg(struct task_struct * task, unsigned long regno) return *get_reg_addr(task, regno); } +static void alpha_elf_fpregs_get(struct task_struct *target, + elf_fpreg_t *fpregs) /* points to ELF_NFPREG entries */ +{ + memcpy(fpregs, task_thread_info(target)->fp, sizeof(elf_fpregset_t)); +} + +static void alpha_elf_fpregs_set(struct task_struct *target, + const elf_fpreg_t *fpregs, + size_t nwords) +{ + size_t n = min_t(size_t, nwords, ELF_NFPREG); + + memcpy(task_thread_info(target)->fp, fpregs, n * sizeof(elf_fpreg_t)); +} + +static void alpha_elf_gregs_set(struct task_struct *child, + const elf_greg_t *src, + size_t nwords) +{ + struct pt_regs *pt = task_pt_regs(child); + struct thread_info *ti = task_thread_info(child); + struct switch_stack *sw = ((struct switch_stack *)pt) - 1; + + /* GPRs r0..r8 live in pt_regs */ + if (nwords > 0) + pt->r0 = src[0]; + if (nwords > 1) + pt->r1 = src[1]; + if (nwords > 2) + pt->r2 = src[2]; + if (nwords > 3) + pt->r3 = src[3]; + if (nwords > 4) + pt->r4 = src[4]; + if (nwords > 5) + pt->r5 = src[5]; + if (nwords > 6) + pt->r6 = src[6]; + if (nwords > 7) + pt->r7 = src[7]; + if (nwords > 8) + pt->r8 = src[8]; + + /* r9..r15 live in switch_stack */ + if (nwords > 9) + sw->r9 = src[9]; + if (nwords > 10) + sw->r10 = src[10]; + if (nwords > 11) + sw->r11 = src[11]; + if (nwords > 12) + sw->r12 = src[12]; + if (nwords > 13) + sw->r13 = src[13]; + if (nwords > 14) + sw->r14 = src[14]; + if (nwords > 15) + sw->r15 = src[15]; + + /* r16..r28 live in pt_regs */ + if (nwords > 16) + pt->r16 = src[16]; + if (nwords > 17) + pt->r17 = src[17]; + if (nwords > 18) + pt->r18 = src[18]; + if (nwords > 19) + pt->r19 = src[19]; + if (nwords > 20) + pt->r20 = src[20]; + if (nwords > 21) + pt->r21 = src[21]; + if (nwords > 22) + pt->r22 = src[22]; + if (nwords > 23) + pt->r23 = src[23]; + if (nwords > 24) + pt->r24 = src[24]; + if (nwords > 25) + pt->r25 = src[25]; + if (nwords > 26) + pt->r26 = src[26]; + if (nwords > 27) + pt->r27 = src[27]; + if (nwords > 28) + pt->r28 = src[28]; + + /* gp, usp, pc, unique */ + if (nwords > 29) + pt->gp = src[29]; + + if (nwords > 30) { + ti->pcb.usp = src[30]; + /* + * If someone ever does this to current (rare), keep the + * hardware usp consistent. + */ + if (child == current) + wrusp(src[30]); + } + + if (nwords > 31) + pt->pc = src[31]; + + if (nwords > 32) + ti->pcb.unique = src[32]; + +/* + * PTRACE_SETREGSET can be used at a syscall-entry stop to skip the + * syscall by setting the syscall number to -1. The seccomp/ptrace + * selftests use this to synthesize errno returns. + * + * Alpha uses r19/a3 as the error flag, so a skipped syscall with a + * small positive r0 and a clear r19 must be normalized to an error + * return. + */ + if (pt->r1 == (unsigned long)-1 && + pt->r19 == 0 && + pt->r0 > 0 && + pt->r0 < MAX_ERRNO) + 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); + if (regno == 63) { task_thread_info(task)->ieee_state = ((task_thread_info(task)->ieee_state & ~IEEE_SW_MASK) | (data & IEEE_SW_MASK)); data = (data & FPCR_DYN_MASK) | ieee_swcr_to_fpcr(data); } + *get_reg_addr(task, regno) = 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. + */ + + if (regno == 0 && data == (unsigned long)-1) { + regs->r1 = data; + regs->r19 = 0; + } + return 0; } @@ -315,54 +464,6 @@ long arch_ptrace(struct task_struct *child, long request, DBG(DBG_MEM, ("poke $%lu<-%#lx\n", addr, data)); ret = put_reg(child, addr, data); break; - case PTRACE_GETREGSET: - case PTRACE_SETREGSET: { - struct iovec __user *uiov = (struct iovec __user *)data; - struct iovec iov; - struct pt_regs *regs; - size_t len; - - /* Only support NT_PRSTATUS (general registers) for now. */ - if (addr != NT_PRSTATUS) { - ret = -EIO; - break; - } - - if (copy_from_user(&iov, uiov, sizeof(iov))) { - ret = -EFAULT; - break; - } - - regs = task_pt_regs(child); - len = min_t(size_t, iov.iov_len, sizeof(*regs)); - - if (request == PTRACE_GETREGSET) { - if (copy_to_user(iov.iov_base, regs, len)) { - ret = -EFAULT; - break; - } - } else { - /* - * Allow writing back regs. This is needed by the TRACE_syscall - * tests (they change PC/syscall nr/retval). - */ - if (copy_from_user(regs, iov.iov_base, len)) { - ret = -EFAULT; - break; - } - } - - /* Per API, update iov_len with amount transferred. */ - iov.iov_len = len; - if (copy_to_user(uiov, &iov, sizeof(iov))) { - ret = -EFAULT; - break; - } - - ret = 0; - break; - } - default: ret = ptrace_request(child, request, addr, data); break; @@ -410,3 +511,126 @@ syscall_trace_leave(void) if (test_thread_flag(TIF_SYSCALL_TRACE)) ptrace_report_syscall_exit(current_pt_regs(), 0); } + +/* + * Minimal regset support for Alpha. + * + * Alpha-specific notes: + * - Do NOT use ELF_CORE_COPY_REGS(): it uses current_thread_info(), + * which is wrong for non-current tasks. + * - dump_elf_task() returns 1 unconditionally in this tree, while + * 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, + const void *kbuf, + const void __user *ubuf) +{ + elf_gregset_t gregs; + unsigned int nwords; + + if (pos + count > sizeof(gregs)) + return -EIO; + + /* + * Preserve registers outside the written range. + */ + dump_elf_thread(gregs, task_pt_regs(target), + task_thread_info(target)); + + if (user_regset_copyin(&pos, &count, &kbuf, &ubuf, + gregs, 0, sizeof(gregs))) + return -EFAULT; + + nwords = sizeof(gregs) / sizeof(elf_greg_t); + alpha_elf_gregs_set(target, gregs, nwords); + + return 0; +} + +static int alpha_fpregset_set(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + const void *kbuf, + const void __user *ubuf) +{ + elf_fpregset_t fpregs; + unsigned int nwords; + + if (pos + count > sizeof(fpregs)) + return -EIO; + + alpha_elf_fpregs_get(target, fpregs); + + if (user_regset_copyin(&pos, &count, &kbuf, &ubuf, + fpregs, 0, sizeof(fpregs))) + return -EFAULT; + + nwords = sizeof(fpregs) / sizeof(elf_fpreg_t); + alpha_elf_fpregs_set(target, fpregs, nwords); + + return 0; +} + +static int alpha_regset_get(struct task_struct *target, + const struct user_regset *regset, + struct membuf to) +{ + struct pt_regs *pt = task_pt_regs(target); + struct thread_info *ti = task_thread_info(target); + elf_gregset_t gregs; + + dump_elf_thread(gregs, pt, ti); + return membuf_write(&to, gregs, sizeof(gregs)); +} + +static int alpha_fpregset_get(struct task_struct *target, + const struct user_regset *regset, + struct membuf to) +{ + elf_fpregset_t fpregs; + + alpha_elf_fpregs_get(target, fpregs); + return membuf_write(&to, fpregs, sizeof(fpregs)); +} + +enum alpha_regset { + REGSET_GPR, + REGSET_FPR, +}; + +static const struct user_regset alpha_user_regsets[] = { + [REGSET_GPR] = { + .core_note_type = NT_PRSTATUS, + .n = ELF_NGREG, + .size = sizeof(elf_greg_t), + .align = sizeof(elf_greg_t), + .regset_get = alpha_regset_get, + .set = alpha_regset_set, + }, + [REGSET_FPR] = { + .core_note_type = NT_PRFPREG, + .core_note_name = "CORE", + .n = ELF_NFPREG, + .size = sizeof(elf_fpreg_t), + .align = sizeof(elf_fpreg_t), + .regset_get = alpha_fpregset_get, + .set = alpha_fpregset_set, + }, +}; + +static const struct user_regset_view user_alpha_view = { + .name = "alpha", + .e_machine = EM_ALPHA, + .ei_osabi = ELF_OSABI, + .regsets = alpha_user_regsets, + .n = ARRAY_SIZE(alpha_user_regsets), +}; + +const struct user_regset_view *task_user_regset_view(struct task_struct *task) +{ + return &user_alpha_view; +} diff --git a/arch/alpha/kernel/traps.c b/arch/alpha/kernel/traps.c index 7004397937cf..7631129ac914 100644 --- a/arch/alpha/kernel/traps.c +++ b/arch/alpha/kernel/traps.c @@ -30,6 +30,12 @@ #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) { @@ -180,6 +186,7 @@ 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 @@ -201,6 +208,7 @@ 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 46651c650facce7cac33610ccc7a07a9c9d90df4 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Mon, 6 Jul 2026 18:56:43 +0200 Subject: [PATCH 05/15] alpha: add ARCH_STACKWALK-based stacktrace support Implement arch_stack_walk() for Alpha using a simple kernel stack scanning walker. Start from regs+1 for current tasks to skip pt_regs and use pcb.ksp for blocked tasks. Filter candidates with __kernel_text_address() and stop at stack bounds via kstack_end(). Enable CONFIG_STACKTRACE_SUPPORT and CONFIG_ARCH_STACKWALK so generic stacktrace users (dump_stack(), /proc/*/stack, SysRq backtraces, etc.) work on Alpha. This provides functional in-kernel stack traces without requiring frame pointer unwinding. Reviewed-by: Matt Turner Tested-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260706170019.2941459-3-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 4 +++ arch/alpha/kernel/Makefile | 3 +- arch/alpha/kernel/stacktrace.c | 61 +++++++++++++++++++++++++++++++++ arch/alpha/kernel/vmlinux.lds.S | 2 ++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 arch/alpha/kernel/stacktrace.c diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index f3b882835617..7ac435c56845 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -39,6 +39,7 @@ config ALPHA select MODULES_USE_ELF_RELA select ODD_RT_SIGACTION select OLD_SIGSUSPEND + select ARCH_STACKWALK select CPU_NO_EFFICIENT_FFS if !ALPHA_EV67 select MMU_GATHER_NO_RANGE select MMU_GATHER_RCU_TABLE_FREE @@ -80,6 +81,9 @@ config PGTABLE_LEVELS config AUDIT_ARCH bool +config STACKTRACE_SUPPORT + def_bool y + menu "System setup" choice diff --git a/arch/alpha/kernel/Makefile b/arch/alpha/kernel/Makefile index 187cd8df2faf..4ea5c189e60e 100644 --- a/arch/alpha/kernel/Makefile +++ b/arch/alpha/kernel/Makefile @@ -9,7 +9,8 @@ ccflags-y := -Wno-sign-compare obj-y := head.o entry.o traps.o process.o osf_sys.o irq.o \ irq_alpha.o signal.o setup.o ptrace.o time.o \ - systbls.o err_common.o io.o bugs.o termios.o + systbls.o err_common.o io.o bugs.o termios.o \ + stacktrace.o obj-$(CONFIG_VGA_HOSE) += console.o obj-$(CONFIG_SMP) += smp.o diff --git a/arch/alpha/kernel/stacktrace.c b/arch/alpha/kernel/stacktrace.c new file mode 100644 index 000000000000..74d95f591039 --- /dev/null +++ b/arch/alpha/kernel/stacktrace.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include + +#include +#include + +static __always_inline unsigned long alpha_get_current_ksp(void) +{ + unsigned long sp; + + asm volatile("mov $30, %0" : "=r"(sp)); + return sp; +} + +static void alpha_scan_kernel_stack(unsigned long ksp, + stack_trace_consume_fn consume_entry, + void *cookie) +{ + unsigned long *p = (unsigned long *)ksp; + + if (unlikely(ksp & (sizeof(unsigned long) - 1))) + return; + + while (!kstack_end(p)) { + unsigned long addr = READ_ONCE_NOCHECK(*p++); + + if (!__kernel_text_address(addr)) + continue; + + if (!consume_entry(cookie, addr)) + break; + } +} + +noinline void arch_stack_walk(stack_trace_consume_fn consume_entry, + void *cookie, + struct task_struct *task, + struct pt_regs *regs) +{ + unsigned long ksp; + + if (!task) + task = current; + + if (regs && task == current) { + /* + * pt_regs is stored on the kernel stack; regs+1 matches + * what arch/alpha/kernel/traps.c uses as the trace start. + */ + ksp = (unsigned long)(regs + 1); + } else if (task == current) { + ksp = alpha_get_current_ksp(); + } else { + ksp = task_thread_info(task)->pcb.ksp; + } + + alpha_scan_kernel_stack(ksp, consume_entry, cookie); +} diff --git a/arch/alpha/kernel/vmlinux.lds.S b/arch/alpha/kernel/vmlinux.lds.S index 2d136c63db16..95704e64b6a6 100644 --- a/arch/alpha/kernel/vmlinux.lds.S +++ b/arch/alpha/kernel/vmlinux.lds.S @@ -28,6 +28,8 @@ SECTIONS TEXT_TEXT SCHED_TEXT LOCK_TEXT + IRQENTRY_TEXT + SOFTIRQENTRY_TEXT *(.fixup) *(.gnu.warning) } :text From 05dc12d664f54cd23dad1e13c2fadbbd73f08450 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Mon, 6 Jul 2026 18:56:44 +0200 Subject: [PATCH 06/15] alpha: make irqflags helpers operate on IPL state Alpha interrupt masking is controlled by the PAL IPL value, not by the full processor status word. Make arch_local_save_flags() return the current IPL directly, and make arch_local_irq_restore() and arch_irqs_disabled_flags() treat their argument as IPL state. Mask the low IPL bits in the restore and test helpers so callers which still pass a saved PS value continue to behave as expected. This prepares the irqflags helpers for lockdep IRQ-state tracking, where the saved flags value is used to determine whether hard IRQs are enabled or disabled. Reviewed-by: Matt Turner Tested-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260706170019.2941459-4-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/irqflags.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/alpha/include/asm/irqflags.h b/arch/alpha/include/asm/irqflags.h index 9f25d4e0d37e..f207544f52de 100644 --- a/arch/alpha/include/asm/irqflags.h +++ b/arch/alpha/include/asm/irqflags.h @@ -26,7 +26,7 @@ extern int __min_ipl; static inline unsigned long arch_local_save_flags(void) { - return rdps(); + return getipl(); } static inline void arch_local_irq_disable(void) @@ -51,13 +51,13 @@ static inline void arch_local_irq_enable(void) static inline void arch_local_irq_restore(unsigned long flags) { barrier(); - setipl(flags); + setipl(flags & 7); barrier(); } static inline bool arch_irqs_disabled_flags(unsigned long flags) { - return flags == IPL_MAX; + return (flags & 7) == IPL_MAX; } static inline bool arch_irqs_disabled(void) From cfdf5b879d092ef277a0a657278f7f7f47be8adc Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Mon, 6 Jul 2026 18:56:45 +0200 Subject: [PATCH 07/15] alpha: provide ftrace return address support for lockdep Lockdep uses ftrace_return_address() to report useful call sites for lock acquisition and IRQ-state tracking diagnostics. Provide the Alpha architecture hook using the compiler return-address builtin when frame pointers are available. Return zero when frame pointers are disabled, matching the existing fallback behavior of architectures that cannot provide a reliable return address. This is a preparatory change for enabling lockdep support on Alpha. Reviewed-by: Matt Turner Tested-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260706170019.2941459-5-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/asm/ftrace.h | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/arch/alpha/include/asm/ftrace.h b/arch/alpha/include/asm/ftrace.h index 40a8c178f10d..7ec44134c804 100644 --- a/arch/alpha/include/asm/ftrace.h +++ b/arch/alpha/include/asm/ftrace.h @@ -1 +1,29 @@ -/* empty */ +/* SPDX-License-Identifier: GPL-2.0 */ +#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) +{ + return __builtin_return_address(0); +} + +#define ftrace_return_address0 alpha_ftrace_return_address0() + +/* + * __builtin_return_address() requires a constant integer argument. + * Keep this as a macro so the value is seen at the callsite. + */ +#define ftrace_return_address(n) __builtin_return_address(n) + +#else /* !CONFIG_FRAME_POINTER */ + +#define ftrace_return_address0 0UL +#define ftrace_return_address(n) ((void)(n), 0UL) + +#endif /* CONFIG_FRAME_POINTER */ + +#endif /* _ASM_ALPHA_FTRACE_H */ From 3e4c9102e0af08f1d0206a59f0f9962964ae2193 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Mon, 6 Jul 2026 18:56:46 +0200 Subject: [PATCH 08/15] alpha: use raw spinlocks for low-level platform locks Some Alpha platform locks are used as low-level hardware serialization locks in interrupt-controller and chipset access paths. These paths can run while IRQ state is being changed or while lockdep is tracking that state, so regular spinlock instrumentation is not appropriate once lockdep is enabled. Convert the affected Tsunami and Rawhide platform locks to raw_spinlock_t. This keeps the locks as simple hardware serialization locks and avoids lockdep recursion or IRQ-state mismatches when CONFIG_PROVE_LOCKING is enabled. This is a preparatory change for enabling lockdep hardirq state tracking on Alpha. Reviewed-by: Matt Turner Tested-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260706170019.2941459-6-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/irq_i8259.c | 19 +++++++++++------ arch/alpha/kernel/sys_dp264.c | 38 ++++++++++++++++++++++----------- arch/alpha/kernel/sys_rawhide.c | 17 +++++++++------ 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/arch/alpha/kernel/irq_i8259.c b/arch/alpha/kernel/irq_i8259.c index 29c6c477ac35..28f7b0680564 100644 --- a/arch/alpha/kernel/irq_i8259.c +++ b/arch/alpha/kernel/irq_i8259.c @@ -22,7 +22,7 @@ /* Note mask bit is true for DISABLED irqs. */ static unsigned int cached_irq_mask = 0xffff; -static DEFINE_SPINLOCK(i8259_irq_lock); +static DEFINE_RAW_SPINLOCK(i8259_irq_lock); static inline void i8259_update_irq_hw(unsigned int irq, unsigned long mask) @@ -36,9 +36,11 @@ i8259_update_irq_hw(unsigned int irq, unsigned long mask) inline void i8259a_enable_irq(struct irq_data *d) { - spin_lock(&i8259_irq_lock); + unsigned long flags; + + raw_spin_lock_irqsave(&i8259_irq_lock, flags); i8259_update_irq_hw(d->irq, cached_irq_mask &= ~(1 << d->irq)); - spin_unlock(&i8259_irq_lock); + raw_spin_unlock_irqrestore(&i8259_irq_lock, flags); } static inline void @@ -50,17 +52,20 @@ __i8259a_disable_irq(unsigned int irq) void i8259a_disable_irq(struct irq_data *d) { - spin_lock(&i8259_irq_lock); + unsigned long flags; + + raw_spin_lock_irqsave(&i8259_irq_lock, flags); __i8259a_disable_irq(d->irq); - spin_unlock(&i8259_irq_lock); + raw_spin_unlock_irqrestore(&i8259_irq_lock, flags); } void i8259a_mask_and_ack_irq(struct irq_data *d) { unsigned int irq = d->irq; + unsigned long flags; - spin_lock(&i8259_irq_lock); + raw_spin_lock_irqsave(&i8259_irq_lock, flags); __i8259a_disable_irq(irq); /* Ack the interrupt making it the lowest priority. */ @@ -69,7 +74,7 @@ i8259a_mask_and_ack_irq(struct irq_data *d) irq = 2; } outb(0xE0 | irq, 0x20); /* ack the master */ - spin_unlock(&i8259_irq_lock); + raw_spin_unlock_irqrestore(&i8259_irq_lock, flags); } struct irq_chip i8259a_irq_type = { diff --git a/arch/alpha/kernel/sys_dp264.c b/arch/alpha/kernel/sys_dp264.c index 9fb445d7dca5..0a2d319bb1c8 100644 --- a/arch/alpha/kernel/sys_dp264.c +++ b/arch/alpha/kernel/sys_dp264.c @@ -41,7 +41,7 @@ static unsigned long cached_irq_mask; /* dp264 boards handle at max four CPUs */ static unsigned long cpu_irq_affinity[4] = { 0UL, 0UL, 0UL, 0UL }; -DEFINE_SPINLOCK(dp264_irq_lock); +static DEFINE_RAW_SPINLOCK(dp264_irq_lock); static void tsunami_update_irq_hw(unsigned long mask) @@ -99,37 +99,45 @@ tsunami_update_irq_hw(unsigned long mask) static void dp264_enable_irq(struct irq_data *d) { - spin_lock(&dp264_irq_lock); + unsigned long flags; + + raw_spin_lock_irqsave(&dp264_irq_lock, flags); cached_irq_mask |= 1UL << d->irq; tsunami_update_irq_hw(cached_irq_mask); - spin_unlock(&dp264_irq_lock); + raw_spin_unlock_irqrestore(&dp264_irq_lock, flags); } static void dp264_disable_irq(struct irq_data *d) { - spin_lock(&dp264_irq_lock); + unsigned long flags; + + raw_spin_lock_irqsave(&dp264_irq_lock, flags); cached_irq_mask &= ~(1UL << d->irq); tsunami_update_irq_hw(cached_irq_mask); - spin_unlock(&dp264_irq_lock); + raw_spin_unlock_irqrestore(&dp264_irq_lock, flags); } static void clipper_enable_irq(struct irq_data *d) { - spin_lock(&dp264_irq_lock); + unsigned long flags; + + raw_spin_lock_irqsave(&dp264_irq_lock, flags); cached_irq_mask |= 1UL << (d->irq - 16); tsunami_update_irq_hw(cached_irq_mask); - spin_unlock(&dp264_irq_lock); + raw_spin_unlock_irqrestore(&dp264_irq_lock, flags); } static void clipper_disable_irq(struct irq_data *d) { - spin_lock(&dp264_irq_lock); + unsigned long flags; + + raw_spin_lock_irqsave(&dp264_irq_lock, flags); cached_irq_mask &= ~(1UL << (d->irq - 16)); tsunami_update_irq_hw(cached_irq_mask); - spin_unlock(&dp264_irq_lock); + raw_spin_unlock_irqrestore(&dp264_irq_lock, flags); } static void @@ -151,10 +159,12 @@ static int dp264_set_affinity(struct irq_data *d, const struct cpumask *affinity, bool force) { - spin_lock(&dp264_irq_lock); + unsigned long flags; + + raw_spin_lock_irqsave(&dp264_irq_lock, flags); cpu_set_irq_affinity(d->irq, *affinity); tsunami_update_irq_hw(cached_irq_mask); - spin_unlock(&dp264_irq_lock); + raw_spin_unlock_irqrestore(&dp264_irq_lock, flags); return 0; } @@ -163,10 +173,12 @@ static int clipper_set_affinity(struct irq_data *d, const struct cpumask *affinity, bool force) { - spin_lock(&dp264_irq_lock); + unsigned long flags; + + raw_spin_lock_irqsave(&dp264_irq_lock, flags); cpu_set_irq_affinity(d->irq - 16, *affinity); tsunami_update_irq_hw(cached_irq_mask); - spin_unlock(&dp264_irq_lock); + raw_spin_unlock_irqrestore(&dp264_irq_lock, flags); return 0; } diff --git a/arch/alpha/kernel/sys_rawhide.c b/arch/alpha/kernel/sys_rawhide.c index b5846ffdadce..b4a08890dce9 100644 --- a/arch/alpha/kernel/sys_rawhide.c +++ b/arch/alpha/kernel/sys_rawhide.c @@ -41,7 +41,7 @@ static unsigned int hose_irq_masks[4] = { 0xff0000, 0xfe0000, 0xff0000, 0xff0000 }; static unsigned int cached_irq_masks[4]; -DEFINE_SPINLOCK(rawhide_irq_lock); +DEFINE_RAW_SPINLOCK(rawhide_irq_lock); static inline void rawhide_update_irq_hw(int hose, int mask) @@ -59,6 +59,7 @@ rawhide_enable_irq(struct irq_data *d) { unsigned int mask, hose; unsigned int irq = d->irq; + unsigned long flags; irq -= 16; hose = irq / 24; @@ -68,11 +69,11 @@ rawhide_enable_irq(struct irq_data *d) irq -= hose * 24; mask = 1 << irq; - spin_lock(&rawhide_irq_lock); + raw_spin_lock_irqsave(&rawhide_irq_lock, flags); mask |= cached_irq_masks[hose]; cached_irq_masks[hose] = mask; rawhide_update_irq_hw(hose, mask); - spin_unlock(&rawhide_irq_lock); + raw_spin_unlock_irqrestore(&rawhide_irq_lock, flags); } static void @@ -80,6 +81,7 @@ rawhide_disable_irq(struct irq_data *d) { unsigned int mask, hose; unsigned int irq = d->irq; + unsigned long flags; irq -= 16; hose = irq / 24; @@ -89,11 +91,11 @@ rawhide_disable_irq(struct irq_data *d) irq -= hose * 24; mask = ~(1 << irq) | hose_irq_masks[hose]; - spin_lock(&rawhide_irq_lock); + raw_spin_lock_irqsave(&rawhide_irq_lock, flags); mask &= cached_irq_masks[hose]; cached_irq_masks[hose] = mask; rawhide_update_irq_hw(hose, mask); - spin_unlock(&rawhide_irq_lock); + raw_spin_unlock_irqrestore(&rawhide_irq_lock, flags); } static void @@ -101,6 +103,7 @@ rawhide_mask_and_ack_irq(struct irq_data *d) { unsigned int mask, mask1, hose; unsigned int irq = d->irq; + unsigned long flags; irq -= 16; hose = irq / 24; @@ -111,7 +114,7 @@ rawhide_mask_and_ack_irq(struct irq_data *d) mask1 = 1 << irq; mask = ~mask1 | hose_irq_masks[hose]; - spin_lock(&rawhide_irq_lock); + raw_spin_lock_irqsave(&rawhide_irq_lock, flags); mask &= cached_irq_masks[hose]; cached_irq_masks[hose] = mask; @@ -120,7 +123,7 @@ rawhide_mask_and_ack_irq(struct irq_data *d) /* Clear the interrupt. */ *(vuip)MCPCIA_INT_REQ(MCPCIA_HOSE2MID(hose)) = mask1; - spin_unlock(&rawhide_irq_lock); + raw_spin_unlock_irqrestore(&rawhide_irq_lock, flags); } static struct irq_chip rawhide_irq_type = { From d1ee975398a357be8f56836a87ae55c51770e4f0 Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Mon, 6 Jul 2026 18:56:47 +0200 Subject: [PATCH 09/15] alpha: enable lockdep hardirq state tracking Alpha masks interrupts through the PAL IPL state, so lockdep cannot infer hardirq state transitions from generic code alone. Add explicit hardirq on/off annotations to the low-level entry and return paths so lockdep's IRQ state follows the hardware IPL state. Annotate the PAL IPL transitions and the shared return-to-user/kernel paths where interrupts become enabled or disabled. With the preceding irqflags, raw-lock, sysfs, and ftrace return-address preparations in place, select LOCKDEP_SUPPORT and TRACE_IRQFLAGS_SUPPORT for Alpha. This keeps CONFIG_PROVE_LOCKING usable on Alpha instead of disabling debug_locks due to IRQ-state mismatches. Reviewed-by: Matt Turner Tested-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260706170019.2941459-7-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- .../features/locking/lockdep/arch-support.txt | 2 +- arch/alpha/Kconfig | 5 ++ arch/alpha/kernel/entry.S | 17 ++++- arch/alpha/kernel/irq_alpha.c | 74 ++++++++++++++----- arch/alpha/kernel/proto.h | 4 + arch/alpha/kernel/signal.c | 9 +++ 6 files changed, 91 insertions(+), 20 deletions(-) diff --git a/Documentation/features/locking/lockdep/arch-support.txt b/Documentation/features/locking/lockdep/arch-support.txt index b6b00469f7d0..87a534c89636 100644 --- a/Documentation/features/locking/lockdep/arch-support.txt +++ b/Documentation/features/locking/lockdep/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 7ac435c56845..e53ef2d88463 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -45,6 +45,8 @@ config ALPHA select MMU_GATHER_RCU_TABLE_FREE select SPARSEMEM_EXTREME if SPARSEMEM select ZONE_DMA + select TRACE_IRQFLAGS_SUPPORT + select ARCH_WANT_FRAME_POINTERS help The Alpha is a 64-bit general-purpose processor designed and marketed by the Digital Equipment Corporation of blessed memory, @@ -84,6 +86,9 @@ config AUDIT_ARCH config STACKTRACE_SUPPORT def_bool y +config LOCKDEP_SUPPORT + def_bool y + menu "System setup" choice diff --git a/arch/alpha/kernel/entry.S b/arch/alpha/kernel/entry.S index 449092a31eef..9f2608de2544 100644 --- a/arch/alpha/kernel/entry.S +++ b/arch/alpha/kernel/entry.S @@ -93,6 +93,19 @@ 4: .endm +.macro LOCKDEP_HARDIRQS_ON_RESTORE +#ifdef CONFIG_PROVE_LOCKING + /* a0 = saved PS */ + ldq $16, SP_OFF($sp) + + /* a1 = callsite IP for lockdep */ + lda $17, 1f + + jsr $26, lockdep_on_restore + ldgp $gp, 0($26) +1: +#endif +.endm /* * This defines the normal kernel pt-regs layout. @@ -427,6 +440,7 @@ CFI_START_OSF_FRAME entUna .cfi_restore $28 .cfi_restore $29 .cfi_adjust_cfa_offset -256 + LOCKDEP_HARDIRQS_ON_RESTORE call_pal PAL_rti .align 4 @@ -577,6 +591,7 @@ restore_all: bne $3, restore_fpu restore_other: .cfi_remember_state + LOCKDEP_HARDIRQS_ON_RESTORE RESTORE_ALL call_pal PAL_rti @@ -622,7 +637,7 @@ $work_resched: * or got through work_notifysig already. Either case means no syscall * restarts for us, so let $18 and $19 burn. */ - jsr $26, schedule + jsr $26, alpha_schedule_user_work mov 0, $18 br ret_to_user diff --git a/arch/alpha/kernel/irq_alpha.c b/arch/alpha/kernel/irq_alpha.c index d17e44c99df9..736294d3dd51 100644 --- a/arch/alpha/kernel/irq_alpha.c +++ b/arch/alpha/kernel/irq_alpha.c @@ -41,7 +41,7 @@ EXPORT_SYMBOL(perf_irq); * The main interrupt entry point. */ -asmlinkage void +asmlinkage void do_entInt(unsigned long type, unsigned long vector, unsigned long la_ptr, struct pt_regs *regs) { @@ -54,40 +54,78 @@ do_entInt(unsigned long type, unsigned long vector, * (namely LX164). */ local_irq_disable(); + old_regs = set_irq_regs(regs); + switch (type) { case 0: #ifdef CONFIG_SMP + irq_enter(); handle_ipi(regs); - return; + irq_exit(); + break; #else irq_err_count++; - printk(KERN_CRIT "Interprocessor interrupt? " - "You must be kidding!\n"); -#endif + pr_crit("Interprocessor interrupt? You must be kidding!\n"); break; +#endif case 1: - old_regs = set_irq_regs(regs); + /* handle_irq() already does irq_enter()/irq_exit() */ handle_irq(RTC_IRQ); - set_irq_regs(old_regs); - return; + break; case 2: - old_regs = set_irq_regs(regs); + irq_enter(); alpha_mv.machine_check(vector, la_ptr); - set_irq_regs(old_regs); - return; + irq_exit(); + break; case 3: - old_regs = set_irq_regs(regs); + irq_enter(); alpha_mv.device_interrupt(vector); - set_irq_regs(old_regs); - return; + irq_exit(); + break; case 4: + irq_enter(); perf_irq(la_ptr, regs); - return; + irq_exit(); + break; default: - printk(KERN_CRIT "Hardware intr %ld %lx? Huh?\n", - type, vector); + pr_crit("Hardware intr %lu %lx? Huh?\n", type, vector); + pr_crit("PC = %016lx PS=%04lx\n", regs->pc, regs->ps); + break; } - printk(KERN_CRIT "PC = %016lx PS=%04lx\n", regs->pc, regs->ps); + + set_irq_regs(old_regs); + + /* + * Intentionally no local_irq_enable(): Alpha historically avoids + * enabling at IPL0 here due to PAL/RTI issues (LX164/MILO note). + */ +} + +void notrace lockdep_on_restore(unsigned long ps, + unsigned long ip) +{ +#ifdef CONFIG_PROVE_LOCKING + /* Restoring IPL==7 means interrupts remain disabled. */ + if ((ps & 7) == 7) + return; + + /* + * If hardware IRQs are already enabled here, then emitting a + * hardirqs-on transition is redundant. + */ + if (!irqs_disabled()) + return; + + /* + * Only emit the transition if lockdep currently believes + * hardirqs are off. + */ + if (lockdep_hardirqs_enabled()) + return; + + lockdep_hardirqs_on_prepare(); + lockdep_hardirqs_on(ip); +#endif } void __init diff --git a/arch/alpha/kernel/proto.h b/arch/alpha/kernel/proto.h index a8bc3ead776b..9b262ef09a3a 100644 --- a/arch/alpha/kernel/proto.h +++ b/arch/alpha/kernel/proto.h @@ -173,6 +173,7 @@ 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); /* traps.c */ extern void dik_show_regs(struct pt_regs *regs, unsigned long *r9_15); @@ -185,6 +186,9 @@ struct allregs; extern void do_entUna(void *, unsigned long, unsigned long, struct allregs *); extern void do_entUnaUser(void __user *, unsigned long, unsigned long, struct pt_regs *); +/* irq_alpha.c */ +extern void notrace lockdep_on_restore(unsigned long ps, unsigned long ip); + /* sys_titan.c */ extern void titan_dispatch_irqs(u64); diff --git a/arch/alpha/kernel/signal.c b/arch/alpha/kernel/signal.c index e62d1d461b1f..ce40a49b8496 100644 --- a/arch/alpha/kernel/signal.c +++ b/arch/alpha/kernel/signal.c @@ -41,6 +41,14 @@ 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; @@ -525,6 +533,7 @@ do_work_pending(struct pt_regs *regs, unsigned long thread_flags, { do { if (thread_flags & _TIF_NEED_RESCHED) { + local_irq_enable(); schedule(); } else { local_irq_enable(); From 49672d026cc4773608e1222b69b29fd70f41336b Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Mon, 3 Aug 2026 19:40:45 -0400 Subject: [PATCH 10/15] alpha: fix ieee_swcr_to_fpcr setting FPCR_DNOD unconditionally ieee_swcr_to_fpcr() converts the software IEEE trap-enable and status bits kept in thread_info.ieee_state into the hardware FPCR format. It contained: fp |= (~sw & IEEE_TRAP_ENABLE_DNO) << 41; FPCR_DNOD (bit 47) disables denormal operand traps: with it set the hardware handles a denormal operand itself, treating it as zero, instead of trapping for software completion. The intent was to set DNOD when the user has not asked for SIGFPE on denormal operands, but IEEE_TRAP_ENABLE_DNO is clear by default, so ieee_swcr_to_fpcr(0) always set DNOD. Instructions built with the software completion suffix therefore never trapped on a denormal operand. The hardware silently substituted zero and produced wrong results, affecting every program compiled with -mieee and default FPU settings, glibc included. Set FPCR_DNOD only when IEEE_MAP_DMZ is requested, which is exactly the case where flushing denormal inputs to zero is what the user asked for. DNOD then encodes MAP_DMZ, which ieee_fpcr_to_swcr() already recovers from FPCR_DNZ, so drop its attempt to recover IEEE_TRAP_ENABLE_DNO from DNOD; the DNO trap enable lives solely in ieee_state. Both functions are in a uapi header, so the encoding change is visible to userspace, but nothing outside the kernel is known to depend on DNOD carrying the DNO trap enable, and the kernel is the only writer of the FPCR. This must not be backported on its own. Re-enabling denormal operand traps exposes a second bug, fixed in the following patch: those traps usually find an exact result, and for an exact result the emulator did not write the FPCR back, leaving hardware-fabricated exception bits visible to user space. Taken alone this change would make spurious exception flags more common. The bug predates the git history, so there is no commit to reference in a Fixes tag. Cc: stable@vger.kernel.org # 5.15+ Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260803-alpha-fp-exceptions-v1-1-c99d75608e60@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/include/uapi/asm/fpu.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/alpha/include/uapi/asm/fpu.h b/arch/alpha/include/uapi/asm/fpu.h index cea9eafa056f..d28dc36786e2 100644 --- a/arch/alpha/include/uapi/asm/fpu.h +++ b/arch/alpha/include/uapi/asm/fpu.h @@ -101,7 +101,12 @@ ieee_swcr_to_fpcr(unsigned long sw) | IEEE_TRAP_ENABLE_OVF)) << 48; fp |= (~sw & (IEEE_TRAP_ENABLE_UNF | IEEE_TRAP_ENABLE_INE)) << 57; fp |= (sw & IEEE_MAP_UMZ ? FPCR_UNDZ | FPCR_UNFD : 0); - fp |= (~sw & IEEE_TRAP_ENABLE_DNO) << 41; + /* + * Disable denormal operand traps only when denormal inputs are to be + * flushed to zero. Otherwise they must keep trapping, so that /S + * instructions reach the kernel emulation handler. + */ + fp |= (sw & IEEE_MAP_DMZ ? FPCR_DNOD : 0); return fp; } @@ -116,7 +121,6 @@ ieee_fpcr_to_swcr(unsigned long fp) | IEEE_TRAP_ENABLE_OVF); sw |= (~fp >> 57) & (IEEE_TRAP_ENABLE_UNF | IEEE_TRAP_ENABLE_INE); sw |= (fp >> 47) & IEEE_MAP_UMZ; - sw |= (~fp >> 41) & IEEE_TRAP_ENABLE_DNO; return sw; } From bcfe3187412e342b4619efb92c945f073855ebc0 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Mon, 3 Aug 2026 19:40:46 -0400 Subject: [PATCH 11/15] alpha: don't leak hardware-fabricated FP exception bits to user space On EV6 and later the hardware records exception status bits in the FPCR before delivering a software completion trap, and those bits can be wrong for the instruction that trapped. Converting a double that is exactly representable as a subnormal float sets FPCR_UNF even though the result is exact, and an underflow trap additionally sets FPCR_INE even when the emulated operation turns out to be exact. alpha_fp_emul() only wrote the FPCR when soft-fp raised an exception, so whenever it determined that the instruction was exact the fabricated bits stayed in the FPCR and were reported to user space by fetestexcept(). Pass the exception summary register down from do_entArith() so the handler can tell which exceptions the hardware attributed to the trapping instruction, and always write the FPCR. Clear the exceptions that the trap reported but that soft-fp did not raise. EXC_SUM reports only the underflow or overflow when the hardware also set INE, so treat INE as a candidate in that case, and treat a trap with no reported exception as a denormal operand trap, for which the hardware can fabricate INE and UNF as well. Bits that software has already confirmed in ieee_state belong to this or an earlier instruction and are never cleared. The imprecise path passes no summary. There the trap was taken somewhere in the trap shadow, so EXC_SUM is not attribution for the instruction being re-executed -- and only EV6, which traps precisely and so never takes that path, has fabricated bits to clear. For the same reason the clearing is guarded by implver(), matching swcr_update_status(). On an UP1500 (EV68) this takes the glibc math testsuite from 831 failures to 28, the remainder being unrelated to exception status. This belongs with the preceding fix to ieee_swcr_to_fpcr(), and should not be backported without it -- nor it without this. That fix stops FPCR_DNOD being set unconditionally, so denormal operand traps start firing again. Those traps very often find an exact result, which is precisely the case where the old code left the FPCR unwritten and the fabricated bits visible. Applied alone it would make spurious exception flags more common, not less. One case cannot be resolved here: an inexact instruction without the software completion suffix never traps, so its INE reaches the FPCR without being recorded anywhere else. Such a bit is indistinguishable from an INE the hardware fabricated for a trapping instruction, and is lost if an underflow or overflow trap with an exact result follows it. The FPCR is the only record of those instructions and it carries no attribution. The bug predates the git history, so there is no commit to reference in a Fixes tag. Cc: stable@vger.kernel.org # 5.15+ Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260803-alpha-fp-exceptions-v1-2-c99d75608e60@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/traps.c | 6 +-- arch/alpha/math-emu/math.c | 88 +++++++++++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/arch/alpha/kernel/traps.c b/arch/alpha/kernel/traps.c index 7631129ac914..4492a90a579f 100644 --- a/arch/alpha/kernel/traps.c +++ b/arch/alpha/kernel/traps.c @@ -172,12 +172,12 @@ static long dummy_emul(void) { return 0; } long (*alpha_fp_emul_imprecise)(struct pt_regs *regs, unsigned long writemask) = (void *)dummy_emul; EXPORT_SYMBOL_GPL(alpha_fp_emul_imprecise); -long (*alpha_fp_emul) (unsigned long pc) +long (*alpha_fp_emul) (unsigned long pc, unsigned long summary) = (void *)dummy_emul; EXPORT_SYMBOL_GPL(alpha_fp_emul); #else long alpha_fp_emul_imprecise(struct pt_regs *regs, unsigned long writemask); -long alpha_fp_emul (unsigned long pc); +long alpha_fp_emul (unsigned long pc, unsigned long summary); #endif asmlinkage void @@ -192,7 +192,7 @@ do_entArith(unsigned long summary, unsigned long write_mask, emulate the instruction. If the processor supports precise exceptions, we don't have to search. */ if (!amask(AMASK_PRECISE_TRAP)) - si_code = alpha_fp_emul(regs->pc - 4); + si_code = alpha_fp_emul(regs->pc - 4, summary); else si_code = alpha_fp_emul_imprecise(regs, write_mask); if (si_code == 0) diff --git a/arch/alpha/math-emu/math.c b/arch/alpha/math-emu/math.c index 68d420bfd3c0..e3f2df3729e3 100644 --- a/arch/alpha/math-emu/math.c +++ b/arch/alpha/math-emu/math.c @@ -52,13 +52,13 @@ MODULE_DESCRIPTION("FP Software completion module"); MODULE_LICENSE("GPL v2"); extern long (*alpha_fp_emul_imprecise)(struct pt_regs *, unsigned long); -extern long (*alpha_fp_emul) (unsigned long pc); +extern long (*alpha_fp_emul) (unsigned long pc, unsigned long summary); static long (*save_emul_imprecise)(struct pt_regs *, unsigned long); -static long (*save_emul) (unsigned long pc); +static long (*save_emul) (unsigned long pc, unsigned long summary); long do_alpha_fp_emul_imprecise(struct pt_regs *, unsigned long); -long do_alpha_fp_emul(unsigned long); +long do_alpha_fp_emul(unsigned long, unsigned long); static int alpha_fp_emul_init_module(void) { @@ -86,7 +86,22 @@ module_exit(alpha_fp_emul_cleanup_module); /* - * Emulate the floating point instruction at address PC. Returns -1 if the + * Exception bits of the exception summary register (EXC_SUM). Bit 0 is the + * software completion bit; bits 1 through 5 report the exceptions the + * hardware attributed to the trapping instruction, and lie at the same + * positions as the corresponding IEEE_TRAP_ENABLE_* bits. + */ +#define EXC_SUM_INV (1UL << 1) +#define EXC_SUM_DZE (1UL << 2) +#define EXC_SUM_OVF (1UL << 3) +#define EXC_SUM_UNF (1UL << 4) +#define EXC_SUM_INE (1UL << 5) +#define EXC_SUM_MASK (EXC_SUM_INV | EXC_SUM_DZE | EXC_SUM_OVF \ + | EXC_SUM_UNF | EXC_SUM_INE) + +/* + * Emulate the floating point instruction at address PC. SUMMARY is the + * exception summary register the trap was delivered with. Returns -1 if the * instruction to be emulated is illegal (such as with the opDEC trap), else * the SI_CODE for a SIGFPE signal, else 0 if everything's ok. * @@ -95,7 +110,7 @@ module_exit(alpha_fp_emul_cleanup_module); * stick the result of the operation into the appropriate register. */ long -alpha_fp_emul (unsigned long pc) +alpha_fp_emul (unsigned long pc, unsigned long summary) { FP_DECL_EX; FP_DECL_S(SA); FP_DECL_S(SB); FP_DECL_S(SR); @@ -300,12 +315,56 @@ alpha_fp_emul (unsigned long pc) swcr |= (_fex << IEEE_STATUS_TO_EXCSUM_SHIFT); current_thread_info()->ieee_state |= (_fex << IEEE_STATUS_TO_EXCSUM_SHIFT); + } - /* Update hardware control register. */ - fpcr &= (~FPCR_MASK | FPCR_DYN_MASK); - fpcr |= ieee_swcr_to_fpcr(swcr); - wrfpcr(fpcr); + /* + * EV6 records exception status bits in the FPCR before delivering the + * software completion trap, and swcr_update_status() above merged them + * into SWCR. Some can be wrong for the instruction we just emulated: + * a CVTTS of a value exactly representable as a subnormal sets FPCR_UNF + * even though the result is exact. Clear the exceptions the trap + * reported but that soft-fp did not raise. + */ + if (implver() == IMPLVER_EV6) { + unsigned long spurious = summary & EXC_SUM_MASK; + if (spurious & (EXC_SUM_UNF | EXC_SUM_OVF)) { + /* + * EXC_SUM reports only the underflow or overflow, + * but the hardware sets INE alongside it in the FPCR. + */ + spurious |= EXC_SUM_INE; + } else if (!spurious) { + /* + * No exception reported, so this was a denormal + * operand trap, for which INE and UNF can be + * fabricated as well. + */ + spurious = EXC_SUM_INE | EXC_SUM_UNF; + } + + /* + * Never clear an exception software has confirmed. Every + * instruction that genuinely raises one traps for software + * completion and is recorded in ieee_state above, so a bit + * found there -- including one just set from _fex -- belongs + * to this or an earlier instruction and must survive. + */ + spurious &= ~(current_thread_info()->ieee_state + >> IEEE_STATUS_TO_EXCSUM_SHIFT); + + swcr &= ~(spurious << IEEE_STATUS_TO_EXCSUM_SHIFT); + } + + /* + * Update hardware control register. This has to happen even when + * soft-fp raised nothing, to clear any fabricated bits. + */ + fpcr &= (~FPCR_MASK | FPCR_DYN_MASK); + fpcr |= ieee_swcr_to_fpcr(swcr); + wrfpcr(fpcr); + + if (_fex) { /* Do we generate a signal? */ _fex = _fex & swcr & IEEE_TRAP_ENABLE_MASK; si_code = 0; @@ -387,9 +446,16 @@ alpha_fp_emul_imprecise (struct pt_regs *regs, unsigned long write_mask) break; } if (!write_mask) { - /* Re-execute insns in the trap-shadow. */ + /* + * Re-execute insns in the trap-shadow. Pass no + * exception summary: it describes the trap, which + * was taken anywhere in the shadow, and so is not + * attribution for this instruction. Nothing is + * lost, since only EV6 -- which traps precisely and + * never comes this way -- needs it. + */ regs->pc = trigger_pc + 4; - si_code = alpha_fp_emul(trigger_pc); + si_code = alpha_fp_emul(trigger_pc, 0); goto egress; } trigger_pc -= 4; From 6f45effbd74012f1715584ba51abaa3196a47c08 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Mon, 10 Aug 2026 16:28:34 -0400 Subject: [PATCH 12/15] alpha: run the remote RTC access in a worker, not an IPI callback On Marvel the CMOS clock is only reachable from the boot cpu, so remote_read_time() and remote_set_time() bounce the access there with smp_call_function_single(), whose callback runs in hard interrupt context. alpha_rtc_read_time() calls mc146818_get_time() with a 10 ms timeout. That waits out the RTC update cycle in mc146818_avoid_UIP(), which drops rtc_lock and udelay()s 100 us at a time until the update completes or the timeout expires: for (i = 0; UIP_RECHECK_LOOPS_MS(i) < timeout; i++) { spin_lock_irqsave(&rtc_lock, flags); ... if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) { spin_unlock_irqrestore(&rtc_lock, flags); udelay(UIP_RECHECK_DELAY); continue; } So a clock read from a non-boot cpu can spin for up to 10 ms in hard interrupt context on the boot cpu, while the cpu that sent the request spins in smp_call_function_single() waiting for it to finish. mc146818_set_time() does not poll, but it takes rtc_lock too, and rtc_lock is a spinlock_t. Only raw spinlocks may be taken in hard interrupt context, so lockdep reports the write path as soon as a non-boot cpu sets the clock: [ BUG: Invalid wait context ] ----------------------------- swapper/0/0 is trying to lock: fffffc0003690470 (rtc_lock){....}-{3:3}, at: mc146818_set_time+0x74/0x450 other info that might help us debug this: context-{2:2} no locks held by swapper/0/0. stack backtrace: CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.2.0-rc1 #1 NONE Trace: [] dump_stack+0x28/0x44 [] __lock_acquire+0xb0c/0x1060 [] lock_acquire.part.0+0xd0/0x300 [...] [] mc146818_set_time+0x74/0x450 [] _raw_spin_lock_irqsave+0x7c/0xc0 [] do_remote_set+0x90/0xc0 [] __flush_smp_call_function_queue+0x314/0x5c0 [] generic_smp_call_function_single_interrupt+0x24/0x40 [] handle_ipi+0xa4/0x230 [] do_entInt+0x1a4/0x2e0 The rtc class ops are always called from process context, so there is no reason to run the access from an interrupt at all. Use work_on_cpu() to run it in a worker on the boot cpu. Alpha does not support cpu hotplug, so the boot cpu cannot go offline while the work is pending. Tested on an AlphaServer ES47 (Marvel/EV7): hwclock read and write pinned to a non-boot cpu, twenty times, with no splat. Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260810202835.3592833-1-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/rtc.c | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/arch/alpha/kernel/rtc.c b/arch/alpha/kernel/rtc.c index cfdf90bc8b3f..9e7d714ef6f8 100644 --- a/arch/alpha/kernel/rtc.c +++ b/arch/alpha/kernel/rtc.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "proto.h" @@ -142,54 +143,40 @@ static const struct rtc_class_ops alpha_rtc_ops = { }; /* - * Similarly, except do the actual CMOS access on the boot cpu only. - * This requires marshalling the data across an interprocessor call. + * Similarly, except do the actual CMOS access on the boot cpu only. The + * access polls for the RTC update cycle and takes rtc_lock, so run it in a + * worker on that cpu rather than from an interprocessor interrupt. */ #if defined(CONFIG_SMP) && \ (defined(CONFIG_ALPHA_GENERIC) || defined(CONFIG_ALPHA_MARVEL)) # define HAVE_REMOTE_RTC 1 -union remote_data { - struct rtc_time *tm; - long retval; -}; - -static void +static long do_remote_read(void *data) { - union remote_data *x = data; - x->retval = alpha_rtc_read_time(NULL, x->tm); + return alpha_rtc_read_time(NULL, data); } static int remote_read_time(struct device *dev, struct rtc_time *tm) { - union remote_data x; - if (smp_processor_id() != boot_cpuid) { - x.tm = tm; - smp_call_function_single(boot_cpuid, do_remote_read, &x, 1); - return x.retval; - } + if (smp_processor_id() != boot_cpuid) + return work_on_cpu(boot_cpuid, do_remote_read, tm); return alpha_rtc_read_time(NULL, tm); } -static void +static long do_remote_set(void *data) { - union remote_data *x = data; - x->retval = alpha_rtc_set_time(NULL, x->tm); + return alpha_rtc_set_time(NULL, data); } static int remote_set_time(struct device *dev, struct rtc_time *tm) { - union remote_data x; - if (smp_processor_id() != boot_cpuid) { - x.tm = tm; - smp_call_function_single(boot_cpuid, do_remote_set, &x, 1); - return x.retval; - } + if (smp_processor_id() != boot_cpuid) + return work_on_cpu(boot_cpuid, do_remote_set, tm); return alpha_rtc_set_time(NULL, tm); } From f32977deb1f6bfac0c5e0121905477bf21c973bd Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Mon, 10 Aug 2026 16:28:35 -0400 Subject: [PATCH 13/15] alpha: annotate hardirqs-off on IPL 7 interrupt entry do_entInt() opens with local_irq_disable(), which with CONFIG_TRACE_IRQFLAGS only calls trace_hardirqs_off() if interrupts were not already off: #define local_irq_disable() \ do { \ bool was_disabled = raw_irqs_disabled();\ raw_local_irq_disable(); \ if (!was_disabled) \ trace_hardirqs_off(); \ } while (0) On alpha raw_irqs_disabled() is (rdps() & 7) == IPL_MAX, i.e. IPL 7. PALcode raises PS.IPL to the level of the interrupt before entInt runs, so for an IPL 7 entry - a processor machine check (vector 0x660) or a system event (vector 0x680), both IPL_MCHECK == IPL_MAX - the gate is already true and the annotation is skipped. lockdep keeps whatever hardirq state the interrupted context had. If that context had interrupts enabled, lockdep believes they are still enabled for the duration of the handler, and every lockdep_assert_irqs_disabled() in the interrupt path fires: WARNING: kernel/context_tracking.c:346 at ct_irq_enter+0xc4/0xd0, CPU#0: swapper/0/0 [...] [] ct_irq_enter+0xc4/0xd0 [] irq_enter+0x20/0x50 [] do_entInt+0x1dc/0x2e0 [] ret_from_exception+0x0/0x10 irq event stamp: 735356346 hardirqs last enabled at (735356346): trace_hardirqs_on+0x68/0x220 hardirqs last disabled at (735356345): do_idle+0xf0/0x270 The stamps show the problem directly: the most recent event is the enable from the interrupted idle loop, and do_entInt() recorded no disable at all. ct_irq_exit() warns the same way on the way out. Ordinary device interrupts arrive at IPL 3-5 and IPIs and performance counter interrupts at IPL 6, so was_disabled is false for them and the annotation happens normally. Only the two IPL 7 vectors are affected, which is why this needs an environmental event to show up. Take the hardware IPL out of the decision and drive the annotation from lockdep's own state instead. This corrects the annotation only. An IPL 7 event can also interrupt a region that has legitimately disabled interrupts, where irq_enter() and irq_exit() are not the right primitives and NMI semantics are needed; that is a larger change and is left alone here. Tested on an AlphaServer ES47 (Marvel/EV7) by injecting system events through the system management path: fifteen injections, idle and under load, with no splat. The same injection on a freshly booted kernel without this change reproduces both warnings. Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260810202835.3592833-2-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/irq_alpha.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/arch/alpha/kernel/irq_alpha.c b/arch/alpha/kernel/irq_alpha.c index 736294d3dd51..1b799aafb7dc 100644 --- a/arch/alpha/kernel/irq_alpha.c +++ b/arch/alpha/kernel/irq_alpha.c @@ -52,8 +52,19 @@ do_entInt(unsigned long type, unsigned long vector, * Note that there is no matching local_irq_enable() due to * severe problems with RTI at IPL0 and some MILO PALcode * (namely LX164). + * + * PALcode has already raised PS.IPL to the level of the interrupt + * being delivered. For an IPL 7 entry - a machine check or a system + * event - that is IPL_MAX, which is what arch_irqs_disabled() tests + * for, so local_irq_disable() would decide interrupts were already + * off and skip trace_hardirqs_off(). lockdep would then spend the + * whole handler believing interrupts are enabled. Drive the + * annotation from lockdep's own state rather than the hardware IPL. */ - local_irq_disable(); + raw_local_irq_disable(); + if (lockdep_hardirqs_enabled()) + trace_hardirqs_off(); + old_regs = set_irq_regs(regs); switch (type) { From 89679d7908e7f09dfa45cf9fe1ab59795455d197 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Wed, 5 Aug 2026 14:55:57 -0400 Subject: [PATCH 14/15] alpha: pass -Wa,-mev6 only when using GNU as That flag exists to stop gas emulating instructions the assembler thinks the target lacks. It is a gas-only option, and LLVM's integrated assembler does not emulate instructions in the first place, so nothing is needed there. Condition it on CONFIG_AS_IS_GNU rather than the compiler, so it is still passed for clang builds using GNU as (LLVM_IAS=0) and omitted only for the integrated assembler. Signed-off-by: Matt Turner Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260805185557.3283233-1-mattst88@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/arch/alpha/Makefile b/arch/alpha/Makefile index 35445ff2e489..7074602dca94 100644 --- a/arch/alpha/Makefile +++ b/arch/alpha/Makefile @@ -27,10 +27,16 @@ cpuflags-$(CONFIG_ALPHA_GENERIC) := -mcpu=ev56 -mtune=ev6 cflags-y += $(cpuflags-y) +KBUILD_CFLAGS += $(cflags-y) + # For TSUNAMI, we must have the assembler not emulate our instructions. # The same is true for IRONGATE, POLARIS, PYXIS. # BWX is most important, but we don't really want any emulation ever. -KBUILD_CFLAGS += $(cflags-y) -Wa,-mev6 +# Only gas emulates instructions the target does not implement, and only gas +# understands -mev6. LLVM's integrated assembler never emulates. +ifdef CONFIG_AS_IS_GNU +KBUILD_CFLAGS += -Wa,-mev6 +endif libs-y += arch/alpha/lib/ From b39c748d9db0c6f3ada5e7cef7f56415827d5f81 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Mon, 3 Aug 2026 13:08:42 -0400 Subject: [PATCH 15/15] alpha: read $gp and $sp explicitly for clang clang honors a local `register unsigned long x __asm__("$N")` variable only where it appears as an inline-asm operand; merely reading it does not produce the contents of that register. So trap_init() passed an undefined global pointer to PAL_wrkgp, and load_PCB() stored an undefined stack pointer into the PCB that swpctx then loaded. Either one wedges an early boot. Read the registers explicitly instead: an inline mov for $gp in trap_init(), and the file-scope current_stack_pointer for $sp in load_PCB(). A file-scope register-asm variable is the form clang does support. Signed-off-by: Matt Turner Reviewed-by: Maciej W. Rozycki Reviewed-by: Magnus Lindholm Tested-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260803-alpha-clang-v1-2-1c4ba5ba7a64@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/traps.c | 4 +++- arch/alpha/mm/init.c | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/alpha/kernel/traps.c b/arch/alpha/kernel/traps.c index 4492a90a579f..cfaef96c4ce8 100644 --- a/arch/alpha/kernel/traps.c +++ b/arch/alpha/kernel/traps.c @@ -925,7 +925,9 @@ void trap_init(void) { /* Tell PAL-code what global pointer we want in the kernel. */ - register unsigned long gptr __asm__("$29"); + unsigned long gptr; + + __asm__ __volatile__("mov $29, %0" : "=r" (gptr)); wrkgp(gptr); wrent(entArith, 1); diff --git a/arch/alpha/mm/init.c b/arch/alpha/mm/init.c index 9531cbc761c0..f4d65a60c869 100644 --- a/arch/alpha/mm/init.c +++ b/arch/alpha/mm/init.c @@ -63,8 +63,7 @@ pgd_alloc(struct mm_struct *mm) static inline unsigned long load_PCB(struct pcb_struct *pcb) { - register unsigned long sp __asm__("$30"); - pcb->ksp = sp; + pcb->ksp = (unsigned long)current_stack_pointer; return __reload_thread(pcb); }