162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * haltpoll.c - haltpoll idle governor 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2019 Red Hat, Inc. and/or its affiliates. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * This work is licensed under the terms of the GNU GPL, version 2. See 862306a36Sopenharmony_ci * the COPYING file in the top-level directory. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * Authors: Marcelo Tosatti <mtosatti@redhat.com> 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/cpuidle.h> 1562306a36Sopenharmony_ci#include <linux/time.h> 1662306a36Sopenharmony_ci#include <linux/ktime.h> 1762306a36Sopenharmony_ci#include <linux/hrtimer.h> 1862306a36Sopenharmony_ci#include <linux/tick.h> 1962306a36Sopenharmony_ci#include <linux/sched.h> 2062306a36Sopenharmony_ci#include <linux/module.h> 2162306a36Sopenharmony_ci#include <linux/kvm_para.h> 2262306a36Sopenharmony_ci#include <trace/events/power.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistatic unsigned int guest_halt_poll_ns __read_mostly = 200000; 2562306a36Sopenharmony_cimodule_param(guest_halt_poll_ns, uint, 0644); 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/* division factor to shrink halt_poll_ns */ 2862306a36Sopenharmony_cistatic unsigned int guest_halt_poll_shrink __read_mostly = 2; 2962306a36Sopenharmony_cimodule_param(guest_halt_poll_shrink, uint, 0644); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* multiplication factor to grow per-cpu poll_limit_ns */ 3262306a36Sopenharmony_cistatic unsigned int guest_halt_poll_grow __read_mostly = 2; 3362306a36Sopenharmony_cimodule_param(guest_halt_poll_grow, uint, 0644); 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* value in us to start growing per-cpu halt_poll_ns */ 3662306a36Sopenharmony_cistatic unsigned int guest_halt_poll_grow_start __read_mostly = 50000; 3762306a36Sopenharmony_cimodule_param(guest_halt_poll_grow_start, uint, 0644); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* allow shrinking guest halt poll */ 4062306a36Sopenharmony_cistatic bool guest_halt_poll_allow_shrink __read_mostly = true; 4162306a36Sopenharmony_cimodule_param(guest_halt_poll_allow_shrink, bool, 0644); 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci/** 4462306a36Sopenharmony_ci * haltpoll_select - selects the next idle state to enter 4562306a36Sopenharmony_ci * @drv: cpuidle driver containing state data 4662306a36Sopenharmony_ci * @dev: the CPU 4762306a36Sopenharmony_ci * @stop_tick: indication on whether or not to stop the tick 4862306a36Sopenharmony_ci */ 4962306a36Sopenharmony_cistatic int haltpoll_select(struct cpuidle_driver *drv, 5062306a36Sopenharmony_ci struct cpuidle_device *dev, 5162306a36Sopenharmony_ci bool *stop_tick) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci s64 latency_req = cpuidle_governor_latency_req(dev->cpu); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci if (!drv->state_count || latency_req == 0) { 5662306a36Sopenharmony_ci *stop_tick = false; 5762306a36Sopenharmony_ci return 0; 5862306a36Sopenharmony_ci } 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci if (dev->poll_limit_ns == 0) 6162306a36Sopenharmony_ci return 1; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci /* Last state was poll? */ 6462306a36Sopenharmony_ci if (dev->last_state_idx == 0) { 6562306a36Sopenharmony_ci /* Halt if no event occurred on poll window */ 6662306a36Sopenharmony_ci if (dev->poll_time_limit == true) 6762306a36Sopenharmony_ci return 1; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci *stop_tick = false; 7062306a36Sopenharmony_ci /* Otherwise, poll again */ 7162306a36Sopenharmony_ci return 0; 7262306a36Sopenharmony_ci } 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci *stop_tick = false; 7562306a36Sopenharmony_ci /* Last state was halt: poll */ 7662306a36Sopenharmony_ci return 0; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic void adjust_poll_limit(struct cpuidle_device *dev, u64 block_ns) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci unsigned int val; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci /* Grow cpu_halt_poll_us if 8462306a36Sopenharmony_ci * cpu_halt_poll_us < block_ns < guest_halt_poll_us 8562306a36Sopenharmony_ci */ 8662306a36Sopenharmony_ci if (block_ns > dev->poll_limit_ns && block_ns <= guest_halt_poll_ns) { 8762306a36Sopenharmony_ci val = dev->poll_limit_ns * guest_halt_poll_grow; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (val < guest_halt_poll_grow_start) 9062306a36Sopenharmony_ci val = guest_halt_poll_grow_start; 9162306a36Sopenharmony_ci if (val > guest_halt_poll_ns) 9262306a36Sopenharmony_ci val = guest_halt_poll_ns; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci trace_guest_halt_poll_ns_grow(val, dev->poll_limit_ns); 9562306a36Sopenharmony_ci dev->poll_limit_ns = val; 9662306a36Sopenharmony_ci } else if (block_ns > guest_halt_poll_ns && 9762306a36Sopenharmony_ci guest_halt_poll_allow_shrink) { 9862306a36Sopenharmony_ci unsigned int shrink = guest_halt_poll_shrink; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci val = dev->poll_limit_ns; 10162306a36Sopenharmony_ci if (shrink == 0) 10262306a36Sopenharmony_ci val = 0; 10362306a36Sopenharmony_ci else 10462306a36Sopenharmony_ci val /= shrink; 10562306a36Sopenharmony_ci trace_guest_halt_poll_ns_shrink(val, dev->poll_limit_ns); 10662306a36Sopenharmony_ci dev->poll_limit_ns = val; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci/** 11162306a36Sopenharmony_ci * haltpoll_reflect - update variables and update poll time 11262306a36Sopenharmony_ci * @dev: the CPU 11362306a36Sopenharmony_ci * @index: the index of actual entered state 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_cistatic void haltpoll_reflect(struct cpuidle_device *dev, int index) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci dev->last_state_idx = index; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (index != 0) 12062306a36Sopenharmony_ci adjust_poll_limit(dev, dev->last_residency_ns); 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/** 12462306a36Sopenharmony_ci * haltpoll_enable_device - scans a CPU's states and does setup 12562306a36Sopenharmony_ci * @drv: cpuidle driver 12662306a36Sopenharmony_ci * @dev: the CPU 12762306a36Sopenharmony_ci */ 12862306a36Sopenharmony_cistatic int haltpoll_enable_device(struct cpuidle_driver *drv, 12962306a36Sopenharmony_ci struct cpuidle_device *dev) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci dev->poll_limit_ns = 0; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci return 0; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic struct cpuidle_governor haltpoll_governor = { 13762306a36Sopenharmony_ci .name = "haltpoll", 13862306a36Sopenharmony_ci .rating = 9, 13962306a36Sopenharmony_ci .enable = haltpoll_enable_device, 14062306a36Sopenharmony_ci .select = haltpoll_select, 14162306a36Sopenharmony_ci .reflect = haltpoll_reflect, 14262306a36Sopenharmony_ci}; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic int __init init_haltpoll(void) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci if (kvm_para_available()) 14762306a36Sopenharmony_ci return cpuidle_register_governor(&haltpoll_governor); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci return 0; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cipostcore_initcall(init_haltpoll); 153