2019-07-04 02:51:25 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* cpuidle driver for haltpoll 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/init.h>
2019-09-02 13:40:31 +03:00
# include <linux/cpu.h>
2019-07-04 02:51:25 +03:00
# include <linux/cpuidle.h>
# include <linux/module.h>
# include <linux/sched/idle.h>
# include <linux/kvm_para.h>
2019-07-04 02:51:29 +03:00
# include <linux/cpuidle_haltpoll.h>
2019-07-04 02:51:25 +03:00
2020-03-04 14:32:48 +03:00
static bool force __read_mostly ;
module_param ( force , bool , 0444 ) ;
MODULE_PARM_DESC ( force , " Load unconditionally " ) ;
2019-09-02 13:40:31 +03:00
static struct cpuidle_device __percpu * haltpoll_cpuidle_devices ;
static enum cpuhp_state haltpoll_hp_state ;
2019-07-04 02:51:25 +03:00
static int default_enter_idle ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv , int index )
{
if ( current_clr_polling_and_test ( ) ) {
local_irq_enable ( ) ;
return index ;
}
2023-01-06 07:03:42 +03:00
arch_cpu_idle ( ) ;
2019-07-04 02:51:25 +03:00
return index ;
}
static struct cpuidle_driver haltpoll_driver = {
. name = " haltpoll " ,
2019-09-08 02:45:22 +03:00
. governor = " haltpoll " ,
2019-07-04 02:51:25 +03:00
. states = {
{ /* entry 0 is for polling */ } ,
{
. enter = default_enter_idle ,
. exit_latency = 1 ,
. target_residency = 1 ,
. power_usage = - 1 ,
. name = " haltpoll idle " ,
. desc = " default architecture idle " ,
} ,
} ,
. safe_state_index = 0 ,
. state_count = 2 ,
} ;
2019-09-02 13:40:31 +03:00
static int haltpoll_cpu_online ( unsigned int cpu )
{
struct cpuidle_device * dev ;
dev = per_cpu_ptr ( haltpoll_cpuidle_devices , cpu ) ;
if ( ! dev - > registered ) {
dev - > cpu = cpu ;
if ( cpuidle_register_device ( dev ) ) {
pr_notice ( " cpuidle_register_device %d failed! \n " , cpu ) ;
return - EIO ;
}
arch_haltpoll_enable ( cpu ) ;
}
return 0 ;
}
static int haltpoll_cpu_offline ( unsigned int cpu )
{
struct cpuidle_device * dev ;
dev = per_cpu_ptr ( haltpoll_cpuidle_devices , cpu ) ;
if ( dev - > registered ) {
arch_haltpoll_disable ( cpu ) ;
cpuidle_unregister_device ( dev ) ;
}
return 0 ;
}
static void haltpoll_uninit ( void )
{
if ( haltpoll_hp_state )
cpuhp_remove_state ( haltpoll_hp_state ) ;
cpuidle_unregister_driver ( & haltpoll_driver ) ;
free_percpu ( haltpoll_cpuidle_devices ) ;
haltpoll_cpuidle_devices = NULL ;
}
2020-04-08 13:11:36 +03:00
static bool haltpoll_want ( void )
2020-03-04 14:32:48 +03:00
{
return kvm_para_has_hint ( KVM_HINTS_REALTIME ) | | force ;
}
2019-07-04 02:51:25 +03:00
static int __init haltpoll_init ( void )
{
2019-07-04 02:51:29 +03:00
int ret ;
2019-07-04 02:51:25 +03:00
struct cpuidle_driver * drv = & haltpoll_driver ;
2019-10-18 03:49:29 +03:00
/* Do not load haltpoll if idle= is passed */
if ( boot_option_idle_override ! = IDLE_NO_OVERRIDE )
return - ENODEV ;
2020-04-08 13:11:36 +03:00
if ( ! kvm_para_available ( ) | | ! haltpoll_want ( ) )
2019-09-08 02:45:23 +03:00
return - ENODEV ;
2019-07-04 02:51:25 +03:00
2022-03-04 10:53:28 +03:00
cpuidle_poll_state_init ( drv ) ;
2019-09-02 13:40:31 +03:00
ret = cpuidle_register_driver ( drv ) ;
if ( ret < 0 )
return ret ;
haltpoll_cpuidle_devices = alloc_percpu ( struct cpuidle_device ) ;
if ( haltpoll_cpuidle_devices = = NULL ) {
cpuidle_unregister_driver ( drv ) ;
return - ENOMEM ;
}
ret = cpuhp_setup_state ( CPUHP_AP_ONLINE_DYN , " cpuidle/haltpoll:online " ,
haltpoll_cpu_online , haltpoll_cpu_offline ) ;
if ( ret < 0 ) {
haltpoll_uninit ( ) ;
} else {
haltpoll_hp_state = ret ;
ret = 0 ;
}
2019-07-04 02:51:29 +03:00
return ret ;
2019-07-04 02:51:25 +03:00
}
static void __exit haltpoll_exit ( void )
{
2019-09-02 13:40:31 +03:00
haltpoll_uninit ( ) ;
2019-07-04 02:51:25 +03:00
}
module_init ( haltpoll_init ) ;
module_exit ( haltpoll_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Marcelo Tosatti <mtosatti@redhat.com> " ) ;