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