xref: /linux/drivers/hv/mshv_common.c (revision 6a069876eb1402478900ee0eb7d7fe276bb1f4e3)
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 
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 
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 
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 #ifdef CONFIG_X86
146 /*
147  * Corresponding sleep states have to be initialized in order for a subsequent
148  * HVCALL_ENTER_SLEEP_STATE call to succeed. Currently only S5 state as per
149  * ACPI 6.4 chapter 7.4.2 is relevant, while S1, S2 and S3 can be supported.
150  *
151  * In order to pass proper PM values to mshv, ACPI should be initialized and
152  * should support S5 sleep state when this method is invoked.
153  */
154 static int hv_initialize_sleep_states(void)
155 {
156 	u64 status;
157 	unsigned long flags;
158 	struct hv_input_set_system_property *in;
159 	acpi_status acpi_status;
160 	u8 sleep_type_a, sleep_type_b;
161 
162 	if (!acpi_sleep_state_supported(ACPI_STATE_S5)) {
163 		pr_err("%s: S5 sleep state not supported.\n", __func__);
164 		return -ENODEV;
165 	}
166 
167 	acpi_status = acpi_get_sleep_type_data(ACPI_STATE_S5, &sleep_type_a,
168 					       &sleep_type_b);
169 	if (ACPI_FAILURE(acpi_status))
170 		return -ENODEV;
171 
172 	local_irq_save(flags);
173 	in = *this_cpu_ptr(hyperv_pcpu_input_arg);
174 	memset(in, 0, sizeof(*in));
175 
176 	in->property_id = HV_SYSTEM_PROPERTY_SLEEP_STATE;
177 	in->set_sleep_state_info.sleep_state = HV_SLEEP_STATE_S5;
178 	in->set_sleep_state_info.pm1a_slp_typ = sleep_type_a;
179 	in->set_sleep_state_info.pm1b_slp_typ = sleep_type_b;
180 
181 	status = hv_do_hypercall(HVCALL_SET_SYSTEM_PROPERTY, in, NULL);
182 	local_irq_restore(flags);
183 
184 	if (!hv_result_success(status)) {
185 		hv_status_err(status, "\n");
186 		return hv_result_to_errno(status);
187 	}
188 
189 	return 0;
190 }
191 
192 /*
193  * This notifier initializes sleep states in mshv hypervisor which will be
194  * used during power off.
195  */
196 static int hv_reboot_notifier_handler(struct notifier_block *this,
197 				      unsigned long code, void *another)
198 {
199 	int ret = 0;
200 
201 	if (code == SYS_HALT || code == SYS_POWER_OFF)
202 		ret = hv_initialize_sleep_states();
203 
204 	return ret ? NOTIFY_DONE : NOTIFY_OK;
205 }
206 
207 static struct notifier_block hv_reboot_notifier = {
208 	.notifier_call = hv_reboot_notifier_handler,
209 };
210 
211 void hv_sleep_notifiers_register(void)
212 {
213 	int ret;
214 
215 	ret = register_reboot_notifier(&hv_reboot_notifier);
216 	if (ret)
217 		pr_err("%s: cannot register reboot notifier %d\n", __func__,
218 		       ret);
219 }
220 
221 /*
222  * Power off the machine by entering S5 sleep state via Hyper-V hypercall.
223  * This call does not return if successful.
224  */
225 void hv_machine_power_off(void)
226 {
227 	unsigned long flags;
228 	struct hv_input_enter_sleep_state *in;
229 
230 	local_irq_save(flags);
231 	in = *this_cpu_ptr(hyperv_pcpu_input_arg);
232 	in->sleep_state = HV_SLEEP_STATE_S5;
233 
234 	(void)hv_do_hypercall(HVCALL_ENTER_SLEEP_STATE, in, NULL);
235 	local_irq_restore(flags);
236 
237 	/* should never reach here */
238 	BUG();
239 
240 }
241 #endif
242