Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | // 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 <asm/paravirt.h> #include <asm/pvclock-abi.h> #include <asm/smp_plat.h> struct static_key paravirt_steal_enabled; struct static_key paravirt_steal_rq_enabled; static u64 native_steal_clock(int cpu) { return 0; } DEFINE_STATIC_CALL(pv_steal_clock, native_steal_clock); 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 para_steal_clock(int cpu) { struct pv_time_stolen_time_region *reg; 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. */ if (!reg->kaddr) return 0; return le64_to_cpu(READ_ONCE(reg->kaddr->stolen_time)); } static int stolen_time_cpu_down_prepare(unsigned int cpu) { 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; } static int stolen_time_cpu_online(unsigned int cpu) { 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; } 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; /* To detect the presence of PV time support we require SMCCC 1.1+ */ if (arm_smccc_1_1_get_conduit() == SMCCC_CONDUIT_NONE) 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; 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; } |