2019-05-27 09:55:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-11-23 13:33:49 +03:00
/*
*
* Copyright ( C ) 2013 Citrix Systems
*
* Author : Stefano Stabellini < stefano . stabellini @ eu . citrix . com >
*/
2019-10-21 18:28:23 +03:00
# define pr_fmt(fmt) "arm-pv: " fmt
# include <linux/arm-smccc.h>
# include <linux/cpuhotplug.h>
2015-11-23 13:33:49 +03:00
# include <linux/export.h>
2019-10-21 18:28:23 +03:00
# include <linux/io.h>
2015-11-23 13:33:49 +03:00
# include <linux/jump_label.h>
2019-10-21 18:28:23 +03:00
# include <linux/printk.h>
# include <linux/psci.h>
# include <linux/reboot.h>
# include <linux/slab.h>
2015-11-23 13:33:49 +03:00
# include <linux/types.h>
2019-10-21 18:28:23 +03:00
2015-11-23 13:33:49 +03:00
# include <asm/paravirt.h>
2019-10-21 18:28:23 +03:00
# include <asm/pvclock-abi.h>
# include <asm/smp_plat.h>
2015-11-23 13:33:49 +03:00
struct static_key paravirt_steal_enabled ;
struct static_key paravirt_steal_rq_enabled ;
2018-08-28 10:40:19 +03:00
struct paravirt_patch_template pv_ops ;
EXPORT_SYMBOL_GPL ( pv_ops ) ;
2019-10-21 18:28:23 +03:00
struct pv_time_stolen_time_region {
struct pvclock_vcpu_stolen_time * kaddr ;
} ;
static DEFINE_PER_CPU ( struct pv_time_stolen_time_region , stolen_time_region ) ;
static bool steal_acc = true ;
static int __init parse_no_stealacc ( char * arg )
{
steal_acc = false ;
return 0 ;
}
early_param ( " no-steal-acc " , parse_no_stealacc ) ;
/* return stolen time in ns by asking the hypervisor */
static u64 pv_steal_clock ( int cpu )
{
struct pv_time_stolen_time_region * reg ;
reg = per_cpu_ptr ( & stolen_time_region , cpu ) ;
2020-09-16 18:45:30 +03:00
/*
* paravirt_steal_clock ( ) may be called before the CPU
* online notification callback runs . Until the callback
* has run we just return zero .
*/
if ( ! reg - > kaddr )
2019-10-21 18:28:23 +03:00
return 0 ;
return le64_to_cpu ( READ_ONCE ( reg - > kaddr - > stolen_time ) ) ;
}
2020-09-16 18:45:30 +03:00
static int stolen_time_cpu_down_prepare ( unsigned int cpu )
2019-10-21 18:28:23 +03:00
{
struct pv_time_stolen_time_region * reg ;
reg = this_cpu_ptr ( & stolen_time_region ) ;
if ( ! reg - > kaddr )
return 0 ;
memunmap ( reg - > kaddr ) ;
memset ( reg , 0 , sizeof ( * reg ) ) ;
return 0 ;
}
2020-09-16 18:45:30 +03:00
static int stolen_time_cpu_online ( unsigned int cpu )
2019-10-21 18:28:23 +03:00
{
struct pv_time_stolen_time_region * reg ;
struct arm_smccc_res res ;
reg = this_cpu_ptr ( & stolen_time_region ) ;
arm_smccc_1_1_invoke ( ARM_SMCCC_HV_PV_TIME_ST , & res ) ;
if ( res . a0 = = SMCCC_RET_NOT_SUPPORTED )
return - EINVAL ;
reg - > kaddr = memremap ( res . a0 ,
sizeof ( struct pvclock_vcpu_stolen_time ) ,
MEMREMAP_WB ) ;
if ( ! reg - > kaddr ) {
pr_warn ( " Failed to map stolen time data structure \n " ) ;
return - ENOMEM ;
}
if ( le32_to_cpu ( reg - > kaddr - > revision ) ! = 0 | |
le32_to_cpu ( reg - > kaddr - > attributes ) ! = 0 ) {
pr_warn_once ( " Unexpected revision or attributes in stolen time data \n " ) ;
return - ENXIO ;
}
return 0 ;
}
2020-09-16 18:45:30 +03:00
static int __init pv_time_init_stolen_time ( void )
2019-10-21 18:28:23 +03:00
{
int ret ;
2020-09-16 18:45:30 +03:00
ret = cpuhp_setup_state ( CPUHP_AP_ONLINE_DYN ,
" hypervisor/arm/pvtime:online " ,
stolen_time_cpu_online ,
stolen_time_cpu_down_prepare ) ;
2019-10-21 18:28:23 +03:00
if ( ret < 0 )
return ret ;
return 0 ;
}
2020-09-16 18:45:30 +03:00
static bool __init has_pv_steal_clock ( void )
2019-10-21 18:28:23 +03:00
{
struct arm_smccc_res res ;
/* To detect the presence of PV time support we require SMCCC 1.1+ */
2020-05-18 12:12:19 +03:00
if ( arm_smccc_1_1_get_conduit ( ) = = SMCCC_CONDUIT_NONE )
2019-10-21 18:28:23 +03:00
return false ;
arm_smccc_1_1_invoke ( ARM_SMCCC_ARCH_FEATURES_FUNC_ID ,
ARM_SMCCC_HV_PV_TIME_FEATURES , & res ) ;
if ( res . a0 ! = SMCCC_RET_SUCCESS )
return false ;
arm_smccc_1_1_invoke ( ARM_SMCCC_HV_PV_TIME_FEATURES ,
ARM_SMCCC_HV_PV_TIME_ST , & res ) ;
return ( res . a0 = = SMCCC_RET_SUCCESS ) ;
}
int __init pv_time_init ( void )
{
int ret ;
if ( ! has_pv_steal_clock ( ) )
return 0 ;
ret = pv_time_init_stolen_time ( ) ;
if ( ret )
return ret ;
pv_ops . time . steal_clock = pv_steal_clock ;
static_key_slow_inc ( & paravirt_steal_enabled ) ;
if ( steal_acc )
static_key_slow_inc ( & paravirt_steal_rq_enabled ) ;
pr_info ( " using stolen time PV \n " ) ;
return 0 ;
}