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 #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 */
hv_initialize_sleep_states(void)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 */
hv_reboot_notifier_handler(struct notifier_block * this,unsigned long code,void * another)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
hv_sleep_notifiers_register(void)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 */
hv_machine_power_off(void)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