linux/arch/arm64/kernel/paravirt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *
   4 * Copyright (C) 2013 Citrix Systems
   5 *
   6 * Author: Stefano Stabellini <stefano.stabellini@eu.citrix.com>
   7 */
   8
   9#define pr_fmt(fmt) "arm-pv: " fmt
  10
  11#include <linux/arm-smccc.h>
  12#include <linux/cpuhotplug.h>
  13#include <linux/export.h>
  14#include <linux/io.h>
  15#include <linux/jump_label.h>
  16#include <linux/printk.h>
  17#include <linux/psci.h>
  18#include <linux/reboot.h>
  19#include <linux/slab.h>
  20#include <linux/types.h>
  21#include <linux/static_call.h>
  22
  23#include <asm/paravirt.h>
  24#include <asm/pvclock-abi.h>
  25#include <asm/smp_plat.h>
  26
  27struct static_key paravirt_steal_enabled;
  28struct static_key paravirt_steal_rq_enabled;
  29
  30static u64 native_steal_clock(int cpu)
  31{
  32        return 0;
  33}
  34
  35DEFINE_STATIC_CALL(pv_steal_clock, native_steal_clock);
  36
  37struct pv_time_stolen_time_region {
  38        struct pvclock_vcpu_stolen_time *kaddr;
  39};
  40
  41static DEFINE_PER_CPU(struct pv_time_stolen_time_region, stolen_time_region);
  42
  43static bool steal_acc = true;
  44static int __init parse_no_stealacc(char *arg)
  45{
  46        steal_acc = false;
  47        return 0;
  48}
  49
  50early_param("no-steal-acc", parse_no_stealacc);
  51
  52/* return stolen time in ns by asking the hypervisor */
  53static u64 para_steal_clock(int cpu)
  54{
  55        struct pv_time_stolen_time_region *reg;
  56
  57        reg = per_cpu_ptr(&stolen_time_region, cpu);
  58
  59        /*
  60         * paravirt_steal_clock() may be called before the CPU
  61         * online notification callback runs. Until the callback
  62         * has run we just return zero.
  63         */
  64        if (!reg->kaddr)
  65                return 0;
  66
  67        return le64_to_cpu(READ_ONCE(reg->kaddr->stolen_time));
  68}
  69
  70static int stolen_time_cpu_down_prepare(unsigned int cpu)
  71{
  72        struct pv_time_stolen_time_region *reg;
  73
  74        reg = this_cpu_ptr(&stolen_time_region);
  75        if (!reg->kaddr)
  76                return 0;
  77
  78        memunmap(reg->kaddr);
  79        memset(reg, 0, sizeof(*reg));
  80
  81        return 0;
  82}
  83
  84static int stolen_time_cpu_online(unsigned int cpu)
  85{
  86        struct pv_time_stolen_time_region *reg;
  87        struct arm_smccc_res res;
  88
  89        reg = this_cpu_ptr(&stolen_time_region);
  90
  91        arm_smccc_1_1_invoke(ARM_SMCCC_HV_PV_TIME_ST, &res);
  92
  93        if (res.a0 == SMCCC_RET_NOT_SUPPORTED)
  94                return -EINVAL;
  95
  96        reg->kaddr = memremap(res.a0,
  97                              sizeof(struct pvclock_vcpu_stolen_time),
  98                              MEMREMAP_WB);
  99
 100        if (!reg->kaddr) {
 101                pr_warn("Failed to map stolen time data structure\n");
 102                return -ENOMEM;
 103        }
 104
 105        if (le32_to_cpu(reg->kaddr->revision) != 0 ||
 106            le32_to_cpu(reg->kaddr->attributes) != 0) {
 107                pr_warn_once("Unexpected revision or attributes in stolen time data\n");
 108                return -ENXIO;
 109        }
 110
 111        return 0;
 112}
 113
 114static int __init pv_time_init_stolen_time(void)
 115{
 116        int ret;
 117
 118        ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
 119                                "hypervisor/arm/pvtime:online",
 120                                stolen_time_cpu_online,
 121                                stolen_time_cpu_down_prepare);
 122        if (ret < 0)
 123                return ret;
 124        return 0;
 125}
 126
 127static bool __init has_pv_steal_clock(void)
 128{
 129        struct arm_smccc_res res;
 130
 131        /* To detect the presence of PV time support we require SMCCC 1.1+ */
 132        if (arm_smccc_1_1_get_conduit() == SMCCC_CONDUIT_NONE)
 133                return false;
 134
 135        arm_smccc_1_1_invoke(ARM_SMCCC_ARCH_FEATURES_FUNC_ID,
 136                             ARM_SMCCC_HV_PV_TIME_FEATURES, &res);
 137
 138        if (res.a0 != SMCCC_RET_SUCCESS)
 139                return false;
 140
 141        arm_smccc_1_1_invoke(ARM_SMCCC_HV_PV_TIME_FEATURES,
 142                             ARM_SMCCC_HV_PV_TIME_ST, &res);
 143
 144        return (res.a0 == SMCCC_RET_SUCCESS);
 145}
 146
 147int __init pv_time_init(void)
 148{
 149        int ret;
 150
 151        if (!has_pv_steal_clock())
 152                return 0;
 153
 154        ret = pv_time_init_stolen_time();
 155        if (ret)
 156                return ret;
 157
 158        static_call_update(pv_steal_clock, para_steal_clock);
 159
 160        static_key_slow_inc(&paravirt_steal_enabled);
 161        if (steal_acc)
 162                static_key_slow_inc(&paravirt_steal_rq_enabled);
 163
 164        pr_info("using stolen time PV\n");
 165
 166        return 0;
 167}
 168