diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-27 14:04:29 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-27 14:04:29 +0100 |
| commit | 3eded33495da0d14fe3fef7405860289f2354148 (patch) | |
| tree | 0965a046b38ef9fc515ad4c78abbde44705b87f4 | |
| parent | 1bf0c156581b1f81e8b5bd4b51d9632a444b9128 (diff) | |
| parent | 8b313d717bcf4f944fb46fe4ed4d8680bdbc5648 (diff) | |
| download | linux-next-3eded33495da0d14fe3fef7405860289f2354148.tar.gz linux-next-3eded33495da0d14fe3fef7405860289f2354148.zip | |
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/s390/linux.git
| -rw-r--r-- | MAINTAINERS | 8 | ||||
| -rw-r--r-- | arch/s390/Kconfig | 5 | ||||
| -rw-r--r-- | arch/s390/configs/debug_defconfig | 2 | ||||
| -rw-r--r-- | arch/s390/configs/defconfig | 2 | ||||
| -rw-r--r-- | arch/s390/include/asm/hugetlb.h | 4 | ||||
| -rw-r--r-- | arch/s390/include/asm/processor.h | 4 | ||||
| -rw-r--r-- | arch/s390/include/asm/thread_info.h | 1 | ||||
| -rw-r--r-- | arch/s390/kernel/idle.c | 1 | ||||
| -rw-r--r-- | arch/s390/kernel/irq.c | 4 | ||||
| -rw-r--r-- | arch/s390/kernel/smp.c | 1 | ||||
| -rw-r--r-- | arch/s390/kernel/traps.c | 41 | ||||
| -rw-r--r-- | arch/s390/mm/hugetlbpage.c | 10 | ||||
| -rw-r--r-- | drivers/cpuidle/Kconfig | 5 | ||||
| -rw-r--r-- | drivers/cpuidle/Kconfig.s390 | 11 | ||||
| -rw-r--r-- | drivers/cpuidle/Makefile | 4 | ||||
| -rw-r--r-- | drivers/cpuidle/cpuidle-s390.c | 115 | ||||
| -rw-r--r-- | include/linux/tick.h | 3 | ||||
| -rw-r--r-- | kernel/time/tick-sched.c | 7 |
18 files changed, 204 insertions, 24 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 9cc50449ecc2..1113199518b6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6854,6 +6854,13 @@ L: linux-riscv@lists.infradead.org S: Maintained F: drivers/cpuidle/cpuidle-riscv-sbi.c +CPUIDLE DRIVER - S390 +M: Mete Durlu <meted@linux.ibm.com> +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 <viresh.kumar@linaro.org> R: Yury Norov <yury.norov@gmail.com> @@ -23810,6 +23817,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 ab8fccc2cc4e..1911144e9128 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 @@ -706,6 +709,8 @@ config KERNEL_IMAGE_BASE endmenu +source "drivers/cpuidle/Kconfig" + menu "Memory setup" config ARCH_SPARSEMEM_ENABLE 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 diff --git a/arch/s390/include/asm/hugetlb.h b/arch/s390/include/asm/hugetlb.h index 02821a7d9d91..aea754b67c89 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/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 <linux/bits.h> -#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/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 <asm-generic/thread_info_tif.h> 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; } 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 <linux/capability.h> #include <linux/cpufeature.h> +#include <linux/debugfs.h> #include <linux/kprobes.h> #include <linux/kdebug.h> #include <linux/randomize_kstack.h> @@ -33,6 +35,12 @@ #include <asm/fault.h> #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 @@ out: 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 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, 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 <linux/init.h> +#include <linux/cpuidle.h> +#include <linux/cpu.h> +#include <linux/sched/clock.h> + +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); 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 { /* |
