xref: /openbmc/linux/arch/arm64/kernel/paravirt.c (revision 7b73a9c8)
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 
22 #include <asm/paravirt.h>
23 #include <asm/pvclock-abi.h>
24 #include <asm/smp_plat.h>
25 
26 struct static_key paravirt_steal_enabled;
27 struct static_key paravirt_steal_rq_enabled;
28 
29 struct paravirt_patch_template pv_ops;
30 EXPORT_SYMBOL_GPL(pv_ops);
31 
32 struct pv_time_stolen_time_region {
33 	struct pvclock_vcpu_stolen_time *kaddr;
34 };
35 
36 static DEFINE_PER_CPU(struct pv_time_stolen_time_region, stolen_time_region);
37 
38 static bool steal_acc = true;
39 static int __init parse_no_stealacc(char *arg)
40 {
41 	steal_acc = false;
42 	return 0;
43 }
44 
45 early_param("no-steal-acc", parse_no_stealacc);
46 
47 /* return stolen time in ns by asking the hypervisor */
48 static u64 pv_steal_clock(int cpu)
49 {
50 	struct pv_time_stolen_time_region *reg;
51 
52 	reg = per_cpu_ptr(&stolen_time_region, cpu);
53 	if (!reg->kaddr) {
54 		pr_warn_once("stolen time enabled but not configured for cpu %d\n",
55 			     cpu);
56 		return 0;
57 	}
58 
59 	return le64_to_cpu(READ_ONCE(reg->kaddr->stolen_time));
60 }
61 
62 static int stolen_time_dying_cpu(unsigned int cpu)
63 {
64 	struct pv_time_stolen_time_region *reg;
65 
66 	reg = this_cpu_ptr(&stolen_time_region);
67 	if (!reg->kaddr)
68 		return 0;
69 
70 	memunmap(reg->kaddr);
71 	memset(reg, 0, sizeof(*reg));
72 
73 	return 0;
74 }
75 
76 static int init_stolen_time_cpu(unsigned int cpu)
77 {
78 	struct pv_time_stolen_time_region *reg;
79 	struct arm_smccc_res res;
80 
81 	reg = this_cpu_ptr(&stolen_time_region);
82 
83 	arm_smccc_1_1_invoke(ARM_SMCCC_HV_PV_TIME_ST, &res);
84 
85 	if (res.a0 == SMCCC_RET_NOT_SUPPORTED)
86 		return -EINVAL;
87 
88 	reg->kaddr = memremap(res.a0,
89 			      sizeof(struct pvclock_vcpu_stolen_time),
90 			      MEMREMAP_WB);
91 
92 	if (!reg->kaddr) {
93 		pr_warn("Failed to map stolen time data structure\n");
94 		return -ENOMEM;
95 	}
96 
97 	if (le32_to_cpu(reg->kaddr->revision) != 0 ||
98 	    le32_to_cpu(reg->kaddr->attributes) != 0) {
99 		pr_warn_once("Unexpected revision or attributes in stolen time data\n");
100 		return -ENXIO;
101 	}
102 
103 	return 0;
104 }
105 
106 static int pv_time_init_stolen_time(void)
107 {
108 	int ret;
109 
110 	ret = cpuhp_setup_state(CPUHP_AP_ARM_KVMPV_STARTING,
111 				"hypervisor/arm/pvtime:starting",
112 				init_stolen_time_cpu, stolen_time_dying_cpu);
113 	if (ret < 0)
114 		return ret;
115 	return 0;
116 }
117 
118 static bool has_pv_steal_clock(void)
119 {
120 	struct arm_smccc_res res;
121 
122 	/* To detect the presence of PV time support we require SMCCC 1.1+ */
123 	if (psci_ops.smccc_version < SMCCC_VERSION_1_1)
124 		return false;
125 
126 	arm_smccc_1_1_invoke(ARM_SMCCC_ARCH_FEATURES_FUNC_ID,
127 			     ARM_SMCCC_HV_PV_TIME_FEATURES, &res);
128 
129 	if (res.a0 != SMCCC_RET_SUCCESS)
130 		return false;
131 
132 	arm_smccc_1_1_invoke(ARM_SMCCC_HV_PV_TIME_FEATURES,
133 			     ARM_SMCCC_HV_PV_TIME_ST, &res);
134 
135 	return (res.a0 == SMCCC_RET_SUCCESS);
136 }
137 
138 int __init pv_time_init(void)
139 {
140 	int ret;
141 
142 	if (!has_pv_steal_clock())
143 		return 0;
144 
145 	ret = pv_time_init_stolen_time();
146 	if (ret)
147 		return ret;
148 
149 	pv_ops.time.steal_clock = pv_steal_clock;
150 
151 	static_key_slow_inc(&paravirt_steal_enabled);
152 	if (steal_acc)
153 		static_key_slow_inc(&paravirt_steal_rq_enabled);
154 
155 	pr_info("using stolen time PV\n");
156 
157 	return 0;
158 }
159