xref: /linux/drivers/virt/steal_governor.c (revision cb95904b470d2d347012d5e360303cf478ca5362)
19a8e740eSShrikanth Hegde // SPDX-License-Identifier: GPL-2.0-only
29a8e740eSShrikanth Hegde /*
39a8e740eSShrikanth Hegde  * Steal time governor driver periodically computes steal time.
49a8e740eSShrikanth Hegde  * Based on the thresholds it either reduce/increase the preferred
59a8e740eSShrikanth Hegde  * CPUs which can be used by the workload to avoid vCPU preemption
69a8e740eSShrikanth Hegde  * to an extent possible in paravirtualized environment.
79a8e740eSShrikanth Hegde  *
89a8e740eSShrikanth Hegde  * Available with CONFIG_STEAL_GOVERNOR
99a8e740eSShrikanth Hegde  *
109a8e740eSShrikanth Hegde  * Copyright (C) 2026 IBM
119a8e740eSShrikanth Hegde  * Author: Shrikanth Hegde <sshegde@linux.ibm.com>
129a8e740eSShrikanth Hegde  */
139a8e740eSShrikanth Hegde 
149a8e740eSShrikanth Hegde #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
159a8e740eSShrikanth Hegde 
16*27d47ebcSShrikanth Hegde #include <linux/cleanup.h>
179a8e740eSShrikanth Hegde #include <linux/cpuhplock.h>
189a8e740eSShrikanth Hegde #include <linux/cpumask.h>
199a8e740eSShrikanth Hegde #include <linux/init.h>
209a8e740eSShrikanth Hegde #include <linux/kernel.h>
21*27d47ebcSShrikanth Hegde #include <linux/kernel_stat.h>
229a8e740eSShrikanth Hegde #include <linux/kconfig.h>
239a8e740eSShrikanth Hegde #include <linux/ktime.h>
24*27d47ebcSShrikanth Hegde #include <linux/math64.h>
259a8e740eSShrikanth Hegde #include <linux/module.h>
26*27d47ebcSShrikanth Hegde #include <linux/sched/isolation.h>
27*27d47ebcSShrikanth Hegde #include <linux/topology.h>
289a8e740eSShrikanth Hegde #include <linux/types.h>
299a8e740eSShrikanth Hegde #include <linux/workqueue.h>
309a8e740eSShrikanth Hegde #ifdef CONFIG_XEN
319a8e740eSShrikanth Hegde #include <xen/xen.h>
329a8e740eSShrikanth Hegde #endif
339a8e740eSShrikanth Hegde 
349a8e740eSShrikanth Hegde #if !IS_ENABLED(CONFIG_PREFERRED_CPU)
359a8e740eSShrikanth Hegde #error "Steal Governor requires CONFIG_PREFERRED_CPU"
369a8e740eSShrikanth Hegde #endif
379a8e740eSShrikanth Hegde 
389a8e740eSShrikanth Hegde struct steal_governor {
399a8e740eSShrikanth Hegde 	ktime_t			time;
409a8e740eSShrikanth Hegde 	u64			steal;
419a8e740eSShrikanth Hegde 	unsigned long		delay;
429a8e740eSShrikanth Hegde 	unsigned int		interval_ms;
439a8e740eSShrikanth Hegde 	unsigned int		high_threshold;
449a8e740eSShrikanth Hegde 	unsigned int		low_threshold;
459a8e740eSShrikanth Hegde 	struct delayed_work	work;
469a8e740eSShrikanth Hegde };
479a8e740eSShrikanth Hegde 
484b9302d4SShrikanth Hegde static struct steal_governor sg_ctx = {
494b9302d4SShrikanth Hegde 	.interval_ms	=	1000,	/* 1 second */
504b9302d4SShrikanth Hegde 	.high_threshold =	500,	/* 5% */
514b9302d4SShrikanth Hegde 	.low_threshold	=	200,	/* 2% */
524b9302d4SShrikanth Hegde };
539a8e740eSShrikanth Hegde 
restore_preferred_to_active(void)549a8e740eSShrikanth Hegde static void restore_preferred_to_active(void)
559a8e740eSShrikanth Hegde {
569a8e740eSShrikanth Hegde 	int cpu;
579a8e740eSShrikanth Hegde 
589a8e740eSShrikanth Hegde 	guard(cpus_read_lock)();
599a8e740eSShrikanth Hegde 	for_each_cpu(cpu, cpu_active_mask)
609a8e740eSShrikanth Hegde 		set_cpu_preferred(cpu, true);
619a8e740eSShrikanth Hegde }
629a8e740eSShrikanth Hegde 
param_set_interval_ms(const char * val,const struct kernel_param * kp)634b9302d4SShrikanth Hegde static int param_set_interval_ms(const char *val, const struct kernel_param *kp)
644b9302d4SShrikanth Hegde {
654b9302d4SShrikanth Hegde 	unsigned int interval;
664b9302d4SShrikanth Hegde 	int ret;
674b9302d4SShrikanth Hegde 
684b9302d4SShrikanth Hegde 	ret = kstrtouint(val, 0, &interval);
694b9302d4SShrikanth Hegde 	if (ret)
704b9302d4SShrikanth Hegde 		return ret;
714b9302d4SShrikanth Hegde 
724b9302d4SShrikanth Hegde 	if (interval < 100 || interval > 100000) {
734b9302d4SShrikanth Hegde 		pr_err("interval_ms must be between 100 and 100000\n");
744b9302d4SShrikanth Hegde 		return -EINVAL;
754b9302d4SShrikanth Hegde 	}
764b9302d4SShrikanth Hegde 
774b9302d4SShrikanth Hegde 	return param_set_uint(val, kp);
784b9302d4SShrikanth Hegde }
794b9302d4SShrikanth Hegde 
804b9302d4SShrikanth Hegde static const struct kernel_param_ops interval_ms_ops = {
814b9302d4SShrikanth Hegde 	.set = param_set_interval_ms,
824b9302d4SShrikanth Hegde 	.get = param_get_uint,
834b9302d4SShrikanth Hegde };
844b9302d4SShrikanth Hegde 
854b9302d4SShrikanth Hegde module_param_cb(interval_ms, &interval_ms_ops, &sg_ctx.interval_ms, 0444);
864b9302d4SShrikanth Hegde MODULE_PARM_DESC(interval_ms,
874b9302d4SShrikanth Hegde 		 "Sampling frequency in milliseconds. default: 1000");
884b9302d4SShrikanth Hegde 
param_set_high_threshold(const char * val,const struct kernel_param * kp)894b9302d4SShrikanth Hegde static int param_set_high_threshold(const char *val, const struct kernel_param *kp)
904b9302d4SShrikanth Hegde {
914b9302d4SShrikanth Hegde 	unsigned int threshold;
924b9302d4SShrikanth Hegde 	int ret;
934b9302d4SShrikanth Hegde 
944b9302d4SShrikanth Hegde 	ret = kstrtouint(val, 0, &threshold);
954b9302d4SShrikanth Hegde 	if (ret)
964b9302d4SShrikanth Hegde 		return ret;
974b9302d4SShrikanth Hegde 
984b9302d4SShrikanth Hegde 	if (threshold >= 100 * 100) {
994b9302d4SShrikanth Hegde 		pr_err("high_threshold (%u) can't be more than 99.99%%\n", threshold);
1004b9302d4SShrikanth Hegde 		return -EINVAL;
1014b9302d4SShrikanth Hegde 	}
1024b9302d4SShrikanth Hegde 
1034b9302d4SShrikanth Hegde 	return param_set_uint(val, kp);
1044b9302d4SShrikanth Hegde }
1054b9302d4SShrikanth Hegde 
1064b9302d4SShrikanth Hegde static const struct kernel_param_ops high_threshold_ops = {
1074b9302d4SShrikanth Hegde 	.set = param_set_high_threshold,
1084b9302d4SShrikanth Hegde 	.get = param_get_uint,
1094b9302d4SShrikanth Hegde };
1104b9302d4SShrikanth Hegde 
1114b9302d4SShrikanth Hegde module_param_cb(high_threshold, &high_threshold_ops, &sg_ctx.high_threshold, 0444);
1124b9302d4SShrikanth Hegde MODULE_PARM_DESC(high_threshold,
1134b9302d4SShrikanth Hegde 		 "High steal threshold. default: 500 i.e 5%. Must be > low_threshold");
1144b9302d4SShrikanth Hegde 
1154b9302d4SShrikanth Hegde module_param_named(low_threshold, sg_ctx.low_threshold, uint, 0444);
1164b9302d4SShrikanth Hegde MODULE_PARM_DESC(low_threshold,
1174b9302d4SShrikanth Hegde 		 "Low steal threshold. default: 200 i.e 2%. Must be < high_threshold");
1184b9302d4SShrikanth Hegde 
119*27d47ebcSShrikanth Hegde /* Return collective steal time across system. */
get_system_steal_time(void)120*27d47ebcSShrikanth Hegde static u64 get_system_steal_time(void)
121*27d47ebcSShrikanth Hegde {
122*27d47ebcSShrikanth Hegde 	return kcpustat_field_total(CPUTIME_STEAL, cpu_possible_mask);
123*27d47ebcSShrikanth Hegde }
124*27d47ebcSShrikanth Hegde 
125*27d47ebcSShrikanth Hegde /* Return number of CPUs to consider for steal ratio. */
get_system_cpus(void)126*27d47ebcSShrikanth Hegde static unsigned int get_system_cpus(void)
127*27d47ebcSShrikanth Hegde {
128*27d47ebcSShrikanth Hegde 	return num_active_cpus();
129*27d47ebcSShrikanth Hegde }
130*27d47ebcSShrikanth Hegde 
131*27d47ebcSShrikanth Hegde /*
132*27d47ebcSShrikanth Hegde  * Called when the steal governor detects high physical CPU contention.
133*27d47ebcSShrikanth Hegde  * It finds the last active core in the preferred mask and mark those
134*27d47ebcSShrikanth Hegde  * CPUs as non-preferred.
135*27d47ebcSShrikanth Hegde  *
136*27d47ebcSShrikanth Hegde  * Must ensure:
137*27d47ebcSShrikanth Hegde  * - at least one core is always kept as preferred
138*27d47ebcSShrikanth Hegde  * - preferred is always subset of active.
139*27d47ebcSShrikanth Hegde  */
decrease_preferred_cpus(void)140*27d47ebcSShrikanth Hegde static void decrease_preferred_cpus(void)
141*27d47ebcSShrikanth Hegde {
142*27d47ebcSShrikanth Hegde 	const struct cpumask *first_hk_core;
143*27d47ebcSShrikanth Hegde 	int target_cpu = nr_cpu_ids;
144*27d47ebcSShrikanth Hegde 	int cpu;
145*27d47ebcSShrikanth Hegde 
146*27d47ebcSShrikanth Hegde 	guard(cpus_read_lock)();
147*27d47ebcSShrikanth Hegde 	cpu = cpumask_first_and(housekeeping_cpumask(HK_TYPE_KERNEL_NOISE),
148*27d47ebcSShrikanth Hegde 				cpu_preferred_mask);
149*27d47ebcSShrikanth Hegde 	if (cpu >= nr_cpu_ids)
150*27d47ebcSShrikanth Hegde 		return;
151*27d47ebcSShrikanth Hegde 
152*27d47ebcSShrikanth Hegde 	/* Always leave first housekeeping core as preferred. */
153*27d47ebcSShrikanth Hegde 	first_hk_core = topology_sibling_cpumask(cpu);
154*27d47ebcSShrikanth Hegde 	cpu = cpumask_last(cpu_preferred_mask);
155*27d47ebcSShrikanth Hegde 	if (cpu >= nr_cpu_ids)
156*27d47ebcSShrikanth Hegde 		return;
157*27d47ebcSShrikanth Hegde 
158*27d47ebcSShrikanth Hegde 	/* Find the last CPU which doesn't belong to that first hk_core. */
159*27d47ebcSShrikanth Hegde 	if (!cpumask_test_cpu(cpu, first_hk_core)) {
160*27d47ebcSShrikanth Hegde 		target_cpu = cpu;
161*27d47ebcSShrikanth Hegde 	} else {
162*27d47ebcSShrikanth Hegde 		for_each_cpu_andnot(cpu, cpu_preferred_mask, first_hk_core)
163*27d47ebcSShrikanth Hegde 			target_cpu = cpu;
164*27d47ebcSShrikanth Hegde 	}
165*27d47ebcSShrikanth Hegde 
166*27d47ebcSShrikanth Hegde 	/* Only the first housekeeping core remains */
167*27d47ebcSShrikanth Hegde 	if (target_cpu >= nr_cpu_ids)
168*27d47ebcSShrikanth Hegde 		return;
169*27d47ebcSShrikanth Hegde 
170*27d47ebcSShrikanth Hegde 	for_each_cpu_and(cpu, topology_sibling_cpumask(target_cpu),
171*27d47ebcSShrikanth Hegde 			 cpu_preferred_mask)
172*27d47ebcSShrikanth Hegde 		set_cpu_preferred(cpu, false);
173*27d47ebcSShrikanth Hegde }
174*27d47ebcSShrikanth Hegde 
175*27d47ebcSShrikanth Hegde /*
176*27d47ebcSShrikanth Hegde  * Called when the steal governor detects no/low physical CPU contention.
177*27d47ebcSShrikanth Hegde  * It finds the first active core outside of preferred mask and mark
178*27d47ebcSShrikanth Hegde  * those CPUs as preferred.
179*27d47ebcSShrikanth Hegde  *
180*27d47ebcSShrikanth Hegde  * Must ensure preferred is subset of active.
181*27d47ebcSShrikanth Hegde  */
increase_preferred_cpus(void)182*27d47ebcSShrikanth Hegde static void increase_preferred_cpus(void)
183*27d47ebcSShrikanth Hegde {
184*27d47ebcSShrikanth Hegde 	int first_cpu, cpu;
185*27d47ebcSShrikanth Hegde 
186*27d47ebcSShrikanth Hegde 	guard(cpus_read_lock)();
187*27d47ebcSShrikanth Hegde 	first_cpu = cpumask_first_andnot(cpu_active_mask, cpu_preferred_mask);
188*27d47ebcSShrikanth Hegde 
189*27d47ebcSShrikanth Hegde 	/* All CPUs are preferred. Nothing to increase further */
190*27d47ebcSShrikanth Hegde 	if (first_cpu >= nr_cpu_ids)
191*27d47ebcSShrikanth Hegde 		return;
192*27d47ebcSShrikanth Hegde 
193*27d47ebcSShrikanth Hegde 	for_each_cpu_and(cpu, topology_sibling_cpumask(first_cpu),
194*27d47ebcSShrikanth Hegde 			 cpu_active_mask)
195*27d47ebcSShrikanth Hegde 		set_cpu_preferred(cpu, true);
196*27d47ebcSShrikanth Hegde }
197*27d47ebcSShrikanth Hegde 
preferred_cpus_valid(void)198*27d47ebcSShrikanth Hegde static bool preferred_cpus_valid(void)
199*27d47ebcSShrikanth Hegde {
200*27d47ebcSShrikanth Hegde 	if (cpumask_empty(cpu_preferred_mask)) {
201*27d47ebcSShrikanth Hegde 		pr_err("empty preferred mask. stopping\n");
202*27d47ebcSShrikanth Hegde 		return false;
203*27d47ebcSShrikanth Hegde 	}
204*27d47ebcSShrikanth Hegde 
205*27d47ebcSShrikanth Hegde 	if (!cpumask_subset(cpu_preferred_mask, cpu_active_mask)) {
206*27d47ebcSShrikanth Hegde 		pr_err("preferred: %*pbl is not subset of active: %*pbl, stopping\n",
207*27d47ebcSShrikanth Hegde 		       cpumask_pr_args(cpu_preferred_mask),
208*27d47ebcSShrikanth Hegde 		       cpumask_pr_args(cpu_active_mask));
209*27d47ebcSShrikanth Hegde 		return false;
210*27d47ebcSShrikanth Hegde 	}
211*27d47ebcSShrikanth Hegde 
212*27d47ebcSShrikanth Hegde 	return true;
213*27d47ebcSShrikanth Hegde }
214*27d47ebcSShrikanth Hegde 
steal_governor_loop(struct work_struct * work)215*27d47ebcSShrikanth Hegde static void steal_governor_loop(struct work_struct *work)
216*27d47ebcSShrikanth Hegde {
217*27d47ebcSShrikanth Hegde 	u64 curr_steal, delta_steal, delta_ns, steal_ratio;
218*27d47ebcSShrikanth Hegde 	ktime_t now;
219*27d47ebcSShrikanth Hegde 
220*27d47ebcSShrikanth Hegde 	now = ktime_get();
221*27d47ebcSShrikanth Hegde 	delta_ns = ktime_to_ns(ktime_sub(now, sg_ctx.time));
222*27d47ebcSShrikanth Hegde 
223*27d47ebcSShrikanth Hegde 	if (unlikely(delta_ns < NSEC_PER_MSEC)) {
224*27d47ebcSShrikanth Hegde 		pr_err_ratelimited("work scheduled too soon delta_ns: %llu\n", delta_ns);
225*27d47ebcSShrikanth Hegde 		goto requeue_work;
226*27d47ebcSShrikanth Hegde 	}
227*27d47ebcSShrikanth Hegde 
228*27d47ebcSShrikanth Hegde 	curr_steal = get_system_steal_time();
229*27d47ebcSShrikanth Hegde 	delta_steal = curr_steal > sg_ctx.steal ? curr_steal - sg_ctx.steal : 0;
230*27d47ebcSShrikanth Hegde 	sg_ctx.steal = curr_steal;
231*27d47ebcSShrikanth Hegde 	sg_ctx.time = now;
232*27d47ebcSShrikanth Hegde 
233*27d47ebcSShrikanth Hegde 	/*
234*27d47ebcSShrikanth Hegde 	 * steal_ratio = (delta_steal * 100*100)/(delta_ns * num_cpus())
235*27d47ebcSShrikanth Hegde 	 * To avoid possible overflow, divide the denominator early.
236*27d47ebcSShrikanth Hegde 	 * Note minimum interval is 100ms.
237*27d47ebcSShrikanth Hegde 	 */
238*27d47ebcSShrikanth Hegde 	delta_ns = max_t(u64, div_u64(delta_ns * get_system_cpus(), 10000), 1);
239*27d47ebcSShrikanth Hegde 	steal_ratio = div64_u64(delta_steal, delta_ns);
240*27d47ebcSShrikanth Hegde 
241*27d47ebcSShrikanth Hegde 	if (steal_ratio > sg_ctx.high_threshold)
242*27d47ebcSShrikanth Hegde 		decrease_preferred_cpus();
243*27d47ebcSShrikanth Hegde 	else if (steal_ratio <= sg_ctx.low_threshold)
244*27d47ebcSShrikanth Hegde 		increase_preferred_cpus();
245*27d47ebcSShrikanth Hegde 	/*
246*27d47ebcSShrikanth Hegde 	 * else: steal ratio is within bounds. Still do design checks so that
247*27d47ebcSShrikanth Hegde 	 * module restores to active if CPU hotplug breaks those assumptions.
248*27d47ebcSShrikanth Hegde 	 */
249*27d47ebcSShrikanth Hegde 	if (!preferred_cpus_valid()) {
250*27d47ebcSShrikanth Hegde 		restore_preferred_to_active();
251*27d47ebcSShrikanth Hegde 		return;
252*27d47ebcSShrikanth Hegde 	}
253*27d47ebcSShrikanth Hegde 
254*27d47ebcSShrikanth Hegde requeue_work:
255*27d47ebcSShrikanth Hegde 	schedule_delayed_work(&sg_ctx.work, sg_ctx.delay);
256*27d47ebcSShrikanth Hegde }
257*27d47ebcSShrikanth Hegde 
steal_governor_init(void)2589a8e740eSShrikanth Hegde static int __init steal_governor_init(void)
2599a8e740eSShrikanth Hegde {
2609a8e740eSShrikanth Hegde #ifdef CONFIG_XEN
2619a8e740eSShrikanth Hegde 	if (xen_initial_domain()) {
2629a8e740eSShrikanth Hegde 		pr_err("Cannot load in Xen Dom0 (Host OS). Driver is for guests only.\n");
2639a8e740eSShrikanth Hegde 		return -ENODEV;
2649a8e740eSShrikanth Hegde 	}
2659a8e740eSShrikanth Hegde #endif
2669a8e740eSShrikanth Hegde 
2674b9302d4SShrikanth Hegde 	if (sg_ctx.low_threshold >= sg_ctx.high_threshold) {
2684b9302d4SShrikanth Hegde 		pr_err("low_threshold (%u) must be less than high_threshold (%u)\n",
2694b9302d4SShrikanth Hegde 		       sg_ctx.low_threshold, sg_ctx.high_threshold);
2704b9302d4SShrikanth Hegde 		return -EINVAL;
2714b9302d4SShrikanth Hegde 	}
2724b9302d4SShrikanth Hegde 
2734b9302d4SShrikanth Hegde 	sg_ctx.delay = msecs_to_jiffies(sg_ctx.interval_ms);
274*27d47ebcSShrikanth Hegde 	INIT_DELAYED_WORK(&sg_ctx.work, steal_governor_loop);
275*27d47ebcSShrikanth Hegde 	sg_ctx.steal = get_system_steal_time();
276*27d47ebcSShrikanth Hegde 	sg_ctx.time = ktime_get();
277*27d47ebcSShrikanth Hegde 	schedule_delayed_work(&sg_ctx.work, sg_ctx.delay);
2784b9302d4SShrikanth Hegde 	pr_info("enabled. interval: %ums, high_threshold: %u, low_threshold: %u\n",
2794b9302d4SShrikanth Hegde 		sg_ctx.interval_ms, sg_ctx.high_threshold, sg_ctx.low_threshold);
2804b9302d4SShrikanth Hegde 
2819a8e740eSShrikanth Hegde 	return 0;
2829a8e740eSShrikanth Hegde }
2839a8e740eSShrikanth Hegde 
steal_governor_exit(void)2849a8e740eSShrikanth Hegde static void __exit steal_governor_exit(void)
2859a8e740eSShrikanth Hegde {
286*27d47ebcSShrikanth Hegde 	disable_delayed_work_sync(&sg_ctx.work);
2879a8e740eSShrikanth Hegde 	restore_preferred_to_active();
2889a8e740eSShrikanth Hegde 	pr_info("disabled\n");
2899a8e740eSShrikanth Hegde }
2909a8e740eSShrikanth Hegde 
2919a8e740eSShrikanth Hegde module_init(steal_governor_init);
2929a8e740eSShrikanth Hegde module_exit(steal_governor_exit);
2939a8e740eSShrikanth Hegde 
2949a8e740eSShrikanth Hegde MODULE_LICENSE("GPL");
2959a8e740eSShrikanth Hegde MODULE_AUTHOR("IBM Corporation");
2969a8e740eSShrikanth Hegde MODULE_DESCRIPTION("Virtualization Steal Time Governor");
297