aboutsummaryrefslogtreecommitdiffstats
path: root/virt/kvm/arm/hypercalls.c
blob: 14a7863cbcafef0ffc79bc16897b1f35da3efd23 (plain)
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
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2019 Arm Ltd.

#include <linux/arm-smccc.h>
#include <linux/kvm_host.h>
#include <linux/cpufreq.h>
#include <linux/sched.h>
#include <uapi/linux/sched/types.h>

#include <asm/kvm_emulate.h>

#include <kvm/arm_hypercalls.h>
#include <kvm/arm_psci.h>

static void kvm_sched_get_cur_cpufreq(struct kvm_vcpu *vcpu, long *val)
{
	unsigned long ret_freq;

	ret_freq = cpufreq_get(task_cpu(current));

	*(unsigned long *)val = ret_freq;
}

static void kvm_sched_set_util(struct kvm_vcpu *vcpu, long *val)
{
	struct sched_attr attr = {
		.sched_flags = SCHED_FLAG_UTIL_GUEST,
	};
	int ret;

	attr.sched_util_min = smccc_get_arg1(vcpu);

	ret = sched_setattr_nocheck(current, &attr);

	*val = (long)ret;
}

static void kvm_sched_get_cpufreq_table(struct kvm_vcpu *vcpu,
					long *val, long *val1)
{
	struct cpufreq_policy *policy;
	u32 idx = smccc_get_arg1(vcpu);

	policy = cpufreq_cpu_get(task_cpu(current));

	if (!policy)
		return;

	*val = SMCCC_RET_SUCCESS;
	*val1 = (long)policy->freq_table[idx].frequency;

	cpufreq_cpu_put(policy);
}

int kvm_hvc_call_handler(struct kvm_vcpu *vcpu)
{
	u32 func_id = smccc_get_function(vcpu);
	long val = SMCCC_RET_NOT_SUPPORTED, val1 = 0, val2 = 0;
	u32 feature;
	gpa_t gpa;

	switch (func_id) {
	case ARM_SMCCC_VERSION_FUNC_ID:
		val = ARM_SMCCC_VERSION_1_1;
		break;
	case ARM_SMCCC_ARCH_FEATURES_FUNC_ID:
		feature = smccc_get_arg1(vcpu);
		switch (feature) {
		case ARM_SMCCC_ARCH_WORKAROUND_1:
			switch (kvm_arm_harden_branch_predictor()) {
			case KVM_BP_HARDEN_UNKNOWN:
				break;
			case KVM_BP_HARDEN_WA_NEEDED:
				val = SMCCC_RET_SUCCESS;
				break;
			case KVM_BP_HARDEN_NOT_REQUIRED:
				val = SMCCC_ARCH_WORKAROUND_RET_UNAFFECTED;
				break;
			}
			break;
		case ARM_SMCCC_ARCH_WORKAROUND_2:
			switch (kvm_arm_have_ssbd()) {
			case KVM_SSBD_FORCE_DISABLE:
			case KVM_SSBD_UNKNOWN:
				break;
			case KVM_SSBD_KERNEL:
				val = SMCCC_RET_SUCCESS;
				break;
			case KVM_SSBD_FORCE_ENABLE:
			case KVM_SSBD_MITIGATED:
				val = SMCCC_RET_NOT_REQUIRED;
				break;
			}
			break;
		case ARM_SMCCC_ARCH_WORKAROUND_3:
			switch (kvm_arm_get_spectre_bhb_state()) {
			case SPECTRE_VULNERABLE:
				break;
			case SPECTRE_MITIGATED:
				val = SMCCC_RET_SUCCESS;
				break;
			case SPECTRE_UNAFFECTED:
				val = SMCCC_ARCH_WORKAROUND_RET_UNAFFECTED;
				break;
			}
			break;
		case ARM_SMCCC_HV_PV_TIME_FEATURES:
			val = SMCCC_RET_SUCCESS;
			break;
		}
		break;
	case ARM_SMCCC_HV_PV_TIME_FEATURES:
		val = kvm_hypercall_pv_features(vcpu);
		break;
	case ARM_SMCCC_HV_PV_TIME_ST:
		gpa = kvm_init_stolen_time(vcpu);
		if (gpa != GPA_INVALID)
			val = gpa;
		break;
	case ARM_SMCCC_VENDOR_HYP_KVM_FEATURES_FUNC_ID:
		val = BIT(ARM_SMCCC_KVM_FUNC_FEATURES);
		val2 |= BIT(ARM_SMCCC_KVM_FUNC_GET_CUR_CPUFREQ % 32) |
			BIT(ARM_SMCCC_KVM_FUNC_UTIL_HINT % 32) |
			BIT(ARM_SMCCC_KVM_FUNC_GET_CPUFREQ_TBL % 32);
		break;
	case ARM_SMCCC_VENDOR_HYP_KVM_GET_CUR_CPUFREQ_FUNC_ID:
		kvm_sched_get_cur_cpufreq(vcpu, &val);
		break;
	case ARM_SMCCC_VENDOR_HYP_KVM_UTIL_HINT_FUNC_ID:
		kvm_sched_set_util(vcpu, &val);
		break;
	case ARM_SMCCC_VENDOR_HYP_KVM_GET_CPUFREQ_TBL_FUNC_ID:
		kvm_sched_get_cpufreq_table(vcpu, &val, &val1);
		break;
	default:
		return kvm_psci_call(vcpu);
	}

	smccc_set_retval(vcpu, val, val1, val2, 0);
	return 1;
}