xref: /linux/drivers/hv/mshv_common.c (revision feb06d2690bb826fd33798a99ce5cff8d07b38f9)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2024, Microsoft Corporation.
4  *
5  * This file contains functions that will be called from one or more modules.
6  * If any of these modules are configured to build, this file is built and just
7  * statically linked in.
8  *
9  * Authors: Microsoft Linux virtualization team
10  */
11 
12 #include <linux/kernel.h>
13 #include <linux/mm.h>
14 #include <asm/mshyperv.h>
15 #include <linux/resume_user_mode.h>
16 #include <linux/export.h>
17 #include <linux/acpi.h>
18 #include <linux/notifier.h>
19 #include <linux/reboot.h>
20 
21 #include "mshv.h"
22 
23 #define HV_GET_REGISTER_BATCH_SIZE	\
24 	(HV_HYP_PAGE_SIZE / sizeof(union hv_register_value))
25 #define HV_SET_REGISTER_BATCH_SIZE	\
26 	((HV_HYP_PAGE_SIZE - sizeof(struct hv_input_set_vp_registers)) \
27 		/ sizeof(struct hv_register_assoc))
28 
hv_call_get_vp_registers(u32 vp_index,u64 partition_id,u16 count,union hv_input_vtl input_vtl,struct hv_register_assoc * registers)29 int hv_call_get_vp_registers(u32 vp_index, u64 partition_id, u16 count,
30 			     union hv_input_vtl input_vtl,
31 			     struct hv_register_assoc *registers)
32 {
33 	struct hv_input_get_vp_registers *input_page;
34 	union hv_register_value *output_page;
35 	u16 completed = 0;
36 	unsigned long remaining = count;
37 	int rep_count, i;
38 	u64 status = HV_STATUS_SUCCESS;
39 	unsigned long flags;
40 
41 	local_irq_save(flags);
42 
43 	input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
44 	output_page = *this_cpu_ptr(hyperv_pcpu_output_arg);
45 
46 	input_page->partition_id = partition_id;
47 	input_page->vp_index = vp_index;
48 	input_page->input_vtl.as_uint8 = input_vtl.as_uint8;
49 	input_page->rsvd_z8 = 0;
50 	input_page->rsvd_z16 = 0;
51 
52 	while (remaining) {
53 		rep_count = min(remaining, HV_GET_REGISTER_BATCH_SIZE);
54 		for (i = 0; i < rep_count; ++i)
55 			input_page->names[i] = registers[i].name;
56 
57 		status = hv_do_rep_hypercall(HVCALL_GET_VP_REGISTERS, rep_count,
58 					     0, input_page, output_page);
59 		if (!hv_result_success(status))
60 			break;
61 
62 		completed = hv_repcomp(status);
63 		for (i = 0; i < completed; ++i)
64 			registers[i].value = output_page[i];
65 
66 		registers += completed;
67 		remaining -= completed;
68 	}
69 	local_irq_restore(flags);
70 
71 	return hv_result_to_errno(status);
72 }
73 EXPORT_SYMBOL_GPL(hv_call_get_vp_registers);
74 
hv_call_set_vp_registers(u32 vp_index,u64 partition_id,u16 count,union hv_input_vtl input_vtl,struct hv_register_assoc * registers)75 int hv_call_set_vp_registers(u32 vp_index, u64 partition_id, u16 count,
76 			     union hv_input_vtl input_vtl,
77 			     struct hv_register_assoc *registers)
78 {
79 	struct hv_input_set_vp_registers *input_page;
80 	u16 completed = 0;
81 	unsigned long remaining = count;
82 	int rep_count;
83 	u64 status = HV_STATUS_SUCCESS;
84 	unsigned long flags;
85 
86 	local_irq_save(flags);
87 	input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
88 
89 	input_page->partition_id = partition_id;
90 	input_page->vp_index = vp_index;
91 	input_page->input_vtl.as_uint8 = input_vtl.as_uint8;
92 	input_page->rsvd_z8 = 0;
93 	input_page->rsvd_z16 = 0;
94 
95 	while (remaining) {
96 		rep_count = min(remaining, HV_SET_REGISTER_BATCH_SIZE);
97 		memcpy(input_page->elements, registers,
98 		       sizeof(struct hv_register_assoc) * rep_count);
99 
100 		status = hv_do_rep_hypercall(HVCALL_SET_VP_REGISTERS, rep_count,
101 					     0, input_page, NULL);
102 		if (!hv_result_success(status))
103 			break;
104 
105 		completed = hv_repcomp(status);
106 		registers += completed;
107 		remaining -= completed;
108 	}
109 
110 	local_irq_restore(flags);
111 
112 	return hv_result_to_errno(status);
113 }
114 EXPORT_SYMBOL_GPL(hv_call_set_vp_registers);
115 
hv_call_get_partition_property(u64 partition_id,u64 property_code,u64 * property_value)116 int hv_call_get_partition_property(u64 partition_id,
117 				   u64 property_code,
118 				   u64 *property_value)
119 {
120 	u64 status;
121 	unsigned long flags;
122 	struct hv_input_get_partition_property *input;
123 	struct hv_output_get_partition_property *output;
124 
125 	local_irq_save(flags);
126 	input = *this_cpu_ptr(hyperv_pcpu_input_arg);
127 	output = *this_cpu_ptr(hyperv_pcpu_output_arg);
128 	memset(input, 0, sizeof(*input));
129 	input->partition_id = partition_id;
130 	input->property_code = property_code;
131 	status = hv_do_hypercall(HVCALL_GET_PARTITION_PROPERTY, input, output);
132 
133 	if (!hv_result_success(status)) {
134 		local_irq_restore(flags);
135 		return hv_result_to_errno(status);
136 	}
137 	*property_value = output->property_value;
138 
139 	local_irq_restore(flags);
140 
141 	return 0;
142 }
143 EXPORT_SYMBOL_GPL(hv_call_get_partition_property);
144 
145 /*
146  * Corresponding sleep states have to be initialized in order for a subsequent
147  * HVCALL_ENTER_SLEEP_STATE call to succeed. Currently only S5 state as per
148  * ACPI 6.4 chapter 7.4.2 is relevant, while S1, S2 and S3 can be supported.
149  *
150  * In order to pass proper PM values to mshv, ACPI should be initialized and
151  * should support S5 sleep state when this method is invoked.
152  */
hv_initialize_sleep_states(void)153 static int hv_initialize_sleep_states(void)
154 {
155 	u64 status;
156 	unsigned long flags;
157 	struct hv_input_set_system_property *in;
158 	acpi_status acpi_status;
159 	u8 sleep_type_a, sleep_type_b;
160 
161 	if (!acpi_sleep_state_supported(ACPI_STATE_S5)) {
162 		pr_err("%s: S5 sleep state not supported.\n", __func__);
163 		return -ENODEV;
164 	}
165 
166 	acpi_status = acpi_get_sleep_type_data(ACPI_STATE_S5, &sleep_type_a,
167 					       &sleep_type_b);
168 	if (ACPI_FAILURE(acpi_status))
169 		return -ENODEV;
170 
171 	local_irq_save(flags);
172 	in = *this_cpu_ptr(hyperv_pcpu_input_arg);
173 	memset(in, 0, sizeof(*in));
174 
175 	in->property_id = HV_SYSTEM_PROPERTY_SLEEP_STATE;
176 	in->set_sleep_state_info.sleep_state = HV_SLEEP_STATE_S5;
177 	in->set_sleep_state_info.pm1a_slp_typ = sleep_type_a;
178 	in->set_sleep_state_info.pm1b_slp_typ = sleep_type_b;
179 
180 	status = hv_do_hypercall(HVCALL_SET_SYSTEM_PROPERTY, in, NULL);
181 	local_irq_restore(flags);
182 
183 	if (!hv_result_success(status)) {
184 		hv_status_err(status, "\n");
185 		return hv_result_to_errno(status);
186 	}
187 
188 	return 0;
189 }
190 
191 /*
192  * This notifier initializes sleep states in mshv hypervisor which will be
193  * used during power off.
194  */
hv_reboot_notifier_handler(struct notifier_block * this,unsigned long code,void * another)195 static int hv_reboot_notifier_handler(struct notifier_block *this,
196 				      unsigned long code, void *another)
197 {
198 	int ret = 0;
199 
200 	if (code == SYS_HALT || code == SYS_POWER_OFF)
201 		ret = hv_initialize_sleep_states();
202 
203 	return ret ? NOTIFY_DONE : NOTIFY_OK;
204 }
205 
206 static struct notifier_block hv_reboot_notifier = {
207 	.notifier_call = hv_reboot_notifier_handler,
208 };
209 
hv_sleep_notifiers_register(void)210 void hv_sleep_notifiers_register(void)
211 {
212 	int ret;
213 
214 	ret = register_reboot_notifier(&hv_reboot_notifier);
215 	if (ret)
216 		pr_err("%s: cannot register reboot notifier %d\n", __func__,
217 		       ret);
218 }
219 
220 /*
221  * Power off the machine by entering S5 sleep state via Hyper-V hypercall.
222  * This call does not return if successful.
223  */
hv_machine_power_off(void)224 void hv_machine_power_off(void)
225 {
226 	unsigned long flags;
227 	struct hv_input_enter_sleep_state *in;
228 
229 	local_irq_save(flags);
230 	in = *this_cpu_ptr(hyperv_pcpu_input_arg);
231 	in->sleep_state = HV_SLEEP_STATE_S5;
232 
233 	(void)hv_do_hypercall(HVCALL_ENTER_SLEEP_STATE, in, NULL);
234 	local_irq_restore(flags);
235 
236 	/* should never reach here */
237 	BUG();
238 
239 }
240