summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-27 14:04:29 +0100
committerMark Brown <broonie@kernel.org>2026-07-27 14:04:29 +0100
commit3eded33495da0d14fe3fef7405860289f2354148 (patch)
tree0965a046b38ef9fc515ad4c78abbde44705b87f4
parent1bf0c156581b1f81e8b5bd4b51d9632a444b9128 (diff)
parent8b313d717bcf4f944fb46fe4ed4d8680bdbc5648 (diff)
downloadlinux-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--MAINTAINERS8
-rw-r--r--arch/s390/Kconfig5
-rw-r--r--arch/s390/configs/debug_defconfig2
-rw-r--r--arch/s390/configs/defconfig2
-rw-r--r--arch/s390/include/asm/hugetlb.h4
-rw-r--r--arch/s390/include/asm/processor.h4
-rw-r--r--arch/s390/include/asm/thread_info.h1
-rw-r--r--arch/s390/kernel/idle.c1
-rw-r--r--arch/s390/kernel/irq.c4
-rw-r--r--arch/s390/kernel/smp.c1
-rw-r--r--arch/s390/kernel/traps.c41
-rw-r--r--arch/s390/mm/hugetlbpage.c10
-rw-r--r--drivers/cpuidle/Kconfig5
-rw-r--r--drivers/cpuidle/Kconfig.s39011
-rw-r--r--drivers/cpuidle/Makefile4
-rw-r--r--drivers/cpuidle/cpuidle-s390.c115
-rw-r--r--include/linux/tick.h3
-rw-r--r--kernel/time/tick-sched.c7
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 {
/*