| 1 | /* |
| 2 | * MSHV in-kernel APIC support |
| 3 | * |
| 4 | * Copyright Microsoft, Corp. 2026 |
| 5 | * |
| 6 | * Authors: Magnus Kulke <magnuskulke@microsoft.com> |
| 7 | * |
| 8 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 9 | */ |
| 10 | |
| 11 | #include "qemu/osdep.h" |
| 12 | #include "qemu/module.h" |
| 13 | #include "qemu/memalign.h" |
| 14 | #include "qemu/error-report.h" |
| 15 | #include "hw/i386/apic_internal.h" |
| 16 | #include "hw/i386/apic-msidef.h" |
| 17 | #include "hw/pci/msi.h" |
| 18 | #include "migration/vmstate.h" |
| 19 | #include "qemu/typedefs.h" |
| 20 | #include "system/hw_accel.h" |
| 21 | #include "system/mshv.h" |
| 22 | #include "system/mshv_int.h" |
| 23 | |
| 24 | typedef struct hv_local_interrupt_controller_state |
| 25 | hv_local_interrupt_controller_state; |
| 26 | |
| 27 | #define TYPE_MSHV_APIC "mshv-apic" |
| 28 | OBJECT_DECLARE_SIMPLE_TYPE(MshvAPICState, MSHV_APIC) |
| 29 | |
| 30 | struct MshvAPICState { |
| 31 | APICCommonState parent_obj; |
| 32 | |
| 33 | uint32_t apic_version; |
| 34 | uint32_t apic_lvt_cmci; |
| 35 | uint32_t apic_error_status; |
| 36 | uint32_t apic_counter_value; |
| 37 | uint32_t apic_remote_read; |
| 38 | }; |
| 39 | |
| 40 | static int get_lapic(int cpu_fd, |
| 41 | struct hv_local_interrupt_controller_state *state) |
| 42 | { |
| 43 | int ret; |
| 44 | size_t size = 4096; |
| 45 | /* buffer aligned to 4k, as *state requires that */ |
| 46 | void *buffer = qemu_memalign(size, size); |
| 47 | struct mshv_get_set_vp_state mshv_state = { 0 }; |
| 48 | |
| 49 | mshv_state.buf_ptr = (uint64_t) buffer; |
| 50 | mshv_state.buf_sz = size; |
| 51 | mshv_state.type = MSHV_VP_STATE_LAPIC; |
| 52 | |
| 53 | ret = mshv_get_vp_state(cpu_fd, &mshv_state); |
| 54 | if (ret == 0) { |
| 55 | memcpy(state, buffer, sizeof(*state)); |
| 56 | } |
| 57 | qemu_vfree(buffer); |
| 58 | if (ret < 0) { |
| 59 | error_report("failed to get lapic"); |
| 60 | return -1; |
| 61 | } |
| 62 | |
| 63 | return 0; |
| 64 | } |
| 65 | |
| 66 | static int set_lapic(int cpu_fd, |
| 67 | const struct hv_local_interrupt_controller_state *state) |
| 68 | { |
| 69 | int ret; |
| 70 | size_t size = 4096; |
| 71 | /* buffer aligned to 4k, as *state requires that */ |
| 72 | void *buffer = qemu_memalign(size, size); |
| 73 | struct mshv_get_set_vp_state mshv_state = { 0 }; |
| 74 | |
| 75 | if (!state) { |
| 76 | error_report("lapic state is NULL"); |
| 77 | return -1; |
| 78 | } |
| 79 | memcpy(buffer, state, sizeof(*state)); |
| 80 | |
| 81 | mshv_state.buf_ptr = (uint64_t) buffer; |
| 82 | mshv_state.buf_sz = size; |
| 83 | mshv_state.type = MSHV_VP_STATE_LAPIC; |
| 84 | |
| 85 | ret = mshv_set_vp_state(cpu_fd, &mshv_state); |
| 86 | qemu_vfree(buffer); |
| 87 | if (ret < 0) { |
| 88 | error_report("failed to set lapic: %s", strerror(errno)); |
| 89 | return -1; |
| 90 | } |
| 91 | |
| 92 | return 0; |
| 93 | } |
| 94 | |
| 95 | static void populate_apic_state(CPUState *cpu, |
| 96 | const hv_local_interrupt_controller_state *hv) |
| 97 | { |
| 98 | X86CPU *x86cpu = X86_CPU(cpu); |
| 99 | MshvAPICState *ms = MSHV_APIC(x86cpu->apic_state); |
| 100 | APICCommonState *s = &ms->parent_obj; |
| 101 | size_t i; |
| 102 | |
| 103 | /* |
| 104 | * x2APIC: |
| 105 | * - APIC ID is the full 32-bit initial_apic_id |
| 106 | * - LDR is read-only, architecturally derived from the ID |
| 107 | * - DFR does not exist in x2APIC mode |
| 108 | */ |
| 109 | if (is_x2apic_mode(s)) { |
| 110 | s->initial_apic_id = hv->apic_id; |
| 111 | } else { |
| 112 | s->id = hv->apic_id >> 24; |
| 113 | s->log_dest = hv->apic_ldr >> 24; |
| 114 | s->dest_mode = hv->apic_dfr >> 28; |
| 115 | } |
| 116 | ms->apic_version = hv->apic_version; |
| 117 | s->spurious_vec = hv->apic_spurious; |
| 118 | for (i = 0; i < 8; i++) { |
| 119 | s->isr[i] = hv->apic_isr[i]; |
| 120 | s->tmr[i] = hv->apic_tmr[i]; |
| 121 | s->irr[i] = hv->apic_irr[i]; |
| 122 | } |
| 123 | s->esr = hv->apic_esr; |
| 124 | s->icr[1] = hv->apic_icr_high; |
| 125 | s->icr[0] = hv->apic_icr_low; |
| 126 | |
| 127 | s->lvt[APIC_LVT_TIMER] = hv->apic_lvt_timer; |
| 128 | s->lvt[APIC_LVT_THERMAL] = hv->apic_lvt_thermal; |
| 129 | s->lvt[APIC_LVT_PERFORM] = hv->apic_lvt_perfmon; |
| 130 | s->lvt[APIC_LVT_LINT0] = hv->apic_lvt_lint0; |
| 131 | s->lvt[APIC_LVT_LINT1] = hv->apic_lvt_lint1; |
| 132 | s->lvt[APIC_LVT_ERROR] = hv->apic_lvt_error; |
| 133 | ms->apic_lvt_cmci = hv->apic_lvt_cmci; |
| 134 | |
| 135 | ms->apic_error_status = hv->apic_error_status; |
| 136 | s->initial_count = hv->apic_initial_count; |
| 137 | ms->apic_counter_value = hv->apic_counter_value; |
| 138 | s->divide_conf = hv->apic_divide_configuration; |
| 139 | ms->apic_remote_read = hv->apic_remote_read; |
| 140 | } |
| 141 | |
| 142 | static uint32_t set_apic_delivery_mode(uint32_t reg, uint32_t mode) |
| 143 | { |
| 144 | return ((reg) & ~0x700) | ((mode) << 8); |
| 145 | } |
| 146 | |
| 147 | int mshv_init_lint(CPUState *cpu) |
| 148 | { |
| 149 | uint32_t *lvt_lint0, *lvt_lint1; |
| 150 | int cpu_fd = mshv_vcpufd(cpu); |
| 151 | int ret; |
| 152 | struct hv_local_interrupt_controller_state lapic_state = { 0 }; |
| 153 | |
| 154 | ret = get_lapic(cpu_fd, &lapic_state); |
| 155 | if (ret < 0) { |
| 156 | return ret; |
| 157 | } |
| 158 | |
| 159 | lvt_lint0 = &lapic_state.apic_lvt_lint0; |
| 160 | *lvt_lint0 = set_apic_delivery_mode(*lvt_lint0, APIC_DM_EXTINT); |
| 161 | |
| 162 | lvt_lint1 = &lapic_state.apic_lvt_lint1; |
| 163 | *lvt_lint1 = set_apic_delivery_mode(*lvt_lint1, APIC_DM_NMI); |
| 164 | |
| 165 | /* TODO: should we skip setting lapic if the values are the same? */ |
| 166 | |
| 167 | ret = set_lapic(cpu_fd, &lapic_state); |
| 168 | if (ret < 0) { |
| 169 | return -1; |
| 170 | } |
| 171 | |
| 172 | populate_apic_state(cpu, &lapic_state); |
| 173 | |
| 174 | return 0; |
| 175 | } |
| 176 | |
| 177 | static void populate_hv_lapic_state(hv_local_interrupt_controller_state *hv, |
| 178 | const CPUState *cpu) |
| 179 | { |
| 180 | uint32_t x2apic_id; |
| 181 | X86CPU *x86cpu = X86_CPU(cpu); |
| 182 | MshvAPICState *ms = MSHV_APIC(x86cpu->apic_state); |
| 183 | APICCommonState *s = &ms->parent_obj; |
| 184 | size_t i; |
| 185 | |
| 186 | /* |
| 187 | * x2APIC: |
| 188 | * - APIC ID is the full 32-bit initial_apic_id |
| 189 | * - LDR is read-only, architecturally derived from the ID |
| 190 | * - DFR does not exist in x2APIC mode |
| 191 | */ |
| 192 | if (is_x2apic_mode(s)) { |
| 193 | x2apic_id = s->initial_apic_id; |
| 194 | |
| 195 | hv->apic_id = x2apic_id; |
| 196 | hv->apic_ldr = ((x2apic_id >> 4) << 16) | (1 << (x2apic_id & 0xf)); |
| 197 | hv->apic_dfr = 0; |
| 198 | } else { |
| 199 | hv->apic_id = s->id << 24; |
| 200 | hv->apic_ldr = s->log_dest << 24; |
| 201 | hv->apic_dfr = s->dest_mode << 28 | 0x0fffffff; |
| 202 | } |
| 203 | hv->apic_version = ms->apic_version; |
| 204 | hv->apic_spurious = s->spurious_vec; |
| 205 | for (i = 0; i < 8; i++) { |
| 206 | hv->apic_isr[i] = s->isr[i]; |
| 207 | hv->apic_tmr[i] = s->tmr[i]; |
| 208 | hv->apic_irr[i] = s->irr[i]; |
| 209 | } |
| 210 | hv->apic_esr = s->esr; |
| 211 | hv->apic_icr_high = s->icr[1]; |
| 212 | hv->apic_icr_low = s->icr[0]; |
| 213 | |
| 214 | hv->apic_lvt_timer = s->lvt[APIC_LVT_TIMER]; |
| 215 | hv->apic_lvt_thermal = s->lvt[APIC_LVT_THERMAL]; |
| 216 | hv->apic_lvt_perfmon = s->lvt[APIC_LVT_PERFORM]; |
| 217 | hv->apic_lvt_lint0 = s->lvt[APIC_LVT_LINT0]; |
| 218 | hv->apic_lvt_lint1 = s->lvt[APIC_LVT_LINT1]; |
| 219 | hv->apic_lvt_error = s->lvt[APIC_LVT_ERROR]; |
| 220 | hv->apic_lvt_cmci = ms->apic_lvt_cmci; |
| 221 | |
| 222 | hv->apic_error_status = ms->apic_error_status; |
| 223 | hv->apic_initial_count = s->initial_count; |
| 224 | hv->apic_counter_value = ms->apic_counter_value; |
| 225 | hv->apic_divide_configuration = s->divide_conf; |
| 226 | hv->apic_remote_read = ms->apic_remote_read; |
| 227 | } |
| 228 | |
| 229 | int mshv_set_lapic(const CPUState *cpu) |
| 230 | { |
| 231 | int cpu_fd = mshv_vcpufd(cpu); |
| 232 | struct hv_local_interrupt_controller_state lapic_state = { 0 }; |
| 233 | |
| 234 | populate_hv_lapic_state(&lapic_state, cpu); |
| 235 | |
| 236 | return set_lapic(cpu_fd, &lapic_state); |
| 237 | } |
| 238 | |
| 239 | int mshv_get_lapic(CPUState *cpu) |
| 240 | { |
| 241 | int cpu_fd = mshv_vcpufd(cpu); |
| 242 | int ret; |
| 243 | struct hv_local_interrupt_controller_state lapic_state = { 0 }; |
| 244 | |
| 245 | ret = get_lapic(cpu_fd, &lapic_state); |
| 246 | if (ret < 0) { |
| 247 | return -1; |
| 248 | } |
| 249 | |
| 250 | populate_apic_state(cpu, &lapic_state); |
| 251 | |
| 252 | return 0; |
| 253 | } |
| 254 | |
| 255 | static int mshv_apic_set_base(APICCommonState *s, uint64_t val) |
| 256 | { |
| 257 | s->apicbase = val; |
| 258 | |
| 259 | return 0; |
| 260 | } |
| 261 | |
| 262 | static void mshv_apic_set_tpr(APICCommonState *s, uint8_t val) |
| 263 | { |
| 264 | s->tpr = (val & APIC_PR_SUB_CLASS) << APIC_PR_CLASS_SHIFT; |
| 265 | } |
| 266 | |
| 267 | static uint8_t mshv_apic_get_tpr(APICCommonState *s) |
| 268 | { |
| 269 | return s->tpr >> APIC_PR_CLASS_SHIFT; |
| 270 | } |
| 271 | |
| 272 | static void mshv_apic_external_nmi(APICCommonState *s) |
| 273 | { |
| 274 | } |
| 275 | |
| 276 | static void mshv_apic_vapic_base_update(APICCommonState *s) |
| 277 | { |
| 278 | } |
| 279 | |
| 280 | static void mshv_send_msi(MSIMessage *msi) |
| 281 | { |
| 282 | uint64_t addr; |
| 283 | uint32_t data, dest; |
| 284 | uint8_t vector, dest_mode, trigger_mode, delivery; |
| 285 | |
| 286 | addr = msi->address; |
| 287 | data = msi->data; |
| 288 | dest = (addr & MSI_ADDR_DEST_ID_MASK) >> MSI_ADDR_DEST_ID_SHIFT | |
| 289 | (addr >> 32); |
| 290 | vector = (data & MSI_DATA_VECTOR_MASK) >> MSI_DATA_VECTOR_SHIFT; |
| 291 | dest_mode = (addr >> MSI_ADDR_DEST_MODE_SHIFT) & 0x1; |
| 292 | trigger_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1; |
| 293 | delivery = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) & |
| 294 | MSI_DATA_DELIVERY_MODE_MASK; |
| 295 | |
| 296 | /* |
| 297 | * Vector 0 is not a valid interrupt vector (0-15 are reserved for CPU |
| 298 | * exceptions). This can trigger during machine reset, if hpet_reset() |
| 299 | * forces the PIT to pulse GSI 2 before IOAPIC's own reset has masked its |
| 300 | * redirection entries. |
| 301 | */ |
| 302 | if (vector == 0) { |
| 303 | return; |
| 304 | } |
| 305 | |
| 306 | mshv_request_interrupt(mshv_state, delivery, vector, dest, dest_mode, |
| 307 | trigger_mode); |
| 308 | } |
| 309 | |
| 310 | static uint64_t mshv_apic_mem_read(void *opaque, hwaddr addr, |
| 311 | unsigned size) |
| 312 | { |
| 313 | return UINT64_MAX; |
| 314 | } |
| 315 | |
| 316 | static void mshv_apic_mem_write(void *opaque, hwaddr addr, |
| 317 | uint64_t data, unsigned size) |
| 318 | { |
| 319 | MSIMessage msg = { .address = addr, .data = data }; |
| 320 | |
| 321 | mshv_send_msi(&msg); |
| 322 | } |
| 323 | |
| 324 | static const MemoryRegionOps mshv_apic_io_ops = { |
| 325 | .read = mshv_apic_mem_read, |
| 326 | .write = mshv_apic_mem_write, |
| 327 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 328 | }; |
| 329 | |
| 330 | static void mshv_apic_reset(APICCommonState *s) |
| 331 | { |
| 332 | s->wait_for_sipi = 0; |
| 333 | } |
| 334 | |
| 335 | static const VMStateDescription vmstate_mshv_apic = { |
| 336 | .name = "mshv-apic", |
| 337 | .version_id = 1, |
| 338 | .minimum_version_id = 1, |
| 339 | .fields = (const VMStateField[]) { |
| 340 | VMSTATE_UINT32(apic_version, MshvAPICState), |
| 341 | VMSTATE_UINT32(apic_lvt_cmci, MshvAPICState), |
| 342 | VMSTATE_UINT32(apic_error_status, MshvAPICState), |
| 343 | VMSTATE_UINT32(apic_counter_value, MshvAPICState), |
| 344 | VMSTATE_UINT32(apic_remote_read, MshvAPICState), |
| 345 | VMSTATE_END_OF_LIST() |
| 346 | } |
| 347 | }; |
| 348 | |
| 349 | static void mshv_apic_realize(DeviceState *dev, Error **errp) |
| 350 | { |
| 351 | APICCommonState *s = APIC_COMMON(dev); |
| 352 | MshvAPICState *ms = MSHV_APIC(dev); |
| 353 | |
| 354 | memory_region_init_io(&s->io_memory, OBJECT(s), &mshv_apic_io_ops, s, |
| 355 | "mshv-apic-msi", APIC_SPACE_SIZE); |
| 356 | |
| 357 | msi_nonbroken = true; |
| 358 | |
| 359 | /* |
| 360 | * We register this state explicity, rather than going via dc->vmsd. |
| 361 | * The auto-wiring would register the state with |
| 362 | * instance_id == VMSTATE_INSTANCE_ID_ANY, which for the APIC doesn't |
| 363 | * work, b/c the ID carries semantic meaning for restoring the state |
| 364 | * on the destination (which vcpu it belongs to). |
| 365 | */ |
| 366 | vmstate_register_with_alias_id(NULL, |
| 367 | s->initial_apic_id, &vmstate_mshv_apic, ms, |
| 368 | -1, 0, NULL); |
| 369 | } |
| 370 | |
| 371 | static void mshv_apic_unrealize(DeviceState *dev) |
| 372 | { |
| 373 | MshvAPICState *ms = MSHV_APIC(dev); |
| 374 | |
| 375 | vmstate_unregister(NULL, &vmstate_mshv_apic, ms); |
| 376 | } |
| 377 | |
| 378 | static void mshv_apic_class_init(ObjectClass *klass, const void *data) |
| 379 | { |
| 380 | APICCommonClass *k = APIC_COMMON_CLASS(klass); |
| 381 | |
| 382 | k->realize = mshv_apic_realize; |
| 383 | k->unrealize = mshv_apic_unrealize; |
| 384 | k->reset = mshv_apic_reset; |
| 385 | k->set_base = mshv_apic_set_base; |
| 386 | k->set_tpr = mshv_apic_set_tpr; |
| 387 | k->get_tpr = mshv_apic_get_tpr; |
| 388 | k->external_nmi = mshv_apic_external_nmi; |
| 389 | k->vapic_base_update = mshv_apic_vapic_base_update; |
| 390 | k->send_msi = mshv_send_msi; |
| 391 | } |
| 392 | |
| 393 | static const TypeInfo mshv_apic_info = { |
| 394 | .name = TYPE_MSHV_APIC, |
| 395 | .parent = TYPE_APIC_COMMON, |
| 396 | .instance_size = sizeof(MshvAPICState), |
| 397 | .class_init = mshv_apic_class_init, |
| 398 | }; |
| 399 | |
| 400 | static void mshv_apic_register_types(void) |
| 401 | { |
| 402 | type_register_static(&mshv_apic_info); |
| 403 | } |
| 404 | |
| 405 | type_init(mshv_apic_register_types) |