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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024, Microsoft Corporation.
*
* This file contains functions that will be called from one or more modules.
* If any of these modules are configured to build, this file is built and just
* statically linked in.
*
* Authors: Microsoft Linux virtualization team
*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/mshyperv.h>
#include <linux/resume_user_mode.h>
#include <linux/export.h>
#include <linux/acpi.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include "mshv.h"
#define HV_GET_REGISTER_BATCH_SIZE \
(HV_HYP_PAGE_SIZE / sizeof(union hv_register_value))
#define HV_SET_REGISTER_BATCH_SIZE \
((HV_HYP_PAGE_SIZE - sizeof(struct hv_input_set_vp_registers)) \
/ sizeof(struct hv_register_assoc))
int hv_call_get_vp_registers(u32 vp_index, u64 partition_id, u16 count,
union hv_input_vtl input_vtl,
struct hv_register_assoc *registers)
{
struct hv_input_get_vp_registers *input_page;
union hv_register_value *output_page;
u16 completed = 0;
unsigned long remaining = count;
int rep_count, i;
u64 status = HV_STATUS_SUCCESS;
unsigned long flags;
local_irq_save(flags);
input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
output_page = *this_cpu_ptr(hyperv_pcpu_output_arg);
input_page->partition_id = partition_id;
input_page->vp_index = vp_index;
input_page->input_vtl.as_uint8 = input_vtl.as_uint8;
input_page->rsvd_z8 = 0;
input_page->rsvd_z16 = 0;
while (remaining) {
rep_count = min(remaining, HV_GET_REGISTER_BATCH_SIZE);
for (i = 0; i < rep_count; ++i)
input_page->names[i] = registers[i].name;
status = hv_do_rep_hypercall(HVCALL_GET_VP_REGISTERS, rep_count,
0, input_page, output_page);
if (!hv_result_success(status))
break;
completed = hv_repcomp(status);
for (i = 0; i < completed; ++i)
registers[i].value = output_page[i];
registers += completed;
remaining -= completed;
}
local_irq_restore(flags);
return hv_result_to_errno(status);
}
EXPORT_SYMBOL_GPL(hv_call_get_vp_registers);
int hv_call_set_vp_registers(u32 vp_index, u64 partition_id, u16 count,
union hv_input_vtl input_vtl,
struct hv_register_assoc *registers)
{
struct hv_input_set_vp_registers *input_page;
u16 completed = 0;
unsigned long remaining = count;
int rep_count;
u64 status = HV_STATUS_SUCCESS;
unsigned long flags;
local_irq_save(flags);
input_page = *this_cpu_ptr(hyperv_pcpu_input_arg);
input_page->partition_id = partition_id;
input_page->vp_index = vp_index;
input_page->input_vtl.as_uint8 = input_vtl.as_uint8;
input_page->rsvd_z8 = 0;
input_page->rsvd_z16 = 0;
while (remaining) {
rep_count = min(remaining, HV_SET_REGISTER_BATCH_SIZE);
memcpy(input_page->elements, registers,
sizeof(struct hv_register_assoc) * rep_count);
status = hv_do_rep_hypercall(HVCALL_SET_VP_REGISTERS, rep_count,
0, input_page, NULL);
if (!hv_result_success(status))
break;
completed = hv_repcomp(status);
registers += completed;
remaining -= completed;
}
local_irq_restore(flags);
return hv_result_to_errno(status);
}
EXPORT_SYMBOL_GPL(hv_call_set_vp_registers);
int hv_call_get_partition_property(u64 partition_id,
u64 property_code,
u64 *property_value)
{
u64 status;
unsigned long flags;
struct hv_input_get_partition_property *input;
struct hv_output_get_partition_property *output;
local_irq_save(flags);
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
output = *this_cpu_ptr(hyperv_pcpu_output_arg);
memset(input, 0, sizeof(*input));
input->partition_id = partition_id;
input->property_code = property_code;
status = hv_do_hypercall(HVCALL_GET_PARTITION_PROPERTY, input, output);
if (!hv_result_success(status)) {
local_irq_restore(flags);
return hv_result_to_errno(status);
}
*property_value = output->property_value;
local_irq_restore(flags);
return 0;
}
EXPORT_SYMBOL_GPL(hv_call_get_partition_property);
/*
* Corresponding sleep states have to be initialized in order for a subsequent
* HVCALL_ENTER_SLEEP_STATE call to succeed. Currently only S5 state as per
* ACPI 6.4 chapter 7.4.2 is relevant, while S1, S2 and S3 can be supported.
*
* In order to pass proper PM values to mshv, ACPI should be initialized and
* should support S5 sleep state when this method is invoked.
*/
static int hv_initialize_sleep_states(void)
{
u64 status;
unsigned long flags;
struct hv_input_set_system_property *in;
acpi_status acpi_status;
u8 sleep_type_a, sleep_type_b;
if (!acpi_sleep_state_supported(ACPI_STATE_S5)) {
pr_err("%s: S5 sleep state not supported.\n", __func__);
return -ENODEV;
}
acpi_status = acpi_get_sleep_type_data(ACPI_STATE_S5, &sleep_type_a,
&sleep_type_b);
if (ACPI_FAILURE(acpi_status))
return -ENODEV;
local_irq_save(flags);
in = *this_cpu_ptr(hyperv_pcpu_input_arg);
memset(in, 0, sizeof(*in));
in->property_id = HV_SYSTEM_PROPERTY_SLEEP_STATE;
in->set_sleep_state_info.sleep_state = HV_SLEEP_STATE_S5;
in->set_sleep_state_info.pm1a_slp_typ = sleep_type_a;
in->set_sleep_state_info.pm1b_slp_typ = sleep_type_b;
status = hv_do_hypercall(HVCALL_SET_SYSTEM_PROPERTY, in, NULL);
local_irq_restore(flags);
if (!hv_result_success(status)) {
hv_status_err(status, "\n");
return hv_result_to_errno(status);
}
return 0;
}
/*
* This notifier initializes sleep states in mshv hypervisor which will be
* used during power off.
*/
static int hv_reboot_notifier_handler(struct notifier_block *this,
unsigned long code, void *another)
{
int ret = 0;
if (code == SYS_HALT || code == SYS_POWER_OFF)
ret = hv_initialize_sleep_states();
return ret ? NOTIFY_DONE : NOTIFY_OK;
}
static struct notifier_block hv_reboot_notifier = {
.notifier_call = hv_reboot_notifier_handler,
};
void hv_sleep_notifiers_register(void)
{
int ret;
ret = register_reboot_notifier(&hv_reboot_notifier);
if (ret)
pr_err("%s: cannot register reboot notifier %d\n", __func__,
ret);
}
/*
* Power off the machine by entering S5 sleep state via Hyper-V hypercall.
* This call does not return if successful.
*/
void hv_machine_power_off(void)
{
unsigned long flags;
struct hv_input_enter_sleep_state *in;
local_irq_save(flags);
in = *this_cpu_ptr(hyperv_pcpu_input_arg);
in->sleep_state = HV_SLEEP_STATE_S5;
(void)hv_do_hypercall(HVCALL_ENTER_SLEEP_STATE, in, NULL);
local_irq_restore(flags);
/* should never reach here */
BUG();
}
|