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