1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright IBM Corp. 2024
4 */
5
6 #define KMSG_COMPONENT "hd"
7 #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
8
9 /*
10 * Hiperdispatch:
11 * Dynamically calculates the optimum number of high capacity COREs
12 * by considering the state the system is in. When hiperdispatch decides
13 * that a capacity update is necessary, it schedules a topology update.
14 * During topology updates the CPU capacities are always re-adjusted.
15 *
16 * There is two places where CPU capacities are being accessed within
17 * hiperdispatch.
18 * -> hiperdispatch's reoccuring work function reads CPU capacities to
19 * determine high capacity CPU count.
20 * -> during a topology update hiperdispatch's adjustment function
21 * updates CPU capacities.
22 * These two can run on different CPUs in parallel which can cause
23 * hiperdispatch to make wrong decisions. This can potentially cause
24 * some overhead by leading to extra rebuild_sched_domains() calls
25 * for correction. Access to capacities within hiperdispatch has to be
26 * serialized to prevent the overhead.
27 *
28 * Hiperdispatch decision making revolves around steal time.
29 * HD_STEAL_THRESHOLD value is taken as reference. Whenever steal time
30 * crosses the threshold value hiperdispatch falls back to giving high
31 * capacities to entitled CPUs. When steal time drops below the
32 * threshold boundary, hiperdispatch utilizes all CPUs by giving all
33 * of them high capacity.
34 *
35 * The theory behind HD_STEAL_THRESHOLD is related to the SMP thread
36 * performance. Comparing the throughput of;
37 * - single CORE, with N threads, running N tasks
38 * - N separate COREs running N tasks,
39 * using individual COREs for individual tasks yield better
40 * performance. This performance difference is roughly ~30% (can change
41 * between machine generations)
42 *
43 * Hiperdispatch tries to hint scheduler to use individual COREs for
44 * each task, as long as steal time on those COREs are less than 30%,
45 * therefore delaying the throughput loss caused by using SMP threads.
46 */
47
48 #include <linux/cpumask.h>
49 #include <linux/debugfs.h>
50 #include <linux/device.h>
51 #include <linux/kernel_stat.h>
52 #include <linux/kstrtox.h>
53 #include <linux/ktime.h>
54 #include <linux/sysctl.h>
55 #include <linux/types.h>
56 #include <linux/workqueue.h>
57 #include <asm/hiperdispatch.h>
58 #include <asm/setup.h>
59 #include <asm/smp.h>
60 #include <asm/topology.h>
61
62 #define CREATE_TRACE_POINTS
63 #include <asm/trace/hiperdispatch.h>
64
65 #define HD_DELAY_FACTOR (4)
66 #define HD_DELAY_INTERVAL (HZ / 4)
67 #define HD_STEAL_THRESHOLD 30
68 #define HD_STEAL_AVG_WEIGHT 16
69
70 static cpumask_t hd_vl_coremask; /* Mask containing all vertical low COREs */
71 static cpumask_t hd_vmvl_cpumask; /* Mask containing vertical medium and low CPUs */
72 static int hd_high_capacity_cores; /* Current CORE count with high capacity */
73 static int hd_entitled_cores; /* Total vertical high and medium CORE count */
74 static int hd_online_cores; /* Current online CORE count */
75
76 static unsigned long hd_previous_steal; /* Previous iteration's CPU steal timer total */
77 static unsigned long hd_high_time; /* Total time spent while all cpus have high capacity */
78 static unsigned long hd_low_time; /* Total time spent while vl cpus have low capacity */
79 static atomic64_t hd_adjustments; /* Total occurrence count of hiperdispatch adjustments */
80
81 static unsigned int hd_steal_threshold = HD_STEAL_THRESHOLD;
82 static unsigned int hd_delay_factor = HD_DELAY_FACTOR;
83 static int hd_enabled;
84
85 static void hd_capacity_work_fn(struct work_struct *work);
86 static DECLARE_DELAYED_WORK(hd_capacity_work, hd_capacity_work_fn);
87
hd_set_hiperdispatch_mode(int enable)88 static int hd_set_hiperdispatch_mode(int enable)
89 {
90 if (!MACHINE_HAS_TOPOLOGY)
91 enable = 0;
92 if (hd_enabled == enable)
93 return 0;
94 hd_enabled = enable;
95 return 1;
96 }
97
hd_reset_state(void)98 void hd_reset_state(void)
99 {
100 cpumask_clear(&hd_vl_coremask);
101 cpumask_clear(&hd_vmvl_cpumask);
102 hd_entitled_cores = 0;
103 hd_online_cores = 0;
104 }
105
hd_add_core(int cpu)106 void hd_add_core(int cpu)
107 {
108 const struct cpumask *siblings;
109 int polarization;
110
111 hd_online_cores++;
112 polarization = smp_cpu_get_polarization(cpu);
113 siblings = topology_sibling_cpumask(cpu);
114 switch (polarization) {
115 case POLARIZATION_VH:
116 hd_entitled_cores++;
117 break;
118 case POLARIZATION_VM:
119 hd_entitled_cores++;
120 cpumask_or(&hd_vmvl_cpumask, &hd_vmvl_cpumask, siblings);
121 break;
122 case POLARIZATION_VL:
123 cpumask_set_cpu(cpu, &hd_vl_coremask);
124 cpumask_or(&hd_vmvl_cpumask, &hd_vmvl_cpumask, siblings);
125 break;
126 }
127 }
128
129 /* Serialize update and read operations of debug counters. */
130 static DEFINE_MUTEX(hd_counter_mutex);
131
hd_update_times(void)132 static void hd_update_times(void)
133 {
134 static ktime_t prev;
135 ktime_t now;
136
137 /*
138 * Check if hiperdispatch is active, if not set the prev to 0.
139 * This way it is possible to differentiate the first update iteration after
140 * enabling hiperdispatch.
141 */
142 if (hd_entitled_cores == 0 || hd_enabled == 0) {
143 prev = ktime_set(0, 0);
144 return;
145 }
146 now = ktime_get();
147 if (ktime_after(prev, 0)) {
148 if (hd_high_capacity_cores == hd_online_cores)
149 hd_high_time += ktime_ms_delta(now, prev);
150 else
151 hd_low_time += ktime_ms_delta(now, prev);
152 }
153 prev = now;
154 }
155
hd_update_capacities(void)156 static void hd_update_capacities(void)
157 {
158 int cpu, upscaling_cores;
159 unsigned long capacity;
160
161 upscaling_cores = hd_high_capacity_cores - hd_entitled_cores;
162 capacity = upscaling_cores > 0 ? CPU_CAPACITY_HIGH : CPU_CAPACITY_LOW;
163 hd_high_capacity_cores = hd_entitled_cores;
164 for_each_cpu(cpu, &hd_vl_coremask) {
165 smp_set_core_capacity(cpu, capacity);
166 if (capacity != CPU_CAPACITY_HIGH)
167 continue;
168 hd_high_capacity_cores++;
169 upscaling_cores--;
170 if (upscaling_cores == 0)
171 capacity = CPU_CAPACITY_LOW;
172 }
173 }
174
hd_disable_hiperdispatch(void)175 void hd_disable_hiperdispatch(void)
176 {
177 cancel_delayed_work_sync(&hd_capacity_work);
178 hd_high_capacity_cores = hd_online_cores;
179 hd_previous_steal = 0;
180 }
181
hd_enable_hiperdispatch(void)182 int hd_enable_hiperdispatch(void)
183 {
184 mutex_lock(&hd_counter_mutex);
185 hd_update_times();
186 mutex_unlock(&hd_counter_mutex);
187 if (hd_enabled == 0)
188 return 0;
189 if (hd_entitled_cores == 0)
190 return 0;
191 if (hd_online_cores <= hd_entitled_cores)
192 return 0;
193 mod_delayed_work(system_wq, &hd_capacity_work, HD_DELAY_INTERVAL * hd_delay_factor);
194 hd_update_capacities();
195 return 1;
196 }
197
hd_steal_avg(unsigned long new)198 static unsigned long hd_steal_avg(unsigned long new)
199 {
200 static unsigned long steal;
201
202 steal = (steal * (HD_STEAL_AVG_WEIGHT - 1) + new) / HD_STEAL_AVG_WEIGHT;
203 return steal;
204 }
205
hd_calculate_steal_percentage(void)206 static unsigned long hd_calculate_steal_percentage(void)
207 {
208 unsigned long time_delta, steal_delta, steal, percentage;
209 static ktime_t prev;
210 int cpus, cpu;
211 ktime_t now;
212
213 cpus = 0;
214 steal = 0;
215 percentage = 0;
216 for_each_cpu(cpu, &hd_vmvl_cpumask) {
217 steal += kcpustat_cpu(cpu).cpustat[CPUTIME_STEAL];
218 cpus++;
219 }
220 /*
221 * If there is no vertical medium and low CPUs steal time
222 * is 0 as vertical high CPUs shouldn't experience steal time.
223 */
224 if (cpus == 0)
225 return percentage;
226 now = ktime_get();
227 time_delta = ktime_to_ns(ktime_sub(now, prev));
228 if (steal > hd_previous_steal && hd_previous_steal != 0) {
229 steal_delta = (steal - hd_previous_steal) * 100 / time_delta;
230 percentage = steal_delta / cpus;
231 }
232 hd_previous_steal = steal;
233 prev = now;
234 return percentage;
235 }
236
hd_capacity_work_fn(struct work_struct * work)237 static void hd_capacity_work_fn(struct work_struct *work)
238 {
239 unsigned long steal_percentage, new_cores;
240
241 mutex_lock(&smp_cpu_state_mutex);
242 /*
243 * If online cores are less or equal to entitled cores hiperdispatch
244 * does not need to make any adjustments, call a topology update to
245 * disable hiperdispatch.
246 * Normally this check is handled on topology update, but during cpu
247 * unhotplug, topology and cpu mask updates are done in reverse
248 * order, causing hd_enable_hiperdispatch() to get stale data.
249 */
250 if (hd_online_cores <= hd_entitled_cores) {
251 topology_schedule_update();
252 mutex_unlock(&smp_cpu_state_mutex);
253 return;
254 }
255 steal_percentage = hd_steal_avg(hd_calculate_steal_percentage());
256 if (steal_percentage < hd_steal_threshold)
257 new_cores = hd_online_cores;
258 else
259 new_cores = hd_entitled_cores;
260 if (hd_high_capacity_cores != new_cores) {
261 trace_s390_hd_rebuild_domains(hd_high_capacity_cores, new_cores);
262 hd_high_capacity_cores = new_cores;
263 atomic64_inc(&hd_adjustments);
264 topology_schedule_update();
265 }
266 trace_s390_hd_work_fn(steal_percentage, hd_entitled_cores, hd_high_capacity_cores);
267 mutex_unlock(&smp_cpu_state_mutex);
268 schedule_delayed_work(&hd_capacity_work, HD_DELAY_INTERVAL);
269 }
270
hiperdispatch_ctl_handler(const struct ctl_table * ctl,int write,void * buffer,size_t * lenp,loff_t * ppos)271 static int hiperdispatch_ctl_handler(const struct ctl_table *ctl, int write,
272 void *buffer, size_t *lenp, loff_t *ppos)
273 {
274 int hiperdispatch;
275 int rc;
276 struct ctl_table ctl_entry = {
277 .procname = ctl->procname,
278 .data = &hiperdispatch,
279 .maxlen = sizeof(int),
280 .extra1 = SYSCTL_ZERO,
281 .extra2 = SYSCTL_ONE,
282 };
283
284 hiperdispatch = hd_enabled;
285 rc = proc_douintvec_minmax(&ctl_entry, write, buffer, lenp, ppos);
286 if (rc < 0 || !write)
287 return rc;
288 mutex_lock(&smp_cpu_state_mutex);
289 if (hd_set_hiperdispatch_mode(hiperdispatch))
290 topology_schedule_update();
291 mutex_unlock(&smp_cpu_state_mutex);
292 return 0;
293 }
294
295 static struct ctl_table hiperdispatch_ctl_table[] = {
296 {
297 .procname = "hiperdispatch",
298 .mode = 0644,
299 .proc_handler = hiperdispatch_ctl_handler,
300 },
301 };
302
hd_steal_threshold_show(struct device * dev,struct device_attribute * attr,char * buf)303 static ssize_t hd_steal_threshold_show(struct device *dev,
304 struct device_attribute *attr,
305 char *buf)
306 {
307 return sysfs_emit(buf, "%u\n", hd_steal_threshold);
308 }
309
hd_steal_threshold_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)310 static ssize_t hd_steal_threshold_store(struct device *dev,
311 struct device_attribute *attr,
312 const char *buf,
313 size_t count)
314 {
315 unsigned int val;
316 int rc;
317
318 rc = kstrtouint(buf, 0, &val);
319 if (rc)
320 return rc;
321 if (val > 100)
322 return -ERANGE;
323 hd_steal_threshold = val;
324 return count;
325 }
326
327 static DEVICE_ATTR_RW(hd_steal_threshold);
328
hd_delay_factor_show(struct device * dev,struct device_attribute * attr,char * buf)329 static ssize_t hd_delay_factor_show(struct device *dev,
330 struct device_attribute *attr,
331 char *buf)
332 {
333 return sysfs_emit(buf, "%u\n", hd_delay_factor);
334 }
335
hd_delay_factor_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)336 static ssize_t hd_delay_factor_store(struct device *dev,
337 struct device_attribute *attr,
338 const char *buf,
339 size_t count)
340 {
341 unsigned int val;
342 int rc;
343
344 rc = kstrtouint(buf, 0, &val);
345 if (rc)
346 return rc;
347 if (!val)
348 return -ERANGE;
349 hd_delay_factor = val;
350 return count;
351 }
352
353 static DEVICE_ATTR_RW(hd_delay_factor);
354
355 static struct attribute *hd_attrs[] = {
356 &dev_attr_hd_steal_threshold.attr,
357 &dev_attr_hd_delay_factor.attr,
358 NULL,
359 };
360
361 static const struct attribute_group hd_attr_group = {
362 .name = "hiperdispatch",
363 .attrs = hd_attrs,
364 };
365
hd_greedy_time_get(void * unused,u64 * val)366 static int hd_greedy_time_get(void *unused, u64 *val)
367 {
368 mutex_lock(&hd_counter_mutex);
369 hd_update_times();
370 *val = hd_high_time;
371 mutex_unlock(&hd_counter_mutex);
372 return 0;
373 }
374
375 DEFINE_SIMPLE_ATTRIBUTE(hd_greedy_time_fops, hd_greedy_time_get, NULL, "%llu\n");
376
hd_conservative_time_get(void * unused,u64 * val)377 static int hd_conservative_time_get(void *unused, u64 *val)
378 {
379 mutex_lock(&hd_counter_mutex);
380 hd_update_times();
381 *val = hd_low_time;
382 mutex_unlock(&hd_counter_mutex);
383 return 0;
384 }
385
386 DEFINE_SIMPLE_ATTRIBUTE(hd_conservative_time_fops, hd_conservative_time_get, NULL, "%llu\n");
387
hd_adjustment_count_get(void * unused,u64 * val)388 static int hd_adjustment_count_get(void *unused, u64 *val)
389 {
390 *val = atomic64_read(&hd_adjustments);
391 return 0;
392 }
393
394 DEFINE_SIMPLE_ATTRIBUTE(hd_adjustments_fops, hd_adjustment_count_get, NULL, "%llu\n");
395
hd_create_debugfs_counters(void)396 static void __init hd_create_debugfs_counters(void)
397 {
398 struct dentry *dir;
399
400 dir = debugfs_create_dir("hiperdispatch", arch_debugfs_dir);
401 debugfs_create_file("conservative_time_ms", 0400, dir, NULL, &hd_conservative_time_fops);
402 debugfs_create_file("greedy_time_ms", 0400, dir, NULL, &hd_greedy_time_fops);
403 debugfs_create_file("adjustment_count", 0400, dir, NULL, &hd_adjustments_fops);
404 }
405
hd_create_attributes(void)406 static void __init hd_create_attributes(void)
407 {
408 struct device *dev;
409
410 dev = bus_get_dev_root(&cpu_subsys);
411 if (!dev)
412 return;
413 if (sysfs_create_group(&dev->kobj, &hd_attr_group))
414 pr_warn("Unable to create hiperdispatch attribute group\n");
415 put_device(dev);
416 }
417
hd_init(void)418 static int __init hd_init(void)
419 {
420 if (IS_ENABLED(CONFIG_HIPERDISPATCH_ON)) {
421 hd_set_hiperdispatch_mode(1);
422 topology_schedule_update();
423 }
424 if (!register_sysctl("s390", hiperdispatch_ctl_table))
425 pr_warn("Failed to register s390.hiperdispatch sysctl attribute\n");
426 hd_create_debugfs_counters();
427 hd_create_attributes();
428 return 0;
429 }
430 late_initcall(hd_init);
431