summaryrefslogtreecommitdiff
path: root/arch/s390/kernel/idle.c
blob: 08f3520c678530e0d668653e9e6f5de9993e0e37 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// 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/sched/stat.h>
#include <linux/notifier.h>
#include <linux/init.h>
#include <linux/cpu.h>
#include <linux/export.h>
#include <trace/events/power.h>
#include <asm/cpu_mf.h>
#include <asm/cputime.h>
#include <asm/idle.h>
#include <asm/nmi.h>
#include <asm/smp.h>

DEFINE_PER_CPU(struct s390_idle_data, s390_idle);

static __always_inline void __account_idle_time_irq(void)
{
	struct s390_idle_data *idle = this_cpu_ptr(&s390_idle);
	unsigned long idle_time;

	idle_time = idle->clock_idle_exit.tod - idle->clock_idle_enter.tod;
	account_idle_time(cputime_to_nsecs(idle_time));
}

static __always_inline void __account_idle_time_setup(void)
{
	struct s390_idle_data *idle = this_cpu_ptr(&s390_idle);

	store_tod_clock_ext(&idle->clock_idle_enter);
	idle->timer_idle_enter = get_cpu_timer();
	idle->clock_idle_exit = idle->clock_idle_enter;
}

#ifdef CONFIG_NO_HZ_COMMON

static u64 arch_cpu_in_idle_time(int cpu)
{
	struct s390_idle_data *idle = &per_cpu(s390_idle, cpu);
	union tod_clock now;
	u64 idle_time;

	if (!idle->in_idle)
		return 0;
	store_tod_clock_ext(&now);
	if (tod_after(idle->clock_idle_exit.tod, idle->clock_idle_enter.tod))
		idle_time = idle->clock_idle_exit.tod - idle->clock_idle_enter.tod;
	else
		idle_time = now.tod - idle->clock_idle_enter.tod;
	return cputime_to_nsecs(idle_time);
}

static u64 arch_cpu_idle_time(int cpu, enum cpu_usage_stat idx, bool compute_delta)
{
	struct kernel_cpustat *kc = &kcpustat_cpu(cpu);
	u64 *cpustat = kc->cpustat;
	unsigned int seq;
	u64 idle_time;

	/*
	 * The open coded seqcount writer in entry.S relies on the
	 * raw counting mechanism without any writer protection.
	 */
	typecheck(typeof(kc->idle_sleeptime_seq), seqcount_t);
	do {
		seq = read_seqcount_begin(&kc->idle_sleeptime_seq);
		idle_time = cpustat[idx];
		if (compute_delta)
			idle_time += arch_cpu_in_idle_time(cpu);
	} while (read_seqcount_retry(&kc->idle_sleeptime_seq, seq));
	return idle_time;
}

u64 arch_kcpustat_field_idle(int cpu)
{
	return arch_cpu_idle_time(cpu, CPUTIME_IDLE, !nr_iowait_cpu(cpu));
}
EXPORT_SYMBOL_GPL(arch_kcpustat_field_idle);

u64 arch_kcpustat_field_iowait(int cpu)
{
	return arch_cpu_idle_time(cpu, CPUTIME_IOWAIT, nr_iowait_cpu(cpu));
}
EXPORT_SYMBOL_GPL(arch_kcpustat_field_iowait);

void account_idle_time_irq(void)
{
	struct s390_idle_data *idle = this_cpu_ptr(&s390_idle);
	struct kernel_cpustat *kc = kcpustat_this_cpu;

	write_seqcount_begin(&kc->idle_sleeptime_seq);
	idle->in_idle = false;
	__account_idle_time_irq();
	write_seqcount_end(&kc->idle_sleeptime_seq);
}

static __always_inline void account_idle_time_setup(void)
{
	struct s390_idle_data *idle = this_cpu_ptr(&s390_idle);
	struct kernel_cpustat *kc = kcpustat_this_cpu;

	raw_write_seqcount_begin(&kc->idle_sleeptime_seq);
	idle->in_idle = true;
	__account_idle_time_setup();
	raw_write_seqcount_end(&kc->idle_sleeptime_seq);
}

#else  /* CONFIG_NO_HZ_COMMON */

void account_idle_time_irq(void)
{
	__account_idle_time_irq();
}

static __always_inline void account_idle_time_setup(void)
{
	__account_idle_time_setup();
}

#endif /* CONFIG_NO_HZ_COMMON */

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);
	account_idle_time_setup();
	bpon();
	__load_psw_mask(psw_mask);
}

void arch_cpu_idle_enter(void)
{
}

void arch_cpu_idle_exit(void)
{
}

void __noreturn arch_cpu_idle_dead(void)
{
	cpu_die();
}