mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-04-03 12:05:13 +02:00
[ Upstream commit0d785e2c32] 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:56e62a7370("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> Signed-off-by: Sasha Levin <sashal@kernel.org>
105 lines
2.7 KiB
C
105 lines
2.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Idle functions for s390.
|
|
*
|
|
* Copyright IBM Corp. 2014
|
|
*
|
|
* Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/kernel_stat.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/init.h>
|
|
#include <linux/cpu.h>
|
|
#include <trace/events/power.h>
|
|
#include <asm/cpu_mf.h>
|
|
#include <asm/cputime.h>
|
|
#include <asm/nmi.h>
|
|
#include <asm/smp.h>
|
|
#include "entry.h"
|
|
|
|
static DEFINE_PER_CPU(struct s390_idle_data, s390_idle);
|
|
|
|
void update_timer_idle(void)
|
|
{
|
|
struct s390_idle_data *idle = this_cpu_ptr(&s390_idle);
|
|
struct lowcore *lc = get_lowcore();
|
|
u64 cycles_new[8];
|
|
int i;
|
|
|
|
if (smp_cpu_mtid) {
|
|
stcctm(MT_DIAG, smp_cpu_mtid, cycles_new);
|
|
for (i = 0; i < smp_cpu_mtid; i++)
|
|
this_cpu_add(mt_cycles[i], cycles_new[i] - idle->mt_cycles_enter[i]);
|
|
}
|
|
|
|
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);
|
|
WRITE_ONCE(idle->idle_count, READ_ONCE(idle->idle_count) + 1);
|
|
account_idle_time(cputime_to_nsecs(idle_time));
|
|
}
|
|
|
|
void noinstr arch_cpu_idle(void)
|
|
{
|
|
struct s390_idle_data *idle = this_cpu_ptr(&s390_idle);
|
|
unsigned long psw_mask;
|
|
|
|
/* 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);
|
|
idle->clock_idle_enter = get_tod_clock_fast();
|
|
idle->timer_idle_enter = get_cpu_timer();
|
|
bpon();
|
|
__load_psw_mask(psw_mask);
|
|
}
|
|
|
|
static ssize_t show_idle_count(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct s390_idle_data *idle = &per_cpu(s390_idle, dev->id);
|
|
|
|
return sysfs_emit(buf, "%lu\n", READ_ONCE(idle->idle_count));
|
|
}
|
|
DEVICE_ATTR(idle_count, 0444, show_idle_count, NULL);
|
|
|
|
static ssize_t show_idle_time(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct s390_idle_data *idle = &per_cpu(s390_idle, dev->id);
|
|
|
|
return sysfs_emit(buf, "%lu\n", READ_ONCE(idle->idle_time) >> 12);
|
|
}
|
|
DEVICE_ATTR(idle_time_us, 0444, show_idle_time, NULL);
|
|
|
|
void arch_cpu_idle_enter(void)
|
|
{
|
|
}
|
|
|
|
void arch_cpu_idle_exit(void)
|
|
{
|
|
}
|
|
|
|
void __noreturn arch_cpu_idle_dead(void)
|
|
{
|
|
cpu_die();
|
|
}
|