summaryrefslogtreecommitdiffstats
path: root/drivers/cpuidle/governors/haltpoll.c
blob: 1dff3a52917de9a6b79d1cd9e301a24659b412f6 (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
// SPDX-License-Identifier: GPL-2.0
/*
 * haltpoll.c - haltpoll idle governor
 *
 * Copyright 2019 Red Hat, Inc. and/or its affiliates.
 *
 * This work is licensed under the terms of the GNU GPL, version 2.  See
 * the COPYING file in the top-level directory.
 *
 * Authors: Marcelo Tosatti <mtosatti@redhat.com>
 */

#include <linux/kernel.h>
#include <linux/cpuidle.h>
#include <linux/time.h>
#include <linux/ktime.h>
#include <linux/hrtimer.h>
#include <linux/tick.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/kvm_para.h>
#include <trace/events/power.h>

static unsigned int guest_halt_poll_ns __read_mostly = 200000;
module_param(guest_halt_poll_ns, uint, 0644);

/* division factor to shrink halt_poll_ns */
static unsigned int guest_halt_poll_shrink __read_mostly = 2;
module_param(guest_halt_poll_shrink, uint, 0644);

/* multiplication factor to grow per-cpu poll_limit_ns */
static unsigned int guest_halt_poll_grow __read_mostly = 2;
module_param(guest_halt_poll_grow, uint, 0644);

/* value in us to start growing per-cpu halt_poll_ns */
static unsigned int guest_halt_poll_grow_start __read_mostly = 50000;
module_param(guest_halt_poll_grow_start, uint, 0644);

/* allow shrinking guest halt poll */
static bool guest_halt_poll_allow_shrink __read_mostly = true;
module_param(guest_halt_poll_allow_shrink, bool, 0644);

/**
 * haltpoll_select - selects the next idle state to enter
 * @drv: cpuidle driver containing state data
 * @dev: the CPU
 * @stop_tick: indication on whether or not to stop the tick
 */
static int haltpoll_select(struct cpuidle_driver *drv,
			   struct cpuidle_device *dev,
			   bool *stop_tick)
{
	s64 latency_req = cpuidle_governor_latency_req(dev->cpu);

	if (!drv->state_count || latency_req == 0) {
		*stop_tick = false;
		return 0;
	}

	if (dev->poll_limit_ns == 0)
		return 1;

	/* Last state was poll? */
	if (dev->last_state_idx == 0) {
		/* Halt if no event occurred on poll window */
		if (dev->poll_time_limit == true)
			return 1;

		*stop_tick = false;
		/* Otherwise, poll again */
		return 0;
	}

	*stop_tick = false;
	/* Last state was halt: poll */
	return 0;
}

static void adjust_poll_limit(struct cpuidle_device *dev, u64 block_ns)
{
	unsigned int val;

	/* Grow cpu_halt_poll_us if
	 * cpu_halt_poll_us < block_ns < guest_halt_poll_us
	 */
	if (block_ns > dev->poll_limit_ns && block_ns <= guest_halt_poll_ns) {
		val = dev->poll_limit_ns * guest_halt_poll_grow;

		if (val < guest_halt_poll_grow_start)
			val = guest_halt_poll_grow_start;
		if (val > guest_halt_poll_ns)
			val = guest_halt_poll_ns;

		trace_guest_halt_poll_ns_grow(val, dev->poll_limit_ns);
		dev->poll_limit_ns = val;
	} else if (block_ns > guest_halt_poll_ns &&
		   guest_halt_poll_allow_shrink) {
		unsigned int shrink = guest_halt_poll_shrink;

		val = dev->poll_limit_ns;
		if (shrink == 0)
			val = 0;
		else
			val /= shrink;
		trace_guest_halt_poll_ns_shrink(val, dev->poll_limit_ns);
		dev->poll_limit_ns = val;
	}
}

/**
 * haltpoll_reflect - update variables and update poll time
 * @dev: the CPU
 * @index: the index of actual entered state
 */
static void haltpoll_reflect(struct cpuidle_device *dev, int index)
{
	dev->last_state_idx = index;

	if (index != 0)
		adjust_poll_limit(dev, dev->last_residency_ns);
}

/**
 * haltpoll_enable_device - scans a CPU's states and does setup
 * @drv: cpuidle driver
 * @dev: the CPU
 */
static int haltpoll_enable_device(struct cpuidle_driver *drv,
				  struct cpuidle_device *dev)
{
	dev->poll_limit_ns = 0;

	return 0;
}

static struct cpuidle_governor haltpoll_governor = {
	.name =			"haltpoll",
	.rating =		9,
	.enable =		haltpoll_enable_device,
	.select =		haltpoll_select,
	.reflect =		haltpoll_reflect,
};

static int __init init_haltpoll(void)
{
	if (kvm_para_available())
		return cpuidle_register_governor(&haltpoll_governor);

	return 0;
}

postcore_initcall(init_haltpoll);