This change implements loading and storing the hyperv lapic state as part of the load/store routines for a vcpu.
The HyperV LAPIC is similar to the the split-irqchip in KVM. MSHV currently keeps PIC/IOAPIC emulation in userspace, while LAPIC interrupt injection is handled through hypercalls. We introduced dedicated apic infra in hw/i386/mshv to handle the migration and move lapic related functions from target/i386/mshv there. References have been the WHPX's whpx-apic implemenation and the mshv-ioctls crate's get_/set_lapic() impl for the mapping between MSHV/QEMU lapic state. We are mapping the lapic state that we receive from the hypervisor to fields in APICCommonState. Common fields are used where feasible, with an mshv-specific MshvAPICState object that carries mshv-specific fields. We have introduced a guard in pic_irq_request() that will early exit for the mshv accelerator, because mshv cannot take part in the userland path for legacy PIC interrupt injection. The TSC_DEADLINE MSR is also migrated as part of LAPIC migration. Signed-off-by: Magnus Kulke <[email protected]> --- accel/mshv/mshv-all.c | 1 + hw/i386/meson.build | 2 +- hw/i386/mshv/apic.c | 395 +++++++++++++++++++++++++++++++++ hw/i386/mshv/meson.build | 1 + hw/i386/x86-cpu.c | 6 + include/hw/hyperv/hvgdk_mini.h | 2 + include/hw/i386/apic-msidef.h | 1 + include/system/mshv.h | 2 + include/system/mshv_int.h | 8 +- target/i386/cpu-apic.c | 3 + target/i386/mshv/mshv-cpu.c | 151 ++++--------- target/i386/mshv/msr.c | 2 + 12 files changed, 460 insertions(+), 114 deletions(-) create mode 100644 hw/i386/mshv/apic.c diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c index f9511ca050..1516475f34 100644 --- a/accel/mshv/mshv-all.c +++ b/accel/mshv/mshv-all.c @@ -813,6 +813,7 @@ static void mshv_accel_ops_class_init(ObjectClass *oc, const void *data) ops->synchronize_state = mshv_cpu_synchronize; ops->synchronize_pre_loadvm = mshv_cpu_synchronize_pre_loadvm; ops->cpus_are_resettable = mshv_cpus_are_resettable; + ops->cpu_thread_is_idle = mshv_vcpu_thread_is_idle; ops->handle_interrupt = generic_handle_interrupt; } diff --git a/hw/i386/meson.build b/hw/i386/meson.build index f4d3122863..39ac8c9edc 100644 --- a/hw/i386/meson.build +++ b/hw/i386/meson.build @@ -38,8 +38,8 @@ i386_ss.add(when: 'CONFIG_X86_FW_OVMF', if_true: files('pc_sysfw_ovmf.c'), i386_ss.add(when: 'CONFIG_TDX', if_true: files('tdvf.c', 'tdvf-hob.c')) subdir('kvm') -subdir('xen') subdir('mshv') +subdir('xen') i386_ss.add_all(xenpv_ss) diff --git a/hw/i386/mshv/apic.c b/hw/i386/mshv/apic.c new file mode 100644 index 0000000000..9188a93b2f --- /dev/null +++ b/hw/i386/mshv/apic.c @@ -0,0 +1,395 @@ +/* + * MSHV in-kernel APIC support + * + * Copyright Microsoft, Corp. 2026 + * + * Authors: Magnus Kulke <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/memalign.h" +#include "qemu/error-report.h" +#include "hw/i386/apic_internal.h" +#include "hw/i386/apic-msidef.h" +#include "hw/pci/msi.h" +#include "migration/vmstate.h" +#include "qemu/typedefs.h" +#include "system/hw_accel.h" +#include "system/mshv.h" +#include "system/mshv_int.h" + +typedef struct hv_local_interrupt_controller_state + hv_local_interrupt_controller_state; + +#define TYPE_MSHV_APIC "mshv-apic" +OBJECT_DECLARE_SIMPLE_TYPE(MshvAPICState, MSHV_APIC) + +struct MshvAPICState { + APICCommonState parent_obj; + + uint32_t apic_version; + uint32_t apic_lvt_cmci; + uint32_t apic_error_status; + uint32_t apic_counter_value; + uint32_t apic_remote_read; +}; + +static int get_lapic(int cpu_fd, + struct hv_local_interrupt_controller_state *state) +{ + int ret; + size_t size = 4096; + /* buffer aligned to 4k, as *state requires that */ + void *buffer = qemu_memalign(size, size); + struct mshv_get_set_vp_state mshv_state = { 0 }; + + mshv_state.buf_ptr = (uint64_t) buffer; + mshv_state.buf_sz = size; + mshv_state.type = MSHV_VP_STATE_LAPIC; + + ret = mshv_get_vp_state(cpu_fd, &mshv_state); + if (ret == 0) { + memcpy(state, buffer, sizeof(*state)); + } + qemu_vfree(buffer); + if (ret < 0) { + error_report("failed to get lapic"); + return -1; + } + + return 0; +} + +static int set_lapic(int cpu_fd, + const struct hv_local_interrupt_controller_state *state) +{ + int ret; + size_t size = 4096; + /* buffer aligned to 4k, as *state requires that */ + void *buffer = qemu_memalign(size, size); + struct mshv_get_set_vp_state mshv_state = { 0 }; + + if (!state) { + error_report("lapic state is NULL"); + return -1; + } + memcpy(buffer, state, sizeof(*state)); + + mshv_state.buf_ptr = (uint64_t) buffer; + mshv_state.buf_sz = size; + mshv_state.type = MSHV_VP_STATE_LAPIC; + + ret = mshv_set_vp_state(cpu_fd, &mshv_state); + qemu_vfree(buffer); + if (ret < 0) { + error_report("failed to set lapic: %s", strerror(errno)); + return -1; + } + + return 0; +} + +static void populate_apic_state(CPUState *cpu, + const hv_local_interrupt_controller_state *hv) +{ + X86CPU *x86cpu = X86_CPU(cpu); + MshvAPICState *ms = MSHV_APIC(x86cpu->apic_state); + APICCommonState *s = &ms->parent_obj; + size_t i; + + /* + * x2APIC: + * - APIC ID is the full 32-bit initial_apic_id + * - LDR is read-only, architecturally derived from the ID + * - DFR does not exist in x2APIC mode + */ + if (is_x2apic_mode(s)) { + s->initial_apic_id = hv->apic_id; + } else { + s->id = hv->apic_id >> 24; + s->log_dest = hv->apic_ldr >> 24; + s->dest_mode = hv->apic_dfr >> 28; + } + ms->apic_version = hv->apic_version; + s->spurious_vec = hv->apic_spurious; + for (i = 0; i < 8; i++) { + s->isr[i] = hv->apic_isr[i]; + s->tmr[i] = hv->apic_tmr[i]; + s->irr[i] = hv->apic_irr[i]; + } + s->esr = hv->apic_esr; + s->icr[1] = hv->apic_icr_high; + s->icr[0] = hv->apic_icr_low; + + s->lvt[APIC_LVT_TIMER] = hv->apic_lvt_timer; + s->lvt[APIC_LVT_THERMAL] = hv->apic_lvt_thermal; + s->lvt[APIC_LVT_PERFORM] = hv->apic_lvt_perfmon; + s->lvt[APIC_LVT_LINT0] = hv->apic_lvt_lint0; + s->lvt[APIC_LVT_LINT1] = hv->apic_lvt_lint1; + s->lvt[APIC_LVT_ERROR] = hv->apic_lvt_error; + ms->apic_lvt_cmci = hv->apic_lvt_cmci; + + ms->apic_error_status = hv->apic_error_status; + s->initial_count = hv->apic_initial_count; + ms->apic_counter_value = hv->apic_counter_value; + s->divide_conf = hv->apic_divide_configuration; + ms->apic_remote_read = hv->apic_remote_read; +} + +static uint32_t set_apic_delivery_mode(uint32_t reg, uint32_t mode) +{ + return ((reg) & ~0x700) | ((mode) << 8); +} + +int mshv_init_lint(CPUState *cpu) +{ + uint32_t *lvt_lint0, *lvt_lint1; + int cpu_fd = mshv_vcpufd(cpu); + int ret; + struct hv_local_interrupt_controller_state lapic_state = { 0 }; + + ret = get_lapic(cpu_fd, &lapic_state); + if (ret < 0) { + return ret; + } + + lvt_lint0 = &lapic_state.apic_lvt_lint0; + *lvt_lint0 = set_apic_delivery_mode(*lvt_lint0, APIC_DM_EXTINT); + + lvt_lint1 = &lapic_state.apic_lvt_lint1; + *lvt_lint1 = set_apic_delivery_mode(*lvt_lint1, APIC_DM_NMI); + + /* TODO: should we skip setting lapic if the values are the same? */ + + ret = set_lapic(cpu_fd, &lapic_state); + if (ret < 0) { + return -1; + } + + populate_apic_state(cpu, &lapic_state); + + return 0; +} + +static void populate_hv_lapic_state(hv_local_interrupt_controller_state *hv, + const CPUState *cpu) +{ + uint32_t x2apic_id; + X86CPU *x86cpu = X86_CPU(cpu); + MshvAPICState *ms = MSHV_APIC(x86cpu->apic_state); + APICCommonState *s = &ms->parent_obj; + size_t i; + + /* + * x2APIC: + * - APIC ID is the full 32-bit initial_apic_id + * - LDR is read-only, architecturally derived from the ID + * - DFR does not exist in x2APIC mode + */ + if (is_x2apic_mode(s)) { + x2apic_id = s->initial_apic_id; + + hv->apic_id = x2apic_id; + hv->apic_ldr = ((x2apic_id >> 4) << 16) | (1 << (x2apic_id & 0xf)); + hv->apic_dfr = 0; + } else { + hv->apic_id = s->id << 24; + hv->apic_ldr = s->log_dest << 24; + hv->apic_dfr = s->dest_mode << 28 | 0x0fffffff; + } + hv->apic_version = ms->apic_version; + hv->apic_spurious = s->spurious_vec; + for (i = 0; i < 8; i++) { + hv->apic_isr[i] = s->isr[i]; + hv->apic_tmr[i] = s->tmr[i]; + hv->apic_irr[i] = s->irr[i]; + } + hv->apic_esr = s->esr; + hv->apic_icr_high = s->icr[1]; + hv->apic_icr_low = s->icr[0]; + + hv->apic_lvt_timer = s->lvt[APIC_LVT_TIMER]; + hv->apic_lvt_thermal = s->lvt[APIC_LVT_THERMAL]; + hv->apic_lvt_perfmon = s->lvt[APIC_LVT_PERFORM]; + hv->apic_lvt_lint0 = s->lvt[APIC_LVT_LINT0]; + hv->apic_lvt_lint1 = s->lvt[APIC_LVT_LINT1]; + hv->apic_lvt_error = s->lvt[APIC_LVT_ERROR]; + hv->apic_lvt_cmci = ms->apic_lvt_cmci; + + hv->apic_error_status = ms->apic_error_status; + hv->apic_initial_count = s->initial_count; + hv->apic_counter_value = ms->apic_counter_value; + hv->apic_divide_configuration = s->divide_conf; + hv->apic_remote_read = ms->apic_remote_read; +} + +int mshv_set_lapic(const CPUState *cpu) +{ + int cpu_fd = mshv_vcpufd(cpu); + struct hv_local_interrupt_controller_state lapic_state = { 0 }; + + populate_hv_lapic_state(&lapic_state, cpu); + + return set_lapic(cpu_fd, &lapic_state); +} + +int mshv_get_lapic(CPUState *cpu) +{ + int cpu_fd = mshv_vcpufd(cpu); + int ret; + struct hv_local_interrupt_controller_state lapic_state = { 0 }; + + ret = get_lapic(cpu_fd, &lapic_state); + if (ret < 0) { + return -1; + } + + populate_apic_state(cpu, &lapic_state); + + return 0; +} + +static int mshv_apic_set_base(APICCommonState *s, uint64_t val) +{ + s->apicbase = val; + + return 0; +} + +static void mshv_apic_set_tpr(APICCommonState *s, uint8_t val) +{ + s->tpr = (val & APIC_PR_SUB_CLASS) << APIC_PR_CLASS_SHIFT; +} + +static uint8_t mshv_apic_get_tpr(APICCommonState *s) +{ + return s->tpr >> APIC_PR_CLASS_SHIFT; +} + +static void mshv_apic_external_nmi(APICCommonState *s) +{ +} + +static void mshv_apic_vapic_base_update(APICCommonState *s) +{ +} + +static void mshv_send_msi(MSIMessage *msi) +{ + uint64_t addr; + uint32_t data, dest; + uint8_t vector, dest_mode, trigger_mode, delivery; + + addr = msi->address; + data = msi->data; + dest = (addr & MSI_ADDR_DEST_ID_MASK) >> MSI_ADDR_DEST_ID_SHIFT | + (addr >> 32); + vector = (data & MSI_DATA_VECTOR_MASK) >> MSI_DATA_VECTOR_SHIFT; + dest_mode = (addr >> MSI_ADDR_DEST_MODE_SHIFT) & 0x1; + trigger_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1; + delivery = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) & + MSI_DATA_DELIVERY_MODE_MASK; + + mshv_request_interrupt(mshv_state, delivery, vector, dest, dest_mode, + trigger_mode); +} + +static uint64_t mshv_apic_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + return UINT64_MAX; +} + +static void mshv_apic_mem_write(void *opaque, hwaddr addr, + uint64_t data, unsigned size) +{ + MSIMessage msg = { .address = addr, .data = data }; + + mshv_send_msi(&msg); +} + +static const MemoryRegionOps mshv_apic_io_ops = { + .read = mshv_apic_mem_read, + .write = mshv_apic_mem_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void mshv_apic_reset(APICCommonState *s) +{ + s->wait_for_sipi = 0; +} + +static const VMStateDescription vmstate_mshv_apic = { + .name = "mshv-apic", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(apic_version, MshvAPICState), + VMSTATE_UINT32(apic_lvt_cmci, MshvAPICState), + VMSTATE_UINT32(apic_error_status, MshvAPICState), + VMSTATE_UINT32(apic_counter_value, MshvAPICState), + VMSTATE_UINT32(apic_remote_read, MshvAPICState), + VMSTATE_END_OF_LIST() + } +}; + +static void mshv_apic_realize(DeviceState *dev, Error **errp) +{ + APICCommonState *s = APIC_COMMON(dev); + MshvAPICState *ms = MSHV_APIC(dev); + + memory_region_init_io(&s->io_memory, OBJECT(s), &mshv_apic_io_ops, s, + "mshv-apic-msi", APIC_SPACE_SIZE); + + msi_nonbroken = true; + + /* + * We register this state explicity, rather than going via dc->vmsd. + * The auto-wiring would register the state with + * instance_id == VMSTATE_INSTANCE_ID_ANY, which for the APIC doesn't + * work, b/c the ID carries semantic meaning for restoring the state + * on the destination (which vcpu it belongs to). + */ + vmstate_register_with_alias_id(NULL, + s->initial_apic_id, &vmstate_mshv_apic, ms, + -1, 0, NULL); +} + +static void mshv_apic_unrealize(DeviceState *dev) +{ + MshvAPICState *ms = MSHV_APIC(dev); + + vmstate_unregister(NULL, &vmstate_mshv_apic, ms); +} + +static void mshv_apic_class_init(ObjectClass *klass, const void *data) +{ + APICCommonClass *k = APIC_COMMON_CLASS(klass); + + k->realize = mshv_apic_realize; + k->unrealize = mshv_apic_unrealize; + k->reset = mshv_apic_reset; + k->set_base = mshv_apic_set_base; + k->set_tpr = mshv_apic_set_tpr; + k->get_tpr = mshv_apic_get_tpr; + k->external_nmi = mshv_apic_external_nmi; + k->vapic_base_update = mshv_apic_vapic_base_update; + k->send_msi = mshv_send_msi; +} + +static const TypeInfo mshv_apic_info = { + .name = TYPE_MSHV_APIC, + .parent = TYPE_APIC_COMMON, + .instance_size = sizeof(MshvAPICState), + .class_init = mshv_apic_class_init, +}; + +static void mshv_apic_register_types(void) +{ + type_register_static(&mshv_apic_info); +} + +type_init(mshv_apic_register_types) diff --git a/hw/i386/mshv/meson.build b/hw/i386/mshv/meson.build index 0e556851b6..c631ee1302 100644 --- a/hw/i386/mshv/meson.build +++ b/hw/i386/mshv/meson.build @@ -1,4 +1,5 @@ i386_mshv_ss = ss.source_set() i386_mshv_ss.add(files('clock.c')) +i386_mshv_ss.add(when: 'CONFIG_APIC', if_true: files('apic.c')) i386_ss.add_all(when: 'CONFIG_MSHV', if_true: i386_mshv_ss) diff --git a/hw/i386/x86-cpu.c b/hw/i386/x86-cpu.c index 95e08e3c2a..fba313376c 100644 --- a/hw/i386/x86-cpu.c +++ b/hw/i386/x86-cpu.c @@ -22,6 +22,7 @@ */ #include "qemu/osdep.h" #include "system/whpx.h" +#include "system/mshv.h" #include "system/cpu-timers.h" #include "trace.h" @@ -44,6 +45,11 @@ static void pic_irq_request(void *opaque, int irq, int level) X86CPU *cpu = X86_CPU(cs); trace_x86_pic_interrupt(irq, level); + + if (mshv_irqchip_in_kernel()) { + return; + } + if (cpu_is_apic_enabled(cpu->apic_state) && !kvm_irqchip_in_kernel() && !whpx_irqchip_in_kernel()) { CPU_FOREACH(cs) { diff --git a/include/hw/hyperv/hvgdk_mini.h b/include/hw/hyperv/hvgdk_mini.h index f8838a31bb..0602a7c6cc 100644 --- a/include/hw/hyperv/hvgdk_mini.h +++ b/include/hw/hyperv/hvgdk_mini.h @@ -168,6 +168,7 @@ typedef enum hv_register_name { /* Available */ HV_X64_REGISTER_SPEC_CTRL = 0x00080084, + HV_X64_REGISTER_TSC_DEADLINE = 0x00080095, HV_X64_REGISTER_TSC_ADJUST = 0x00080096, /* CET / Shadow Stack */ @@ -930,6 +931,7 @@ struct hv_cpuid { #define IA32_MSR_DEBUG_CTL 0x1D9 #define IA32_MSR_SPEC_CTRL 0x00000048 #define IA32_MSR_TSC_ADJUST 0x0000003b +#define IA32_MSR_TSC_DEADLINE 0x000006e0 #define IA32_MSR_MISC_ENABLE 0x000001a0 diff --git a/include/hw/i386/apic-msidef.h b/include/hw/i386/apic-msidef.h index 420b41167d..6b860b5807 100644 --- a/include/hw/i386/apic-msidef.h +++ b/include/hw/i386/apic-msidef.h @@ -13,6 +13,7 @@ #define MSI_DATA_VECTOR_MASK 0x000000ff #define MSI_DATA_DELIVERY_MODE_SHIFT 8 +#define MSI_DATA_DELIVERY_MODE_MASK 7 #define MSI_DATA_LEVEL_SHIFT 14 #define MSI_DATA_TRIGGER_SHIFT 15 diff --git a/include/system/mshv.h b/include/system/mshv.h index a6f815b822..f98ddff9b5 100644 --- a/include/system/mshv.h +++ b/include/system/mshv.h @@ -41,9 +41,11 @@ extern bool mshv_allowed; #define mshv_enabled() (mshv_allowed) #define mshv_msi_via_irqfd_enabled() mshv_enabled() +#define mshv_irqchip_in_kernel() mshv_enabled() #else /* CONFIG_MSHV_IS_POSSIBLE */ #define mshv_enabled() false #define mshv_msi_via_irqfd_enabled() mshv_enabled() +#define mshv_irqchip_in_kernel() mshv_enabled() #endif #define TYPE_MSHV_ACCEL ACCEL_CLASS_NAME("mshv") diff --git a/include/system/mshv_int.h b/include/system/mshv_int.h index b91c4d661a..c2f13c0194 100644 --- a/include/system/mshv_int.h +++ b/include/system/mshv_int.h @@ -105,10 +105,16 @@ void mshv_arch_amend_proc_features( void mshv_arch_disable_partition_proc_features( union hv_partition_processor_features *disabled_features); int mshv_arch_post_init_vm(int vm_fd); - +int mshv_get_vp_state(int cpu_fd, struct mshv_get_set_vp_state *state); +int mshv_set_vp_state(int cpu_fd, const struct mshv_get_set_vp_state *state); typedef struct mshv_root_hvcall mshv_root_hvcall; int mshv_hvcall(int fd, const mshv_root_hvcall *args); +/* apic */ +int mshv_init_lint(CPUState *cpu); +int mshv_set_lapic(const CPUState *cpu); +int mshv_get_lapic(CPUState *cpu); + /* memory */ typedef struct MshvMemoryRegion { uint64_t guest_phys_addr; diff --git a/target/i386/cpu-apic.c b/target/i386/cpu-apic.c index 04b7257ad1..b4cf048a7c 100644 --- a/target/i386/cpu-apic.c +++ b/target/i386/cpu-apic.c @@ -14,6 +14,7 @@ #include "system/hw_accel.h" #include "system/kvm.h" #include "system/xen.h" +#include "system/mshv.h" #include "system/address-spaces.h" #include "hw/core/qdev-properties.h" #include "hw/i386/apic_internal.h" @@ -34,6 +35,8 @@ APICCommonClass *apic_get_class(Error **errp) apic_type = "xen-apic"; } else if (whpx_irqchip_in_kernel()) { apic_type = "whpx-apic"; + } else if (mshv_enabled()) { + apic_type = "mshv-apic"; } return APIC_COMMON_CLASS(object_class_by_name(apic_type)); diff --git a/target/i386/mshv/mshv-cpu.c b/target/i386/mshv/mshv-cpu.c index 8eca01a8fa..333ee35e72 100644 --- a/target/i386/mshv/mshv-cpu.c +++ b/target/i386/mshv/mshv-cpu.c @@ -21,7 +21,6 @@ #include "hw/hyperv/hvgdk.h" #include "hw/hyperv/hvgdk_mini.h" #include "hw/hyperv/hvhdk_mini.h" -#include "hw/i386/apic_internal.h" #include "cpu.h" #include "host-cpu.h" @@ -955,6 +954,11 @@ int mshv_arch_load_vcpu_state(CPUState *cpu) return ret; } + ret = mshv_get_lapic(cpu); + if (ret < 0) { + return ret; + } + ret = mshv_get_msrs(cpu); if (ret < 0) { return ret; @@ -1377,116 +1381,6 @@ static int set_xc_reg(const CPUState *cpu) return 0; } -static int get_vp_state(int cpu_fd, struct mshv_get_set_vp_state *state) -{ - int ret; - - ret = ioctl(cpu_fd, MSHV_GET_VP_STATE, state); - if (ret < 0) { - error_report("failed to get partition state: %s", strerror(errno)); - return -1; - } - - return 0; -} - -static int get_lapic(const CPUState *cpu, - struct hv_local_interrupt_controller_state *state) -{ - int ret; - size_t size = 4096; - /* buffer aligned to 4k, as *state requires that */ - void *buffer = qemu_memalign(size, size); - struct mshv_get_set_vp_state mshv_state = { 0 }; - int cpu_fd = mshv_vcpufd(cpu); - - mshv_state.buf_ptr = (uint64_t) buffer; - mshv_state.buf_sz = size; - mshv_state.type = MSHV_VP_STATE_LAPIC; - - ret = get_vp_state(cpu_fd, &mshv_state); - if (ret == 0) { - memcpy(state, buffer, sizeof(*state)); - } - qemu_vfree(buffer); - if (ret < 0) { - error_report("failed to get lapic"); - return -1; - } - - return 0; -} - -static uint32_t set_apic_delivery_mode(uint32_t reg, uint32_t mode) -{ - return ((reg) & ~0x700) | ((mode) << 8); -} - -static int set_vp_state(int cpu_fd, const struct mshv_get_set_vp_state *state) -{ - int ret; - - ret = ioctl(cpu_fd, MSHV_SET_VP_STATE, state); - if (ret < 0) { - error_report("failed to set partition state: %s", strerror(errno)); - return -1; - } - - return 0; -} - -static int set_lapic(const CPUState *cpu, - const struct hv_local_interrupt_controller_state *state) -{ - int ret; - size_t size = 4096; - /* buffer aligned to 4k, as *state requires that */ - void *buffer = qemu_memalign(size, size); - struct mshv_get_set_vp_state mshv_state = { 0 }; - int cpu_fd = mshv_vcpufd(cpu); - - if (!state) { - error_report("lapic state is NULL"); - return -1; - } - memcpy(buffer, state, sizeof(*state)); - - mshv_state.buf_ptr = (uint64_t) buffer; - mshv_state.buf_sz = size; - mshv_state.type = MSHV_VP_STATE_LAPIC; - - ret = set_vp_state(cpu_fd, &mshv_state); - qemu_vfree(buffer); - if (ret < 0) { - error_report("failed to set lapic: %s", strerror(errno)); - return -1; - } - - return 0; -} - -static int init_lint(const CPUState *cpu) -{ - int ret; - uint32_t *lvt_lint0, *lvt_lint1; - - struct hv_local_interrupt_controller_state lapic_state = { 0 }; - ret = get_lapic(cpu, &lapic_state); - if (ret < 0) { - return ret; - } - - lvt_lint0 = &lapic_state.apic_lvt_lint0; - *lvt_lint0 = set_apic_delivery_mode(*lvt_lint0, APIC_DM_EXTINT); - - lvt_lint1 = &lapic_state.apic_lvt_lint1; - *lvt_lint1 = set_apic_delivery_mode(*lvt_lint1, APIC_DM_NMI); - - /* TODO: should we skip setting lapic if the values are the same? */ - - return set_lapic(cpu, &lapic_state); -} - int mshv_arch_store_vcpu_state(const CPUState *cpu) { int ret; @@ -1511,6 +1405,12 @@ int mshv_arch_store_vcpu_state(const CPUState *cpu) return ret; } + /* INVARIANT: special regs (APIC_BASE) must be restored before LAPIC */ + ret = mshv_set_lapic(cpu); + if (ret < 0) { + return ret; + } + ret = mshv_set_msrs(cpu); if (ret < 0) { return ret; @@ -2099,7 +1999,7 @@ void mshv_arch_init_vcpu(CPUState *cpu) ret = mshv_init_msrs(cpu); assert(ret == 0); - ret = init_lint(cpu); + ret = mshv_init_lint(cpu); assert(ret == 0); } @@ -2246,6 +2146,33 @@ static void mshv_cpu_xsave_init(void) } } +int mshv_set_vp_state(int cpu_fd, const struct mshv_get_set_vp_state *state) +{ + int ret; + + ret = ioctl(cpu_fd, MSHV_SET_VP_STATE, state); + if (ret < 0) { + error_report("failed to set partition state: %s", strerror(errno)); + return -1; + } + + return 0; +} + + +int mshv_get_vp_state(int cpu_fd, struct mshv_get_set_vp_state *state) +{ + int ret; + + ret = ioctl(cpu_fd, MSHV_GET_VP_STATE, state); + if (ret < 0) { + error_report("failed to get partition state: %s", strerror(errno)); + return -1; + } + + return 0; +} + static void mshv_cpu_instance_init(CPUState *cs) { X86CPU *cpu = X86_CPU(cs); diff --git a/target/i386/mshv/msr.c b/target/i386/mshv/msr.c index 8c220a9942..a5f639c3ca 100644 --- a/target/i386/mshv/msr.c +++ b/target/i386/mshv/msr.c @@ -60,6 +60,8 @@ static const MshvMsrEnvMap msr_env_map[] = { offsetof(CPUX86State, tsc_aux) }, { IA32_MSR_TSC_ADJUST, HV_X64_REGISTER_TSC_ADJUST, offsetof(CPUX86State, tsc_adjust) }, + { IA32_MSR_TSC_DEADLINE, HV_X64_REGISTER_TSC_DEADLINE, + offsetof(CPUX86State, tsc_deadline) }, /* Hyper-V per-partition MSRs */ { HV_X64_MSR_HYPERCALL, HV_X64_REGISTER_HYPERCALL, -- 2.34.1
