From 0e3d3b4bb06a435a26788143a89724225c238240 Mon Sep 17 00:00:00 2001 From: Gerald Schaefer Date: Tue, 16 Jun 2026 19:46:27 +0200 Subject: [PATCH 01/59] s390/mm: Use set_pmd() / set_pud() for hugetlb pagetable entries hugetlb code is known to view all pagetable entries as PTEs, instead of corresponding upper levels like PMD or PUD. For s390, with different pagetable entry layout for different levels, this requires some conversion action under the hood. The converted PMD and PUD entries are then written via set_pte() function, but that might add some PTE-specific modifications. There is no functional problem with current code, and the clearing of _PAGE_UNUSED in set_pte(). Avoid future problems by using the set_pmd() and set_pud() functions instead. Signed-off-by: Gerald Schaefer Reviewed-by: Claudio Imbrenda Reviewed-by: Alexander Gordeev Signed-off-by: Alexander Gordeev Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/hugetlb.h | 4 ++-- arch/s390/mm/hugetlbpage.c | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/arch/s390/include/asm/hugetlb.h b/arch/s390/include/asm/hugetlb.h index e33a5b587ee4..02db08429b7c 100644 --- a/arch/s390/include/asm/hugetlb.h +++ b/arch/s390/include/asm/hugetlb.h @@ -42,9 +42,9 @@ static inline void huge_pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *ptep, unsigned long sz) { if ((pte_val(ptep_get(ptep)) & _REGION_ENTRY_TYPE_MASK) == _REGION_ENTRY_TYPE_R3) - set_pte(ptep, __pte(_REGION3_ENTRY_EMPTY)); + set_pud((pud_t *)ptep, __pud(_REGION3_ENTRY_EMPTY)); else - set_pte(ptep, __pte(_SEGMENT_ENTRY_EMPTY)); + set_pmd((pmd_t *)ptep, __pmd(_SEGMENT_ENTRY_EMPTY)); } #define __HAVE_ARCH_HUGE_PTEP_CLEAR_FLUSH diff --git a/arch/s390/mm/hugetlbpage.c b/arch/s390/mm/hugetlbpage.c index db35d8fe8609..f84aa9265430 100644 --- a/arch/s390/mm/hugetlbpage.c +++ b/arch/s390/mm/hugetlbpage.c @@ -147,10 +147,12 @@ void __set_huge_pte_at(struct mm_struct *mm, unsigned long addr, if (likely(pte_present(pte))) rste |= _REGION3_ENTRY_LARGE; rste |= _REGION_ENTRY_TYPE_R3; - } else if (likely(pte_present(pte))) - rste |= _SEGMENT_ENTRY_LARGE; - - set_pte(ptep, __pte(rste)); + set_pud((pud_t *)ptep, __pud(rste)); + } else { + if (likely(pte_present(pte))) + rste |= _SEGMENT_ENTRY_LARGE; + set_pmd((pmd_t *)ptep, __pmd(rste)); + } } void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, From 71eabd104e529dd6334439bd5f01695e5745e88f Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Thu, 18 Jun 2026 14:00:15 +0200 Subject: [PATCH 02/59] s390/tick: Remove CIF_NOHZ_DELAY flag Remove obsolete tick delay heuristic [1]. The upcoming cpuidle driver handles frequent sleep/wakeup cycles more effectively. [1] https://lore.kernel.org/all/20090929122533.402715150@de.ibm.com/ Suggested-by: Heiko Carstens Signed-off-by: Mete Durlu Reviewed-by: Heiko Carstens Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Alexander Gordeev Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/processor.h | 4 ---- arch/s390/kernel/idle.c | 1 - arch/s390/kernel/irq.c | 4 ---- arch/s390/kernel/smp.c | 1 - 4 files changed, 10 deletions(-) diff --git a/arch/s390/include/asm/processor.h b/arch/s390/include/asm/processor.h index ecd3341686eb..be8369115f6d 100644 --- a/arch/s390/include/asm/processor.h +++ b/arch/s390/include/asm/processor.h @@ -14,12 +14,10 @@ #include -#define CIF_NOHZ_DELAY 2 /* delay HZ disable for a tick */ #define CIF_ENABLED_WAIT 5 /* in enabled wait state */ #define CIF_MCCK_GUEST 6 /* machine check happening in guest */ #define CIF_DEDICATED_CPU 7 /* this CPU is dedicated */ -#define _CIF_NOHZ_DELAY BIT(CIF_NOHZ_DELAY) #define _CIF_ENABLED_WAIT BIT(CIF_ENABLED_WAIT) #define _CIF_MCCK_GUEST BIT(CIF_MCCK_GUEST) #define _CIF_DEDICATED_CPU BIT(CIF_DEDICATED_CPU) @@ -97,8 +95,6 @@ static __always_inline bool test_cpu_flag_of(int flag, int cpu) return test_bit(flag, &per_cpu(pcpu_devices, cpu).flags); } -#define arch_needs_cpu() test_cpu_flag(CIF_NOHZ_DELAY) - static inline void get_cpu_id(struct cpuid *ptr) { asm volatile("stidp %0" : "=Q" (*ptr)); diff --git a/arch/s390/kernel/idle.c b/arch/s390/kernel/idle.c index 08f3520c6785..dd12efb58201 100644 --- a/arch/s390/kernel/idle.c +++ b/arch/s390/kernel/idle.c @@ -136,7 +136,6 @@ void noinstr arch_cpu_idle(void) /* Wait for external, I/O or machine check interrupt. */ psw_mask = PSW_KERNEL_BITS | PSW_MASK_WAIT | PSW_MASK_IO | PSW_MASK_EXT | PSW_MASK_MCHECK; - clear_cpu_flag(CIF_NOHZ_DELAY); set_cpu_flag(CIF_ENABLED_WAIT); if (smp_cpu_mtid) stcctm(MT_DIAG, smp_cpu_mtid, (u64 *)&idle->mt_cycles_enter); diff --git a/arch/s390/kernel/irq.c b/arch/s390/kernel/irq.c index 04d528639b94..c923496aa7b4 100644 --- a/arch/s390/kernel/irq.c +++ b/arch/s390/kernel/irq.c @@ -166,7 +166,6 @@ void noinstr do_io_irq(struct pt_regs *regs) if (from_idle) account_idle_time_irq(); - set_cpu_flag(CIF_NOHZ_DELAY); do { regs->tpi_info = get_lowcore()->tpi_info; if (get_lowcore()->tpi_info.adapter_IO) @@ -369,9 +368,6 @@ static irqreturn_t do_ext_interrupt(int irq, void *dummy) int index; ext_code.int_code = regs->int_code; - if (ext_code.code != EXT_IRQ_CLK_COMP) - set_cpu_flag(CIF_NOHZ_DELAY); - index = ext_hash(ext_code.code); rcu_read_lock(); hlist_for_each_entry_rcu(p, &ext_int_hash[index], entry) { diff --git a/arch/s390/kernel/smp.c b/arch/s390/kernel/smp.c index 0ba7f89b8161..4cc2c119318d 100644 --- a/arch/s390/kernel/smp.c +++ b/arch/s390/kernel/smp.c @@ -909,7 +909,6 @@ int __cpu_disable(void) cregs[6].val &= ~0xff000000UL; /* disable all I/O interrupts */ cregs[14].val &= ~0x1f000000UL; /* disable most machine checks */ __local_ctl_load(0, 15, cregs); - clear_cpu_flag(CIF_NOHZ_DELAY); return 0; } From 2bfc9e417ab815b65cbdf5497de685fcee50c501 Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Thu, 18 Jun 2026 14:00:16 +0200 Subject: [PATCH 03/59] tick: Remove arch_needs_cpu Remove unused arch_needs_cpu() hook. No architectures use it after s390 removed its use case. Suggested-by: Heiko Carstens Signed-off-by: Mete Durlu Reviewed-by: Heiko Carstens Reviewed-by: Thomas Gleixner Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Alexander Gordeev Signed-off-by: Vasily Gorbik --- include/linux/tick.h | 3 --- kernel/time/tick-sched.c | 7 +++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/include/linux/tick.h b/include/linux/tick.h index 1cf4651f09ad..a43b7bd8ba3f 100644 --- a/include/linux/tick.h +++ b/include/linux/tick.h @@ -44,9 +44,6 @@ static inline void tick_unfreeze(void) { } #ifdef CONFIG_TICK_ONESHOT extern void tick_irq_enter(void); -# ifndef arch_needs_cpu -# define arch_needs_cpu() (0) -# endif # else static inline void tick_irq_enter(void) { } #endif diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index 98a9cae915c0..6c3fea386713 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -828,8 +828,7 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) ts->timer_expires_base = basemono; /* - * Keep the periodic tick, when RCU, architecture or irq_work - * requests it. + * Keep the periodic tick, when RCU or irq_work requests it. * Aside of that, check whether the local timer softirq is * pending. If so, its a bad idea to call get_next_timer_interrupt(), * because there is an already expired timer, so it will request @@ -837,8 +836,8 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) * minimal delta, which brings us back to this place * immediately. Lather, rinse and repeat... */ - if (rcu_needs_cpu() || arch_needs_cpu() || - irq_work_needs_cpu() || local_timer_softirq_pending()) { + if (rcu_needs_cpu() || irq_work_needs_cpu() || + local_timer_softirq_pending()) { next_tick = basemono + TICK_NSEC; } else { /* From 397565b51950b8cf0a402b259a51ecc40eabe25f Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Thu, 18 Jun 2026 14:00:17 +0200 Subject: [PATCH 04/59] s390: Enable TIF_POLLING_NRFLAG Enable TIF_POLLING_NRFLAG to support idle polling state in the upcoming cpuidle driver. Signed-off-by: Mete Durlu Reviewed-by: Heiko Carstens Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Alexander Gordeev Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/thread_info.h | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/s390/include/asm/thread_info.h b/arch/s390/include/asm/thread_info.h index 6a548a819400..2554cef3a6a5 100644 --- a/arch/s390/include/asm/thread_info.h +++ b/arch/s390/include/asm/thread_info.h @@ -61,6 +61,7 @@ void arch_setup_new_exec(void); */ #define HAVE_TIF_NEED_RESCHED_LAZY #define HAVE_TIF_RESTORE_SIGMASK +#define HAVE_TIF_POLLING_NRFLAG #include From 01c162e27a0547b74afae1c2ae2ec5125ff78d0d Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Thu, 18 Jun 2026 14:00:18 +0200 Subject: [PATCH 05/59] s390/idle: Introduce cpuidle for s390 Introduce generic cpuidle driver on s390. Use a two stage approach to handle idle scenarios and use idle governor for idle stage selection. Two stages are, from shallow to deep, idle polling and enabled wait. Suggested-by: Christian Borntraeger Suggested-by: Heiko Carstens Signed-off-by: Mete Durlu Reviewed-by: Heiko Carstens Reviewed-by: Christian Loehle Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Alexander Gordeev Signed-off-by: Vasily Gorbik --- MAINTAINERS | 8 +++ arch/s390/Kconfig | 5 ++ drivers/cpuidle/Kconfig | 5 ++ drivers/cpuidle/Kconfig.s390 | 11 ++++ drivers/cpuidle/Makefile | 4 ++ drivers/cpuidle/cpuidle-s390.c | 115 +++++++++++++++++++++++++++++++++ 6 files changed, 148 insertions(+) create mode 100644 drivers/cpuidle/Kconfig.s390 create mode 100644 drivers/cpuidle/cpuidle-s390.c diff --git a/MAINTAINERS b/MAINTAINERS index 4a8b0fd665ce..0877031e6b34 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6851,6 +6851,13 @@ L: linux-riscv@lists.infradead.org S: Maintained F: drivers/cpuidle/cpuidle-riscv-sbi.c +CPUIDLE DRIVER - S390 +M: Mete Durlu +L: linux-pm@vger.kernel.org +L: linux-s390@vger.kernel.org +S: Maintained +F: drivers/cpuidle/cpuidle-s390.c + CPUMASK API [RUST] M: Viresh Kumar R: Yury Norov @@ -23790,6 +23797,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux.git F: Documentation/driver-api/s390-drivers.rst F: Documentation/arch/s390/ F: arch/s390/ +F: drivers/cpuidle/cpuidle-s390.c F: drivers/s390/ F: drivers/watchdog/diag288_wdt.c diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 84404e6778d5..3b9d352cadef 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -20,6 +20,9 @@ config ARCH_HAS_ILOG2_U64 config ARCH_PROC_KCORE_TEXT def_bool y +config ARCH_HAS_CPU_RELAX + def_bool y + config GENERIC_HWEIGHT def_bool !HAVE_MARCH_Z196_FEATURES @@ -705,6 +708,8 @@ config KERNEL_IMAGE_BASE endmenu +source "drivers/cpuidle/Kconfig" + menu "Memory setup" config ARCH_SPARSEMEM_ENABLE diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index d6d8386d3f02..00e2562041fd 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -71,6 +71,11 @@ depends on RISCV source "drivers/cpuidle/Kconfig.riscv" endmenu +menu "S390 CPU Idle Drivers" +depends on S390 +source "drivers/cpuidle/Kconfig.s390" +endmenu + config HALTPOLL_CPUIDLE tristate "Halt poll cpuidle driver" depends on X86 && KVM_GUEST diff --git a/drivers/cpuidle/Kconfig.s390 b/drivers/cpuidle/Kconfig.s390 new file mode 100644 index 000000000000..c9acf7ba8eee --- /dev/null +++ b/drivers/cpuidle/Kconfig.s390 @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# S390 CPU Idle drivers +# + +config S390_CPUIDLE + bool "S390 CPU idle driver" + default y + help + Select this option to enable processor idle state management + through cpuidle subsystem. diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 1de9e92c5b0f..88cbc2a7aea8 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -42,3 +42,7 @@ obj-$(CONFIG_POWERNV_CPUIDLE) += cpuidle-powernv.o ############################################################################### # RISC-V drivers obj-$(CONFIG_RISCV_SBI_CPUIDLE) += cpuidle-riscv-sbi.o + +############################################################################### +# S390 drivers +obj-$(CONFIG_S390_CPUIDLE) += cpuidle-s390.o diff --git a/drivers/cpuidle/cpuidle-s390.c b/drivers/cpuidle/cpuidle-s390.c new file mode 100644 index 000000000000..1d02a77ebce0 --- /dev/null +++ b/drivers/cpuidle/cpuidle-s390.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * s390 generic CPU idle driver. + * + * Copyright IBM Corp. 2026 + */ + +#define pr_fmt(fmt) "CPUidle s390: " fmt + +#include +#include +#include +#include + +static __cpuidle int s390_enter_idle(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + arch_cpu_idle(); + return index; +} + +static struct cpuidle_driver s390_cpuidle_driver = { + .cpumask = (struct cpumask *)cpu_present_mask, + .name = "s390-idle", + .states = { + { /* entry 0 is for polling */}, + { + .enter = s390_enter_idle, + .name = "IDLE", + .desc = "ENABLED WAIT", + }, + }, + .safe_state_index = 0, + .state_count = 2, +}; + +static int s390_cpuidle_cpu_online(unsigned int cpu) +{ + struct cpuidle_device *dev = &per_cpu(cpuidle_dev, cpu); + int rc; + + if (dev->registered) { + cpuidle_pause_and_lock(); + rc = cpuidle_enable_device(dev); + cpuidle_resume_and_unlock(); + if (rc) + pr_err("Failed to enable cpuidle device on cpu %u\n", cpu); + } else { + dev->cpu = cpu; + rc = cpuidle_register_device(dev); + if (rc) + pr_err("Failed to register cpuidle driver on cpu %u\n", cpu); + } + return rc; +} + +static int s390_cpuidle_cpu_dead(unsigned int cpu) +{ + struct cpuidle_device *dev = &per_cpu(cpuidle_dev, cpu); + + if (!dev->registered) + return 0; + cpuidle_pause_and_lock(); + cpuidle_disable_device(dev); + cpuidle_resume_and_unlock(); + return 0; +} + +/* + * The target_residency and exit_latency values are benchmark-derived estimates + * that remain non-deterministic due to s390's virtualized architecture. + * + * Configuration strategy: + * - Poll idle state: Values derived from the next enabled idle state (EW) + * - Enabled Wait state: Values selected based on idle behavior and empirical + * measurement data + * + * Goal is to improve responsiveness for workloads with frequent sleep/wakeup + * cycles while minimizing any side effects. + */ +static void __init s390_cpuidle_ew_tune(void) +{ + struct cpuidle_state *state = &s390_cpuidle_driver.states[1]; + + if (machine_is_lpar()) { + state->target_residency = 5; + state->exit_latency = 5; + } else { + state->target_residency = 1; + state->exit_latency = 1; + } +} + +static int __init s390_cpuidle_init(void) +{ + int rc; + + s390_cpuidle_ew_tune(); + cpuidle_poll_state_init(&s390_cpuidle_driver); + rc = cpuidle_register(&s390_cpuidle_driver, NULL); + if (rc) + return rc; + rc = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, + "cpuidle/s390:online", + s390_cpuidle_cpu_online, + s390_cpuidle_cpu_dead); + if (rc < 0) { + cpuidle_unregister(&s390_cpuidle_driver); + pr_err("Failed to allocate hotplug state: cpuidle/s390:online\n"); + return rc; + } + return 0; +} +device_initcall(s390_cpuidle_init); From debfa8d0a7737533498b12595771399e6b7a3c92 Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Thu, 18 Jun 2026 14:00:19 +0200 Subject: [PATCH 06/59] s390/configs: Enable cpuidle driver on s390 Enable cpuidle infrastructure, idle governor and s390 cpuidle driver by default. They can be disabled via config options before compile or via setting cpuidle.off kernel commandline option. When cpuidle.off=1 set, given architecture's specific implementation is used as a fallback without any governor or idle state support. During runtime individual states of cpuidle driver can also be disabled via sysfs attributes: echo 1 > /sys/devices/system/cpu/cpuidle/state/disable Signed-off-by: Mete Durlu Reviewed-by: Heiko Carstens Acked-by: Christian Borntraeger Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Alexander Gordeev Signed-off-by: Vasily Gorbik --- arch/s390/configs/debug_defconfig | 2 ++ arch/s390/configs/defconfig | 2 ++ 2 files changed, 4 insertions(+) diff --git a/arch/s390/configs/debug_defconfig b/arch/s390/configs/debug_defconfig index 54637be87fb7..007668f95028 100644 --- a/arch/s390/configs/debug_defconfig +++ b/arch/s390/configs/debug_defconfig @@ -29,6 +29,8 @@ CONFIG_CGROUP_PIDS=y CONFIG_CGROUP_RDMA=y CONFIG_CGROUP_FREEZER=y CONFIG_CGROUP_HUGETLB=y +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_TEO=y CONFIG_CPUSETS=y CONFIG_CGROUP_DEVICE=y CONFIG_CGROUP_CPUACCT=y diff --git a/arch/s390/configs/defconfig b/arch/s390/configs/defconfig index 5f5114a253cf..6f5722634b4d 100644 --- a/arch/s390/configs/defconfig +++ b/arch/s390/configs/defconfig @@ -27,6 +27,8 @@ CONFIG_CGROUP_PIDS=y CONFIG_CGROUP_RDMA=y CONFIG_CGROUP_FREEZER=y CONFIG_CGROUP_HUGETLB=y +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_TEO=y CONFIG_CPUSETS=y CONFIG_CGROUP_DEVICE=y CONFIG_CGROUP_CPUACCT=y From a7325d0d771c887847420f92be3e9b73e5c117e7 Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Wed, 17 Jun 2026 14:33:50 +0200 Subject: [PATCH 07/59] s390/traps: Add exception statistics Add a new debugfs file which displays the number of exceptions (program checks) per CPU. This is helpful for debugging purposes. The statistics are typically available at /sys/kernel/debug/s390/exceptions. [ hca@linux.ibm.com: Forward ported code, changed file location ] Suggested-by: Christian Borntraeger Signed-off-by: Sven Schnelle Signed-off-by: Heiko Carstens Tested-by: Christian Borntraeger Reviewed-by: Christian Borntraeger > Signed-off-by: Alexander Gordeev Signed-off-by: Vasily Gorbik --- arch/s390/kernel/traps.c | 41 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c index 564403496a7c..fcd8c1122ef5 100644 --- a/arch/s390/kernel/traps.c +++ b/arch/s390/kernel/traps.c @@ -9,7 +9,9 @@ * Copyright (C) 1991, 1992 Linus Torvalds */ +#include #include +#include #include #include #include @@ -33,6 +35,12 @@ #include #include "entry.h" +struct pgm_stat { + unsigned int count[128]; +}; + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct pgm_stat, pgm_stat); + static inline void __user *get_trap_ip(struct pt_regs *regs) { unsigned long address; @@ -332,6 +340,7 @@ void noinstr __do_pgm_check(struct pt_regs *regs) struct lowcore *lc = get_lowcore(); bool percpu_needs_fixup; irqentry_state_t state; + struct pgm_stat *stat; unsigned int trapnr; union teid teid; @@ -339,6 +348,10 @@ void noinstr __do_pgm_check(struct pt_regs *regs) regs->int_code = lc->pgm_int_code; regs->int_parm_long = teid.val; regs->monitor_code = lc->monitor_code; + + trapnr = regs->int_code & PGM_INT_CODE_MASK; + stat = this_cpu_ptr(&pgm_stat); + stat->count[trapnr]++; /* * In case of a guest fault, short-circuit the fault handler and return. * This way the sie64a() function will return 0; fault address and @@ -383,7 +396,6 @@ void noinstr __do_pgm_check(struct pt_regs *regs) if (!irqs_disabled_flags(regs->psw.mask)) trace_hardirqs_on(); __arch_local_irq_ssm(regs->psw.mask & ~PSW_MASK_PER); - trapnr = regs->int_code & PGM_INT_CODE_MASK; if (trapnr) pgm_check_table[trapnr](regs); out: @@ -393,6 +405,33 @@ void noinstr __do_pgm_check(struct pt_regs *regs) percpu_exit(regs, percpu_needs_fixup); } +static int pgm_check_stat_show(struct seq_file *p, void *v) +{ + int i, cpu; + + cpus_read_lock(); + seq_puts(p, " "); + for_each_online_cpu(cpu) + seq_printf(p, "CPU%-8d", cpu); + seq_putc(p, '\n'); + for (i = 0; i < 128; i++) { + seq_printf(p, "%02x: ", i); + for_each_online_cpu(cpu) + seq_printf(p, "%10u ", per_cpu(pgm_stat, cpu).count[i]); + seq_putc(p, '\n'); + } + cpus_read_unlock(); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(pgm_check_stat); + +static int __init debugfs_pgm_check_init(void) +{ + debugfs_create_file("exceptions", 0400, arch_debugfs_dir, NULL, &pgm_check_stat_fops); + return 0; +} +late_initcall(debugfs_pgm_check_init); + /* * The program check table contains exactly 128 (0x00-0x7f) entries. Each * line defines the function to be called corresponding to the program check From 28b0d6897484aa6119ac63d813afd6f31d1b8246 Mon Sep 17 00:00:00 2001 From: Jaehoon Kim Date: Tue, 19 May 2026 11:49:33 -0500 Subject: [PATCH 08/59] s390/pv: Enable SWIOTLB_ANY for s390 PV In s390 protected virtualization (PV) environments, guests use SWIOTLB for virtio DMA. Because SWIOTLB_ANY is currently not passed to swiotlb_init(), the SWIOTLB buffer is allocated from low memory, i.e. from the first 2G of the physical address space on s390. This restriction is unnecessary for s390 PV guests and limits the available address range for the SWIOTLB buffer. In turn, that can limit the maximum practical SWIOTLB size and make larger allocations more likely to fail at boot, especially under memory fragmentation. Pass SWIOTLB_ANY in pv_init() so the SWIOTLB buffer can be allocated from any suitable memory instead of being restricted to low memory. Signed-off-by: Jaehoon Kim Acked-by: Christian Borntraeger Reviewed-by: Halil Pasic Tested-by: Joshua Daley Signed-off-by: Vasily Gorbik --- arch/s390/mm/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/mm/init.c b/arch/s390/mm/init.c index f07168a0d3dd..01050a8e7e05 100644 --- a/arch/s390/mm/init.c +++ b/arch/s390/mm/init.c @@ -151,7 +151,7 @@ static void __init pv_init(void) virtio_set_mem_acc_cb(virtio_require_restricted_mem_acc); /* make sure bounce buffers are shared */ - swiotlb_init(true, SWIOTLB_FORCE | SWIOTLB_VERBOSE); + swiotlb_init(true, SWIOTLB_FORCE | SWIOTLB_VERBOSE | SWIOTLB_ANY); swiotlb_update_mem_attributes(); } From 14ddf10e93d4404731b0c07d4df7f3bb4068ca01 Mon Sep 17 00:00:00 2001 From: Jaehoon Kim Date: Tue, 19 May 2026 11:49:39 -0500 Subject: [PATCH 09/59] s390: Select SWIOTLB_DYNAMIC and DMA_COHERENT_POOL SWIOTLB_DYNAMIC allows the SWIOTLB to grow on demand when the initial bounce-buffer area is exhausted, instead of relying only on the pool reserved at boot. This improves behavior under sustained I/O pressure. In testing, exhaustion of the initial SWIOTLB area was observed under real workloads such as large file transfers and package installation workloads, where continued availability of bounce buffering was needed to avoid guest-visible I/O stalls or failures. When the current SWIOTLB capacity is exhausted, the dynamic expansion path schedules asynchronous pool growth and then attempts immediate non-blocking allocation of transient bounce-buffer memory. In that path, the current implementation uses the generic DMA coherent atomic pool for transient bounce-buffer allocations. Selecting DMA_COHERENT_POOL together with SWIOTLB_DYNAMIC on s390 makes that atomic fallback path available. The coherent atomic pool is a generic DMA facility. By default, its size is 128 KiB per 1 GiB of memory, with a minimum default size of 128 KiB, and it can be adjusted with the coherent_pool= kernel parameter. The pool itself can be exhausted, so this improves behavior under pressure, but does not guarantee that DMA mappings will always succeed. Select both options from s390 Kconfig so this support is available by default on s390 systems. Signed-off-by: Jaehoon Kim Acked-by: Niklas Schnelle Reviewed-by: Halil Pasic Tested-by: Joshua Daley Signed-off-by: Vasily Gorbik --- arch/s390/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 3b9d352cadef..6ceae6f61ebb 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -278,6 +278,8 @@ config S390 select PCI_QUIRKS if PCI select SPARSE_IRQ select SWIOTLB + select SWIOTLB_DYNAMIC + select DMA_COHERENT_POOL select SYSCTL_EXCEPTION_TRACE select SYSTEM_DATA_VERIFICATION if KEXEC_SIG select THREAD_INFO_IN_TASK From 6bef2c8b36544c83e306fbeae78b346bd9774527 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 1 Jul 2026 15:47:27 +0200 Subject: [PATCH 10/59] s390/uapi: Remove obsolete unistd_32.h from Kbuild file unistd_32.h is not build anymore since commit 4ac286c4a8d90 ("s390/syscalls: Switch to generic system call table generation"). Thus drop the superfluous line from the Kbuild file now, too. Signed-off-by: Thomas Huth Signed-off-by: Vasily Gorbik --- arch/s390/include/uapi/asm/Kbuild | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/s390/include/uapi/asm/Kbuild b/arch/s390/include/uapi/asm/Kbuild index 46c1ff0b842a..378151cef3cc 100644 --- a/arch/s390/include/uapi/asm/Kbuild +++ b/arch/s390/include/uapi/asm/Kbuild @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 -generated-y += unistd_32.h generated-y += unistd_64.h From f588c5cb0b645d9318f609c2835211ebc7887d92 Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Thu, 9 Jul 2026 21:54:59 +0200 Subject: [PATCH 11/59] s390/traps: Remove PIF_GUEST_FAULT PIF_GUEST_FAULT is only used to pass information whether a fault was caused when executing SIE or when executing host code. Instead of using ptregs for this, just pass the flag directly as argument to __do_pgm_check(). This also saves the time required to read the flag from ptregs, although this likely isn't much as it is already in the data cache. Signed-off-by: Sven Schnelle Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/ptrace.h | 4 +--- arch/s390/kernel/entry.S | 7 ++++--- arch/s390/kernel/entry.h | 7 ++++++- arch/s390/kernel/traps.c | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/arch/s390/include/asm/ptrace.h b/arch/s390/include/asm/ptrace.h index 495e310c3d6d..6411e3584283 100644 --- a/arch/s390/include/asm/ptrace.h +++ b/arch/s390/include/asm/ptrace.h @@ -16,13 +16,11 @@ #define PIF_SYSCALL 0 /* inside a system call */ #define PIF_PSW_ADDR_ADJUSTED 1 /* psw address has been adjusted */ #define PIF_SYSCALL_RET_SET 2 /* return value was set via ptrace */ -#define PIF_GUEST_FAULT 3 /* indicates program check in sie64a */ -#define PIF_FTRACE_FULL_REGS 4 /* all register contents valid (ftrace) */ +#define PIF_FTRACE_FULL_REGS 3 /* all register contents valid (ftrace) */ #define _PIF_SYSCALL BIT(PIF_SYSCALL) #define _PIF_ADDR_PSW_ADJUSTED BIT(PIF_PSW_ADDR_ADJUSTED) #define _PIF_SYSCALL_RET_SET BIT(PIF_SYSCALL_RET_SET) -#define _PIF_GUEST_FAULT BIT(PIF_GUEST_FAULT) #define _PIF_FTRACE_FULL_REGS BIT(PIF_FTRACE_FULL_REGS) #define PSW32_MASK_PER _AC(0x40000000, UL) diff --git a/arch/s390/kernel/entry.S b/arch/s390/kernel/entry.S index 79a45efae23d..d70eef7a0b29 100644 --- a/arch/s390/kernel/entry.S +++ b/arch/s390/kernel/entry.S @@ -31,6 +31,7 @@ #include #include #include +#include "entry.h" _LPP_OFFSET = __LC_LPP @@ -321,7 +322,7 @@ SYM_CODE_START(pgm_check_handler) jz 1f BPENTER __SF_SIE_FLAGS(%r15),_TIF_ISOLATE_BP_GUEST SIEEXIT __SF_SIE_CONTROL(%r15),%r13 - lghi %r10,_PIF_GUEST_FAULT + lghi %r10,PGM_FLAG_GUEST_FAULT #endif 1: tmhh %r8,0x4000 # PER bit set in old PSW ? jnz 2f # -> enabled, can't be a double fault @@ -332,7 +333,7 @@ SYM_CODE_START(pgm_check_handler) CHECK_VMAP_STACK __LC_SAVE_AREA,%r13,4f 3: lg %r15,__LC_KERNEL_STACK(%r13) 4: la %r11,STACK_FRAME_OVERHEAD(%r15) - stg %r10,__PT_FLAGS(%r11) + xc __PT_FLAGS(8,%r11),__PT_FLAGS(%r11) xc __SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15) stmg %r0,%r7,__PT_R0(%r11) mvc __PT_R8(64,%r11),__LC_SAVE_AREA(%r13) @@ -341,13 +342,13 @@ SYM_CODE_START(pgm_check_handler) # clear user controlled registers to prevent speculative use xgr %r0,%r0 xgr %r1,%r1 - xgr %r3,%r3 xgr %r4,%r4 xgr %r5,%r5 xgr %r6,%r6 xgr %r7,%r7 xgr %r12,%r12 lgr %r2,%r11 + lgr %r3,%r10 brasl %r14,__do_pgm_check tmhh %r8,0x0001 # returning to user space? jno .Lpgm_exit_kernel diff --git a/arch/s390/kernel/entry.h b/arch/s390/kernel/entry.h index fb67b4abe68c..d18a9a63f6b8 100644 --- a/arch/s390/kernel/entry.h +++ b/arch/s390/kernel/entry.h @@ -2,6 +2,10 @@ #ifndef _ENTRY_H #define _ENTRY_H +#define PGM_FLAG_GUEST_FAULT 1 + +#ifndef __ASSEMBLER__ + #include #include #include @@ -21,7 +25,7 @@ void early_pgm_check_handler(void); struct task_struct *__switch_to_asm(struct task_struct *prev, struct task_struct *next); void __ret_from_fork(struct task_struct *prev, struct pt_regs *regs); -void __do_pgm_check(struct pt_regs *regs); +void __do_pgm_check(struct pt_regs *regs, unsigned long flags); void __do_syscall(struct pt_regs *regs, int per_trap); void __do_early_pgm_check(struct pt_regs *regs); @@ -70,4 +74,5 @@ extern struct exception_table_entry _stop_amode31_ex_table[]; #define __amode31_ref __section(".amode31.refs") extern long _start_amode31_refs[], _end_amode31_refs[]; +#endif /* __ASSEMBLER__ */ #endif /* _ENTRY_H */ diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c index fcd8c1122ef5..b6ba4465f59d 100644 --- a/arch/s390/kernel/traps.c +++ b/arch/s390/kernel/traps.c @@ -335,7 +335,7 @@ void __init trap_init(void) static void (*pgm_check_table[128])(struct pt_regs *regs); -void noinstr __do_pgm_check(struct pt_regs *regs) +void noinstr __do_pgm_check(struct pt_regs *regs, unsigned long flags) { struct lowcore *lc = get_lowcore(); bool percpu_needs_fixup; @@ -359,7 +359,7 @@ void noinstr __do_pgm_check(struct pt_regs *regs) * the fault number in current->thread.gmap_int_code. KVM will be * able to use this information to handle the fault. */ - if (test_pt_regs_flag(regs, PIF_GUEST_FAULT)) { + if (flags & PGM_FLAG_GUEST_FAULT) { current->thread.gmap_teid.val = regs->int_parm_long; current->thread.gmap_int_code = regs->int_code & 0xffff; return; From 5c92744c1f63dd778377f0717f2554ff7231fb7d Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Thu, 9 Jul 2026 21:55:00 +0200 Subject: [PATCH 12/59] s390/syscalls: Use define instead of '1' to indicate PER trap Make the code a bit easier to read by defining SYSCALL_PER_TRAP instead of passing '1' to __do_syscall(). Suggested-by: Heiko Carstens Signed-off-by: Sven Schnelle Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/entry.S | 2 +- arch/s390/kernel/entry.h | 3 ++- arch/s390/kernel/syscall.c | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/arch/s390/kernel/entry.S b/arch/s390/kernel/entry.S index d70eef7a0b29..10dd9bbdf985 100644 --- a/arch/s390/kernel/entry.S +++ b/arch/s390/kernel/entry.S @@ -368,7 +368,7 @@ SYM_CODE_START(pgm_check_handler) mvc __LC_RETURN_PSW(8,%r13),__LC_SVC_NEW_PSW(%r13) larl %r14,.Lsysc_per stg %r14,__LC_RETURN_PSW+8(%r13) - lghi %r14,1 + lghi %r14,SYSCALL_FLAG_PER_TRAP LBEAR __LC_PGM_LAST_BREAK(%r13) LPSWEY __LC_RETURN_PSW,__LC_RETURN_LPSWE # branch to .Lsysc_per SYM_CODE_END(pgm_check_handler) diff --git a/arch/s390/kernel/entry.h b/arch/s390/kernel/entry.h index d18a9a63f6b8..dc84ab497251 100644 --- a/arch/s390/kernel/entry.h +++ b/arch/s390/kernel/entry.h @@ -3,6 +3,7 @@ #define _ENTRY_H #define PGM_FLAG_GUEST_FAULT 1 +#define SYSCALL_FLAG_PER_TRAP 1 #ifndef __ASSEMBLER__ @@ -26,7 +27,7 @@ void early_pgm_check_handler(void); struct task_struct *__switch_to_asm(struct task_struct *prev, struct task_struct *next); void __ret_from_fork(struct task_struct *prev, struct pt_regs *regs); void __do_pgm_check(struct pt_regs *regs, unsigned long flags); -void __do_syscall(struct pt_regs *regs, int per_trap); +void __do_syscall(struct pt_regs *regs, unsigned long flags); void __do_early_pgm_check(struct pt_regs *regs); void do_protection_exception(struct pt_regs *regs); diff --git a/arch/s390/kernel/syscall.c b/arch/s390/kernel/syscall.c index 75d5a3cab14e..b6c643756a7c 100644 --- a/arch/s390/kernel/syscall.c +++ b/arch/s390/kernel/syscall.c @@ -93,7 +93,7 @@ SYSCALL_DEFINE0(ni_syscall) return -ENOSYS; } -void noinstr __do_syscall(struct pt_regs *regs, int per_trap) +void noinstr __do_syscall(struct pt_regs *regs, unsigned long flags) { unsigned long nr; @@ -106,7 +106,7 @@ void noinstr __do_syscall(struct pt_regs *regs, int per_trap) current->thread.last_break = regs->last_break; local_irq_enable(); regs->orig_gpr2 = regs->gprs[2]; - if (unlikely(per_trap)) + if (unlikely(flags & SYSCALL_FLAG_PER_TRAP)) set_thread_flag(TIF_PER_TRAP); regs->flags = 0; set_pt_regs_flag(regs, PIF_SYSCALL); From dc161efb6df8518b3cfa7f0a5efdc16a1aee815b Mon Sep 17 00:00:00 2001 From: Jens Remus Date: Mon, 13 Jul 2026 16:42:30 +0200 Subject: [PATCH 13/59] s390/vdso: Pass --eh-frame-hdr to the linker Commit 2b2a25845d53 ("s390/vdso: Use $(LD) instead of $(CC) to link vDSO") accidentally broke the GNU_EH_FRAME program table entry in the vDSO, causing it to be empty: $ readelf --program-headers arch/s390/kernel/vdso/vdso.so ... Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align ... GNU_EH_FRAME 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x8 ... Originally, the compiler would implicitly add --eh-frame-hdr when invoking the linker, but when this Makefile was converted from invoking the linker via the compiler, to invoking it directly, the option was missed. This is the s390 variant of x86 commit cd01544a268a ("x86/vdso: Pass --eh-frame-hdr to the linker"). Fixes: 2b2a25845d53 ("s390/vdso: Use $(LD) instead of $(CC) to link vDSO") Reviewed-by: Ilya Leoshkevich Acked-by: Heiko Carstens Signed-off-by: Jens Remus Signed-off-by: Vasily Gorbik --- arch/s390/kernel/vdso/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/s390/kernel/vdso/Makefile b/arch/s390/kernel/vdso/Makefile index fece5d975eaf..35c834b895ec 100644 --- a/arch/s390/kernel/vdso/Makefile +++ b/arch/s390/kernel/vdso/Makefile @@ -30,7 +30,8 @@ KBUILD_CFLAGS_VDSO := $(filter-out -fno-asynchronous-unwind-tables,$(KBUILD_CFLA KBUILD_CFLAGS_VDSO += -fPIC -fno-common -fno-builtin -fasynchronous-unwind-tables KBUILD_CFLAGS_VDSO += -fno-stack-protector $(DISABLE_KSTACK_ERASE) ldflags-y := -shared -soname=linux-vdso.so.1 \ - --hash-style=both --build-id=sha1 -T + --hash-style=both --build-id=sha1 \ + $(call ld-option, --eh-frame-hdr) -T $(targets:%=$(obj)/%.dbg): KBUILD_CFLAGS = $(KBUILD_CFLAGS_VDSO) $(targets:%=$(obj)/%.dbg): KBUILD_AFLAGS = $(KBUILD_AFLAGS_VDSO) From 6e17a45b3cf0694d50d05e1159ca700ae342099e Mon Sep 17 00:00:00 2001 From: Jens Remus Date: Mon, 13 Jul 2026 16:42:31 +0200 Subject: [PATCH 14/59] s390/vdso: Use symbolic constants for the PHDR permission flags While at it explicitly specify GNU_EH_FRAME PHDR to be read-only. Inspired by x86 commit 8717b02b8c03 ("x86/entry/vdso: Include GNU_PROPERTY and GNU_STACK PHDRs"). Reviewed-by: Ilya Leoshkevich Acked-by: Heiko Carstens Signed-off-by: Jens Remus Signed-off-by: Vasily Gorbik --- arch/s390/kernel/vdso/vdso.lds.S | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arch/s390/kernel/vdso/vdso.lds.S b/arch/s390/kernel/vdso/vdso.lds.S index 7bec4de0e8e0..841daeec4be2 100644 --- a/arch/s390/kernel/vdso/vdso.lds.S +++ b/arch/s390/kernel/vdso/vdso.lds.S @@ -82,12 +82,15 @@ SECTIONS * We must supply the ELF program headers explicitly to get just one * PT_LOAD segment, and set the flags explicitly to make segments read-only. */ +#define PF_R FLAGS(4) +#define PF_RX FLAGS(5) + PHDRS { - text PT_LOAD FILEHDR PHDRS FLAGS(5); /* PF_R|PF_X */ - dynamic PT_DYNAMIC FLAGS(4); /* PF_R */ - note PT_NOTE FLAGS(4); /* PF_R */ - eh_frame_hdr PT_GNU_EH_FRAME; + text PT_LOAD PF_RX FILEHDR PHDRS; + dynamic PT_DYNAMIC PF_R; + note PT_NOTE PF_R; + eh_frame_hdr PT_GNU_EH_FRAME PF_R; } /* From 2ba7ea7cb0b7bcd03906834235cfb4a73b6ec38a Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Thu, 9 Jul 2026 09:25:00 +0200 Subject: [PATCH 15/59] s390/ap: Use mutex_lock_killable() in ap_bus_force_rescan() A deep dive into the AP bus code and zcrypt device driver about the usage of mutex locking showed that there is one questionable call in ap_bus.c in function ap_bus_force_rescan(). This function may be called in kernel and process context. In both contexts only one info is important: was there a AP bus scan running and did it result in some updates on the AP devices. So only true/false is returned but no info like -EINTR. But still the mutex lock call should be interruptible to be able to kill a user space program blocked forever on this. So this patch replaces mutex_lock_interruptible() with mutex_lock_killable() to be able to handle SIGKILL especially in user space process context. Suggested-by: Heiko Carstens Signed-off-by: Harald Freudenberger Reviewed-by: Finn Callies Signed-off-by: Vasily Gorbik --- drivers/s390/crypto/ap_bus.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/s390/crypto/ap_bus.c b/drivers/s390/crypto/ap_bus.c index 2d4e00a1e48c..d82df5b4e2db 100644 --- a/drivers/s390/crypto/ap_bus.c +++ b/drivers/s390/crypto/ap_bus.c @@ -1173,8 +1173,8 @@ bool ap_bus_force_rescan(void) * for the lock which means the other task has finished and * stored the result in ap_scan_bus_result. */ - if (mutex_lock_interruptible(&ap_scan_bus_mutex)) { - /* some error occurred, ignore and go out */ + if (mutex_lock_killable(&ap_scan_bus_mutex)) { + /* fatal signal received, go out */ goto out; } rc = ap_scan_bus_result; From e6b197e8ec842b0d5b16bf414de72d2c4b545a09 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Wed, 22 Jul 2026 16:29:47 +0200 Subject: [PATCH 16/59] s390/pkey: Rework ioctl functions error paths With the pkey rework there was the suggestion to rework the error and free paths of the pkey ioctl functions. The complain was especially to rewrite the failure handling with goto instead of all repeat the nearly same code (kfree(), kfree_sensitive(), memzero_explicit()) for each path. This patch removes all this duplicated code and introduces one code block at the end of the functions which is jumped into via goto out or executed on regular exit. As some helper functions return an error pointer value (which is NOT NULL) make sure on the error path there is not by accident kfree() or similar called on such ptr values. Suggested-by: Heiko Carstens Signed-off-by: Harald Freudenberger Reviewed-by: Finn Callies Signed-off-by: Vasily Gorbik --- drivers/s390/crypto/pkey_api.c | 290 ++++++++++++++++++--------------- 1 file changed, 158 insertions(+), 132 deletions(-) diff --git a/drivers/s390/crypto/pkey_api.c b/drivers/s390/crypto/pkey_api.c index 5d8f63f390a8..0530b1ce228e 100644 --- a/drivers/s390/crypto/pkey_api.c +++ b/drivers/s390/crypto/pkey_api.c @@ -169,8 +169,8 @@ static int pkey_ioctl_clr2protk(struct pkey_clr2protk __user *ucp) { struct pkey_clr2protk kcp; struct clearkeytoken *t; + u8 *tmpbuf = NULL; u32 keylen; - u8 *tmpbuf; int rc; if (copy_from_user(&kcp, ucp, sizeof(kcp))) @@ -181,13 +181,13 @@ static int pkey_ioctl_clr2protk(struct pkey_clr2protk __user *ucp) if (!keylen) { PKEY_DBF_ERR("%s unknown/unsupported keytype %u\n", __func__, kcp.keytype); - memzero_explicit(&kcp, sizeof(kcp)); - return -EINVAL; + rc = -EINVAL; + goto out; } tmpbuf = kzalloc(sizeof(*t) + keylen, GFP_KERNEL); if (!tmpbuf) { - memzero_explicit(&kcp, sizeof(kcp)); - return -ENOMEM; + rc = -ENOMEM; + goto out; } t = (struct clearkeytoken *)tmpbuf; t->type = TOKTYPE_NON_CCA; @@ -202,20 +202,22 @@ static int pkey_ioctl_clr2protk(struct pkey_clr2protk __user *ucp) kcp.protkey.protkey, &kcp.protkey.len, &kcp.protkey.type, 0); pr_debug("key2protkey()=%d\n", rc); + if (rc) + goto out; - kfree_sensitive(tmpbuf); - - if (!rc && copy_to_user(ucp, &kcp, sizeof(kcp))) + if (copy_to_user(ucp, &kcp, sizeof(kcp))) rc = -EFAULT; - memzero_explicit(&kcp, sizeof(kcp)); +out: + memzero_explicit(&kcp, sizeof(kcp)); + kfree_sensitive(tmpbuf); return rc; } static int pkey_ioctl_findcard(struct pkey_findcard __user *ufc) { + struct pkey_apqn *apqns = NULL; struct pkey_findcard kfc; - struct pkey_apqn *apqns; size_t nr_apqns; int rc; @@ -224,8 +226,10 @@ static int pkey_ioctl_findcard(struct pkey_findcard __user *ufc) nr_apqns = MAXAPQNSINLIST; apqns = kmalloc_objs(struct pkey_apqn, nr_apqns); - if (!apqns) - return -ENOMEM; + if (!apqns) { + rc = -ENOMEM; + goto out; + } rc = pkey_handler_apqns_for_key(kfc.seckey.seckey, sizeof(kfc.seckey.seckey), @@ -237,17 +241,18 @@ static int pkey_ioctl_findcard(struct pkey_findcard __user *ufc) PKEY_FLAGS_MATCH_ALT_MKVP, apqns, &nr_apqns, 0); pr_debug("apqns_for_key()=%d\n", rc); - if (rc) { - kfree(apqns); - return rc; - } + if (rc) + goto out; + kfc.cardnr = apqns[0].card; kfc.domain = apqns[0].domain; - kfree(apqns); - if (copy_to_user(ufc, &kfc, sizeof(kfc))) - return -EFAULT; - return 0; + if (copy_to_user(ufc, &kfc, sizeof(kfc))) + rc = -EFAULT; + +out: + kfree(apqns); + return rc; } static int pkey_ioctl_skey2pkey(struct pkey_skey2pkey __user *usp) @@ -327,7 +332,7 @@ static int pkey_ioctl_verifyprotk(struct pkey_verifyprotk __user *uvp) { struct pkey_verifyprotk kvp; struct protaeskeytoken *t; - u8 *tmpbuf; + u8 *tmpbuf = NULL; int rc; if (copy_from_user(&kvp, uvp, sizeof(kvp))) @@ -336,15 +341,15 @@ static int pkey_ioctl_verifyprotk(struct pkey_verifyprotk __user *uvp) if (kvp.protkey.len > sizeof(kvp.protkey.protkey)) { PKEY_DBF_ERR("%s protkey length %u exceeds protkey buffer size\n", __func__, kvp.protkey.len); - memzero_explicit(&kvp, sizeof(kvp)); - return -EINVAL; + rc = -EINVAL; + goto out; } /* build a 'protected key token' from the raw protected key */ tmpbuf = kzalloc(sizeof(*t), GFP_KERNEL); if (!tmpbuf) { - memzero_explicit(&kvp, sizeof(kvp)); - return -ENOMEM; + rc = -ENOMEM; + goto out; } t = (struct protaeskeytoken *)tmpbuf; t->type = TOKTYPE_NON_CCA; @@ -357,42 +362,48 @@ static int pkey_ioctl_verifyprotk(struct pkey_verifyprotk __user *uvp) NULL, NULL, NULL, NULL, NULL, 0); pr_debug("verify_key()=%d\n", rc); - kfree_sensitive(tmpbuf); +out: memzero_explicit(&kvp, sizeof(kvp)); - + kfree_sensitive(tmpbuf); return rc; } static int pkey_ioctl_kblob2protk(struct pkey_kblob2pkey __user *utp) { struct pkey_kblob2pkey ktp; - u8 *kkey; + u8 *kkey = NULL; int rc; if (copy_from_user(&ktp, utp, sizeof(ktp))) return -EFAULT; kkey = _copy_key_from_user(ktp.key, ktp.keylen); - if (IS_ERR(kkey)) - return PTR_ERR(kkey); + if (IS_ERR(kkey)) { + rc = PTR_ERR(kkey); + kkey = NULL; + goto out; + } ktp.protkey.len = sizeof(ktp.protkey.protkey); rc = key2protkey(NULL, 0, kkey, ktp.keylen, ktp.protkey.protkey, &ktp.protkey.len, &ktp.protkey.type, 0); pr_debug("key2protkey()=%d\n", rc); - kfree_sensitive(kkey); - if (!rc && copy_to_user(utp, &ktp, sizeof(ktp))) + if (rc) + goto out; + if (copy_to_user(utp, &ktp, sizeof(ktp))) rc = -EFAULT; - memzero_explicit(&ktp, sizeof(ktp)); +out: + memzero_explicit(&ktp, sizeof(ktp)); + kfree_sensitive(kkey); return rc; } static int pkey_ioctl_genseck2(struct pkey_genseck2 __user *ugs) { + struct pkey_apqn *apqns = NULL; u32 klen = KEYBLOBBUFSIZE; struct pkey_genseck2 kgs; - struct pkey_apqn *apqns; - u8 *kkey; + u8 *kkey = NULL; int rc; u32 u; @@ -405,46 +416,48 @@ static int pkey_ioctl_genseck2(struct pkey_genseck2 __user *ugs) return -EINVAL; } apqns = _copy_apqns_from_user(kgs.apqns, kgs.apqn_entries); - if (IS_ERR(apqns)) - return PTR_ERR(apqns); + if (IS_ERR(apqns)) { + rc = PTR_ERR(apqns); + apqns = NULL; + goto out; + } kkey = kzalloc(klen, GFP_KERNEL); if (!kkey) { - kfree(apqns); - return -ENOMEM; + rc = -ENOMEM; + goto out; } rc = pkey_handler_gen_key(apqns, kgs.apqn_entries, u, kgs.type, kgs.size, kgs.keygenflags, kkey, &klen, NULL, 0); pr_debug("gen_key()=%d\n", rc); - kfree(apqns); - if (rc) { - kfree_sensitive(kkey); - return rc; - } + if (rc) + goto out; if (kgs.key) { if (kgs.keylen < klen) { - kfree_sensitive(kkey); - return -EINVAL; + rc = -EINVAL; + goto out; } if (copy_to_user(kgs.key, kkey, klen)) { - kfree_sensitive(kkey); - return -EFAULT; + rc = -EFAULT; + goto out; } } kgs.keylen = klen; if (copy_to_user(ugs, &kgs, sizeof(kgs))) rc = -EFAULT; - kfree_sensitive(kkey); +out: + kfree_sensitive(kkey); + kfree(apqns); return rc; } static int pkey_ioctl_clr2seck2(struct pkey_clr2seck2 __user *ucs) { + struct pkey_apqn *apqns = NULL; u32 klen = KEYBLOBBUFSIZE; struct pkey_clr2seck2 kcs; - struct pkey_apqn *apqns; - u8 *kkey; + u8 *kkey = NULL; int rc; u32 u; @@ -454,49 +467,45 @@ static int pkey_ioctl_clr2seck2(struct pkey_clr2seck2 __user *ucs) if (!u) { PKEY_DBF_ERR("%s unknown/unsupported keybitsize %d\n", __func__, kcs.size); - memzero_explicit(&kcs, sizeof(kcs)); - return -EINVAL; + rc = -EINVAL; + goto out; } apqns = _copy_apqns_from_user(kcs.apqns, kcs.apqn_entries); if (IS_ERR(apqns)) { - memzero_explicit(&kcs, sizeof(kcs)); - return PTR_ERR(apqns); + rc = PTR_ERR(apqns); + apqns = NULL; + goto out; } kkey = kzalloc(klen, GFP_KERNEL); if (!kkey) { - kfree(apqns); - memzero_explicit(&kcs, sizeof(kcs)); - return -ENOMEM; + rc = -ENOMEM; + goto out; } rc = pkey_handler_clr_to_key(apqns, kcs.apqn_entries, u, kcs.type, kcs.size, kcs.keygenflags, kcs.clrkey.clrkey, kcs.size / 8, kkey, &klen, NULL, 0); pr_debug("clr_to_key()=%d\n", rc); - kfree(apqns); - if (rc) { - kfree_sensitive(kkey); - memzero_explicit(&kcs, sizeof(kcs)); - return rc; - } + if (rc) + goto out; if (kcs.key) { if (kcs.keylen < klen) { - kfree_sensitive(kkey); - memzero_explicit(&kcs, sizeof(kcs)); - return -EINVAL; + rc = -EINVAL; + goto out; } if (copy_to_user(kcs.key, kkey, klen)) { - kfree_sensitive(kkey); - memzero_explicit(&kcs, sizeof(kcs)); - return -EFAULT; + rc = -EFAULT; + goto out; } } kcs.keylen = klen; if (copy_to_user(ucs, &kcs, sizeof(kcs))) rc = -EFAULT; + +out: memzero_explicit(&kcs, sizeof(kcs)); kfree_sensitive(kkey); - + kfree(apqns); return rc; } @@ -509,18 +518,23 @@ static int pkey_ioctl_verifykey2(struct pkey_verifykey2 __user *uvk) if (copy_from_user(&kvk, uvk, sizeof(kvk))) return -EFAULT; kkey = _copy_key_from_user(kvk.key, kvk.keylen); - if (IS_ERR(kkey)) - return PTR_ERR(kkey); + if (IS_ERR(kkey)) { + rc = PTR_ERR(kkey); + kkey = NULL; + goto out; + } rc = pkey_handler_verify_key(kkey, kvk.keylen, &kvk.cardnr, &kvk.domain, &kvk.type, &kvk.size, &kvk.flags, 0); pr_debug("verify_key()=%d\n", rc); + if (rc) + goto out; + if (copy_to_user(uvk, &kvk, sizeof(kvk))) + rc = -EFAULT; +out: kfree_sensitive(kkey); - if (!rc && copy_to_user(uvk, &kvk, sizeof(kvk))) - return -EFAULT; - return rc; } @@ -528,30 +542,37 @@ static int pkey_ioctl_kblob2protk2(struct pkey_kblob2pkey2 __user *utp) { struct pkey_apqn *apqns = NULL; struct pkey_kblob2pkey2 ktp; - u8 *kkey; + u8 *kkey = NULL; int rc; if (copy_from_user(&ktp, utp, sizeof(ktp))) return -EFAULT; apqns = _copy_apqns_from_user(ktp.apqns, ktp.apqn_entries); - if (IS_ERR(apqns)) - return PTR_ERR(apqns); + if (IS_ERR(apqns)) { + rc = PTR_ERR(apqns); + apqns = NULL; + goto out; + } kkey = _copy_key_from_user(ktp.key, ktp.keylen); if (IS_ERR(kkey)) { - kfree(apqns); - return PTR_ERR(kkey); + rc = PTR_ERR(kkey); + kkey = NULL; + goto out; } ktp.protkey.len = sizeof(ktp.protkey.protkey); rc = key2protkey(apqns, ktp.apqn_entries, kkey, ktp.keylen, ktp.protkey.protkey, &ktp.protkey.len, &ktp.protkey.type, 0); pr_debug("key2protkey()=%d\n", rc); - kfree(apqns); - kfree_sensitive(kkey); - if (!rc && copy_to_user(utp, &ktp, sizeof(ktp))) + if (rc) + goto out; + if (copy_to_user(utp, &ktp, sizeof(ktp))) rc = -EFAULT; - memzero_explicit(&ktp, sizeof(ktp)); +out: + memzero_explicit(&ktp, sizeof(ktp)); + kfree_sensitive(kkey); + kfree(apqns); return rc; } @@ -560,7 +581,7 @@ static int pkey_ioctl_apqns4k(struct pkey_apqns4key __user *uak) struct pkey_apqn *apqns = NULL; struct pkey_apqns4key kak; size_t nr_apqns, len; - u8 *kkey; + u8 *kkey = NULL; int rc; if (copy_from_user(&kak, uak, sizeof(kak))) @@ -568,40 +589,42 @@ static int pkey_ioctl_apqns4k(struct pkey_apqns4key __user *uak) nr_apqns = kak.apqn_entries; if (nr_apqns) { apqns = kmalloc_objs(struct pkey_apqn, nr_apqns); - if (!apqns) - return -ENOMEM; + if (!apqns) { + rc = -ENOMEM; + goto out; + } } kkey = _copy_key_from_user(kak.key, kak.keylen); if (IS_ERR(kkey)) { - kfree(apqns); - return PTR_ERR(kkey); + rc = PTR_ERR(kkey); + kkey = NULL; + goto out; } rc = pkey_handler_apqns_for_key(kkey, kak.keylen, kak.flags, apqns, &nr_apqns, 0); pr_debug("apqns_for_key()=%d\n", rc); - kfree_sensitive(kkey); - if (rc && rc != -ENOSPC) { - kfree(apqns); - return rc; - } + if (rc && rc != -ENOSPC) + goto out; if (!rc && kak.apqns) { if (nr_apqns > kak.apqn_entries) { - kfree(apqns); - return -EINVAL; + rc = -EINVAL; + goto out; } len = nr_apqns * sizeof(struct pkey_apqn); if (len) { if (copy_to_user(kak.apqns, apqns, len)) { - kfree(apqns); - return -EFAULT; + rc = -EFAULT; + goto out; } } } kak.apqn_entries = nr_apqns; if (copy_to_user(uak, &kak, sizeof(kak))) rc = -EFAULT; - kfree(apqns); +out: + kfree_sensitive(kkey); + kfree(apqns); return rc; } @@ -617,87 +640,90 @@ static int pkey_ioctl_apqns4kt(struct pkey_apqns4keytype __user *uat) nr_apqns = kat.apqn_entries; if (nr_apqns) { apqns = kmalloc_objs(struct pkey_apqn, nr_apqns); - if (!apqns) - return -ENOMEM; + if (!apqns) { + rc = -ENOMEM; + goto out; + } } rc = pkey_handler_apqns_for_keytype(kat.type, kat.cur_mkvp, kat.alt_mkvp, kat.flags, apqns, &nr_apqns, 0); pr_debug("apqns_for_keytype()=%d\n", rc); - if (rc && rc != -ENOSPC) { - kfree(apqns); - return rc; - } + if (rc && rc != -ENOSPC) + goto out; if (!rc && kat.apqns) { if (nr_apqns > kat.apqn_entries) { - kfree(apqns); - return -EINVAL; + rc = -EINVAL; + goto out; } len = nr_apqns * sizeof(struct pkey_apqn); if (len) { if (copy_to_user(kat.apqns, apqns, len)) { - kfree(apqns); - return -EFAULT; + rc = -EFAULT; + goto out; } } } kat.apqn_entries = nr_apqns; if (copy_to_user(uat, &kat, sizeof(kat))) rc = -EFAULT; - kfree(apqns); +out: + kfree(apqns); return rc; } static int pkey_ioctl_kblob2protk3(struct pkey_kblob2pkey3 __user *utp) { u32 protkeylen = PROTKEYBLOBBUFSIZE; + u8 *kkey = NULL, *protkey = NULL; struct pkey_apqn *apqns = NULL; struct pkey_kblob2pkey3 ktp; - u8 *kkey, *protkey; int rc; if (copy_from_user(&ktp, utp, sizeof(ktp))) return -EFAULT; apqns = _copy_apqns_from_user(ktp.apqns, ktp.apqn_entries); - if (IS_ERR(apqns)) - return PTR_ERR(apqns); + if (IS_ERR(apqns)) { + rc = PTR_ERR(apqns); + apqns = NULL; + goto out; + } kkey = _copy_key_from_user(ktp.key, ktp.keylen); if (IS_ERR(kkey)) { - kfree(apqns); - return PTR_ERR(kkey); + rc = PTR_ERR(kkey); + kkey = NULL; + goto out; } protkey = kmalloc(protkeylen, GFP_KERNEL); if (!protkey) { - kfree(apqns); - kfree_sensitive(kkey); - return -ENOMEM; + rc = -ENOMEM; + goto out; } rc = key2protkey(apqns, ktp.apqn_entries, kkey, ktp.keylen, protkey, &protkeylen, &ktp.pkeytype, 0); pr_debug("key2protkey()=%d\n", rc); - kfree(apqns); - kfree_sensitive(kkey); - if (rc) { - kfree_sensitive(protkey); - return rc; - } + if (rc) + goto out; if (ktp.pkey && ktp.pkeylen) { if (protkeylen > ktp.pkeylen) { - kfree_sensitive(protkey); - return -EINVAL; + rc = -EINVAL; + goto out; } if (copy_to_user(ktp.pkey, protkey, protkeylen)) { - kfree_sensitive(protkey); - return -EFAULT; + rc = -EFAULT; + goto out; } } - kfree_sensitive(protkey); ktp.pkeylen = protkeylen; if (copy_to_user(utp, &ktp, sizeof(ktp))) - return -EFAULT; + rc = -EFAULT; - return 0; +out: + kfree_sensitive(protkey); + kfree_sensitive(kkey); + kfree(apqns); + return rc; } static long pkey_unlocked_ioctl(struct file *filp, unsigned int cmd, From e1eb7cffd3f6ba2b8ec78c9e6c4b25cc4785d7f5 Mon Sep 17 00:00:00 2001 From: Alexander Gordeev Date: Mon, 20 Jul 2026 16:47:34 +0200 Subject: [PATCH 17/59] s390/maccess: Use proper PTE accessors for copying old memory Follow the pattern established by commit c33c794828f2 ("mm: ptep_get() conversion") and use proper PTE accessors instead of a direct pointer dereference in memcpy_real_iter(). Signed-off-by: Alexander Gordeev Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/mm/maccess.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/mm/maccess.c b/arch/s390/mm/maccess.c index cfd219fe495c..f39968dd8063 100644 --- a/arch/s390/mm/maccess.c +++ b/arch/s390/mm/maccess.c @@ -96,7 +96,7 @@ size_t memcpy_real_iter(struct iov_iter *iter, unsigned long src, size_t count) pte = mk_pte_phys(phys, PAGE_KERNEL_RO); mutex_lock(&memcpy_real_mutex); - if (pte_val(pte) != pte_val(*memcpy_real_ptep)) { + if (pte_val(pte) != pte_val(ptep_get(memcpy_real_ptep))) { __ptep_ipte(__memcpy_real_area, memcpy_real_ptep, 0, 0, IPTE_GLOBAL); set_pte(memcpy_real_ptep, pte); } From 654e97c9d731fbe606da0d0243aa4af0b2fcb89f Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Tue, 21 Jul 2026 20:47:27 +0200 Subject: [PATCH 18/59] s390/ipl: Use ARRAY_SIZE macro Use ARRAY_SIZE macro instead of reimplementing it. Signed-off-by: Mete Durlu Acked-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/ipl.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c index 3c346b02ceb9..2e1e37588d0d 100644 --- a/arch/s390/kernel/ipl.c +++ b/arch/s390/kernel/ipl.c @@ -2044,7 +2044,6 @@ static struct shutdown_action stop_action = {SHUTDOWN_ACTION_STOP_STR, static struct shutdown_action *shutdown_actions_list[] = { &ipl_action, &reipl_action, &dump_reipl_action, &dump_action, &vmcmd_action, &stop_action}; -#define SHUTDOWN_ACTIONS_COUNT (sizeof(shutdown_actions_list) / sizeof(void *)) /* * Trigger section @@ -2057,7 +2056,7 @@ static int set_trigger(const char *buf, struct shutdown_trigger *trigger, { int i; - for (i = 0; i < SHUTDOWN_ACTIONS_COUNT; i++) { + for (i = 0; i < ARRAY_SIZE(shutdown_actions_list); i++) { if (sysfs_streq(buf, shutdown_actions_list[i]->name)) { if (shutdown_actions_list[i]->init_rc) { return shutdown_actions_list[i]->init_rc; @@ -2242,7 +2241,7 @@ static void __init shutdown_actions_init(void) { int i; - for (i = 0; i < SHUTDOWN_ACTIONS_COUNT; i++) { + for (i = 0; i < ARRAY_SIZE(shutdown_actions_list); i++) { if (!shutdown_actions_list[i]->init) continue; shutdown_actions_list[i]->init_rc = From bc5d0909eef4effa9ed4136b42e5650f96856f16 Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Tue, 21 Jul 2026 20:47:28 +0200 Subject: [PATCH 19/59] s390/ipl: Improve readability Use explicit decleration on all shutdown_action/shutdown_trigger declerations and reformat shutdown_actions_list decleration to improve readability. No functional changes. Signed-off-by: Mete Durlu Acked-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/ipl.c | 53 ++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c index 2e1e37588d0d..d74ef30155aa 100644 --- a/arch/s390/kernel/ipl.c +++ b/arch/s390/kernel/ipl.c @@ -2021,8 +2021,11 @@ static int vmcmd_init(void) return sysfs_create_group(&vmcmd_kset->kobj, &vmcmd_attr_group); } -static struct shutdown_action vmcmd_action = {SHUTDOWN_ACTION_VMCMD_STR, - vmcmd_run, vmcmd_init}; +static struct shutdown_action vmcmd_action = { + .name = SHUTDOWN_ACTION_VMCMD_STR, + .fn = vmcmd_run, + .init = vmcmd_init +}; /* * stop shutdown action: Stop Linux on shutdown. @@ -2036,14 +2039,21 @@ static void stop_run(struct shutdown_trigger *trigger) smp_stop_cpu(); } -static struct shutdown_action stop_action = {SHUTDOWN_ACTION_STOP_STR, - stop_run, NULL}; +static struct shutdown_action stop_action = { + .name = SHUTDOWN_ACTION_STOP_STR, + .fn = stop_run +}; /* action list */ static struct shutdown_action *shutdown_actions_list[] = { - &ipl_action, &reipl_action, &dump_reipl_action, &dump_action, - &vmcmd_action, &stop_action}; + &ipl_action, + &reipl_action, + &dump_reipl_action, + &dump_action, + &vmcmd_action, + &stop_action +}; /* * Trigger section @@ -2071,8 +2081,10 @@ static int set_trigger(const char *buf, struct shutdown_trigger *trigger, /* on reipl */ -static struct shutdown_trigger on_reboot_trigger = {ON_REIPL_STR, - &reipl_action}; +static struct shutdown_trigger on_reboot_trigger = { + .name = ON_REIPL_STR, + .action = &reipl_action +}; static ssize_t on_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, char *page) @@ -2097,8 +2109,10 @@ static void do_machine_restart(char *__unused) void (*_machine_restart)(char *command) = do_machine_restart; /* on panic */ - -static struct shutdown_trigger on_panic_trigger = {ON_PANIC_STR, &stop_action}; +static struct shutdown_trigger on_panic_trigger = { + .name = ON_PANIC_STR, + .action = &stop_action +}; static ssize_t on_panic_show(struct kobject *kobj, struct kobj_attribute *attr, char *page) @@ -2122,9 +2136,10 @@ static void do_panic(void) } /* on restart */ - -static struct shutdown_trigger on_restart_trigger = {ON_RESTART_STR, - &stop_action}; +static struct shutdown_trigger on_restart_trigger = { + .name = ON_RESTART_STR, + .action = &stop_action +}; static ssize_t on_restart_show(struct kobject *kobj, struct kobj_attribute *attr, char *page) @@ -2159,8 +2174,10 @@ void do_restart(void *arg) } /* on halt */ - -static struct shutdown_trigger on_halt_trigger = {ON_HALT_STR, &stop_action}; +static struct shutdown_trigger on_halt_trigger = { + .name = ON_HALT_STR, + .action = &stop_action +}; static ssize_t on_halt_show(struct kobject *kobj, struct kobj_attribute *attr, char *page) @@ -2185,8 +2202,10 @@ static void do_machine_halt(void) void (*_machine_halt)(void) = do_machine_halt; /* on power off */ - -static struct shutdown_trigger on_poff_trigger = {ON_POFF_STR, &stop_action}; +static struct shutdown_trigger on_poff_trigger = { + .name = ON_POFF_STR, + .action = &stop_action +}; static ssize_t on_poff_show(struct kobject *kobj, struct kobj_attribute *attr, char *page) From ffa796cc1f45903b55801d5bd437060ea012a12d Mon Sep 17 00:00:00 2001 From: Ciunas Bennett Date: Thu, 23 Jul 2026 10:42:29 +0200 Subject: [PATCH 20/59] s390/spinlock: Add contention tracepoints to lock slowpath Instrument arch_spin_lock_wait() with trace_contention_begin() and trace_contention_end(). These tracepoints are used by lock contention analysis tools such as perf lock contention to identify contended locks and measure wait times. Both the generic implementation and powerpc emit the same events from their spinlock slow path. Place the tracepoints in arch_spin_lock_wait(), which is only entered when lock acquisition falls back to the contention path. Acked-by: Heiko Carstens Signed-off-by: Ciunas Bennett Signed-off-by: Vasily Gorbik --- arch/s390/lib/spinlock.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/s390/lib/spinlock.c b/arch/s390/lib/spinlock.c index 10db1e56a811..dbabca35c008 100644 --- a/arch/s390/lib/spinlock.c +++ b/arch/s390/lib/spinlock.c @@ -18,6 +18,7 @@ #include #include #include +#include int spin_retry = -1; @@ -281,10 +282,12 @@ static inline void arch_spin_lock_classic(arch_spinlock_t *lp) void arch_spin_lock_wait(arch_spinlock_t *lp) { + trace_contention_begin(lp, LCB_F_SPIN); if (test_cpu_flag(CIF_DEDICATED_CPU)) arch_spin_lock_queued(lp); else arch_spin_lock_classic(lp); + trace_contention_end(lp, 0); } EXPORT_SYMBOL(arch_spin_lock_wait); From aab0734094cef631d611e404c389d89f0b869f5d Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 20 Jul 2026 10:58:27 +0200 Subject: [PATCH 21/59] KVM: s390: pv: Use VM_SPARSE area for guest variable storage area The guest variable storage area is allocated with vmalloc and then donated to the ultravisor. Any kernel access to that area will result in a secure storage access exception (aka fault). This is a problem if such a memory area is read via /proc/kcore. This causes an exception via vread_iter() and results in an unexpected short read. Avoid this by allocating a custom VM_SPARSE area. If such an area is read, vread_iter() returns zeroes for the entire area. Note that the function which frees the area does not update ptes. This is intentional to allow for deferred / lazy pte updates and TLB flushing like the generic vfree() code is doing that. See vunmap_pte_range(). This assumes that s390 will gain full support for lazy_mmu_mode_enable() and lazy_mmu_mode_disable() in the future, since as of now the used ptep_get_and_clear() in vunmap_pte_range() does indeed invalidate and flush every single pte entry, but only for s390. Tested-by: Christian Borntraeger Signed-off-by: Heiko Carstens Reviewed-by: Christian Borntraeger Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/uv.h | 2 ++ arch/s390/kernel/uv.c | 65 ++++++++++++++++++++++++++++++++++++++ arch/s390/kvm/pv.c | 6 ++-- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/arch/s390/include/asm/uv.h b/arch/s390/include/asm/uv.h index d919e69662f5..153fed63adda 100644 --- a/arch/s390/include/asm/uv.h +++ b/arch/s390/include/asm/uv.h @@ -635,6 +635,8 @@ int s390_wiggle_split_folio(struct mm_struct *mm, struct folio *folio); int __make_folio_secure(struct folio *folio, struct uv_cb_header *uvcb); int uv_convert_from_secure(unsigned long paddr); int uv_convert_from_secure_folio(struct folio *folio); +void *uv_alloc_stor_var(unsigned long size); +void uv_free_stor_var(void *stor_var); void setup_uv(void); diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c index a284f98d9716..d1cdc5ae5e27 100644 --- a/arch/s390/kernel/uv.c +++ b/arch/s390/kernel/uv.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -209,6 +210,70 @@ int uv_convert_from_secure_pte(pte_t pte) return uv_convert_from_secure_folio(pfn_folio(pte_pfn(pte))); } +static int uv_free_range_cb(pte_t *ptep, unsigned long addr, void *data) +{ + pte_t pte = ptep_get(ptep); + + if (!pte_present(pte)) + return 0; + /* + * Note: do not update the pte here, since there is no code which + * accesses the memory range, besides bugs. The invalidation of ptes + * and TLB flushing is deferred like for regular vfree() calls. + */ + __free_page(pte_page(pte)); + return 0; +} + +void uv_free_stor_var(void *stor_var) +{ + unsigned long addr, size; + struct vm_struct *area; + + if (!stor_var) + return; + area = find_vm_area(stor_var); + if (WARN_ON_ONCE(!area || !(area->flags & VM_SPARSE))) + return; + size = get_vm_area_size(area); + addr = (unsigned long)area->addr; + apply_to_existing_page_range(&init_mm, addr, size, uv_free_range_cb, NULL); + free_vm_area(area); +} +EXPORT_SYMBOL_FOR_MODULES(uv_free_stor_var, "kvm"); + +static int uv_alloc_range_cb(pte_t *ptep, unsigned long addr, void *data) +{ + struct page *page; + pte_t pte; + + page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO); + if (!page) + return -ENOMEM; + pte = __pte(page_to_phys(page) | pgprot_val(PAGE_KERNEL)); + set_pte(ptep, pte); + return 0; +} + +void *uv_alloc_stor_var(unsigned long size) +{ + struct vm_struct *area; + unsigned long addr; + + size = PAGE_ALIGN(size); + area = get_vm_area(size, VM_SPARSE); + if (!area) + return NULL; + addr = (unsigned long)area->addr; + if (apply_to_page_range(&init_mm, addr, size, uv_alloc_range_cb, NULL)) + goto out; + return area->addr; +out: + uv_free_stor_var(area->addr); + return NULL; +} +EXPORT_SYMBOL_FOR_MODULES(uv_alloc_stor_var, "kvm"); + /* * Calculate the expected ref_count for a folio that would otherwise have no * further pins. This was cribbed from similar functions in other places in diff --git a/arch/s390/kvm/pv.c b/arch/s390/kvm/pv.c index 1beacc841ca8..dc5ac29b4c31 100644 --- a/arch/s390/kvm/pv.c +++ b/arch/s390/kvm/pv.c @@ -337,7 +337,7 @@ int kvm_s390_pv_create_cpu(struct kvm_vcpu *vcpu, u16 *rc, u16 *rrc) /* only free resources when the destroy was successful */ static void kvm_s390_pv_dealloc_vm(struct kvm *kvm) { - vfree(kvm->arch.pv.stor_var); + uv_free_stor_var(kvm->arch.pv.stor_var); free_pages(kvm->arch.pv.stor_base, get_order(uv_info.guest_base_stor_len)); kvm_s390_clear_pv_state(kvm); @@ -369,7 +369,7 @@ static int kvm_s390_pv_alloc_vm(struct kvm *kvm) /* Allocate variable storage */ vlen = ALIGN(virt * ((npages * PAGE_SIZE) / HPAGE_SIZE), PAGE_SIZE); vlen += uv_info.guest_virt_base_stor_len; - kvm->arch.pv.stor_var = vzalloc(vlen); + kvm->arch.pv.stor_var = uv_alloc_stor_var(vlen); if (!kvm->arch.pv.stor_var) goto out_err; return 0; @@ -414,7 +414,7 @@ static int kvm_s390_pv_dispose_one_leftover(struct kvm *kvm, */ free_pages(leftover->stor_base, get_order(uv_info.guest_base_stor_len)); free_pages(leftover->old_gmap_table, CRST_ALLOC_ORDER); - vfree(leftover->stor_var); + uv_free_stor_var(leftover->stor_var); done_fast: atomic_dec(&kvm->mm->context.protected_count); return 0; From 7cf227b2707ed0129018c283ffc8f893945586db Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 20 Jul 2026 10:58:28 +0200 Subject: [PATCH 22/59] s390/mm: Add missing mm check to do_secure_storage_access() Similar like the normal exception handler the secure storage access exception handler needs to verify if mm is not NULL, which may happen with kernel threads. Reported-by: sashiko-bot Closes: https://lore.kernel.org/all/20260717132345.539A11F00A3A@smtp.kernel.org/ Signed-off-by: Heiko Carstens Reviewed-by: Christian Borntraeger > --- Signed-off-by: Vasily Gorbik --- arch/s390/mm/fault.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c index 028aeb9c48d6..2839d7a64401 100644 --- a/arch/s390/mm/fault.c +++ b/arch/s390/mm/fault.c @@ -409,9 +409,9 @@ void do_secure_storage_access(struct pt_regs *regs) { union teid teid = { .val = regs->int_parm_long }; unsigned long addr = get_fault_address(regs); + struct mm_struct *mm = current->mm; struct vm_area_struct *vma; struct folio_walk fw; - struct mm_struct *mm; struct folio *folio; int rc; @@ -453,9 +453,8 @@ void do_secure_storage_access(struct pt_regs *regs) if (rc) return handle_fault_error_nolock(regs, 0); } else { - if (faulthandler_disabled()) + if (faulthandler_disabled() || !mm) return handle_fault_error_nolock(regs, 0); - mm = current->mm; mmap_read_lock(mm); vma = find_vma(mm, addr); if (!vma) From 79ef8d384fdfbf837ffe9c07125c88fc39fa58c9 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 20 Jul 2026 10:58:29 +0200 Subject: [PATCH 23/59] s390/mm: Use lock_mm_and_find_vma() in do_secure_storage_access() do_secure_storage_access() uses find_vma() without verifying that the faulting address is within the returned vma. Add this missing check by converting to lock_mm_and_find_vma(). This is not a critical fix, since the worst that could happen is a WARN_ON_ONCE() in folio_walk_start(). Reported-by: sashiko-bot Closes: https://lore.kernel.org/all/20260717093904.E4A421F00A3E@smtp.kernel.org/ Link: https://lore.kernel.org/all/20260717093904.E4A421F00A3E@smtp.kernel.org/ Signed-off-by: Heiko Carstens Reviewed-by: Christian Borntraeger Signed-off-by: Vasily Gorbik --- arch/s390/mm/fault.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c index 2839d7a64401..f150d97cac2e 100644 --- a/arch/s390/mm/fault.c +++ b/arch/s390/mm/fault.c @@ -455,10 +455,9 @@ void do_secure_storage_access(struct pt_regs *regs) } else { if (faulthandler_disabled() || !mm) return handle_fault_error_nolock(regs, 0); - mmap_read_lock(mm); - vma = find_vma(mm, addr); + vma = lock_mm_and_find_vma(mm, addr, regs); if (!vma) - return handle_fault_error(regs, SEGV_MAPERR); + return handle_fault_error_nolock(regs, SEGV_MAPERR); folio = folio_walk_start(&fw, vma, addr, 0); if (!folio) { mmap_read_unlock(mm); From a2e5a9d853e015bc876399eb18435aa88101c60a Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 20 Jul 2026 10:58:30 +0200 Subject: [PATCH 24/59] s390/mm: Fix handling of vmalloc area in do_secure_storage_access() Secure storage access exceptions in vmalloc area need to be handled separately since virt_to_phys() does not work on such addresses. Given that there cannot be a valid access let such exceptions fail immediately. The kernel will either fixup or crash. Tested-by: Christian Borntraeger Signed-off-by: Heiko Carstens Reviewed-by: Christian Borntraeger > --- Signed-off-by: Vasily Gorbik --- arch/s390/mm/fault.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c index f150d97cac2e..e650cdfa2523 100644 --- a/arch/s390/mm/fault.c +++ b/arch/s390/mm/fault.c @@ -438,6 +438,8 @@ void do_secure_storage_access(struct pt_regs *regs) panic("Unexpected PGM 0x3d with TEID bit 61=0"); } if (is_kernel_fault(regs)) { + if (is_vmalloc_addr((void *)addr)) + return handle_fault_error_nolock(regs, 0); folio = virt_to_folio((void *)addr); if (unlikely(!folio_try_get(folio))) return; From ecb00aa6fb7f473136fb2e496ad91116a568fc7f Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 20 Jul 2026 10:58:31 +0200 Subject: [PATCH 25/59] s390/mm: Remove folio handling for kernel faults in do_secure_storage_access() Remove folio handling for secure storage access exceptions that happen via the kernel mapping. Using folio_try_get() for exceptions via the kernel mapping is racy: for example such an exception may happen on one CPU, while a different CPU frees the corresponding page. Subsequently the page can be part of a folio of different size, before the faulting CPU executes folio_try_get(). This may lead to data corruption. As of now this scenario does not seem to be possible, however with the upcoming load_unaligned_zeropad() this is possible. As a side effect PG_arch_1 is not cleared anymore for such faults via the kernel mapping. However given that this bit is over-indicating it shouldn't matter. Tested-by: Christian Borntraeger Signed-off-by: Heiko Carstens Acked-by: Claudio Imbrenda Reviewed-by: Christian Borntraeger Signed-off-by: Vasily Gorbik --- arch/s390/mm/fault.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c index e650cdfa2523..776549d55c94 100644 --- a/arch/s390/mm/fault.c +++ b/arch/s390/mm/fault.c @@ -440,13 +440,7 @@ void do_secure_storage_access(struct pt_regs *regs) if (is_kernel_fault(regs)) { if (is_vmalloc_addr((void *)addr)) return handle_fault_error_nolock(regs, 0); - folio = virt_to_folio((void *)addr); - if (unlikely(!folio_try_get(folio))) - return; - rc = uv_convert_from_secure(folio_to_phys(folio)); - if (!rc) - clear_bit(PG_arch_1, &folio->flags.f); - folio_put(folio); + rc = uv_convert_from_secure(__pa(addr)); /* * There are some valid fixup types for kernel * accesses to donated secure memory. zeropad is one From da1f5a7dcf1826d90b8dffe7a2fb6c280d15227a Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 20 Jul 2026 10:58:32 +0200 Subject: [PATCH 26/59] s390/mm: Use handle_fault_error() in do_secure_storage_access() Similar to the normal exception handler use handle_fault_error() which handles accesses from user space and kernel space differently. This also addresses failing kernel accesses via e.g. copy_from_user() to user space: instead of an endless loop, the fixup handling is performed. Even though this could only happen if arch_make_folio_accessible() would permanently fail, which is not realistic. Therefore this is rather a cleanup patch. Acked-by: Vasily Gorbik Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/mm/fault.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c index 776549d55c94..2a9b9fb30d09 100644 --- a/arch/s390/mm/fault.c +++ b/arch/s390/mm/fault.c @@ -427,10 +427,8 @@ void do_secure_storage_access(struct pt_regs *regs) * was not supposed to do, e.g. branching into secure * memory. Trigger a segmentation fault. */ - if (user_mode(regs)) { - send_sig(SIGSEGV, current, 0); - return; - } + if (user_mode(regs)) + return handle_fault_error_nolock(regs, SEGV_ACCERR); /* * The kernel should never run into this case and * there is no way out of this situation. @@ -465,7 +463,7 @@ void do_secure_storage_access(struct pt_regs *regs) folio_put(folio); folio_walk_end(&fw, vma); if (rc) - send_sig(SIGSEGV, current, 0); + return handle_fault_error(regs, SEGV_ACCERR); mmap_read_unlock(mm); } } From e462200886fb56cc318700b2fbbb2206ac28a656 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 20 Jul 2026 10:58:33 +0200 Subject: [PATCH 27/59] s390/mm: Use goto statement in do_secure_storage_access() Make do_secure_storage_access() look more like the do_exception() by using a goto statement to get rid of an extra open coded mmap_read_unlock() call. Just cosmetic, no functional change. Signed-off-by: Heiko Carstens Reviewed-by: Christian Borntraeger Signed-off-by: Vasily Gorbik --- arch/s390/mm/fault.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c index 2a9b9fb30d09..666fe8e99480 100644 --- a/arch/s390/mm/fault.c +++ b/arch/s390/mm/fault.c @@ -453,10 +453,8 @@ void do_secure_storage_access(struct pt_regs *regs) if (!vma) return handle_fault_error_nolock(regs, SEGV_MAPERR); folio = folio_walk_start(&fw, vma, addr, 0); - if (!folio) { - mmap_read_unlock(mm); - return; - } + if (!folio) + goto out; /* arch_make_folio_accessible() needs a raised refcount. */ folio_get(folio); rc = arch_make_folio_accessible(folio); @@ -464,6 +462,7 @@ void do_secure_storage_access(struct pt_regs *regs) folio_walk_end(&fw, vma); if (rc) return handle_fault_error(regs, SEGV_ACCERR); +out: mmap_read_unlock(mm); } } From 656db3022083ac2239f9f75ddfbd8f598ca64f5f Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 20 Jul 2026 10:58:34 +0200 Subject: [PATCH 28/59] s390: Add support for DCACHE_WORD_ACCESS (again) Implement load_unaligned_zeropad() and enable DCACHE_WORD_ACCESS to speed up string operations in fs/dcache.c and fs/namei.c. With the secure storage access exception cases addressed, add support for DCACHE_WORD_ACCESS again. Tested-by: Christian Borntraeger Signed-off-by: Heiko Carstens Reviewed-by: Sven Schnelle Signed-off-by: Vasily Gorbik --- arch/s390/Kconfig | 1 + arch/s390/include/asm/asm-extable.h | 4 ++++ arch/s390/include/asm/word-at-a-time.h | 22 ++++++++++++++++++++++ arch/s390/mm/extable.c | 18 ++++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 6ceae6f61ebb..2be7346c48da 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -166,6 +166,7 @@ config S390 select ARCH_WANTS_THP_SWAP select BUILDTIME_TABLE_SORT select CLONE_BACKWARDS2 + select DCACHE_WORD_ACCESS if !KMSAN select DYNAMIC_FTRACE if FUNCTION_TRACER select FUNCTION_ALIGNMENT_8B if CC_IS_GCC select FUNCTION_ALIGNMENT_16B if !CC_IS_GCC diff --git a/arch/s390/include/asm/asm-extable.h b/arch/s390/include/asm/asm-extable.h index 99748c20e767..d23ea0c94e4e 100644 --- a/arch/s390/include/asm/asm-extable.h +++ b/arch/s390/include/asm/asm-extable.h @@ -12,6 +12,7 @@ #define EX_TYPE_UA_FAULT 3 #define EX_TYPE_UA_LOAD_REG 5 #define EX_TYPE_UA_LOAD_REGPAIR 6 +#define EX_TYPE_ZEROPAD 7 #define EX_TYPE_FPC 8 #define EX_TYPE_UA_MVCOS_TO 9 #define EX_TYPE_UA_MVCOS_FROM 10 @@ -79,6 +80,9 @@ #define EX_TABLE_UA_LOAD_REGPAIR(_fault, _target, _regerr, _regzero) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_LOAD_REGPAIR, _regerr, _regzero, 0) +#define EX_TABLE_ZEROPAD(_fault, _target, _regdata, _regaddr) \ + __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_ZEROPAD, _regdata, _regaddr, 0) + #define EX_TABLE_FPC(_fault, _target) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_FPC, __stringify(%%r0), __stringify(%%r0), 0) diff --git a/arch/s390/include/asm/word-at-a-time.h b/arch/s390/include/asm/word-at-a-time.h index e9287036392d..eaa19dee7699 100644 --- a/arch/s390/include/asm/word-at-a-time.h +++ b/arch/s390/include/asm/word-at-a-time.h @@ -4,6 +4,7 @@ #include #include +#include #include struct word_at_a_time { @@ -40,4 +41,25 @@ static inline unsigned long zero_bytemask(unsigned long data) return ~1UL << data; } +/* + * Load an unaligned word from kernel space. + * + * In the (very unlikely) case of the word being a page-crosser + * and the next page not being mapped, take the exception and + * return zeroes in the non-existing part. + */ +static inline unsigned long load_unaligned_zeropad(const void *addr) +{ + unsigned long data; + + asm_inline volatile( + "0: lg %[data],0(%[addr])\n" + "1: nopr %%r7\n" + EX_TABLE_ZEROPAD(0b, 1b, %[data], %[addr]) + EX_TABLE_ZEROPAD(1b, 1b, %[data], %[addr]) + : [data] "=d" (data) + : [addr] "a" (addr), "m" (*(unsigned long *)addr)); + return data; +} + #endif /* _ASM_WORD_AT_A_TIME_H */ diff --git a/arch/s390/mm/extable.c b/arch/s390/mm/extable.c index 063b4346742d..7498e858c401 100644 --- a/arch/s390/mm/extable.c +++ b/arch/s390/mm/extable.c @@ -50,6 +50,22 @@ static bool ex_handler_ua_load_reg(const struct exception_table_entry *ex, return true; } +static bool ex_handler_zeropad(const struct exception_table_entry *ex, struct pt_regs *regs) +{ + unsigned int reg_addr = FIELD_GET(EX_DATA_REG_ADDR, ex->data); + unsigned int reg_data = FIELD_GET(EX_DATA_REG_ERR, ex->data); + unsigned long data, addr, offset; + + addr = regs->gprs[reg_addr]; + offset = addr & (sizeof(unsigned long) - 1); + addr &= ~(sizeof(unsigned long) - 1); + data = *(unsigned long *)addr; + data <<= BITS_PER_BYTE * offset; + regs->gprs[reg_data] = data; + regs->psw.addr = extable_fixup(ex); + return true; +} + static bool ex_handler_fpc(const struct exception_table_entry *ex, struct pt_regs *regs) { fpu_sfpc(0); @@ -118,6 +134,8 @@ bool fixup_exception(struct pt_regs *regs) return ex_handler_ua_load_reg(ex, false, regs); case EX_TYPE_UA_LOAD_REGPAIR: return ex_handler_ua_load_reg(ex, true, regs); + case EX_TYPE_ZEROPAD: + return ex_handler_zeropad(ex, regs); case EX_TYPE_FPC: return ex_handler_fpc(ex, regs); case EX_TYPE_UA_MVCOS_TO: From 2d6b38e6619b821f31d52e0abab14560f69dedd3 Mon Sep 17 00:00:00 2001 From: Christian Borntraeger Date: Mon, 27 Jul 2026 11:38:29 +0200 Subject: [PATCH 29/59] s390/configs: Increase CONFIG_LOCKDEP_CHAINS_BITS With btrfs and other complex code paths the message BUG_MAX_LOCKDEP_CHAIN_HLOCKS_TOO_LOW is triggered. To avoid disabling lockdep increase CONFIG_LOCKDEP_CHAINS_BITS to 18. Acked-by: Heiko Carstens Signed-off-by: Christian Borntraeger Signed-off-by: Vasily Gorbik --- arch/s390/configs/debug_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/configs/debug_defconfig b/arch/s390/configs/debug_defconfig index 007668f95028..3dae71474333 100644 --- a/arch/s390/configs/debug_defconfig +++ b/arch/s390/configs/debug_defconfig @@ -869,7 +869,7 @@ CONFIG_DEBUG_PREEMPT=y CONFIG_PROVE_LOCKING=y CONFIG_LOCK_STAT=y CONFIG_LOCKDEP_BITS=16 -CONFIG_LOCKDEP_CHAINS_BITS=17 +CONFIG_LOCKDEP_CHAINS_BITS=18 CONFIG_DEBUG_ATOMIC_SLEEP=y CONFIG_DEBUG_LOCKING_API_SELFTESTS=y CONFIG_DEBUG_IRQFLAGS=y From bdce129e44dfc908762b63dc18d405046f05a325 Mon Sep 17 00:00:00 2001 From: Finn Callies Date: Thu, 23 Jul 2026 17:00:38 +0200 Subject: [PATCH 30/59] s390/ap: Fix queue depth field length The queue depth field is defined as a 5 bit field in the Z architecture instead of a 4 bit field. The queue depth (qd) can be in range 0-31 and is reported in bits 59-63 of the TAPQ response. Currently this has no effect as all CEX generations report a queue depth of 7, which fits into 4 bits. However, future CEX generations reporting a value >15 would not be properly reflected by the ap bus and therefore all user space applications relying on it. Reviewed-by: Harald Freudenberger Signed-off-by: Finn Callies Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/ap.h | 4 ++-- drivers/s390/crypto/ap_bus.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/include/asm/ap.h b/arch/s390/include/asm/ap.h index 3b95c6531a67..c91b6ace199d 100644 --- a/arch/s390/include/asm/ap.h +++ b/arch/s390/include/asm/ap.h @@ -125,8 +125,8 @@ struct ap_tapq_hwinfo { unsigned int nd : 8; /* nr of domains */ unsigned int : 4; unsigned int ml : 4; /* apxl ml */ - unsigned int : 4; - unsigned int qd : 4; /* queue depth */ + unsigned int : 3; + unsigned int qd : 5; /* queue depth */ }; }; }; diff --git a/drivers/s390/crypto/ap_bus.h b/drivers/s390/crypto/ap_bus.h index b2e57e5d6c3f..fb4d678336e4 100644 --- a/drivers/s390/crypto/ap_bus.h +++ b/drivers/s390/crypto/ap_bus.h @@ -181,7 +181,7 @@ struct ap_card { bool chkstop; /* checkstop state */ }; -#define TAPQ_CARD_HWINFO_MASK 0xFFFF0000FFFF0F0FUL +#define TAPQ_CARD_HWINFO_MASK 0xFFFF0000FFFF0F1FUL #define ASSOC_IDX_INVALID 0x10000 #define to_ap_card(x) container_of((x), struct ap_card, ap_dev.device) From 753b3873ce4af5c8c73e1a61a998780772ef8324 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Fri, 24 Jul 2026 14:23:01 +0200 Subject: [PATCH 31/59] s390/sclp: Allow SCLP Action Qualifiers for Spyre card status reporting Add SCLP Action Qualifiers used by the Spyre stack for reporting of the card's initialization status, recoverable errors, and telemetry data. Co-developed-by: Andreas Krebbel Signed-off-by: Andreas Krebbel Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/sclp.h | 3 +++ drivers/s390/char/sclp_pci.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/arch/s390/include/asm/sclp.h b/arch/s390/include/asm/sclp.h index f2d490558054..44066550b9b1 100644 --- a/arch/s390/include/asm/sclp.h +++ b/arch/s390/include/asm/sclp.h @@ -21,6 +21,9 @@ #define SCLP_ERRNOTIFY_AQ_INFO_LOG 2 #define SCLP_ERRNOTIFY_AQ_OPTICS_DATA 3 #define SCLP_ERRNOTIFY_AQ_NVME_SMART_LOG 4 +#define SCLP_ERRNOTIFY_AQ_ADAPTER_INITIALIZED 5 +#define SCLP_ERRNOTIFY_AQ_RECOVERABLE_ERROR 6 +#define SCLP_ERRNOTIFY_AQ_TELEMETRY_DATA 7 #ifndef __ASSEMBLER__ #include diff --git a/drivers/s390/char/sclp_pci.c b/drivers/s390/char/sclp_pci.c index d61a7fc0dd61..b79ef7006e46 100644 --- a/drivers/s390/char/sclp_pci.c +++ b/drivers/s390/char/sclp_pci.c @@ -99,6 +99,9 @@ static int sclp_pci_check_report(struct zpci_report_error_header *report) case SCLP_ERRNOTIFY_AQ_INFO_LOG: case SCLP_ERRNOTIFY_AQ_OPTICS_DATA: case SCLP_ERRNOTIFY_AQ_NVME_SMART_LOG: + case SCLP_ERRNOTIFY_AQ_ADAPTER_INITIALIZED: + case SCLP_ERRNOTIFY_AQ_RECOVERABLE_ERROR: + case SCLP_ERRNOTIFY_AQ_TELEMETRY_DATA: break; default: return -EINVAL; From faa39c2eca192c369686a0b8d3b936e9554236ef Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Thu, 23 Jul 2026 15:36:43 +0200 Subject: [PATCH 32/59] s390/smp: Reflect (de)configured CPUs to cpu_enabled_mask On s390, CPUs can be in a state where it is not possible to hotplug them online before certain prequisite steps. For example the CPUs which get introduced during runtime of a system can posses a "deconfigured" state which prevents them from being hotplugged online before they get configured. Another case is when users set the configured state of CPUs themselves via "chcpu" or sysfs attributes. On s390 available CPUs are being registered as new devices via smp_add_core() either during boot or after a CPU rescan (for newly added CPUs during runtime). Registered CPUs are marked as enabled without considering the configure states. Add necessary checks to smp_add_core() and userspace configure attribute handler. Reflect the configured CPUs to cpu_enabled_mask to correctly represent which CPUs can be hotplugged online. Signed-off-by: Mete Durlu Acked-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/smp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/s390/kernel/smp.c b/arch/s390/kernel/smp.c index 4cc2c119318d..167c72803ccf 100644 --- a/arch/s390/kernel/smp.c +++ b/arch/s390/kernel/smp.c @@ -1038,6 +1038,7 @@ static ssize_t cpu_configure_store(struct device *dev, per_cpu(pcpu_devices, cpu + i).state = CPU_STATE_STANDBY; smp_cpu_set_polarization(cpu + i, POLARIZATION_UNKNOWN); + set_cpu_enabled(cpu + i, false); } topology_expect_change(); break; @@ -1053,6 +1054,7 @@ static ssize_t cpu_configure_store(struct device *dev, per_cpu(pcpu_devices, cpu + i).state = CPU_STATE_CONFIGURED; smp_cpu_set_polarization(cpu + i, POLARIZATION_UNKNOWN); + set_cpu_enabled(cpu + i, true); } topology_expect_change(); break; @@ -1090,6 +1092,7 @@ bool arch_cpu_is_hotpluggable(int cpu) int arch_register_cpu(int cpu) { + struct pcpu *pcpu = per_cpu_ptr(&pcpu_devices, cpu); struct cpu *c = per_cpu_ptr(&cpu_devices, cpu); int rc; @@ -1103,6 +1106,8 @@ int arch_register_cpu(int cpu) rc = topology_cpu_init(c); if (rc) goto out_topology; + if (pcpu->state != CPU_STATE_CONFIGURED) + set_cpu_enabled(cpu, false); return 0; out_topology: From accfabc3aff90d18df6314d37943194758ea0aa1 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 27 Jul 2026 16:05:08 +0200 Subject: [PATCH 33/59] s390/tools: Pass symbol name to do_relocs() Pass the symbol symbol name which corresponds to a relocation to do_relocs(). This is preparation for kCFI support. Given that the s390 specific relocs tool is a stripped down version of the x86 version, add more code from the x86 version to the s390 version, while keeping coding style, etc. in order to add the required functionality. Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens Tested-by: Nathan Chancellor Signed-off-by: Vasily Gorbik --- arch/s390/tools/relocs.c | 142 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 3 deletions(-) diff --git a/arch/s390/tools/relocs.c b/arch/s390/tools/relocs.c index 30a732c808f3..a2774176d38f 100644 --- a/arch/s390/tools/relocs.c +++ b/arch/s390/tools/relocs.c @@ -41,6 +41,10 @@ static Elf_Ehdr ehdr; static unsigned long shnum; static unsigned int shstrndx; +static unsigned int shsymtabndx; +static unsigned int shxsymtabndx; + +static int sym_index(Elf_Sym *sym); struct relocs { uint32_t *offset; @@ -54,11 +58,40 @@ static struct relocs relocs64; struct section { Elf_Shdr shdr; struct section *link; + Elf_Sym *symtab; + Elf32_Word *xsymtab; Elf_Rel *reltab; + char *strtab; }; static struct section *secs; +static const char *sec_name(unsigned shndx) +{ + const char *sec_strtab; + const char *name = ""; + sec_strtab = secs[shstrndx].strtab; + + if (shndx < shnum) + name = sec_strtab + secs[shndx].shdr.sh_name; + else if (shndx == SHN_ABS) + name = "ABSOLUTE"; + else if (shndx == SHN_COMMON) + name = "COMMON"; + return name; +} + +static const char *sym_name(const char *sym_strtab, Elf_Sym *sym) +{ + const char *name; + + if (sym->st_name) + name = sym_strtab + sym->st_name; + else + name = sec_name(sym_index(sym)); + return name; +} + #if BYTE_ORDER == LITTLE_ENDIAN #define le16_to_cpu(val) (val) #define le32_to_cpu(val) (val) @@ -105,6 +138,23 @@ static uint64_t elf64_to_cpu(uint64_t val) #define elf_off_to_cpu(x) elf64_to_cpu(x) #define elf_xword_to_cpu(x) elf64_to_cpu(x) +static int sym_index(Elf_Sym *sym) +{ + Elf_Sym *symtab = secs[shsymtabndx].symtab; + Elf32_Word *xsymtab = secs[shxsymtabndx].xsymtab; + unsigned long offset; + int index; + + if (sym->st_shndx != SHN_XINDEX) + return sym->st_shndx; + + /* calculate offset of sym from head of table. */ + offset = (unsigned long)sym - (unsigned long)symtab; + index = offset / sizeof(*sym); + + return elf32_to_cpu(xsymtab[index]); +} + static void die(char *fmt, ...) { va_list ap; @@ -216,6 +266,81 @@ static void read_shdrs(FILE *fp) } +static void read_strtabs(FILE *fp) +{ + int i; + + for (i = 0; i < shnum; i++) { + struct section *sec = &secs[i]; + + if (sec->shdr.sh_type != SHT_STRTAB) + continue; + + sec->strtab = malloc(sec->shdr.sh_size); + if (!sec->strtab) + die("malloc of %" FMT " bytes for strtab failed\n", sec->shdr.sh_size); + + if (fseek(fp, sec->shdr.sh_offset, SEEK_SET) < 0) + die("Seek to %" FMT " failed: %s\n", sec->shdr.sh_offset, strerror(errno)); + + if (fread(sec->strtab, 1, sec->shdr.sh_size, fp) != sec->shdr.sh_size) + die("Cannot read symbol table: %s\n", strerror(errno)); + } +} + +static void read_symtabs(FILE *fp) +{ + int i, j; + + for (i = 0; i < shnum; i++) { + struct section *sec = &secs[i]; + int num_syms; + + switch (sec->shdr.sh_type) { + case SHT_SYMTAB_SHNDX: + sec->xsymtab = malloc(sec->shdr.sh_size); + if (!sec->xsymtab) + die("malloc of %" FMT " bytes for xsymtab failed\n", sec->shdr.sh_size); + + if (fseek(fp, sec->shdr.sh_offset, SEEK_SET) < 0) + die("Seek to %" FMT " failed: %s\n", sec->shdr.sh_offset, strerror(errno)); + + if (fread(sec->xsymtab, 1, sec->shdr.sh_size, fp) != sec->shdr.sh_size) + die("Cannot read extended symbol table: %s\n", strerror(errno)); + + shxsymtabndx = i; + continue; + + case SHT_SYMTAB: + num_syms = sec->shdr.sh_size / sizeof(Elf_Sym); + + sec->symtab = malloc(sec->shdr.sh_size); + if (!sec->symtab) + die("malloc of %" FMT " bytes for symtab failed\n", sec->shdr.sh_size); + + if (fseek(fp, sec->shdr.sh_offset, SEEK_SET) < 0) + die("Seek to %" FMT " failed: %s\n", sec->shdr.sh_offset, strerror(errno)); + + if (fread(sec->symtab, 1, sec->shdr.sh_size, fp) != sec->shdr.sh_size) + die("Cannot read symbol table: %s\n", strerror(errno)); + + for (j = 0; j < num_syms; j++) { + Elf_Sym *sym = &sec->symtab[j]; + + sym->st_name = elf_word_to_cpu(sym->st_name); + sym->st_value = elf_addr_to_cpu(sym->st_value); + sym->st_size = elf_xword_to_cpu(sym->st_size); + sym->st_shndx = elf_half_to_cpu(sym->st_shndx); + } + shsymtabndx = i; + continue; + + default: + continue; + } + } +} + static void read_relocs(FILE *fp) { int i, j; @@ -263,7 +388,8 @@ static void add_reloc(struct relocs *r, uint32_t offset) r->offset[r->count++] = offset; } -static int do_reloc(struct section *sec, Elf_Rel *rel) +static int do_reloc(struct section *sec, Elf_Rel *rel, ElfW(Sym) *sym, + const char *symname) { unsigned int r_type = ELF64_R_TYPE(rel->r_info); ElfW(Addr) offset = rel->r_offset; @@ -296,21 +422,29 @@ static void walk_relocs(void) /* Walk through the relocations */ for (i = 0; i < shnum; i++) { - struct section *sec_applies; + char *sym_strtab; + Elf_Sym *sh_symtab; + struct section *sec_applies, *sec_symtab; int j; struct section *sec = &secs[i]; if (sec->shdr.sh_type != SHT_REL_TYPE) continue; + sec_symtab = sec->link; sec_applies = &secs[sec->shdr.sh_info]; if (!(sec_applies->shdr.sh_flags & SHF_ALLOC)) continue; + sh_symtab = sec_symtab->symtab; + sym_strtab = sec_symtab->link->strtab; + for (j = 0; j < sec->shdr.sh_size / sizeof(Elf_Rel); j++) { Elf_Rel *rel = &sec->reltab[j]; + Elf_Sym *sym = &sh_symtab[ELF_R_SYM(rel->r_info)]; + const char *symname = sym_name(sym_strtab, sym); - do_reloc(sec, rel); + do_reloc(sec, rel, sym, symname); } } } @@ -349,6 +483,8 @@ static void process(FILE *fp) { read_ehdr(fp); read_shdrs(fp); + read_strtabs(fp); + read_symtabs(fp); read_relocs(fp); emit_relocs(); } From 1ccbfc99ce9e4b0ac8e4c038bd504c70cb0357c4 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 27 Jul 2026 16:05:09 +0200 Subject: [PATCH 34/59] s390/tools/relocs: Ignore __kcfi_typeid_ relocations This is the s390 variant of commit ca7e10bff196 ("x86/tools/relocs: Ignore __kcfi_typeid_ relocations"): "The compiler generates __kcfi_typeid_ symbols for annotating assembly functions with type information. These are constants that can be referenced in assembly code and are resolved by the linker. Ignore them in relocs." Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens Tested-by: Nathan Chancellor Signed-off-by: Vasily Gorbik --- arch/s390/tools/relocs.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/s390/tools/relocs.c b/arch/s390/tools/relocs.c index a2774176d38f..72178dc90a4c 100644 --- a/arch/s390/tools/relocs.c +++ b/arch/s390/tools/relocs.c @@ -405,6 +405,22 @@ static int do_reloc(struct section *sec, Elf_Rel *rel, ElfW(Sym) *sym, case R_390_GOTPCDBL: case R_390_GOTOFF64: break; + case R_390_32: { + static const char kcfipfx[] = "__kcfi_typeid_"; + + if (sym->st_shndx != SHN_ABS) + die("Unsupported relocation type: %d\n", r_type); + /* + * Symbols with __kcfi_typeid_ prefix have constant values, + * which do not change if bzImage is loaded at a different + * physical address than the address for which it has been + * compiled. + */ + if (!strncmp(kcfipfx, symname, sizeof(kcfipfx) - 1)) + break; + die("Invalid absolute R_390_32 relocation: %s\n", symname); + break; + } case R_390_64: add_reloc(&relocs64, offset); break; From 9ac687a280aad334d89eb5af80f3465d3df3e601 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 27 Jul 2026 16:05:10 +0200 Subject: [PATCH 35/59] s390: Add ftrace_stub_graph This is the s390 variant of commit f3a0c23f2539 ("riscv: Add ftrace_stub_graph"): "Commit 883bbbffa5a4 ("ftrace,kcfi: Separate ftrace_stub() and ftrace_stub_graph()") added a separate ftrace_stub_graph function for CFI_CLANG. Add the stub to fix FUNCTION_GRAPH_TRACER compatibility with CFI." Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens Tested-by: Nathan Chancellor Signed-off-by: Vasily Gorbik --- arch/s390/kernel/mcount.S | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arch/s390/kernel/mcount.S b/arch/s390/kernel/mcount.S index 1fec370fecf4..6bc44c767642 100644 --- a/arch/s390/kernel/mcount.S +++ b/arch/s390/kernel/mcount.S @@ -4,6 +4,7 @@ * */ +#include #include #include #include @@ -34,10 +35,16 @@ .section .kprobes.text, "ax" -SYM_FUNC_START(ftrace_stub) +SYM_TYPED_FUNC_START(ftrace_stub) BR_EX %r14 SYM_FUNC_END(ftrace_stub) +#ifdef CONFIG_FUNCTION_GRAPH_TRACER +SYM_TYPED_FUNC_START(ftrace_stub_graph) + BR_EX %r14 +SYM_FUNC_END(ftrace_stub_graph) +#endif + SYM_CODE_START(ftrace_stub_direct_tramp) lgr %r1, %r0 BR_EX %r1 From 4133c9d3f3c2c7ceaf7356ee95d9ea94e5aa033f Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 27 Jul 2026 16:05:11 +0200 Subject: [PATCH 36/59] s390/diag: Generate CFI type information for assembly functions Use SYM_TYPED_FUNC_START to generate __kcfi_typeid_ symbols for assembler functions which are called indirectly. All assembler functions contained in text_amode31.S are called indirectly and require such annotations. Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens Tested-by: Nathan Chancellor Signed-off-by: Vasily Gorbik --- arch/s390/kernel/text_amode31.S | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/arch/s390/kernel/text_amode31.S b/arch/s390/kernel/text_amode31.S index 26f2981aa09e..f007d892d0c5 100644 --- a/arch/s390/kernel/text_amode31.S +++ b/arch/s390/kernel/text_amode31.S @@ -5,6 +5,7 @@ * Copyright IBM Corp. 2019 */ +#include #include #include #include @@ -26,7 +27,7 @@ /* * int _diag14_amode31(unsigned long rx, unsigned long ry1, unsigned long subcode) */ -SYM_FUNC_START(_diag14_amode31) +SYM_TYPED_FUNC_START(_diag14_amode31) lgr %r1,%r2 lgr %r2,%r3 lgr %r3,%r4 @@ -46,7 +47,7 @@ SYM_FUNC_END(_diag14_amode31) /* * int _diag210_amode31(struct diag210 *addr) */ -SYM_FUNC_START(_diag210_amode31) +SYM_TYPED_FUNC_START(_diag210_amode31) lgr %r1,%r2 lhi %r2,-1 sam31 @@ -64,7 +65,7 @@ SYM_FUNC_END(_diag210_amode31) /* * int diag8c(struct diag8c *addr, struct ccw_dev_id *devno, size_t len) */ -SYM_FUNC_START(_diag8c_amode31) +SYM_TYPED_FUNC_START(_diag8c_amode31) llgf %r3,0(%r3) sam31 diag %r2,%r4,0x8c @@ -77,7 +78,7 @@ SYM_FUNC_END(_diag8c_amode31) /* * int _diag26c_amode31(void *req, void *resp, enum diag26c_sc subcode) */ -SYM_FUNC_START(_diag26c_amode31) +SYM_TYPED_FUNC_START(_diag26c_amode31) lghi %r5,-EOPNOTSUPP sam31 diag %r2,%r4,0x26c @@ -91,7 +92,7 @@ SYM_FUNC_END(_diag26c_amode31) /* * void _diag0c_amode31(unsigned long rx) */ -SYM_FUNC_START(_diag0c_amode31) +SYM_TYPED_FUNC_START(_diag0c_amode31) sam31 diag %r2,%r2,0x0c sam64 @@ -103,7 +104,7 @@ SYM_FUNC_END(_diag0c_amode31) * * Calls diag 308 subcode 1 and continues execution */ -SYM_FUNC_START(_diag308_reset_amode31) +SYM_TYPED_FUNC_START(_diag308_reset_amode31) larl %r4,ctlregs # Save control registers stctg %c0,%c15,0(%r4) lg %r2,0(%r4) # Disable lowcore protection From 227b4e108adcc4ea40ba218f5de6b2f3a9890665 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 27 Jul 2026 16:05:12 +0200 Subject: [PATCH 37/59] s390/bpf: Add kCFI support This is the s390 variant of commit 710618c760c0 ("arm64/cfi,bpf: Support kCFI + BPF on arm64"). Signed-off-by: Heiko Carstens Reviewed-by: Ilya Leoshkevich Tested-by: Nathan Chancellor Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/cfi.h | 7 +++++++ arch/s390/net/bpf_jit_comp.c | 28 ++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 arch/s390/include/asm/cfi.h diff --git a/arch/s390/include/asm/cfi.h b/arch/s390/include/asm/cfi.h new file mode 100644 index 000000000000..9af2c7cb70ca --- /dev/null +++ b/arch/s390/include/asm/cfi.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_S390_CFI_H +#define _ASM_S390_CFI_H + +#define __bpfcall + +#endif /* _ASM_S390_CFI_H */ diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c index 31749c0362ca..7f45c106444d 100644 --- a/arch/s390/net/bpf_jit_comp.c +++ b/arch/s390/net/bpf_jit_comp.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -356,6 +357,19 @@ static void emit6_pcrel_rilc(struct bpf_jit *jit, u32 op, u8 mask, s64 pcrel) } \ }) +static inline void emit_u32_data(const u32 data, struct bpf_jit *jit) +{ + if (jit->prg_buf) + *(u32 *)(jit->prg_buf + jit->prg) = data; + jit->prg += 4; +} + +static inline void emit_kcfi(u32 hash, struct bpf_jit *jit) +{ + if (IS_ENABLED(CONFIG_CFI)) + emit_u32_data(hash, jit); +} + /* * Return whether this is the first pass. The first pass is special, since we * don't know any sizes yet, and thus must be conservative. @@ -597,6 +611,8 @@ static void bpf_jit_prologue(struct bpf_jit *jit, struct bpf_prog *fp) { BUILD_BUG_ON(sizeof(struct prog_frame) != STACK_FRAME_OVERHEAD); + emit_kcfi(bpf_is_subprog(fp) ? cfi_bpf_subprog_hash : cfi_bpf_hash, jit); + /* No-op for hotpatching */ /* brcl 0,prologue_plt */ EMIT6_PCREL_RILC(0xc0040000, 0, jit->prologue_plt); @@ -616,7 +632,7 @@ static void bpf_jit_prologue(struct bpf_jit *jit, struct bpf_prog *fp) bpf_skip(jit, 6); } /* Tail calls have to skip above initialization */ - jit->tail_call_start = jit->prg; + jit->tail_call_start = jit->prg - cfi_get_offset(); if (fp->aux->exception_cb) { /* * Switch stack, the new address is in the 2nd parameter. @@ -2401,11 +2417,13 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr jit_data->ctx = jit; jit_data->pass = pass; } - fp->bpf_func = (void *) jit.prg_buf; + fp->bpf_func = (void *)jit.prg_buf + cfi_get_offset(); fp->jited = 1; - fp->jited_len = jit.size; + fp->jited_len = jit.size - cfi_get_offset(); if (!fp->is_func || extra_pass) { + for (int i = 0; i < fp->len; i++) + jit.addrs[i] -= cfi_get_offset(); bpf_prog_fill_jited_linfo(fp, jit.addrs + 1); free_addrs: kvfree(jit.addrs); @@ -2671,8 +2689,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, return -ENOTSUPP; /* Return to %r14 in the struct_ops case. */ - if (flags & BPF_TRAMP_F_INDIRECT) + if (flags & BPF_TRAMP_F_INDIRECT) { flags |= BPF_TRAMP_F_SKIP_FRAME; + emit_kcfi(cfi_get_func_hash(func_addr), jit); + } /* * Compute how many arguments we need to pass to BPF programs. From e54228ad8ffb847c99449999424294a9b724692e Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 27 Jul 2026 16:05:13 +0200 Subject: [PATCH 38/59] s390/Kconfig: Select ARCH_SUPPORTS_CFI With all prerequisites in place select ARCH_SUPPORTS_CFI. Note that this support is supposed to work with the generic kCFI support which is provided by clang. This comes with a couple of limitations: The generic kCFI implementation does not generate a .kcfi_traps section, nor is a special instruction used in case a checksum mismatch is detected. This means in case of checksum mismatch the kernel just crashes. It should be quite easy to tell by the surrounding code that a crash happened because of a checksum mismatch. If clang and/or gcc provide a .kcfi_traps section it will be possible to print proper CFI messages instead of just crashing the kernel (enable ARCH_USES_CFI_TRAPS). In addition this also means that CFI_PERMISSIVE does not work. Even if the option is selected the kernel will crash in case of checksum mismatch. However it seems to be acceptable to enable kCFI support to the kernel now even if it is not perfect. Later clang and gcc extensions are required to improve this. As of now a crash caused by a CFI failure looks like this: illegal operation: 0001 ilc:1 [#1]SMP Modules linked in: bpf_testmod(OE) CPU: 0 UID: 0 PID: 92 Comm: test_progs Tainted: G OE 7.2.0-rc4-00021-gc35ed7a1ca22-dirty #3 PREEMPTLAZY Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE Hardware name: IBM 3931 A01 703 (KVM/Linux) Krnl PSW : 0704e00180000000 00000166d4853a0a (bpf_task_work_callback+0x176/0x290) R:0 T:1 IO:1 EX:1 Key:0 M:1 W:0 P:0 AS:3 CC:2 PM:0 RI:0 EA:3 Krnl GPRS: 0400000069b02e96 000001665471856c 0000000084dc1000 000000008084da58 000000008084da60 000000005ff492bf 0000000000000000 00000000809de300 fffffffffff7ffff 00000000000a0337 00000000809e4d00 000000008437b100 00000000801bc288 00000000801bc280 00000166d48538fc 000000e6d502ba90 Krnl Code: 00000166d48539fa: e320c0400004 lg %r2,64(%r12) 00000166d4853a00: e340c0480004 lg %r4,72(%r12) *00000166d4853a06: a7640001 brc 6,00000166d4853a08 >00000166d4853a0a: 0de1 basr %r14,%r1 00000166d4853a0c: e300b6580095 llh %r0,1624(%r11) 00000166d4853a12: ec040009027e cij %r0,2,4,00000166d4853a24 00000166d4853a18: a70affff ahi %r0,-1 00000166d4853a1c: 4000b658 sth %r0,1624(%r11) Call Trace: [<00000166d4853a0a>] bpf_task_work_callback+0x176/0x290 ... The disassembly contains the brc instruction (branch into the instruction itself with two byte offset to execute an illegal instruction, and the following basr instruction used for an indirect branch. Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens Tested-by: Nathan Chancellor Signed-off-by: Vasily Gorbik --- arch/s390/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 2be7346c48da..6960b398ed06 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -147,6 +147,7 @@ config S390 select ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE select ARCH_STACKWALK select ARCH_SUPPORTS_ATOMIC_RMW + select ARCH_SUPPORTS_CFI select ARCH_SUPPORTS_DEBUG_PAGEALLOC select ARCH_SUPPORTS_HUGETLBFS select ARCH_SUPPORTS_INT128 if CC_HAS_INT128 && CC_IS_CLANG @@ -154,6 +155,7 @@ config S390 select ARCH_SUPPORTS_NUMA_BALANCING select ARCH_SUPPORTS_PAGE_TABLE_CHECK select ARCH_SUPPORTS_PER_VMA_LOCK + select ARCH_USES_CFI_GENERIC_LLVM_PASS if CC_IS_CLANG select ARCH_USE_BUILTIN_BSWAP select ARCH_USE_CMPXCHG_LOCKREF select ARCH_USE_SYM_ANNOTATIONS From e11733826f9779eb954abdcd2b60b273558f1a77 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 31 Jul 2026 18:50:01 +0200 Subject: [PATCH 39/59] s390/crypto: Replace cond_resched() with msleep(1) With [1] cond_resched() is always compiled away and becomes a no-op. The comments for all cond_resched() calls in crypto code however indicate that the current process should be scheduled away to avoid instant re-invocation of a callback. This is not what cond_resched() would do or did. Instead of just removing the cond_resched() calls, replace them with msleep() calls, as suggested by Holger Dengler. This forces the current task to be scheduled away (sleeps) like originally intended. Holger provided information why this intended [2]: " Let me give a bit of background here: The protected key can only get invalid, if the Linux instance (z/VM or KVM guest) is moved to another hypervisor on a different machine (aka life guest relocation). In such a case, the crypto accelerator card and the host has to exchange the "real key", which is wrapped by the host and handed back to the guest as the re-newed protected key. Unfortunately there is no asynchronous trigger on completion, you have to re-try (and maybe get another "in progress" return). And as if that weren't bad enough, if this key exchange between card and host is the first one, card and host has to instantiate a secure communication channel (including a key exchange for the transport layer). " [1] commit 7dadeaa6e851 ("sched: Further restrict the preemption modes") [2] https://lore.kernel.org/all/39570813-27b0-40f9-89c5-8e2dce05e2f0@linux.ibm.com/ [3] https://lore.kernel.org/all/20260731084027.GE776954@noisy.programming.kicks-ass.net/ [hca@linux.ibm.com: took Peter's patch [3] and provided commit message] Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Heiko Carstens Reviewed-by: Holger Dengler Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 44 ++++------------------------------- arch/s390/crypto/phmac_s390.c | 25 +++----------------- arch/s390/include/asm/pkey.h | 16 +++++++++++++ 3 files changed, 23 insertions(+), 62 deletions(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 8cfe6166c193..973436592318 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -548,16 +548,7 @@ static int ecb_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = ecb_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { - /* - * Protected key expired, conversion is in process. - * Trigger a re-schedule of this request by returning - * -ENOSPC ("hardware queue is full") to the crypto engine. - * To avoid immediately re-invocation of this callback, - * tell the scheduler to voluntarily give up the CPU here. - */ - cond_resched(); - pr_debug("rescheduling request\n"); - return -ENOSPC; + return pkey_handle_expired(); } else if (rc) { skcipher_walk_done(walk, rc); } @@ -814,16 +805,7 @@ static int cbc_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = cbc_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { - /* - * Protected key expired, conversion is in process. - * Trigger a re-schedule of this request by returning - * -ENOSPC ("hardware queue is full") to the crypto engine. - * To avoid immediately re-invocation of this callback, - * tell the scheduler to voluntarily give up the CPU here. - */ - cond_resched(); - pr_debug("rescheduling request\n"); - return -ENOSPC; + return pkey_handle_expired(); } else if (rc) { skcipher_walk_done(walk, rc); } @@ -1122,16 +1104,7 @@ static int ctr_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = ctr_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { - /* - * Protected key expired, conversion is in process. - * Trigger a re-schedule of this request by returning - * -ENOSPC ("hardware queue is full") to the crypto engine. - * To avoid immediately re-invocation of this callback, - * tell the scheduler to voluntarily give up the CPU here. - */ - cond_resched(); - pr_debug("rescheduling request\n"); - return -ENOSPC; + return pkey_handle_expired(); } else if (rc) { skcipher_walk_done(walk, rc); } @@ -1565,16 +1538,7 @@ static int xts_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = xts_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { - /* - * Protected key expired, conversion is in process. - * Trigger a re-schedule of this request by returning - * -ENOSPC ("hardware queue is full") to the crypto engine. - * To avoid immediately re-invocation of this callback, - * tell the scheduler to voluntarily give up the CPU here. - */ - cond_resched(); - pr_debug("rescheduling request\n"); - return -ENOSPC; + return pkey_handle_expired(); } else if (rc) { skcipher_walk_done(walk, rc); } diff --git a/arch/s390/crypto/phmac_s390.c b/arch/s390/crypto/phmac_s390.c index 03ca33ffe6cc..020a1beb2e22 100644 --- a/arch/s390/crypto/phmac_s390.c +++ b/arch/s390/crypto/phmac_s390.c @@ -887,16 +887,7 @@ static int phmac_do_one_request(struct crypto_engine *engine, void *areq) case OP_FINUP: rc = phmac_kmac_update(req, true); if (rc == -EKEYEXPIRED) { - /* - * Protected key expired, conversion is in process. - * Trigger a re-schedule of this request by returning - * -ENOSPC ("hardware queue full") to the crypto engine. - * To avoid immediately re-invocation of this callback, - * tell scheduler to voluntarily give up the CPU here. - */ - pr_debug("rescheduling request\n"); - cond_resched(); - return -ENOSPC; + return pkey_handle_expired(); } else if (rc) { hwh_advance(hwh, rc); goto out; @@ -907,18 +898,8 @@ static int phmac_do_one_request(struct crypto_engine *engine, void *areq) fallthrough; case OP_FINAL: rc = phmac_kmac_final(req, true); - if (rc == -EKEYEXPIRED) { - /* - * Protected key expired, conversion is in process. - * Trigger a re-schedule of this request by returning - * -ENOSPC ("hardware queue full") to the crypto engine. - * To avoid immediately re-invocation of this callback, - * tell scheduler to voluntarily give up the CPU here. - */ - pr_debug("rescheduling request\n"); - cond_resched(); - return -ENOSPC; - } + if (rc == -EKEYEXPIRED) + return pkey_handle_expired(); break; default: /* unknown/unsupported/unimplemented asynch op */ diff --git a/arch/s390/include/asm/pkey.h b/arch/s390/include/asm/pkey.h index 0af5ac4f646b..82e403322e89 100644 --- a/arch/s390/include/asm/pkey.h +++ b/arch/s390/include/asm/pkey.h @@ -13,6 +13,7 @@ #include #include +#include #include /* @@ -44,4 +45,19 @@ int pkey_key2protkey(const u8 *key, u32 keylen, */ #define PKEY_XFLAG_NOCLEARKEY 0x0002 +static inline int pkey_handle_expired(void) +{ + /* + * Protected key expired due to relocation to another host. The long + * running re-wrap has no asynchronous completion notification, so + * polling is required. Trigger a re-schedule of this request by + * returning -ENOSPC ("hardware queue full") to the crypto engine. + * To avoid immediately re-invocation of this callback, + * tell the scheduler to voluntarily give up the CPU here. + */ + msleep(1); + pr_debug("rescheduling request\n"); + return -ENOSPC; +} + #endif /* _KAPI_PKEY_H */ From 97d86fc8479eb47b95cc01ddec969c693da287fc Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 31 Jul 2026 18:50:02 +0200 Subject: [PATCH 40/59] KVM: s390: Remove cond_resched() calls Since [1] cond_resched() is a no-op on s390. Remove all calls. This also entirely removes uv_call_sched() and replaces all call sites with uv_call(), since both functions are identical after the removal of cond_resched(). [1] commit 7dadeaa6e851 ("sched: Further restrict the preemption modes") Reviewed-by: Claudio Imbrenda Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/uv.h | 16 ++-------------- arch/s390/kernel/uv.c | 2 +- arch/s390/kvm/gaccess.c | 8 ++------ arch/s390/kvm/gmap.c | 4 ---- arch/s390/kvm/kvm-s390.c | 1 - arch/s390/kvm/pv.c | 11 +++++------ arch/s390/kvm/vsie.c | 1 - arch/s390/mm/gmap_helpers.c | 1 - drivers/s390/char/uvdevice.c | 6 +++--- 9 files changed, 13 insertions(+), 37 deletions(-) diff --git a/arch/s390/include/asm/uv.h b/arch/s390/include/asm/uv.h index 153fed63adda..508075e9e8a9 100644 --- a/arch/s390/include/asm/uv.h +++ b/arch/s390/include/asm/uv.h @@ -454,18 +454,6 @@ static inline int uv_call(unsigned long r1, unsigned long r2) return cc; } -/* Low level uv_call that avoids stalls for long running busy conditions */ -static inline int uv_call_sched(unsigned long r1, unsigned long r2) -{ - int cc; - - do { - cc = __uv_call(r1, r2); - cond_resched(); - } while (cc > 1); - return cc; -} - /* * special variant of uv_call that only transports the cpu or guest * handle and the command, like destroy or verify. @@ -480,7 +468,7 @@ static inline int uv_cmd_nodata(u64 handle, u16 cmd, u16 *rc, u16 *rrc) int cc; WARN(!handle, "No handle provided to Ultravisor call cmd %x\n", cmd); - cc = uv_call_sched(0, (u64)&uvcb); + cc = uv_call(0, (u64)&uvcb); *rc = uvcb.header.rc; *rrc = uvcb.header.rrc; return cc ? -EINVAL : 0; @@ -518,7 +506,7 @@ static inline int uv_list_secrets(struct uv_secret_list *buf, u16 start_idx, .start_idx = start_idx, .list_addr = (u64)buf, }; - int cc = uv_call_sched(0, (u64)&uvcb); + int cc = uv_call(0, (u64)&uvcb); if (rc) *rc = uvcb.header.rc; diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c index d1cdc5ae5e27..dc14ebc0105b 100644 --- a/arch/s390/kernel/uv.c +++ b/arch/s390/kernel/uv.c @@ -896,7 +896,7 @@ int uv_retrieve_secret(u16 secret_idx, u8 *buf, size_t buf_size) .buf_size = buf_size, }; - uv_call_sched(0, (u64)&uvcb); + uv_call(0, (u64)&uvcb); switch (uvcb.header.rc) { case UVC_RC_EXECUTED: diff --git a/arch/s390/kvm/gaccess.c b/arch/s390/kvm/gaccess.c index 36102b2727fb..0d389a302a70 100644 --- a/arch/s390/kvm/gaccess.c +++ b/arch/s390/kvm/gaccess.c @@ -151,10 +151,8 @@ static void ipte_lock_simple(struct kvm *kvm) ic = &kvm->arch.sca->ipte_control; old = READ_ONCE(*ic); do { - if (old.k) { - cond_resched(); + if (old.k) goto retry; - } new = old; new.k = 1; } while (!try_cmpxchg(&ic->val, &old.val, new.val)); @@ -189,10 +187,8 @@ static void ipte_lock_siif(struct kvm *kvm) ic = &kvm->arch.sca->ipte_control; old = READ_ONCE(*ic); do { - if (old.kg) { - cond_resched(); + if (old.kg) goto retry; - } new = old; new.k = 1; new.kh++; diff --git a/arch/s390/kvm/gmap.c b/arch/s390/kvm/gmap.c index 298fbaecec28..0ae0635a4f4c 100644 --- a/arch/s390/kvm/gmap.c +++ b/arch/s390/kvm/gmap.c @@ -941,7 +941,6 @@ void gmap_split_huge_pages(struct gmap *gmap) scoped_guard(read_lock, &gmap->kvm->mmu_lock) start = _dat_walk_gfn_range(start, asce_end(gmap->asce), gmap->asce, &ops, DAT_WALK_IGN_HOLES, gmap); - cond_resched(); } while (start); } @@ -963,7 +962,6 @@ static int _gmap_enable_skeys(struct gmap *gmap) do { scoped_guard(write_lock, &gmap->kvm->mmu_lock) start = dat_reset_skeys(gmap->asce, start); - cond_resched(); } while (start); return 0; } @@ -1019,7 +1017,6 @@ int gmap_pv_destroy_range(struct gmap *gmap, gfn_t start, gfn_t end, bool interr DAT_WALK_IGN_HOLES, NULL); if (interruptible && fatal_signal_pending(current)) return -EINTR; - cond_resched(); } while (start && start < end); return 0; } @@ -1138,7 +1135,6 @@ void _gmap_set_cmma_all(struct gmap *gmap, bool dirty) gfn = _dat_walk_gfn_range(gfn, asce_end(gmap->asce), gmap->asce, &ops, DAT_WALK_IGN_HOLES, &gmap->kvm->arch.cmma_dirty_pages); - cond_resched(); } while (gfn); } diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 23c817595e28..17ebb02acea2 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -1027,7 +1027,6 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att do { scoped_guard(read_lock, &kvm->mmu_lock) start_gfn = dat_reset_cmma(kvm->arch.gmap->asce, start_gfn); - cond_resched(); } while (start_gfn); ret = 0; break; diff --git a/arch/s390/kvm/pv.c b/arch/s390/kvm/pv.c index dc5ac29b4c31..3992a9f9f118 100644 --- a/arch/s390/kvm/pv.c +++ b/arch/s390/kvm/pv.c @@ -429,7 +429,7 @@ static int kvm_s390_pv_deinit_vm_fast(struct kvm *kvm, u16 *rc, u16 *rrc) }; int cc; - cc = uv_call_sched(0, (u64)&uvcb); + cc = uv_call(0, (u64)&uvcb); if (rc) *rc = uvcb.header.rc; if (rrc) @@ -746,7 +746,7 @@ int kvm_s390_pv_init_vm(struct kvm *kvm, u16 *rc, u16 *rrc) } gmap_split_huge_pages(kvm->arch.gmap); - cc = uv_call_sched(0, (u64)&uvcb); + cc = uv_call(0, (u64)&uvcb); *rc = uvcb.header.rc; *rrc = uvcb.header.rrc; KVM_UV_EVENT(kvm, 3, "PROTVIRT CREATE VM: handle %llx len %llx rc %x rrc %x flags %04x", @@ -832,7 +832,6 @@ int kvm_s390_pv_unpack(struct kvm *kvm, unsigned long addr, unsigned long size, while (offset < size) { ret = unpack_one(kvm, addr, tweak, offset, rc, rrc); if (ret == -EAGAIN) { - cond_resched(); if (fatal_signal_pending(current)) break; continue; @@ -875,7 +874,7 @@ int kvm_s390_pv_dump_cpu(struct kvm_vcpu *vcpu, void *buff, u16 *rc, u16 *rrc) }; int cc; - cc = uv_call_sched(0, (u64)&uvcb); + cc = uv_call(0, (u64)&uvcb); *rc = uvcb.header.rc; *rrc = uvcb.header.rrc; return cc; @@ -959,7 +958,7 @@ int kvm_s390_pv_dump_stor_state(struct kvm *kvm, void __user *buff_user, /* We will loop until the user buffer is filled or an error occurs */ do { /* Get 1MB worth of guest storage state data */ - cc = uv_call_sched(0, (u64)&uvcb); + cc = uv_call(0, (u64)&uvcb); /* All or nothing */ if (cc) { @@ -1037,7 +1036,7 @@ int kvm_s390_pv_dump_complete(struct kvm *kvm, void __user *buff_user, return -ENOMEM; complete.dump_area_origin = (u64)compl_data; - ret = uv_call_sched(0, (u64)&complete); + ret = uv_call(0, (u64)&complete); *rc = complete.header.rc; *rrc = complete.header.rrc; KVM_UV_EVENT(kvm, 3, "PROTVIRT DUMP COMPLETE: rc %x rrc %x", diff --git a/arch/s390/kvm/vsie.c b/arch/s390/kvm/vsie.c index eea24562e7db..6dcd3b110c17 100644 --- a/arch/s390/kvm/vsie.c +++ b/arch/s390/kvm/vsie.c @@ -1434,7 +1434,6 @@ static int vsie_run(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page) } if (sg) sg = gmap_put(sg); - cond_resched(); } if (sg) sg = gmap_put(sg); diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c index 4bf7c9012feb..d564cdefb90f 100644 --- a/arch/s390/mm/gmap_helpers.c +++ b/arch/s390/mm/gmap_helpers.c @@ -272,7 +272,6 @@ static int __gmap_helper_unshare_zeropages(struct mm_struct *mm) * truncation. In that case, the shared zeropage would be gone * and we can simply retry and make progress. */ - cond_resched(); goto retry; } diff --git a/drivers/s390/char/uvdevice.c b/drivers/s390/char/uvdevice.c index e6a264c996ce..4d274a2ee84d 100644 --- a/drivers/s390/char/uvdevice.c +++ b/drivers/s390/char/uvdevice.c @@ -224,7 +224,7 @@ static int uvio_attestation(struct uvio_ioctl_cb *uv_ioctl) if (ret) goto out; - uv_call_sched(0, (u64)uvcb_attest); + uv_call(0, (u64)uvcb_attest); uv_ioctl->uv_rc = uvcb_attest->header.rc; uv_ioctl->uv_rrc = uvcb_attest->header.rrc; @@ -291,7 +291,7 @@ static int uvio_add_secret(struct uvio_ioctl_cb *uv_ioctl) ret = 0; uvcb.addr = (u64)asrcb; - uv_call_sched(0, (u64)&uvcb); + uv_call(0, (u64)&uvcb); uv_ioctl->uv_rc = uvcb.header.rc; uv_ioctl->uv_rrc = uvcb.header.rrc; @@ -457,7 +457,7 @@ static int uvio_retr_secret(struct uvio_ioctl_cb *uv_ioctl) uvcb.buf_addr = (u64)buf; uvcb.buf_size = buf_len; - uv_call_sched(0, (u64)&uvcb); + uv_call(0, (u64)&uvcb); if (copy_to_user((__user void *)uv_ioctl->argument_addr, buf, buf_len)) goto err; From de8ca0119a3c02b372be8425e29a7a9d947f749c Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 31 Jul 2026 18:50:03 +0200 Subject: [PATCH 41/59] s390: Remove cond_resched() calls Since [1] cond_resched() is a no-op on s390. Remove all calls. [1] commit 7dadeaa6e851 ("sched: Further restrict the preemption modes") Reviewed-by: Vasily Gorbik Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/perf_cpum_cf.c | 1 - arch/s390/mm/cmm.c | 2 -- arch/s390/mm/pageattr.c | 5 ----- arch/s390/mm/pgalloc.c | 1 - arch/s390/pci/pci.c | 4 +--- 5 files changed, 1 insertion(+), 12 deletions(-) diff --git a/arch/s390/kernel/perf_cpum_cf.c b/arch/s390/kernel/perf_cpum_cf.c index 7aa655664ecc..2cecc86144e1 100644 --- a/arch/s390/kernel/perf_cpum_cf.c +++ b/arch/s390/kernel/perf_cpum_cf.c @@ -1493,7 +1493,6 @@ static int cfset_all_copy(unsigned long arg, cpumask_t *mask) goto out; } uptr += sizeof(struct s390_ctrset_cpudata) + cpuhw->used; - cond_resched(); } cpus = cpumask_weight(mask); if (put_user(cpus, &ctrset_read->no_cpus)) diff --git a/arch/s390/mm/cmm.c b/arch/s390/mm/cmm.c index eb7ef63fab1e..d342b68ee9be 100644 --- a/arch/s390/mm/cmm.c +++ b/arch/s390/mm/cmm.c @@ -95,7 +95,6 @@ static long cmm_alloc_pages(long nr, long *counter, (*counter)++; spin_unlock(&cmm_lock); nr--; - cond_resched(); } return nr; } @@ -134,7 +133,6 @@ static long cmm_free_pages(long nr, long *counter, struct cmm_page_array **list) inc = __cmm_free_pages(inc, counter, list); if (inc) break; - cond_resched(); } return nr + inc; } diff --git a/arch/s390/mm/pageattr.c b/arch/s390/mm/pageattr.c index e6f788696dd1..1e202e3d08e7 100644 --- a/arch/s390/mm/pageattr.c +++ b/arch/s390/mm/pageattr.c @@ -105,7 +105,6 @@ static int walk_pte_level(pmd_t *pmdp, unsigned long addr, unsigned long end, pgt_set((unsigned long *)ptep, pte_val(new), addr, CRDTE_DTT_PAGE); ptep++; addr += PAGE_SIZE; - cond_resched(); } while (addr < end); return 0; } @@ -194,7 +193,6 @@ static int walk_pmd_level(pud_t *pudp, unsigned long addr, unsigned long end, } pmdp++; addr = next; - cond_resched(); } while (addr < end); return rc; } @@ -281,7 +279,6 @@ static int walk_pud_level(p4d_t *p4d, unsigned long addr, unsigned long end, } pudp++; addr = next; - cond_resched(); } while (addr < end && !rc); return rc; } @@ -301,7 +298,6 @@ static int walk_p4d_level(pgd_t *pgd, unsigned long addr, unsigned long end, rc = walk_pud_level(p4dp, addr, next, flags); p4dp++; addr = next; - cond_resched(); } while (addr < end && !rc); return rc; } @@ -323,7 +319,6 @@ static int change_page_attr(unsigned long addr, unsigned long end, rc = walk_p4d_level(pgdp, addr, next, flags); if (rc) break; - cond_resched(); } while (pgdp++, addr = next, addr < end && !rc); return rc; } diff --git a/arch/s390/mm/pgalloc.c b/arch/s390/mm/pgalloc.c index 7ac44543e051..9610770fcf6d 100644 --- a/arch/s390/mm/pgalloc.c +++ b/arch/s390/mm/pgalloc.c @@ -268,7 +268,6 @@ static int base_segment_walk(unsigned long *origin, unsigned long addr, return rc; if (!alloc) base_pgt_free(table); - cond_resched(); } while (ste++, addr = next, addr < end); return 0; } diff --git a/arch/s390/pci/pci.c b/arch/s390/pci/pci.c index 39bd2adfc240..2dec238f3f12 100644 --- a/arch/s390/pci/pci.c +++ b/arch/s390/pci/pci.c @@ -1173,10 +1173,8 @@ int zpci_scan_devices(void) return rc; zpci_add_devices(&scan_list); - zpci_bus_for_each(zbus) { + zpci_bus_for_each(zbus) zpci_bus_scan_bus(zbus); - cond_resched(); - } return 0; } From 5ddf6f12dc8d0535382f854012b2bf83448f4021 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 31 Jul 2026 18:50:04 +0200 Subject: [PATCH 42/59] s390/cio: Remove cond_resched() calls Since [1] cond_resched() is a no-op on s390. Remove all calls. [1] commit 7dadeaa6e851 ("sched: Further restrict the preemption modes") Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- drivers/s390/cio/css.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c index e5a0ec6b4e3e..54b8b8639694 100644 --- a/drivers/s390/cio/css.c +++ b/drivers/s390/cio/css.c @@ -618,11 +618,6 @@ static int slow_eval_known_fn(struct subchannel *sch, void *data) rc = css_evaluate_known_subchannel(sch, 1); if (rc == -EAGAIN) css_schedule_eval(sch->schid); - /* - * The loop might take long time for platforms with lots of - * known devices. Allow scheduling here. - */ - cond_resched(); } return 0; } @@ -654,9 +649,6 @@ static int slow_eval_unknown_fn(struct subchannel_id schid, void *data) default: rc = 0; } - /* Allow scheduling here since the containing loop might - * take a while. */ - cond_resched(); } return rc; } From 0de29a313ccc84b8d7ddd906aeda53b510e17cb2 Mon Sep 17 00:00:00 2001 From: Finn Callies Date: Wed, 5 Aug 2026 11:29:28 +0200 Subject: [PATCH 43/59] s390/ap: Fix MAPML computation When the TAPQ subfunction of PQAP returns a value between 0 and 3 in the ml field, the Principles of Operation states that the Maximum AP Message Length (MAPML) must be 12 KB. The previous code incorrectly calculated smaller sizes for ml values 1-2: - ml=1: 4 KB instead of 12 KB - ml=2: 8 KB instead of 12 KB While no current hardware reports ml=1 or ml=2, this fix ensures correct behavior for specification compliance and future-proofing. The fix ensures all ml values 0-3 correctly use the 12 KB default, with only values 4+ using the formula ml * 4096. Reviewed-by: Harald Freudenberger Signed-off-by: Finn Callies Acked-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- drivers/s390/crypto/ap_card.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/s390/crypto/ap_card.c b/drivers/s390/crypto/ap_card.c index e827fbeb76fb..c86397f4ddcd 100644 --- a/drivers/s390/crypto/ap_card.c +++ b/drivers/s390/crypto/ap_card.c @@ -241,7 +241,7 @@ struct ap_card *ap_card_create(int id, struct ap_tapq_hwinfo hwinfo, ac->ap_dev.device_type = comp_type; ac->hwinfo = hwinfo; ac->id = id; - ac->maxmsgsize = hwinfo.ml > 0 ? + ac->maxmsgsize = hwinfo.ml > 3 ? hwinfo.ml * AP_TAPQ_ML_FIELD_CHUNK_SIZE : AP_DEFAULT_MAX_MSG_SIZE; return ac; From 707b802e5c910708ebf52ded0e41db1a2de5a601 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 6 Aug 2026 17:53:02 +0200 Subject: [PATCH 44/59] s390/con3215: Add __must_hold() attribute to raw3215_make_room() Add __must_hold() attribute to raw3215_make_room() in order to let clang's context analysis know that "get_ccwdev_lock(raw->cdev)" must be held on function entry. This can also be easily verified when inspecting the function. Without this annotation this leads to a valid warning when context analysis is enabled: drivers/s390/char/con3215.c:485:9: warning: expecting spinlock 'raw->cdev->ccwlock' to be held at start of each loop [-Wthread-safety-analysis] 485 | while (RAW3215_BUFFER_SIZE - raw->count < length) { Reviewed-by: Sven Schnelle Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- drivers/s390/char/con3215.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/s390/char/con3215.c b/drivers/s390/char/con3215.c index 46265f4fbfd8..57fefa8986d6 100644 --- a/drivers/s390/char/con3215.c +++ b/drivers/s390/char/con3215.c @@ -481,6 +481,7 @@ static unsigned int raw3215_drop(struct raw3215_info *raw) */ static unsigned int raw3215_make_room(struct raw3215_info *raw, unsigned int length, bool drop) + __must_hold(get_ccwdev_lock(raw->cdev)) { while (RAW3215_BUFFER_SIZE - raw->count < length) { if (drop) From c79e1baef93cff38a38d37092b0fc45456f45985 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 6 Aug 2026 17:53:03 +0200 Subject: [PATCH 45/59] s390/char: Enable CONTEXT_ANALYSIS All character drivers pass clang's compile time context analysis. Therefore enable CONTEXT_ANALYSIS. Reviewed-by: Sven Schnelle Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- drivers/s390/char/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/s390/char/Makefile b/drivers/s390/char/Makefile index 126a87c3c6f8..0ad960d000a2 100644 --- a/drivers/s390/char/Makefile +++ b/drivers/s390/char/Makefile @@ -3,6 +3,8 @@ # S/390 character devices # +CONTEXT_ANALYSIS := y + ifdef CONFIG_FUNCTION_TRACER # Do not trace early setup code CFLAGS_REMOVE_sclp_early_core.o = $(CC_FLAGS_FTRACE) From cc459ed00ae1b50b5eeb78b96347585e19fe4711 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Wed, 5 Aug 2026 15:36:25 +0200 Subject: [PATCH 46/59] s390/pci: Rework __zpci_event_error() to remove conditional locking Clang's compiler based static context analysis does not work with locks that are conditionally taken like in __zpci_event_error(): arch/s390/pci/pci_event.c:320:2: warning: mutex 'get_zdev_by_fid(ccdf->fid).state_lock' is not held on every path through here [-Wthread-safety-analysis] Given that code which takes locks conditionally can be considered suboptimal rework __zpci_event_error() to get rid of this. Reviewed-by: Niklas Schnelle Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/pci/pci_event.c | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index 839bd91c056e..bead4ed5d4ab 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -288,6 +288,12 @@ static void zpci_event_io_failure(struct pci_dev *pdev, pci_channel_state_t es) pci_dev_unlock(pdev); } +static void __zpci_event_print_error(struct pci_dev *pdev, struct zpci_ccdf_err *ccdf) +{ + pr_err("%s: Event 0x%x reports an error for PCI function 0x%x\n", + pdev ? pci_name(pdev) : "n/a", ccdf->pec, ccdf->fid); +} + static void __zpci_event_error(struct zpci_ccdf_err *ccdf) { struct zpci_dev *zdev = get_zdev_by_fid(ccdf->fid); @@ -301,24 +307,24 @@ static void __zpci_event_error(struct zpci_ccdf_err *ccdf) zpci_err("error CCDF:\n"); zpci_err_hex(ccdf, sizeof(*ccdf)); - if (zdev) { - mutex_lock(&zdev->state_lock); - rc = clp_refresh_fh(zdev->fid, &fh); - if (rc) - goto no_pdev; - if (!fh || ccdf->fh != fh) { - /* Ignore events with stale handles */ - zpci_dbg(3, "err fid:%x, fh:%x (stale %x)\n", - ccdf->fid, fh, ccdf->fh); - goto no_pdev; - } - zpci_update_fh(zdev, ccdf->fh); - if (zdev->zbus->bus) - pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn); - } + if (!zdev) + return __zpci_event_print_error(NULL, ccdf); - pr_err("%s: Event 0x%x reports an error for PCI function 0x%x\n", - pdev ? pci_name(pdev) : "n/a", ccdf->pec, ccdf->fid); + mutex_lock(&zdev->state_lock); + rc = clp_refresh_fh(zdev->fid, &fh); + if (rc) + goto no_pdev; + if (!fh || ccdf->fh != fh) { + /* Ignore events with stale handles */ + zpci_dbg(3, "err fid:%x, fh:%x (stale %x)\n", + ccdf->fid, fh, ccdf->fh); + goto no_pdev; + } + zpci_update_fh(zdev, ccdf->fh); + if (zdev->zbus->bus) + pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn); + + __zpci_event_print_error(pdev, ccdf); if (!pdev) goto no_pdev; @@ -340,8 +346,7 @@ static void __zpci_event_error(struct zpci_ccdf_err *ccdf) } pci_dev_put(pdev); no_pdev: - if (zdev) - mutex_unlock(&zdev->state_lock); + mutex_unlock(&zdev->state_lock); zpci_zdev_put(zdev); } From 9985f26717adbad2ab47143b7afd385ed70d4d4f Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Wed, 5 Aug 2026 15:36:26 +0200 Subject: [PATCH 47/59] s390/pci: Rework __zpci_event_availability() to remove conditional locking Clang's compiler based static context analysis does not work with locks that are conditionally taken like in __zpci_event_availability(): arch/s390/pci/pci_event.c:402:10: warning: mutex 'get_zdev_by_fid(ccdf->fid).state_lock' is not held on every path through here [-Wthread-safety-analysis] Given that code which takes locks conditionally can be considered suboptimal rework __zpci_event_availability() to get rid of this. Reviewed-by: Niklas Schnelle Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/pci/pci_event.c | 162 ++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 77 deletions(-) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index bead4ed5d4ab..0a9eecb62bd1 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -387,98 +387,106 @@ static void zpci_event_reappear(struct zpci_dev *zdev) zpci_dbg(1, "rea fid:%x, fh:%x\n", zdev->fid, zdev->fh); } -static void __zpci_event_availability(struct zpci_ccdf_avail *ccdf) +static bool zpci_event_avail_any_device(struct zpci_ccdf_avail *ccdf) { - struct zpci_dev *zdev = get_zdev_by_fid(ccdf->fid); - bool existing_zdev = !!zdev; - enum zpci_state state; + /* 0x0306 - No handle or fid stored */ + if (ccdf->pec != 0x0306) + return false; + /* 0x308 or 0x302 for multiple devices */ + zpci_remove_reserved_devices(); + zpci_scan_devices(); + return true; +} - zpci_dbg(3, "avl fid:%x, fh:%x, pec:%x\n", - ccdf->fid, ccdf->fh, ccdf->pec); - - if (existing_zdev) - mutex_lock(&zdev->state_lock); +static void zpci_event_avail_new_device(struct zpci_ccdf_avail *ccdf) +{ + struct zpci_dev *zdev; switch (ccdf->pec) { case 0x0301: /* Reserved|Standby -> Configured */ - if (!zdev) { - zdev = zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_CONFIGURED); - if (IS_ERR(zdev)) - break; - if (zpci_add_device(zdev)) { - kfree(zdev); - break; - } - } else { - if (zdev->state == ZPCI_FN_STATE_RESERVED) - zpci_event_reappear(zdev); - /* the configuration request may be stale */ - else if (zdev->state != ZPCI_FN_STATE_STANDBY) - break; - zdev->state = ZPCI_FN_STATE_CONFIGURED; + zdev = zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_CONFIGURED); + if (IS_ERR(zdev)) + break; + if (zpci_add_device(zdev)) { + kfree(zdev); + break; } zpci_scan_configured_device(zdev, ccdf->fh); break; case 0x0302: /* Reserved -> Standby */ - if (!zdev) { - zdev = zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_STANDBY); - if (IS_ERR(zdev)) - break; - if (zpci_add_device(zdev)) { - kfree(zdev); - break; - } - } else { - if (zdev->state == ZPCI_FN_STATE_RESERVED) - zpci_event_reappear(zdev); - zpci_update_fh(zdev, ccdf->fh); - } - break; - case 0x0303: /* Deconfiguration requested */ - if (zdev) { - /* The event may have been queued before we configured - * the device. - */ - if (zdev->state != ZPCI_FN_STATE_CONFIGURED) - break; - zpci_update_fh(zdev, ccdf->fh); - zpci_deconfigure_device(zdev); - } - break; - case 0x0304: /* Configured -> Standby|Reserved */ - if (zdev) { - /* The event may have been queued before we configured - * the device.: - */ - if (zdev->state == ZPCI_FN_STATE_CONFIGURED) - zpci_event_hard_deconfigured(zdev, ccdf->fh); - /* The 0x0304 event may immediately reserve the device */ - if (!clp_get_state(zdev->fid, &state) && - state == ZPCI_FN_STATE_RESERVED) { - zpci_device_reserved(zdev); - } - } - break; - case 0x0306: /* 0x308 or 0x302 for multiple devices */ - zpci_remove_reserved_devices(); - zpci_scan_devices(); - break; - case 0x0308: /* Standby -> Reserved */ - if (!zdev) + zdev = zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_STANDBY); + if (IS_ERR(zdev)) break; - zpci_device_reserved(zdev); - break; - default: + if (zpci_add_device(zdev)) { + kfree(zdev); + break; + } break; } - if (existing_zdev) { - mutex_unlock(&zdev->state_lock); - zpci_zdev_put(zdev); +} + +static void zpci_event_avail_existing_device(struct zpci_dev *zdev, struct zpci_ccdf_avail *ccdf) +{ + enum zpci_state state; + + switch (ccdf->pec) { + case 0x0301: /* Reserved|Standby -> Configured */ + if (zdev->state == ZPCI_FN_STATE_RESERVED) + zpci_event_reappear(zdev); + /* the configuration request may be stale */ + else if (zdev->state != ZPCI_FN_STATE_STANDBY) + break; + zdev->state = ZPCI_FN_STATE_CONFIGURED; + zpci_scan_configured_device(zdev, ccdf->fh); + break; + case 0x0302: /* Reserved -> Standby */ + if (zdev->state == ZPCI_FN_STATE_RESERVED) + zpci_event_reappear(zdev); + zpci_update_fh(zdev, ccdf->fh); + break; + case 0x0303: /* Deconfiguration requested */ + /* The event may have been queued before we configured + * the device. + */ + if (zdev->state != ZPCI_FN_STATE_CONFIGURED) + break; + zpci_update_fh(zdev, ccdf->fh); + zpci_deconfigure_device(zdev); + break; + case 0x0304: /* Configured -> Standby|Reserved */ + /* The event may have been queued before we configured + * the device.: + */ + if (zdev->state == ZPCI_FN_STATE_CONFIGURED) + zpci_event_hard_deconfigured(zdev, ccdf->fh); + /* The 0x0304 event may immediately reserve the device */ + if (!clp_get_state(zdev->fid, &state) && + state == ZPCI_FN_STATE_RESERVED) { + zpci_device_reserved(zdev); + } + break; + case 0x0308: /* Standby -> Reserved */ + zpci_device_reserved(zdev); + break; } } void zpci_event_availability(void *data) { - if (zpci_is_enabled()) - __zpci_event_availability(data); + struct zpci_ccdf_avail *ccdf = data; + struct zpci_dev *zdev; + + if (!zpci_is_enabled()) + return; + zpci_dbg(3, "avl fid:%x, fh:%x, pec:%x\n", + ccdf->fid, ccdf->fh, ccdf->pec); + if (zpci_event_avail_any_device(ccdf)) + return; + zdev = get_zdev_by_fid(ccdf->fid); + if (!zdev) + return zpci_event_avail_new_device(ccdf); + mutex_lock(&zdev->state_lock); + zpci_event_avail_existing_device(zdev, ccdf); + mutex_unlock(&zdev->state_lock); + zpci_zdev_put(zdev); } From b99a102ede6a5f1ceb000f7d10ef7f8ef5d71f2a Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Wed, 5 Aug 2026 15:36:27 +0200 Subject: [PATCH 48/59] s390/pci: Enable CONTEXT_ANALYSIS Enable CONTEXT_ANALYSIS since s390's pci code compiles now without warnings. Reviewed-by: Niklas Schnelle Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/pci/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/pci/Makefile b/arch/s390/pci/Makefile index 1810e0944a4e..b8be1316491b 100644 --- a/arch/s390/pci/Makefile +++ b/arch/s390/pci/Makefile @@ -3,6 +3,8 @@ # Makefile for the s390 PCI subsystem. # +CONTEXT_ANALYSIS := y + obj-$(CONFIG_PCI) += pci.o pci_irq.o pci_clp.o \ pci_event.o pci_debug.o pci_insn.o pci_mmio.o \ pci_bus.o pci_kvm_hook.o pci_report.o pci_fixup.o From aa8db8e37ea036cff6f3c0ae00870f3ac5c13d1a Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 6 Aug 2026 17:38:47 +0200 Subject: [PATCH 49/59] s390/vfio_ccw: Add __must_hold() attribute to vfio_ccw_sch_quiesce() Add __must_hold() attribute to vfio_ccw_sch_quiesce() in order to let clang's context analysis know that sch->lock must be held on function entry. This can also be easily verified when inspecting the function. Without this annotation this leads to a valid warning when context analysis is enabled: drivers/s390/cio/vfio_ccw_drv.c:55:9: warning: expecting spinlock 'sch->lock' to be held at start of each loop [-Wthread-safety-analysis] 55 | ret = cio_cancel_halt_clear(sch, &iretry); | ^ Signed-off-by: Heiko Carstens Acked-by: Christian Borntraeger Reviewed-by: Matthew Rosato Signed-off-by: Christian Borntraeger Signed-off-by: Vasily Gorbik --- drivers/s390/cio/vfio_ccw_drv.c | 1 + drivers/s390/cio/vfio_ccw_private.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index 1a095085bc72..ef4c69d70afb 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -35,6 +35,7 @@ debug_info_t *vfio_ccw_debug_trace_id; * Helpers */ int vfio_ccw_sch_quiesce(struct subchannel *sch) + __must_hold(&sch->lock) { struct vfio_ccw_parent *parent = dev_get_drvdata(&sch->dev); struct vfio_ccw_private *private = dev_get_drvdata(&parent->dev); diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index 0501d4bbcdbd..8db29519dbfd 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -127,7 +127,8 @@ struct vfio_ccw_private { struct work_struct crw_work; } __aligned(8); -int vfio_ccw_sch_quiesce(struct subchannel *sch); +int vfio_ccw_sch_quiesce(struct subchannel *sch) + __must_hold(&sch->lock); void vfio_ccw_sch_io_todo(struct work_struct *work); void vfio_ccw_crw_todo(struct work_struct *work); From 6e1eb66b0a637fa8c83e8843fa2980320ad49560 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 6 Aug 2026 17:38:48 +0200 Subject: [PATCH 50/59] s390/cio: Enable CONTEXT_ANALYSIS All cio code passes clang's compile time context analysis. Therefore enable CONTEXT_ANALYSIS. Signed-off-by: Heiko Carstens Acked-by: Christian Borntraeger Signed-off-by: Christian Borntraeger Signed-off-by: Vasily Gorbik --- drivers/s390/cio/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/s390/cio/Makefile b/drivers/s390/cio/Makefile index 3bd1c245183f..611a39103df9 100644 --- a/drivers/s390/cio/Makefile +++ b/drivers/s390/cio/Makefile @@ -3,6 +3,8 @@ # Makefile for the S/390 common i/o drivers # +CONTEXT_ANALYSIS := y + # The following is required for define_trace.h to find ./trace.h CFLAGS_trace.o := -I$(src) CFLAGS_vfio_ccw_trace.o := -I$(src) From f37b4f254fde3fff011051cc95d44f0e02862a97 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 6 Aug 2026 17:38:49 +0200 Subject: [PATCH 51/59] s390/virtio: Enable CONTEXT_ANALYSIS All virtio code passes clang's compile time context analysis. Therefore enable CONTEXT_ANALYSIS. Signed-off-by: Heiko Carstens Acked-by: Matthew Rosato Acked-by: Christian Borntraeger Signed-off-by: Christian Borntraeger Signed-off-by: Vasily Gorbik --- drivers/s390/virtio/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/s390/virtio/Makefile b/drivers/s390/virtio/Makefile index 2dc4d9aab634..cab140fe0b29 100644 --- a/drivers/s390/virtio/Makefile +++ b/drivers/s390/virtio/Makefile @@ -3,4 +3,6 @@ # # Copyright IBM Corp. 2008 +CONTEXT_ANALYSIS := y + obj-$(CONFIG_S390_GUEST) += virtio_ccw.o From 3f7c9f9c36b47a2a5cdbc56597eddf32662ef7fa Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Thu, 6 Aug 2026 21:04:45 +0200 Subject: [PATCH 52/59] s390/irqflags: Add out-of-line definitions of arch_local_irq_*() for KMSAN Inline KMSAN arch_local_irq_*() definitions run afoul of -Wstatic-in-inline. Move them out-of-line. Make sure decompressor and non-GPL modules see the out-of-line definitions. Cc: Boqun Feng Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607131219.euJHPSJ5-lkp@intel.com/ Suggested-by: Heiko Carstens Fixes: 1b301f5f28ba ("s390/irqflags: do not instrument arch_local_irq_*() with KMSAN") Signed-off-by: Ilya Leoshkevich Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/irqflags.h | 20 +++++++++++++------- arch/s390/kernel/Makefile | 1 + arch/s390/kernel/irqflags.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 arch/s390/kernel/irqflags.c diff --git a/arch/s390/include/asm/irqflags.h b/arch/s390/include/asm/irqflags.h index bcab456dfb80..6eb6de6a99f2 100644 --- a/arch/s390/include/asm/irqflags.h +++ b/arch/s390/include/asm/irqflags.h @@ -37,18 +37,24 @@ static __always_inline void __arch_local_irq_ssm(unsigned long flags) asm volatile("ssm %0" : : "Q" (flags) : "memory"); } -#ifdef CONFIG_KMSAN -#define arch_local_irq_attributes noinline notrace __no_sanitize_memory __maybe_unused +#if defined(CONFIG_KMSAN) && !defined(__DECOMPRESSOR) +unsigned long arch_local_save_flags(void); +unsigned long arch_local_irq_save(void); +void arch_local_irq_enable_external(void); +void arch_local_irq_enable(void); #else -#define arch_local_irq_attributes __always_inline +#define arch_local_save_flags __arch_local_save_flags +#define arch_local_irq_save __arch_local_irq_save +#define arch_local_irq_enable_external __arch_local_irq_enable_external +#define arch_local_irq_enable __arch_local_irq_enable #endif -static arch_local_irq_attributes unsigned long arch_local_save_flags(void) +static __always_inline unsigned long __arch_local_save_flags(void) { return __arch_local_irq_stnsm(0xff); } -static arch_local_irq_attributes unsigned long arch_local_irq_save(void) +static __always_inline unsigned long __arch_local_irq_save(void) { return __arch_local_irq_stnsm(0xfc); } @@ -58,12 +64,12 @@ static __always_inline void arch_local_irq_disable(void) arch_local_irq_save(); } -static arch_local_irq_attributes void arch_local_irq_enable_external(void) +static __always_inline void __arch_local_irq_enable_external(void) { __arch_local_irq_stosm(0x01); } -static arch_local_irq_attributes void arch_local_irq_enable(void) +static __always_inline void __arch_local_irq_enable(void) { __arch_local_irq_stosm(0x03); } diff --git a/arch/s390/kernel/Makefile b/arch/s390/kernel/Makefile index 6c88476d79a3..14ef03cb2f72 100644 --- a/arch/s390/kernel/Makefile +++ b/arch/s390/kernel/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_STACKPROTECTOR) += stackprotector.o obj-$(CONFIG_KEXEC_FILE) += machine_kexec_file.o kexec_image.o obj-$(CONFIG_KEXEC_FILE) += kexec_elf.o obj-$(CONFIG_CERT_STORE) += cert_store.o +obj-$(CONFIG_KMSAN) += irqflags.o obj-$(CONFIG_PERF_EVENTS) += perf_event.o obj-$(CONFIG_PERF_EVENTS) += perf_cpum_cf.o perf_cpum_sf.o diff --git a/arch/s390/kernel/irqflags.c b/arch/s390/kernel/irqflags.c new file mode 100644 index 000000000000..e192f59f8918 --- /dev/null +++ b/arch/s390/kernel/irqflags.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +noinstr unsigned long arch_local_save_flags(void) +{ + return __arch_local_save_flags(); +} +EXPORT_SYMBOL(arch_local_save_flags); + +noinstr unsigned long arch_local_irq_save(void) +{ + return __arch_local_irq_save(); +} +EXPORT_SYMBOL(arch_local_irq_save); + +noinstr void arch_local_irq_enable_external(void) +{ + __arch_local_irq_enable_external(); +} +EXPORT_SYMBOL(arch_local_irq_enable_external); + +noinstr void arch_local_irq_enable(void) +{ + __arch_local_irq_enable(); +} +EXPORT_SYMBOL(arch_local_irq_enable); From 1f45a8663a58e89fe3025360a421ec3b7b929aa0 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 3 Aug 2026 19:21:41 +0200 Subject: [PATCH 53/59] s390/sysinfo: Add context analysis attributes Add context analysis attributes to service_level_start() and service_level_stop() to specify that those functions only acquire or release a lock. Addresses the following warnings: arch/s390/kernel/sysinfo.c:331:1: warning: rw_semaphore 'service_level_sem' is still held at the end of function arch/s390/kernel/sysinfo.c:329:2: note: rw_semaphore acquired here 329 | down_read(&service_level_sem); arch/s390/kernel/sysinfo.c:340:2: warning: releasing rw_semaphore 'service_level_sem' that was not held 340 | up_read(&service_level_sem); Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/sysinfo.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/kernel/sysinfo.c b/arch/s390/kernel/sysinfo.c index 33ca3e47a0e6..45b4f448fe3d 100644 --- a/arch/s390/kernel/sysinfo.c +++ b/arch/s390/kernel/sysinfo.c @@ -325,6 +325,7 @@ int unregister_service_level(struct service_level *slr) EXPORT_SYMBOL(unregister_service_level); static void *service_level_start(struct seq_file *m, loff_t *pos) +__acquires_shared(service_level_sem) { down_read(&service_level_sem); return seq_list_start(&service_level_list, *pos); @@ -336,6 +337,7 @@ static void *service_level_next(struct seq_file *m, void *p, loff_t *pos) } static void service_level_stop(struct seq_file *m, void *p) +__releases_shared(service_level_sem) { up_read(&service_level_sem); } From 46a8c8d142ba812ed374c55388979bf16e92f88c Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 3 Aug 2026 19:21:42 +0200 Subject: [PATCH 54/59] s390/mm: Add __context_unsafe() attribute to do_secure_storage_access() Compiling do_secure_storage_access() with context analysis enabled results in this warning: arch/s390/mm/fault.c:472:3: warning: releasing spinlock 'fw.ptl' that was not held 472 | folio_walk_end(&fw, vma); Problem is that folio_walk_end() comes without the required context analysis attribute. Also the proper attribute cannot be added easily, since folio_walk_end() is a macro, and not a function. For the time being disable context analysis only for do_secure_storage_access() until this is resolved. Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/mm/fault.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c index 666fe8e99480..46d828926009 100644 --- a/arch/s390/mm/fault.c +++ b/arch/s390/mm/fault.c @@ -406,6 +406,7 @@ NOKPROBE_SYMBOL(do_dat_exception); #if IS_ENABLED(CONFIG_KVM) void do_secure_storage_access(struct pt_regs *regs) +__context_unsafe(/* folio_walk_end() not instrumented */) { union teid teid = { .val = regs->int_parm_long }; unsigned long addr = get_fault_address(regs); From fa25991cbf7576d65c6603387df26e9cf38f785c Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 3 Aug 2026 19:21:43 +0200 Subject: [PATCH 55/59] s390/mm: Add __context_unsafe() attribute to gmap helper functions Disable context analysis for various gmap helper functions to get rid of a few warnings: arch/s390/mm/gmap_helpers.c:80:1: warning: spinlock 'ptl' is not held on every path through here arch/s390/mm/gmap_helpers.c:116:2: warning: releasing spinlock 'ptl' that was not held arch/s390/mm/gmap_helpers.c:186:2: warning: releasing spinlock 'ptl' that was not held Use __context_unsafe() to give a short comment why for function context analysis is disabled. try_get_locked_pte() is disabled since it may return a nonull value regardless if it returns with a lock held or not. This cannot be reflected with the context analysis attributes. It is however possible to workaround this e.g. by adding a another `contended` function parameter, however this would lead to the next problem: pte_unmap_unlock() is a macro and therefore doesn't come with the required context analysis attribute to address this. For that reason also disable context analysis for gmap_helper_zap_one_page() and gmap_helper_try_set_pte_unused() until this has been addressed. Acked-by: Claudio Imbrenda Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/mm/gmap_helpers.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c index d564cdefb90f..59416aca6ecb 100644 --- a/arch/s390/mm/gmap_helpers.c +++ b/arch/s390/mm/gmap_helpers.c @@ -40,6 +40,7 @@ * and locked. */ pte_t *try_get_locked_pte(struct mm_struct *mm, unsigned long vmaddr, spinlock_t **ptl) +__context_unsafe(/* Returns nonnull if lock taken or not taken */) { pmd_t *pmdp, pmd, pmdval; pud_t *pudp, pud; @@ -90,6 +91,7 @@ EXPORT_SYMBOL_GPL(try_get_locked_pte); * Context: needs to be called while holding the mmap lock. */ void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr) +__context_unsafe(/* pte_unmap_unlock() not instrumented */) { struct vm_area_struct *vma; spinlock_t *ptl; /* Lock for the host (userspace) page table */ @@ -161,6 +163,7 @@ EXPORT_SYMBOL_GPL(gmap_helper_discard); * disabled. */ void gmap_helper_try_set_pte_unused(struct mm_struct *mm, unsigned long vmaddr) +__context_unsafe(/* pte_unmap_unlock() not instrumented */) { spinlock_t *ptl; /* Lock for the host (userspace) page table */ pte_t *ptep; From 29a63aa52d698006211461d49fd3f00dfb62d9cc Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 3 Aug 2026 19:21:44 +0200 Subject: [PATCH 56/59] s390: Enable CONTEXT_ANALYSIS for various directories Enable CONTEXT_ANALYSIS for various directories which do not generate any warnings (anymore). Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/appldata/Makefile | 2 ++ arch/s390/hypfs/Makefile | 2 ++ arch/s390/kernel/Makefile | 2 ++ arch/s390/lib/Makefile | 2 ++ arch/s390/mm/Makefile | 2 ++ arch/s390/net/Makefile | 3 +++ arch/s390/purgatory/Makefile | 2 ++ 7 files changed, 15 insertions(+) diff --git a/arch/s390/appldata/Makefile b/arch/s390/appldata/Makefile index b06def4a4f2f..132381f16fb1 100644 --- a/arch/s390/appldata/Makefile +++ b/arch/s390/appldata/Makefile @@ -3,6 +3,8 @@ # Makefile for the Linux - z/VM Monitor Stream. # +CONTEXT_ANALYSIS := y + obj-$(CONFIG_APPLDATA_BASE) += appldata_base.o obj-$(CONFIG_APPLDATA_MEM) += appldata_mem.o obj-$(CONFIG_APPLDATA_OS) += appldata_os.o diff --git a/arch/s390/hypfs/Makefile b/arch/s390/hypfs/Makefile index c34854d298f8..5a3daeafe926 100644 --- a/arch/s390/hypfs/Makefile +++ b/arch/s390/hypfs/Makefile @@ -3,6 +3,8 @@ # Makefile for the linux hypfs filesystem routines. # +CONTEXT_ANALYSIS := y + obj-$(CONFIG_S390_HYPFS) += hypfs_dbfs.o obj-$(CONFIG_S390_HYPFS) += hypfs_diag.o obj-$(CONFIG_S390_HYPFS) += hypfs_diag0c.o diff --git a/arch/s390/kernel/Makefile b/arch/s390/kernel/Makefile index 14ef03cb2f72..f84b8fb41331 100644 --- a/arch/s390/kernel/Makefile +++ b/arch/s390/kernel/Makefile @@ -3,6 +3,8 @@ # Makefile for the linux kernel. # +CONTEXT_ANALYSIS := y + ifdef CONFIG_FUNCTION_TRACER # Do not trace tracer code diff --git a/arch/s390/lib/Makefile b/arch/s390/lib/Makefile index aa6cc6a1fe88..bb707700d7af 100644 --- a/arch/s390/lib/Makefile +++ b/arch/s390/lib/Makefile @@ -3,6 +3,8 @@ # Makefile for s390-specific library files.. # +CONTEXT_ANALYSIS := y + # string.o implements standard library functions like memset/memcpy etc. # Use -ffreestanding to ensure that the compiler does not try to "optimize" # them into calls to themselves. diff --git a/arch/s390/mm/Makefile b/arch/s390/mm/Makefile index 193899c39ca7..7dea37a5ad3b 100644 --- a/arch/s390/mm/Makefile +++ b/arch/s390/mm/Makefile @@ -3,6 +3,8 @@ # Makefile for the linux s390-specific parts of the memory manager. # +CONTEXT_ANALYSIS := y + obj-y := init.o fault.o extmem.o mmap.o vmem.o maccess.o obj-y += page-states.o pageattr.o pgtable.o pgalloc.o extable.o diff --git a/arch/s390/net/Makefile b/arch/s390/net/Makefile index 9275cf63192a..0a7323c9b372 100644 --- a/arch/s390/net/Makefile +++ b/arch/s390/net/Makefile @@ -2,5 +2,8 @@ # # Arch-specific network modules # + +CONTEXT_ANALYSIS := y + obj-$(CONFIG_BPF_JIT) += bpf_jit_comp.o bpf_timed_may_goto.o obj-$(CONFIG_HAVE_PNETID) += pnet.o diff --git a/arch/s390/purgatory/Makefile b/arch/s390/purgatory/Makefile index e74410bb1b88..b2f17e0d7fd6 100644 --- a/arch/s390/purgatory/Makefile +++ b/arch/s390/purgatory/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 +CONTEXT_ANALYSIS := y + purgatory-y := head.o purgatory.o string.o sha256.o targets += $(purgatory-y) purgatory.lds purgatory purgatory.chk purgatory.ro From 337bd95507a16063687cfc286ea90de5cca48c37 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 11 Aug 2026 15:39:01 +0200 Subject: [PATCH 57/59] s390/cpum_cf: Handle CPU hotplug via prepare/dead callbacks The command 'perf stat -e cycles -- ' crashes the kernel when CPUs are hotplug added during that run. Root cause is the allocation of struct cpu_cf_events at first event initialization. The allocation is dynamic and the first event that has task context creates such a structure for each online CPU. This is not sufficient. CPUs may be offline during event creation and can be set online during the perf run time. For example commands # echo 0 > /sys/devices/system/cpu/cpu1/online # perf stat -e cycles -i -- stress-ng -t10s --matrix X # sleep 1 # echo 1 > /sys/devices/system/cpu/cpu1/online create an event for CPUs 0,2-X. Since the events are created with task-context, the scheduler will eventually schedule the program on CPU1. This CPU has not created and initialized any per CPU event infrastructure as that CPU was not online at the time of the perf invocation. Thus when the scheduler runs stress-ng on CPU1, the function cpumf_pmu_add() refers to a NULL pointer: struct cpu_cf_events *cpuhw = this_cpu_cfhw(); This function call is invoked after the task stress-ng has been made runnable on CPU1. And this_cpu_cfhw() returns NULL. The result is a panic: Unable to handle kernel pointer dereference in virtual kernel address space Failing address: 0000000000000000 TEID: 0000000000000483 .... Krnl PSW : 0404d00180000000 000003ef8291fd0c (cpumf_pmu_add+0x3c/0x80) .... Call Trace: [<000003ef8291fd0c>] cpumf_pmu_add+0x3c/0x80 [<000003ef82bb5e3e>] event_sched_in+0xae/0x190 [<000003ef82bb60d6>] merge_sched_in+0x1b6/0x390 [<000003ef82bb65b8>] visit_groups_merge.constprop.0.isra.0+0x308/0x5b0 [<000003ef82bb689a>] pmu_groups_sched_in+0x3a/0x50 [<000003ef82bb6a30>] ctx_sched_in+0x180/0x260 [<000003ef82bb780c>] perf_event_context_sched_in+0x11c/0x2d0 [<000003ef82bb79ee>] __perf_event_task_sched_in+0x2e/0xc0 [<000003ef82994834>] finish_task_switch.isra.0+0x1a4/0x250 .... Last Breaking-Event-Address: [<000003ef8291f1d8>] this_cpu_cfhw+0x38/0x40 The issue arises only in per-task context when the CPUMF facility is used and the scheduler picks a random CPU for such a process to run on. The scheduler enables the CPUMF infrastructure via PMU callback functions pmu::add() and pmu::del(). Introduce a CPU hotplug prepare/dead callback pair which creates and removes the per CPU counter data while the CPU is offline. Count the users which track every CPU (cpu == -1), that is perf_event_open() events with task context and /dev/hwctr device sessions, in the new counter cpu_cf_root::tskcnt, protected by pmc_reserve_mutex. This ensures the infrastructure is available when new CPU is selected to run the per-task context process. In cpum_cf_free_root() and cpum_cf_free_cpu() ensure the reference pointer to data structures is set to NULL before the data is freed to prevent interrupt handlers to access stale data. [gor@linux.ibm.com: change commit message] Fixes: 9b9cf3c77e7e ("s390/cpum_cf: rework PER_CPU_DEFINE of struct cpu_cf_events") Cc: stable@vger.kernel.org # v6.5+ Suggested-by: Heiko Carstens Suggested-by: Christian Borntraeger Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Thomas Richter Acked-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/perf_cpum_cf.c | 221 +++++++++++++++++++++----------- 1 file changed, 145 insertions(+), 76 deletions(-) diff --git a/arch/s390/kernel/perf_cpum_cf.c b/arch/s390/kernel/perf_cpum_cf.c index 2cecc86144e1..1ad3b34be837 100644 --- a/arch/s390/kernel/perf_cpum_cf.c +++ b/arch/s390/kernel/perf_cpum_cf.c @@ -109,6 +109,7 @@ struct cpu_cf_ptr { static struct cpu_cf_root { /* Anchor to per CPU data */ refcount_t refcnt; /* Overall active events */ + unsigned int tskctx; /* Users tracking all CPUs (cpu == -1) */ struct cpu_cf_ptr __percpu *cfptr; } cpu_cf_root; @@ -117,13 +118,15 @@ static struct cpu_cf_root { /* Anchor to per CPU data */ * user space in task context with perf_event_open() and close() * system calls. * - * This mutex serializes functions cpum_cf_alloc_cpu() called at event - * initialization via cpumf_pmu_event_init() and function cpum_cf_free_cpu() - * called at event removal via call back function hw_perf_event_destroy() - * when the event is deleted. They are serialized to enforce correct - * bookkeeping of pointer and reference counts anchored by - * struct cpu_cf_root and the access to cpu_cf_root::refcnt and the - * per CPU pointers stored in cpu_cf_root::cfptr. + * This mutex serializes the allocation and removal of the per CPU counter + * data via cpum_cf_alloc_cpu() and cpum_cf_free_cpu(). They are called with + * this mutex held at event initialization via cpumf_pmu_event_init(), at + * event removal via call back function hw_perf_event_destroy() when the + * event is deleted, and from the CPU hotplug prepare/dead callbacks. The + * mutex enforces correct bookkeeping of pointer and reference counts + * anchored by struct cpu_cf_root and protects the access to + * cpu_cf_root::refcnt, cpu_cf_root::tskctx and the per CPU pointers + * stored in cpu_cf_root::cfptr. */ static DEFINE_MUTEX(pmc_reserve_mutex); @@ -166,12 +169,14 @@ static void cpum_cf_reset_cpu(void *flags) } /* Free per CPU data when the last event is removed. */ -static void cpum_cf_free_root(void) +static void cpum_cf_free_root(unsigned int num) { - if (!refcount_dec_and_test(&cpu_cf_root.refcnt)) + struct cpu_cf_ptr __percpu *p = cpu_cf_root.cfptr; + + if (!refcount_sub_and_test(num, &cpu_cf_root.refcnt)) return; - free_percpu(cpu_cf_root.cfptr); cpu_cf_root.cfptr = NULL; + free_percpu(p); irq_subclass_unregister(IRQ_SUBCLASS_MEASUREMENT_ALERT); on_each_cpu(cpum_cf_reset_cpu, NULL, 1); debug_sprintf_event(cf_dbg, 4, "%s root.refcnt %u cfptr %d\n", @@ -185,17 +190,17 @@ static void cpum_cf_free_root(void) * CPUs possible, which might be larger than the number of CPUs currently * online. */ -static int cpum_cf_alloc_root(void) +static int cpum_cf_alloc_root(unsigned int num) { int rc = 0; - if (refcount_inc_not_zero(&cpu_cf_root.refcnt)) + if (refcount_add_not_zero(num, &cpu_cf_root.refcnt)) return rc; /* The memory is already zeroed. */ cpu_cf_root.cfptr = alloc_percpu(struct cpu_cf_ptr); if (cpu_cf_root.cfptr) { - refcount_set(&cpu_cf_root.refcnt, 1); + refcount_set(&cpu_cf_root.refcnt, num); on_each_cpu(cpum_cf_reset_cpu, NULL, 1); irq_subclass_register(IRQ_SUBCLASS_MEASUREMENT_ALERT); } else { @@ -205,20 +210,23 @@ static int cpum_cf_alloc_root(void) return rc; } -/* Free CPU counter data structure for a PMU */ -static void cpum_cf_free_cpu(int cpu) +/* + * Remove num references to the CPU counter data structure of a PMU. + * Called with pmc_reserve_mutex held. + */ +static void cpum_cf_free_cpu(int cpu, unsigned int num) { struct cpu_cf_events *cpuhw; struct cpu_cf_ptr *p; - mutex_lock(&pmc_reserve_mutex); + lockdep_assert_held(&pmc_reserve_mutex); /* * When invoked via CPU hotplug handler, there might be no events * installed or that particular CPU might not have an * event installed. This anchor pointer can be NULL! */ if (!cpu_cf_root.cfptr) - goto out; + return; p = per_cpu_ptr(cpu_cf_root.cfptr, cpu); cpuhw = p->cpucf; /* @@ -226,28 +234,29 @@ static void cpum_cf_free_cpu(int cpu) * installed on that CPU, but on different CPUs. */ if (!cpuhw) - goto out; + return; - if (refcount_dec_and_test(&cpuhw->refcnt)) { - kfree(cpuhw); + if (refcount_sub_and_test(num, &cpuhw->refcnt)) { p->cpucf = NULL; + kfree(cpuhw); } - cpum_cf_free_root(); -out: - mutex_unlock(&pmc_reserve_mutex); + cpum_cf_free_root(num); } -/* Allocate CPU counter data structure for a PMU. Called under mutex lock. */ -static int cpum_cf_alloc_cpu(int cpu) +/* + * Add num references to the CPU counter data structure of a PMU and + * allocate it when necessary. Called with pmc_reserve_mutex held. + */ +static int cpum_cf_alloc_cpu(int cpu, unsigned int num) { struct cpu_cf_events *cpuhw; struct cpu_cf_ptr *p; int rc; - mutex_lock(&pmc_reserve_mutex); - rc = cpum_cf_alloc_root(); + lockdep_assert_held(&pmc_reserve_mutex); + rc = cpum_cf_alloc_root(num); if (rc) - goto unlock; + return rc; p = per_cpu_ptr(cpu_cf_root.cfptr, cpu); cpuhw = p->cpucf; @@ -255,12 +264,12 @@ static int cpum_cf_alloc_cpu(int cpu) cpuhw = kzalloc_obj(*cpuhw); if (cpuhw) { p->cpucf = cpuhw; - refcount_set(&cpuhw->refcnt, 1); + refcount_set(&cpuhw->refcnt, num); } else { rc = -ENOMEM; } } else { - refcount_inc(&cpuhw->refcnt); + refcount_add(num, &cpuhw->refcnt); } if (rc) { /* @@ -268,10 +277,8 @@ static int cpum_cf_alloc_cpu(int cpu) * cpu_cf_event in not created, its destroy() function is not * invoked. Adjust the reference counter for the anchor. */ - cpum_cf_free_root(); + cpum_cf_free_root(num); } -unlock: - mutex_unlock(&pmc_reserve_mutex); return rc; } @@ -283,39 +290,70 @@ static int cpum_cf_alloc_cpu(int cpu) * perf_event_open() with task context and /dev/hwctr interface. * If cpu is non-zero install event on this CPU only. This setup handles * perf_event_open() with CPU context. + * Users with cpu == -1 are counted in cpu_cf_root::tskctx. The CPU hotplug + * prepare and dead callbacks use this count to install and remove the per + * CPU counter data on a new or dying CPU. */ -static int cpum_cf_alloc(int cpu) +static int cpum_cf_alloc_cpuslocked(int cpu) { cpumask_var_t mask; int rc; + lockdep_assert_cpus_held(); if (cpu == -1) { if (!zalloc_cpumask_var(&mask, GFP_KERNEL)) return -ENOMEM; + mutex_lock(&pmc_reserve_mutex); for_each_online_cpu(cpu) { - rc = cpum_cf_alloc_cpu(cpu); + rc = cpum_cf_alloc_cpu(cpu, 1); if (rc) { for_each_cpu(cpu, mask) - cpum_cf_free_cpu(cpu); + cpum_cf_free_cpu(cpu, 1); break; } cpumask_set_cpu(cpu, mask); } + if (!rc) + cpu_cf_root.tskctx++; + mutex_unlock(&pmc_reserve_mutex); free_cpumask_var(mask); } else { - rc = cpum_cf_alloc_cpu(cpu); + mutex_lock(&pmc_reserve_mutex); + rc = cpum_cf_alloc_cpu(cpu, 1); + mutex_unlock(&pmc_reserve_mutex); } return rc; } +static int cpum_cf_alloc(int cpu) +{ + int rc; + + cpus_read_lock(); + rc = cpum_cf_alloc_cpuslocked(cpu); + cpus_read_unlock(); + return rc; +} + +static void cpum_cf_free_cpuslocked(int cpu) +{ + lockdep_assert_cpus_held(); + mutex_lock(&pmc_reserve_mutex); + if (cpu == -1) { + cpu_cf_root.tskctx--; + for_each_online_cpu(cpu) + cpum_cf_free_cpu(cpu, 1); + } else { + cpum_cf_free_cpu(cpu, 1); + } + mutex_unlock(&pmc_reserve_mutex); +} + static void cpum_cf_free(int cpu) { - if (cpu == -1) { - for_each_online_cpu(cpu) - cpum_cf_free_cpu(cpu); - } else { - cpum_cf_free_cpu(cpu); - } + cpus_read_lock(); + cpum_cf_free_cpuslocked(cpu); + cpus_read_unlock(); } #define CF_DIAG_CTRSET_DEF 0xfeef /* Counter set header mark */ @@ -1087,53 +1125,67 @@ static refcount_t cfset_opencnt = REFCOUNT_INIT(0); /* Access count */ static DEFINE_MUTEX(cfset_ctrset_mutex); /* - * CPU hotplug handles only /dev/hwctr device. - * For perf_event_open() the CPU hotplug handling is done on kernel common - * code: + * CPU hotplug handling: + * + * cpum_cf_prepare_cpu() and cpum_cf_dead_cpu() run while the new or dying + * CPU is offline. They create and remove the per CPU counter data for all + * users tracking every CPU (cpu == -1), that is perf_event_open() events + * with task context and /dev/hwctr device sessions. Each such user holds + * one reference to the per CPU counter data of each CPU. Therefore install + * and remove one reference per user, tracked in cpu_cf_root::tskctx. This + * guarantees the per CPU counter data exists before the new CPU executes + * its first task and is removed only after the dying CPU is gone. + * + * cpum_cf_online_cpu() and cpum_cf_offline_cpu() run while the new or + * dying CPU is online. They handle only the counter set state of open + * /dev/hwctr device sessions on that CPU. For perf_event_open() events + * nothing is done: * - CPU add: Nothing is done since a file descriptor can not be created * and returned to the user. * - CPU delete: Handled by common code via pmu_disable(), pmu_stop() and - * pmu_delete(). The event itself is removed when the file descriptor is - * closed. + * pmu_delete(). During task exit processing of grouped perf events + * triggered by CPU hotplug processing, pmu_disable() is called as part + * of perf context removal process. The event itself is removed when the + * event file descriptor is closed. */ +static int cpum_cf_prepare_cpu(unsigned int cpu) +{ + int rc = 0; + + mutex_lock(&pmc_reserve_mutex); + if (cpu_cf_root.tskctx) + rc = cpum_cf_alloc_cpu(cpu, cpu_cf_root.tskctx); + mutex_unlock(&pmc_reserve_mutex); + return rc; +} + +static int cpum_cf_dead_cpu(unsigned int cpu) +{ + mutex_lock(&pmc_reserve_mutex); + if (cpu_cf_root.tskctx) + cpum_cf_free_cpu(cpu, cpu_cf_root.tskctx); + mutex_unlock(&pmc_reserve_mutex); + return 0; +} + static int cfset_online_cpu(unsigned int cpu); static int cpum_cf_online_cpu(unsigned int cpu) { - int rc = 0; - - /* - * Ignore notification for perf_event_open(). - * Handle only /dev/hwctr device sessions. - */ mutex_lock(&cfset_ctrset_mutex); - if (refcount_read(&cfset_opencnt)) { - rc = cpum_cf_alloc_cpu(cpu); - if (!rc) - cfset_online_cpu(cpu); - } + if (refcount_read(&cfset_opencnt)) + cfset_online_cpu(cpu); mutex_unlock(&cfset_ctrset_mutex); - return rc; + return 0; } static int cfset_offline_cpu(unsigned int cpu); static int cpum_cf_offline_cpu(unsigned int cpu) { - /* - * During task exit processing of grouped perf events triggered by CPU - * hotplug processing, pmu_disable() is called as part of perf context - * removal process. Therefore do not trigger event removal now for - * perf_event_open() created events. Perf common code triggers event - * destruction when the event file descriptor is closed. - * - * Handle only /dev/hwctr device sessions. - */ mutex_lock(&cfset_ctrset_mutex); - if (refcount_read(&cfset_opencnt)) { + if (refcount_read(&cfset_opencnt)) cfset_offline_cpu(cpu); - cpum_cf_free_cpu(cpu); - } mutex_unlock(&cfset_ctrset_mutex); return 0; } @@ -1180,7 +1232,7 @@ static void cpumf_measurement_alert(struct ext_code ext_code, static int cfset_init(void); static int __init cpumf_pmu_init(void) { - int rc; + int state, rc; /* Extract counter measurement facility information */ if (!cpum_cf_avail() || qctri(&cpumf_ctr_info)) @@ -1222,11 +1274,24 @@ static int __init cpumf_pmu_init(void) cfset_init(); } + rc = cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, + "perf/s390/cf:prepare", + cpum_cf_prepare_cpu, cpum_cf_dead_cpu); + if (rc < 0) + goto out3; + state = rc; + rc = cpuhp_setup_state(CPUHP_AP_PERF_S390_CF_ONLINE, "perf/s390/cf:online", cpum_cf_online_cpu, cpum_cf_offline_cpu); - return rc; + if (rc < 0) + goto out4; + return 0; +out4: + cpuhp_remove_state(state); +out3: + perf_pmu_unregister(&cpumf_pmu); out2: debug_unregister_view(cf_dbg, &debug_sprintf_view); debug_unregister(cf_dbg); @@ -1382,6 +1447,7 @@ static void cfset_all_stop(struct cfset_request *req) */ static int cfset_release(struct inode *inode, struct file *file) { + cpus_read_lock(); mutex_lock(&cfset_ctrset_mutex); /* Open followed by close/exit has no private_data */ if (file->private_data) { @@ -1392,9 +1458,10 @@ static int cfset_release(struct inode *inode, struct file *file) } if (refcount_dec_and_test(&cfset_opencnt)) { /* Last close */ on_each_cpu(cfset_release_cpu, NULL, 1); - cpum_cf_free(-1); + cpum_cf_free_cpuslocked(-1); } mutex_unlock(&cfset_ctrset_mutex); + cpus_read_unlock(); return 0; } @@ -1413,15 +1480,17 @@ static int cfset_open(struct inode *inode, struct file *file) return -EPERM; file->private_data = NULL; + cpus_read_lock(); mutex_lock(&cfset_ctrset_mutex); if (!refcount_inc_not_zero(&cfset_opencnt)) { /* First open */ - rc = cpum_cf_alloc(-1); + rc = cpum_cf_alloc_cpuslocked(-1); if (!rc) { cfset_session_init(); refcount_set(&cfset_opencnt, 1); } } mutex_unlock(&cfset_ctrset_mutex); + cpus_read_unlock(); /* nonseekable_open() never fails */ return rc ?: nonseekable_open(inode, file); From 445c31ac638fd1af203d79bdf25fc0cb3149fbbc Mon Sep 17 00:00:00 2001 From: Peter Oberparleiter Date: Wed, 12 Aug 2026 09:53:19 +0200 Subject: [PATCH 58/59] s390/debug: Fix deadlock during unregister Unregistering an s390dbf debug area while one of the associated debugfs files is being written to can cause a deadlock: $ echo >.../vmur/level $ rmmod vmur =================================================== debugfs write debugfs_file_get() debug_unregister() mutex_lock(debug_mutex) debugfs_remove() wait for debugfs_file_put() debug_file_ops.write() debug_input() mutex_lock(debug_mutex) ==> DEADLOCK Fix this by splitting debug_unregister() into an s390dbf and debugfs part, and running only the s390dbf part with debug_mutex locked. Fixes: 9372a82892c2 ("s390/debug: fix debug area life cycle") Signed-off-by: Peter Oberparleiter Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/debug.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index dbf430f479bd..14d2b58ad093 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -993,8 +993,8 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) mutex_unlock(&debug_mutex); } -/* Remove debugfs entries and remove from internal list. */ -static void _debug_unregister(debug_info_t *id) +/* Remove debugfs entries. */ +static void _debug_unregister_debugfs(debug_info_t *id) { int i; @@ -1004,6 +1004,11 @@ static void _debug_unregister(debug_info_t *id) debugfs_remove(id->debugfs_entries[i]); } debugfs_remove(id->debugfs_root_entry); +} + +/* Remove from internal list. */ +static void _debug_unregister(debug_info_t *id) +{ if (id == debug_area_first) debug_area_first = id->next; if (id == debug_area_last) @@ -1029,6 +1034,7 @@ void debug_unregister(debug_info_t *id) mutex_lock(&debug_mutex); _debug_unregister(id); mutex_unlock(&debug_mutex); + _debug_unregister_debugfs(id); debug_info_put(id); } From 101782f8945a125044347312d74d488c05741c4a Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 12 Aug 2026 22:30:36 +0200 Subject: [PATCH 59/59] s390/percpu: Fix MVIY_PERCPU() with older binutils Commit a737737cdb9c ("s390/percpu: Infrastructure for more efficient this_cpu operations") introduced MVIY_PERCPU(), which stringifies arguments that are already C string literals. This generates an assembler macro invocation with whitespace-separated quoted arguments: GEN_MVIY "459712" "%r3" GNU as versions prior to binutils 2.39 drop the separating whitespace between quoted macro arguments during input scrubbing. They consequently parse the invocation as a single argument and emit repeated warnings: Warning: missing closing `"' The .ifc in GEN_MVIY never matches and GNU as exits successfully without emitting the mviy instruction. As a result, the interrupted per-CPU sequence is not marked in lowcore and the exception return path cannot repair the per-CPU address register after migration. All MVIY_PERCPU() callers pass C string literals. Use them directly and separate the assembler macro arguments with an explicit comma. The resulting invocation is: GEN_MVIY 459712, %r3 This form is unambiguous for GNU as and LLVM's integrated assembler. This behavior was fixed in GNU as from binutils 2.39, but Linux supports binutils 2.30. Fixes: a737737cdb9c ("s390/percpu: Infrastructure for more efficient this_cpu operations") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/percpu.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 1d955dd0defa..feb58acf87d6 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -107,8 +107,8 @@ " .endif\n" \ ".endr\n" \ ".endm\n" \ - ALTERNATIVE("GEN_MVIY " __stringify(disp) " " __stringify(reg) "\n", \ - "GEN_MVIY " __stringify(dispalt) " " __stringify(reg) "\n", \ + ALTERNATIVE("GEN_MVIY " disp ", " reg "\n", \ + "GEN_MVIY " dispalt ", " reg "\n", \ ALT_FEATURE(MFEATURE_LOWCORE)) \ ".purgem GEN_MVIY\n"