| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * | |
| * Copyright (C) 2013 Citrix Systems | |
| * | |
| * Author: Stefano Stabellini <stefano.stabellini@eu.citrix.com> | |
| */ | |
| #define pr_fmt(fmt) "arm-pv: " fmt | |
| #include <linux/arm-smccc.h> | |
| #include <linux/cpuhotplug.h> | |
| #include <linux/export.h> | |
| #include <linux/io.h> | |
| #include <linux/jump_label.h> | |
| #include <linux/printk.h> | |
| #include <linux/psci.h> | |
| #include <linux/reboot.h> | |
| #include <linux/slab.h> | |
| #include <linux/types.h> | |
| #include <linux/static_call.h> | |
| #include <linux/sched/cputime.h> | |
| #include <asm/paravirt.h> | |
| #include <asm/pvclock-abi.h> | |
| #include <asm/smp_plat.h> | |
| struct pv_time_stolen_time_region { | |
| struct pvclock_vcpu_stolen_time __rcu *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 para_steal_clock(int cpu) | |
| { | |
| struct pvclock_vcpu_stolen_time *kaddr = NULL; | |
| struct pv_time_stolen_time_region *reg; | |
| u64 ret = 0; | |
| reg = per_cpu_ptr(&stolen_time_region, cpu); | |
| /* | |
| * paravirt_steal_clock() may be called before the CPU | |
| * online notification callback runs. Until the callback | |
| * has run we just return zero. | |
| */ | |
| rcu_read_lock(); | |
| kaddr = rcu_dereference(reg->kaddr); | |
| if (!kaddr) { | |
| rcu_read_unlock(); | |
| return 0; | |
| } | |
| ret = le64_to_cpu(READ_ONCE(kaddr->stolen_time)); | |
| rcu_read_unlock(); | |
| return ret; | |
| } | |
| static int stolen_time_cpu_down_prepare(unsigned int cpu) | |
| { | |
| struct pvclock_vcpu_stolen_time *kaddr = NULL; | |
| struct pv_time_stolen_time_region *reg; | |
| reg = this_cpu_ptr(&stolen_time_region); | |
| if (!reg->kaddr) | |
| return 0; | |
| kaddr = rcu_replace_pointer(reg->kaddr, NULL, true); | |
| synchronize_rcu(); | |
| memunmap(kaddr); | |
| return 0; | |
| } | |
| static int stolen_time_cpu_online(unsigned int cpu) | |
| { | |
| struct pvclock_vcpu_stolen_time *kaddr = NULL; | |
| 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; | |
| kaddr = memremap(res.a0, | |
| sizeof(struct pvclock_vcpu_stolen_time), | |
| MEMREMAP_WB); | |
| rcu_assign_pointer(reg->kaddr, kaddr); | |
| if (!reg->kaddr) { | |
| pr_warn("Failed to map stolen time data structure\n"); | |
| return -ENOMEM; | |
| } | |
| if (le32_to_cpu(kaddr->revision) != 0 || | |
| le32_to_cpu(kaddr->attributes) != 0) { | |
| pr_warn_once("Unexpected revision or attributes in stolen time data\n"); | |
| return -ENXIO; | |
| } | |
| return 0; | |
| } | |
| static int __init pv_time_init_stolen_time(void) | |
| { | |
| int ret; | |
| ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, | |
| "hypervisor/arm/pvtime:online", | |
| stolen_time_cpu_online, | |
| stolen_time_cpu_down_prepare); | |
| if (ret < 0) | |
| return ret; | |
| return 0; | |
| } | |
| static bool __init has_pv_steal_clock(void) | |
| { | |
| struct arm_smccc_res res; | |
| 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; | |
| static_call_update(pv_steal_clock, para_steal_clock); | |
| static_key_slow_inc(¶virt_steal_enabled); | |
| if (steal_acc) | |
| static_key_slow_inc(¶virt_steal_rq_enabled); | |
| pr_info("using stolen time PV\n"); | |
| return 0; | |
| } |