]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-client.git/commitdiff
s390/idle: Fix cpu idle exit cpu time accounting
authorHeiko Carstens <hca@linux.ibm.com>
Wed, 18 Feb 2026 14:20:04 +0000 (15:20 +0100)
committerVasily Gorbik <gor@linux.ibm.com>
Wed, 25 Feb 2026 15:46:07 +0000 (16:46 +0100)
With the conversion to generic entry [1] cpu idle exit cpu time accounting
was converted from assembly to C. This introduced an reversed order of cpu
time accounting.

On cpu idle exit the current accounting happens with the following call
chain:

-> do_io_irq()/do_ext_irq()
 -> irq_enter_rcu()
  -> account_hardirq_enter()
   -> vtime_account_irq()
    -> vtime_account_kernel()

vtime_account_kernel() accounts the passed cpu time since last_update_timer
as system time, and updates last_update_timer to the current cpu timer
value.

However the subsequent call of

 -> account_idle_time_irq()

will incorrectly subtract passed cpu time from timer_idle_enter to the
updated last_update_timer value from system_timer. Then last_update_timer
is updated to a sys_enter_timer, which means that last_update_timer goes
back in time.

Subsequently account_hardirq_exit() will account too much cpu time as
hardirq time. The sum of all accounted cpu times is still correct, however
some cpu time which was previously accounted as system time is now
accounted as hardirq time, plus there is the oddity that last_update_timer
goes back in time.

Restore previous behavior by extracting cpu time accounting code from
account_idle_time_irq() into a new update_timer_idle() function and call it
before irq_enter_rcu().

Fixes: 56e62a737028 ("s390: convert to generic entry") [1]
Reviewed-by: Sven Schnelle <svens@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
arch/s390/include/asm/idle.h
arch/s390/kernel/idle.c
arch/s390/kernel/irq.c

index 09f763b9eb40aa4d38a80642ecf86a412720fa77..133059d9a949c7e5273cbcddcd6a6b5cf9603a8d 100644 (file)
@@ -23,5 +23,6 @@ extern struct device_attribute dev_attr_idle_count;
 extern struct device_attribute dev_attr_idle_time_us;
 
 void psw_idle(struct s390_idle_data *data, unsigned long psw_mask);
+void update_timer_idle(void);
 
 #endif /* _S390_IDLE_H */
index 39cb8d0ae3480647911f05b833bcc6c015ccfc69..0f9e53f0a06861c01f28fa342a094e34849f0e9d 100644 (file)
 
 static DEFINE_PER_CPU(struct s390_idle_data, s390_idle);
 
-void account_idle_time_irq(void)
+void update_timer_idle(void)
 {
        struct s390_idle_data *idle = this_cpu_ptr(&s390_idle);
        struct lowcore *lc = get_lowcore();
-       unsigned long idle_time;
        u64 cycles_new[8];
        int i;
 
@@ -35,13 +34,19 @@ void account_idle_time_irq(void)
                        this_cpu_add(mt_cycles[i], cycles_new[i] - idle->mt_cycles_enter[i]);
        }
 
-       idle_time = lc->int_clock - idle->clock_idle_enter;
-
        lc->steal_timer += idle->clock_idle_enter - lc->last_update_clock;
        lc->last_update_clock = lc->int_clock;
 
        lc->system_timer += lc->last_update_timer - idle->timer_idle_enter;
        lc->last_update_timer = lc->sys_enter_timer;
+}
+
+void account_idle_time_irq(void)
+{
+       struct s390_idle_data *idle = this_cpu_ptr(&s390_idle);
+       unsigned long idle_time;
+
+       idle_time = get_lowcore()->int_clock - idle->clock_idle_enter;
 
        /* Account time spent with enabled wait psw loaded as idle time. */
        WRITE_ONCE(idle->idle_time, READ_ONCE(idle->idle_time) + idle_time);
index f81723bc8856499e6ed0ac5ef6db68668a0729b4..d10a17e6531dae0d42fac6cd7ffef77f6cb2b131 100644 (file)
@@ -146,6 +146,10 @@ void noinstr do_io_irq(struct pt_regs *regs)
        struct pt_regs *old_regs = set_irq_regs(regs);
        bool from_idle;
 
+       from_idle = test_and_clear_cpu_flag(CIF_ENABLED_WAIT);
+       if (from_idle)
+               update_timer_idle();
+
        irq_enter_rcu();
 
        if (user_mode(regs)) {
@@ -154,7 +158,6 @@ void noinstr do_io_irq(struct pt_regs *regs)
                        current->thread.last_break = regs->last_break;
        }
 
-       from_idle = test_and_clear_cpu_flag(CIF_ENABLED_WAIT);
        if (from_idle)
                account_idle_time_irq();
 
@@ -182,6 +185,10 @@ void noinstr do_ext_irq(struct pt_regs *regs)
        struct pt_regs *old_regs = set_irq_regs(regs);
        bool from_idle;
 
+       from_idle = test_and_clear_cpu_flag(CIF_ENABLED_WAIT);
+       if (from_idle)
+               update_timer_idle();
+
        irq_enter_rcu();
 
        if (user_mode(regs)) {
@@ -194,7 +201,6 @@ void noinstr do_ext_irq(struct pt_regs *regs)
        regs->int_parm = get_lowcore()->ext_params;
        regs->int_parm_long = get_lowcore()->ext_params2;
 
-       from_idle = test_and_clear_cpu_flag(CIF_ENABLED_WAIT);
        if (from_idle)
                account_idle_time_irq();