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 /* 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 */ 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 */ 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 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 */ 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