12cffe9f6SMarcelo Tosatti // SPDX-License-Identifier: GPL-2.0
22cffe9f6SMarcelo Tosatti /*
32cffe9f6SMarcelo Tosatti  * haltpoll.c - haltpoll idle governor
42cffe9f6SMarcelo Tosatti  *
52cffe9f6SMarcelo Tosatti  * Copyright 2019 Red Hat, Inc. and/or its affiliates.
62cffe9f6SMarcelo Tosatti  *
72cffe9f6SMarcelo Tosatti  * This work is licensed under the terms of the GNU GPL, version 2.  See
82cffe9f6SMarcelo Tosatti  * the COPYING file in the top-level directory.
92cffe9f6SMarcelo Tosatti  *
102cffe9f6SMarcelo Tosatti  * Authors: Marcelo Tosatti <mtosatti@redhat.com>
112cffe9f6SMarcelo Tosatti  */
122cffe9f6SMarcelo Tosatti 
132cffe9f6SMarcelo Tosatti #include <linux/kernel.h>
142cffe9f6SMarcelo Tosatti #include <linux/cpuidle.h>
152cffe9f6SMarcelo Tosatti #include <linux/time.h>
162cffe9f6SMarcelo Tosatti #include <linux/ktime.h>
172cffe9f6SMarcelo Tosatti #include <linux/hrtimer.h>
182cffe9f6SMarcelo Tosatti #include <linux/tick.h>
192cffe9f6SMarcelo Tosatti #include <linux/sched.h>
202cffe9f6SMarcelo Tosatti #include <linux/module.h>
212cffe9f6SMarcelo Tosatti #include <linux/kvm_para.h>
222cffe9f6SMarcelo Tosatti 
232cffe9f6SMarcelo Tosatti static unsigned int guest_halt_poll_ns __read_mostly = 200000;
242cffe9f6SMarcelo Tosatti module_param(guest_halt_poll_ns, uint, 0644);
252cffe9f6SMarcelo Tosatti 
262cffe9f6SMarcelo Tosatti /* division factor to shrink halt_poll_ns */
272cffe9f6SMarcelo Tosatti static unsigned int guest_halt_poll_shrink __read_mostly = 2;
282cffe9f6SMarcelo Tosatti module_param(guest_halt_poll_shrink, uint, 0644);
292cffe9f6SMarcelo Tosatti 
302cffe9f6SMarcelo Tosatti /* multiplication factor to grow per-cpu poll_limit_ns */
312cffe9f6SMarcelo Tosatti static unsigned int guest_halt_poll_grow __read_mostly = 2;
322cffe9f6SMarcelo Tosatti module_param(guest_halt_poll_grow, uint, 0644);
332cffe9f6SMarcelo Tosatti 
342cffe9f6SMarcelo Tosatti /* value in us to start growing per-cpu halt_poll_ns */
352cffe9f6SMarcelo Tosatti static unsigned int guest_halt_poll_grow_start __read_mostly = 50000;
362cffe9f6SMarcelo Tosatti module_param(guest_halt_poll_grow_start, uint, 0644);
372cffe9f6SMarcelo Tosatti 
382cffe9f6SMarcelo Tosatti /* allow shrinking guest halt poll */
392cffe9f6SMarcelo Tosatti static bool guest_halt_poll_allow_shrink __read_mostly = true;
402cffe9f6SMarcelo Tosatti module_param(guest_halt_poll_allow_shrink, bool, 0644);
412cffe9f6SMarcelo Tosatti 
422cffe9f6SMarcelo Tosatti /**
432cffe9f6SMarcelo Tosatti  * haltpoll_select - selects the next idle state to enter
442cffe9f6SMarcelo Tosatti  * @drv: cpuidle driver containing state data
452cffe9f6SMarcelo Tosatti  * @dev: the CPU
462cffe9f6SMarcelo Tosatti  * @stop_tick: indication on whether or not to stop the tick
472cffe9f6SMarcelo Tosatti  */
482cffe9f6SMarcelo Tosatti static int haltpoll_select(struct cpuidle_driver *drv,
492cffe9f6SMarcelo Tosatti 			   struct cpuidle_device *dev,
502cffe9f6SMarcelo Tosatti 			   bool *stop_tick)
512cffe9f6SMarcelo Tosatti {
522cffe9f6SMarcelo Tosatti 	int latency_req = cpuidle_governor_latency_req(dev->cpu);
532cffe9f6SMarcelo Tosatti 
542cffe9f6SMarcelo Tosatti 	if (!drv->state_count || latency_req == 0) {
552cffe9f6SMarcelo Tosatti 		*stop_tick = false;
562cffe9f6SMarcelo Tosatti 		return 0;
572cffe9f6SMarcelo Tosatti 	}
582cffe9f6SMarcelo Tosatti 
592cffe9f6SMarcelo Tosatti 	if (dev->poll_limit_ns == 0)
602cffe9f6SMarcelo Tosatti 		return 1;
612cffe9f6SMarcelo Tosatti 
622cffe9f6SMarcelo Tosatti 	/* Last state was poll? */
632cffe9f6SMarcelo Tosatti 	if (dev->last_state_idx == 0) {
642cffe9f6SMarcelo Tosatti 		/* Halt if no event occurred on poll window */
652cffe9f6SMarcelo Tosatti 		if (dev->poll_time_limit == true)
662cffe9f6SMarcelo Tosatti 			return 1;
672cffe9f6SMarcelo Tosatti 
682cffe9f6SMarcelo Tosatti 		*stop_tick = false;
692cffe9f6SMarcelo Tosatti 		/* Otherwise, poll again */
702cffe9f6SMarcelo Tosatti 		return 0;
712cffe9f6SMarcelo Tosatti 	}
722cffe9f6SMarcelo Tosatti 
732cffe9f6SMarcelo Tosatti 	*stop_tick = false;
742cffe9f6SMarcelo Tosatti 	/* Last state was halt: poll */
752cffe9f6SMarcelo Tosatti 	return 0;
762cffe9f6SMarcelo Tosatti }
772cffe9f6SMarcelo Tosatti 
782cffe9f6SMarcelo Tosatti static void adjust_poll_limit(struct cpuidle_device *dev, unsigned int block_us)
792cffe9f6SMarcelo Tosatti {
802cffe9f6SMarcelo Tosatti 	unsigned int val;
812cffe9f6SMarcelo Tosatti 	u64 block_ns = block_us*NSEC_PER_USEC;
822cffe9f6SMarcelo Tosatti 
832cffe9f6SMarcelo Tosatti 	/* Grow cpu_halt_poll_us if
842cffe9f6SMarcelo Tosatti 	 * cpu_halt_poll_us < block_ns < guest_halt_poll_us
852cffe9f6SMarcelo Tosatti 	 */
862cffe9f6SMarcelo Tosatti 	if (block_ns > dev->poll_limit_ns && block_ns <= guest_halt_poll_ns) {
872cffe9f6SMarcelo Tosatti 		val = dev->poll_limit_ns * guest_halt_poll_grow;
882cffe9f6SMarcelo Tosatti 
892cffe9f6SMarcelo Tosatti 		if (val < guest_halt_poll_grow_start)
902cffe9f6SMarcelo Tosatti 			val = guest_halt_poll_grow_start;
912cffe9f6SMarcelo Tosatti 		if (val > guest_halt_poll_ns)
922cffe9f6SMarcelo Tosatti 			val = guest_halt_poll_ns;
932cffe9f6SMarcelo Tosatti 
942cffe9f6SMarcelo Tosatti 		dev->poll_limit_ns = val;
952cffe9f6SMarcelo Tosatti 	} else if (block_ns > guest_halt_poll_ns &&
962cffe9f6SMarcelo Tosatti 		   guest_halt_poll_allow_shrink) {
972cffe9f6SMarcelo Tosatti 		unsigned int shrink = guest_halt_poll_shrink;
982cffe9f6SMarcelo Tosatti 
992cffe9f6SMarcelo Tosatti 		val = dev->poll_limit_ns;
1002cffe9f6SMarcelo Tosatti 		if (shrink == 0)
1012cffe9f6SMarcelo Tosatti 			val = 0;
1022cffe9f6SMarcelo Tosatti 		else
1032cffe9f6SMarcelo Tosatti 			val /= shrink;
1042cffe9f6SMarcelo Tosatti 		dev->poll_limit_ns = val;
1052cffe9f6SMarcelo Tosatti 	}
1062cffe9f6SMarcelo Tosatti }
1072cffe9f6SMarcelo Tosatti 
1082cffe9f6SMarcelo Tosatti /**
1092cffe9f6SMarcelo Tosatti  * haltpoll_reflect - update variables and update poll time
1102cffe9f6SMarcelo Tosatti  * @dev: the CPU
1112cffe9f6SMarcelo Tosatti  * @index: the index of actual entered state
1122cffe9f6SMarcelo Tosatti  */
1132cffe9f6SMarcelo Tosatti static void haltpoll_reflect(struct cpuidle_device *dev, int index)
1142cffe9f6SMarcelo Tosatti {
1152cffe9f6SMarcelo Tosatti 	dev->last_state_idx = index;
1162cffe9f6SMarcelo Tosatti 
1172cffe9f6SMarcelo Tosatti 	if (index != 0)
1182cffe9f6SMarcelo Tosatti 		adjust_poll_limit(dev, dev->last_residency);
1192cffe9f6SMarcelo Tosatti }
1202cffe9f6SMarcelo Tosatti 
1212cffe9f6SMarcelo Tosatti /**
1222cffe9f6SMarcelo Tosatti  * haltpoll_enable_device - scans a CPU's states and does setup
1232cffe9f6SMarcelo Tosatti  * @drv: cpuidle driver
1242cffe9f6SMarcelo Tosatti  * @dev: the CPU
1252cffe9f6SMarcelo Tosatti  */
1262cffe9f6SMarcelo Tosatti static int haltpoll_enable_device(struct cpuidle_driver *drv,
1272cffe9f6SMarcelo Tosatti 				  struct cpuidle_device *dev)
1282cffe9f6SMarcelo Tosatti {
1292cffe9f6SMarcelo Tosatti 	dev->poll_limit_ns = 0;
1302cffe9f6SMarcelo Tosatti 
1312cffe9f6SMarcelo Tosatti 	return 0;
1322cffe9f6SMarcelo Tosatti }
1332cffe9f6SMarcelo Tosatti 
1342cffe9f6SMarcelo Tosatti static struct cpuidle_governor haltpoll_governor = {
1352cffe9f6SMarcelo Tosatti 	.name =			"haltpoll",
13673214408SJoao Martins 	.rating =		9,
1372cffe9f6SMarcelo Tosatti 	.enable =		haltpoll_enable_device,
1382cffe9f6SMarcelo Tosatti 	.select =		haltpoll_select,
1392cffe9f6SMarcelo Tosatti 	.reflect =		haltpoll_reflect,
1402cffe9f6SMarcelo Tosatti };
1412cffe9f6SMarcelo Tosatti 
1422cffe9f6SMarcelo Tosatti static int __init init_haltpoll(void)
1432cffe9f6SMarcelo Tosatti {
1442cffe9f6SMarcelo Tosatti 	if (kvm_para_available())
1452cffe9f6SMarcelo Tosatti 		return cpuidle_register_governor(&haltpoll_governor);
1462cffe9f6SMarcelo Tosatti 
1472cffe9f6SMarcelo Tosatti 	return 0;
1482cffe9f6SMarcelo Tosatti }
1492cffe9f6SMarcelo Tosatti 
1502cffe9f6SMarcelo Tosatti postcore_initcall(init_haltpoll);
151