/* * QEMU MSHV support * * Copyright Microsoft, Corp. 2025 * * Authors: * Ziqiao Zhou * Magnus Kulke * Jinank Jain * Wei Liu * * SPDX-License-Identifier: GPL-2.0-or-later * */ #include "qemu/osdep.h" #include "qapi/error.h" #include "qemu/error-report.h" #include "qemu/event_notifier.h" #include "qemu/module.h" #include "qemu/main-loop.h" #include "hw/core/boards.h" #include "hw/hyperv/hvhdk.h" #include "hw/hyperv/hvhdk_mini.h" #include "hw/hyperv/hvgdk.h" #include "hw/hyperv/hvgdk_mini.h" #include "linux/mshv.h" #include "qemu/accel.h" #include "qemu/guest-random.h" #include "accel/accel-ops.h" #include "accel/accel-cpu-ops.h" #include "exec/cpu-common.h" #include "system/cpus.h" #include "system/runstate.h" #include "system/accel-blocker.h" #include "system/address-spaces.h" #include "system/mshv.h" #include "system/mshv_int.h" #include "system/reset.h" #include "migration/qemu-file-types.h" #include "migration/register.h" #include "trace.h" #include #include bool mshv_allowed; MshvState *mshv_state; static int init_mshv(int *mshv_fd) { int fd = open("/dev/mshv", O_RDWR | O_CLOEXEC); if (fd < 0) { error_report("Failed to open /dev/mshv: %s", strerror(errno)); return -1; } *mshv_fd = fd; return 0; } static int mshv_load_cleanup(void *opaque) { CPUState *cpu; int ret; ret = mshv_arch_set_partition_msrs(first_cpu); if (ret < 0) { error_report("Failed to set partition MSRs: %s", strerror(-ret)); return -1; } CPU_FOREACH(cpu) { ret = mshv_arch_set_mp_state(cpu); if (ret < 0) { error_report("Failed to set mp state for vCPU %d: %s", cpu->cpu_index, strerror(-ret)); return -1; } } return 0; } static int get_host_partition_property(int mshv_fd, uint32_t property_code, uint64_t *value) { int ret; struct hv_input_get_partition_property in = {0}; struct hv_output_get_partition_property out = {0}; struct mshv_root_hvcall args = {0}; in.property_code = property_code; args.code = HVCALL_GET_PARTITION_PROPERTY; args.in_sz = sizeof(in); args.in_ptr = (uint64_t)∈ args.out_sz = sizeof(out); args.out_ptr = (uint64_t)&out; ret = ioctl(mshv_fd, MSHV_ROOT_HVCALL, &args); if (ret < 0) { error_report("Failed to get host partition property bank: %s", strerror(errno)); return -1; } *value = out.property_value; return 0; } static int get_partition_property(int vm_fd, uint32_t feature_bank, uint64_t *value) { struct hv_input_get_partition_property in = {0}; struct hv_output_get_partition_property out = {0}; struct mshv_root_hvcall args = {0}; int ret; in.property_code = feature_bank; args.code = HVCALL_GET_PARTITION_PROPERTY; args.in_sz = sizeof(in); args.in_ptr = (uint64_t)∈ args.out_sz = sizeof(out); args.out_ptr = (uint64_t)&out; ret = ioctl(vm_fd, MSHV_ROOT_HVCALL, &args); if (ret < 0) { error_report("Failed to get guest partition property bank: %s", strerror(errno)); return -1; } *value = out.property_value; return 0; } static int get_proc_features(int vm_fd, union hv_partition_processor_features *features) { int ret; ret = get_partition_property(vm_fd, HV_PARTITION_PROPERTY_PROCESSOR_FEATURES0, &features->as_uint64[0]); if (ret < 0) { error_report("Failed to get processor features bank 0"); return -1; } ret = get_partition_property(vm_fd, HV_PARTITION_PROPERTY_PROCESSOR_FEATURES1, &features->as_uint64[1]); if (ret < 0) { error_report("Failed to get processor features bank 1"); return -1; } return 0; } static int create_partition(int mshv_fd, int *vm_fd) { int ret; uint64_t pt_flags, host_proc_features; union hv_partition_processor_xsave_features disabled_xsave_features; union hv_partition_processor_features disabled_partition_features = {0}; struct mshv_create_partition_v2 args = {0}; QEMU_BUILD_BUG_ON(MSHV_NUM_CPU_FEATURES_BANKS != 2); /* Initialize pt_flags with the desired features */ pt_flags = (1ULL << MSHV_PT_BIT_LAPIC) | (1ULL << MSHV_PT_BIT_X2APIC) | (1ULL << MSHV_PT_BIT_GPA_SUPER_PAGES) | (1ULL << MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES); /* enable all */ disabled_xsave_features.as_uint64 = 0; /* * AMX TILE XSAVE state (XTILE_DATA) is 8KB, which exceeds the * current fixed 4KB XSAVE buffer size. */ disabled_xsave_features.amx_tile_support = 1; disabled_xsave_features.amx_bf16_support = 1; disabled_xsave_features.amx_int8_support = 1; disabled_xsave_features.amx_fp16_support = 1; /* * query host for supported processor features and disable unsupported * features: (0 means supported, 1 means disabled, hence the negation) */ ret = get_host_partition_property(mshv_fd, HV_PARTITION_PROPERTY_PROCESSOR_FEATURES0, &host_proc_features); if (ret < 0) { error_report("Failed to get host processor feature bank 0"); return -1; } args.pt_cpu_fbanks[0] = ~host_proc_features; ret = get_host_partition_property(mshv_fd, HV_PARTITION_PROPERTY_PROCESSOR_FEATURES1, &host_proc_features); if (ret < 0) { error_report("Failed to get host processor feature bank 1"); return -1; } args.pt_cpu_fbanks[1] = ~host_proc_features; /* arch-specific features we disable regardless of host support */ mshv_arch_disable_partition_proc_features(&disabled_partition_features); args.pt_cpu_fbanks[0] |= disabled_partition_features.as_uint64[0]; args.pt_cpu_fbanks[1] |= disabled_partition_features.as_uint64[1]; /* populate args structure */ args.pt_flags = pt_flags; args.pt_isolation = MSHV_PT_ISOLATION_NONE; args.pt_disabled_xsave = disabled_xsave_features.as_uint64; args.pt_num_cpu_fbanks = MSHV_NUM_CPU_FEATURES_BANKS; ret = ioctl(mshv_fd, MSHV_CREATE_PARTITION, &args); if (ret < 0) { error_report("Failed to create partition: %s", strerror(errno)); return -1; } *vm_fd = ret; return 0; } static int set_synthetic_proc_features(int vm_fd) { int ret; struct hv_input_set_partition_property in = {0}; union hv_partition_synthetic_processor_features features = {0}; /* Access the bitfield and set the desired features */ features.hypervisor_present = 1; features.hv1 = 1; features.access_partition_reference_counter = 1; features.access_synic_regs = 1; features.access_synthetic_timer_regs = 1; features.access_partition_reference_tsc = 1; features.access_frequency_regs = 1; features.access_intr_ctrl_regs = 1; features.access_vp_index = 1; features.access_hypercall_regs = 1; features.tb_flush_hypercalls = 1; features.synthetic_cluster_ipi = 1; features.direct_synthetic_timers = 1; mshv_arch_amend_proc_features(&features); in.property_code = HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES; in.property_value = features.as_uint64[0]; struct mshv_root_hvcall args = {0}; args.code = HVCALL_SET_PARTITION_PROPERTY; args.in_sz = sizeof(in); args.in_ptr = (uint64_t)∈ trace_mshv_hvcall_args("synthetic_proc_features", args.code, args.in_sz); ret = mshv_hvcall(vm_fd, &args); if (ret < 0) { error_report("Failed to set synthethic proc features"); return -errno; } return 0; } static int initialize_vm(int vm_fd) { int ret = ioctl(vm_fd, MSHV_INITIALIZE_PARTITION); if (ret < 0) { error_report("Failed to initialize partition: %s", strerror(errno)); return -1; } return 0; } static int create_vm(int mshv_fd, int *vm_fd) { int ret = create_partition(mshv_fd, vm_fd); if (ret < 0) { return -1; } ret = set_synthetic_proc_features(*vm_fd); if (ret < 0) { return -1; } ret = initialize_vm(*vm_fd); if (ret < 0) { return -1; } ret = mshv_arch_post_init_vm(*vm_fd); if (ret < 0) { return -1; } return 0; } static void mem_region_add(MemoryListener *listener, MemoryRegionSection *section) { MshvMemoryListener *mml; mml = container_of(listener, MshvMemoryListener, listener); memory_region_ref(section->mr); mshv_set_phys_mem(mml, section, true); } static void mem_region_del(MemoryListener *listener, MemoryRegionSection *section) { MshvMemoryListener *mml; mml = container_of(listener, MshvMemoryListener, listener); mshv_set_phys_mem(mml, section, false); memory_region_unref(section->mr); } typedef enum { DATAMATCH_NONE, DATAMATCH_U32, DATAMATCH_U64, } DatamatchTag; typedef struct { DatamatchTag tag; union { uint32_t u32; uint64_t u64; } value; } Datamatch; /* flags: determine whether to de/assign */ static int ioeventfd(int vm_fd, int event_fd, uint64_t addr, Datamatch dm, uint32_t flags) { struct mshv_user_ioeventfd args = {0}; args.fd = event_fd; args.addr = addr; args.flags = flags; if (dm.tag == DATAMATCH_NONE) { args.datamatch = 0; } else { flags |= BIT(MSHV_IOEVENTFD_BIT_DATAMATCH); args.flags = flags; if (dm.tag == DATAMATCH_U64) { args.len = sizeof(uint64_t); args.datamatch = dm.value.u64; } else { args.len = sizeof(uint32_t); args.datamatch = dm.value.u32; } } int ret = ioctl(vm_fd, MSHV_IOEVENTFD, &args); if (ret < 0) { return -errno; } return ret; } static int unregister_ioevent(int vm_fd, int event_fd, uint64_t mmio_addr, uint64_t data, uint32_t len, bool data_match) { uint32_t flags = 0; Datamatch dm = {0}; flags |= BIT(MSHV_IOEVENTFD_BIT_DEASSIGN); if (!data_match) { dm.tag = DATAMATCH_NONE; } else if (len == sizeof(uint64_t)) { dm.tag = DATAMATCH_U64; dm.value.u64 = data; } else { dm.tag = DATAMATCH_U32; dm.value.u32 = data; } return ioeventfd(vm_fd, event_fd, mmio_addr, dm, flags); } static int register_ioevent(int vm_fd, int event_fd, uint64_t mmio_addr, uint64_t val, bool is_64bit, bool is_datamatch) { uint32_t flags = 0; Datamatch dm = {0}; if (!is_datamatch) { dm.tag = DATAMATCH_NONE; } else if (is_64bit) { dm.tag = DATAMATCH_U64; dm.value.u64 = val; } else { dm.tag = DATAMATCH_U32; dm.value.u32 = val; } return ioeventfd(vm_fd, event_fd, mmio_addr, dm, flags); } static void mem_ioeventfd_add(MemoryListener *listener, MemoryRegionSection *section, bool match_data, uint64_t data, EventNotifier *e) { int fd = event_notifier_get_fd(e); int ret; bool is_64 = int128_get64(section->size) == 8; uint64_t addr = section->offset_within_address_space; trace_mshv_mem_ioeventfd_add(addr, int128_get64(section->size), data); ret = register_ioevent(mshv_state->vm, fd, addr, data, is_64, match_data); if (ret < 0) { error_report("Failed to register ioeventfd: %s (%d)", strerror(-ret), -ret); abort(); } } static void mem_ioeventfd_del(MemoryListener *listener, MemoryRegionSection *section, bool match_data, uint64_t data, EventNotifier *e) { int fd = event_notifier_get_fd(e); int ret; uint64_t addr = section->offset_within_address_space; uint64_t len = int128_get64(section->size); trace_mshv_mem_ioeventfd_del(section->offset_within_address_space, int128_get64(section->size), data); ret = unregister_ioevent(mshv_state->vm, fd, addr, data, len, match_data); if (ret < 0) { error_report("Failed to unregister ioeventfd: %s (%d)", strerror(-ret), -ret); abort(); } } static MemoryListener mshv_memory_listener = { .name = "mshv", .priority = MEMORY_LISTENER_PRIORITY_ACCEL, .region_add = mem_region_add, .region_del = mem_region_del, .eventfd_add = mem_ioeventfd_add, .eventfd_del = mem_ioeventfd_del, .log_sync = mshv_log_sync, .log_global_start = mshv_log_global_start, .log_global_stop = mshv_log_global_stop, }; static MemoryListener mshv_io_listener = { .name = "mshv", .priority = MEMORY_LISTENER_PRIORITY_DEV_BACKEND, /* MSHV does not support PIO eventfd */ }; static void register_mshv_memory_listener(MshvState *s, MshvMemoryListener *mml, AddressSpace *as, int as_id, const char *name) { int i; mml->listener = mshv_memory_listener; mml->listener.name = name; memory_listener_register(&mml->listener, as); for (i = 0; i < s->nr_as; ++i) { if (!s->as[i].as) { s->as[i].as = as; s->as[i].ml = mml; break; } } } int mshv_hvcall(int fd, const mshv_root_hvcall *args) { int ret = 0; ret = ioctl(fd, MSHV_ROOT_HVCALL, args); if (ret < 0) { error_report("Failed to perform hvcall: %s", strerror(errno)); return -1; } return ret; } static int mshv_init_vcpu(CPUState *cpu) { int vm_fd = mshv_state->vm; uint8_t vp_index = cpu->cpu_index; int ret; cpu->accel = g_new0(AccelCPUState, 1); ret = mshv_create_vcpu(vm_fd, vp_index, &cpu->accel->cpufd); if (ret < 0) { return -1; } mshv_arch_init_vcpu(cpu); cpu->vcpu_dirty = true; return 0; } static SaveVMHandlers savevm_mshv = { .load_cleanup = mshv_load_cleanup, }; static int mshv_init(AccelState *as, MachineState *ms) { MshvState *s; int mshv_fd, vm_fd, ret; if (mshv_state) { warn_report("MSHV accelerator already initialized"); return 0; } s = MSHV_STATE(as); accel_blocker_init(); s->vm = 0; ret = init_mshv(&mshv_fd); if (ret < 0) { return -1; } mshv_init_mmio_emu(); ret = create_vm(mshv_fd, &vm_fd); if (ret < 0) { close(mshv_fd); return -1; } s->vm = vm_fd; s->fd = mshv_fd; ret = get_proc_features(vm_fd, &s->processor_features); if (ret < 0) { return -1; } s->nr_as = 1; s->as = g_new0(MshvAddressSpace, s->nr_as); mshv_state = s; mshv_init_irq_routing(s); register_mshv_memory_listener(s, &s->memory_listener, &address_space_memory, 0, "mshv-memory"); memory_listener_register(&mshv_io_listener, &address_space_io); register_savevm_live("mshv", 0, 1, &savevm_mshv, s); mshv_clock_init(); return 0; } static int mshv_destroy_vcpu(CPUState *cpu) { int cpu_fd = mshv_vcpufd(cpu); int vm_fd = mshv_state->vm; mshv_remove_vcpu(vm_fd, cpu_fd); mshv_vcpufd(cpu) = 0; mshv_arch_destroy_vcpu(cpu); g_clear_pointer(&cpu->accel, g_free); return 0; } static int mshv_cpu_exec(CPUState *cpu) { hv_message mshv_msg; enum MshvVmExit exit_reason; int ret = 0; bql_unlock(); cpu_exec_start(cpu); do { if (cpu->vcpu_dirty) { ret = mshv_arch_store_vcpu_state(cpu); if (ret) { error_report("Failed to put registers after init: %s", strerror(-ret)); ret = -1; break; } cpu->vcpu_dirty = false; } /* Corresponding store-release is in cpu_exit. */ if (qatomic_load_acquire(&cpu->exit_request)) { trace_mshv_interrupt_exit_request(cpu->cpu_index); ret = EXCP_INTERRUPT; break; } ret = mshv_run_vcpu(mshv_state->vm, cpu, &mshv_msg, &exit_reason); if (ret < 0) { error_report("Failed to run on vcpu %d", cpu->cpu_index); abort(); } switch (exit_reason) { case MshvVmExitIgnore: break; default: ret = EXCP_INTERRUPT; break; } } while (ret == 0); cpu_exec_end(cpu); bql_lock(); if (ret < 0) { cpu_dump_state(cpu, stderr, CPU_DUMP_CODE); vm_stop(RUN_STATE_INTERNAL_ERROR); } return ret; } /* * We need a dummy handler to make SIG_IPI a deliverable signal. The kernel * handler will be woken up by the caught signal and instruct the hypervisor * to suspend execution (the concrete mechanism differs between schedulers) * and return to userspace. */ static void dummy_handler(int sig) { } static void init_signal(CPUState *cpu) { /* init cpu signals */ struct sigaction sigact; sigset_t set; memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = dummy_handler; sigaction(SIG_IPI, &sigact, NULL); pthread_sigmask(SIG_BLOCK, NULL, &set); sigdelset(&set, SIG_IPI); pthread_sigmask(SIG_SETMASK, &set, NULL); } static void *mshv_vcpu_thread(void *arg) { CPUState *cpu = arg; int ret; rcu_register_thread(); bql_lock(); qemu_thread_get_self(cpu->thread); cpu->thread_id = qemu_get_thread_id(); current_cpu = cpu; ret = mshv_init_vcpu(cpu); if (ret < 0) { error_report("Failed to init vcpu %d", cpu->cpu_index); goto cleanup; } init_signal(cpu); /* signal CPU creation */ cpu_thread_signal_created(cpu); qemu_guest_random_seed_thread_part2(cpu->random_seed); do { qemu_process_cpu_events(cpu); if (cpu_can_run(cpu)) { mshv_cpu_exec(cpu); } } while (!cpu->unplug || cpu_can_run(cpu)); mshv_destroy_vcpu(cpu); cleanup: cpu_thread_signal_destroyed(cpu); bql_unlock(); rcu_unregister_thread(); return NULL; } static void mshv_start_vcpu_thread(CPUState *cpu) { char thread_name[VCPU_THREAD_NAME_SIZE]; snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/MSHV", cpu->cpu_index); cpu->thread = g_malloc0(sizeof(QemuThread)); cpu->halt_cond = g_malloc0(sizeof(QemuCond)); qemu_cond_init(cpu->halt_cond); trace_mshv_start_vcpu_thread(thread_name, cpu->cpu_index); qemu_thread_create(cpu->thread, thread_name, mshv_vcpu_thread, cpu, QEMU_THREAD_JOINABLE); } static void do_mshv_cpu_synchronize_post_init(CPUState *cpu, run_on_cpu_data arg) { int ret = mshv_arch_store_vcpu_state(cpu); if (ret < 0) { error_report("Failed to put registers after init: %s", strerror(-ret)); abort(); } cpu->vcpu_dirty = false; } static void mshv_cpu_synchronize_post_init(CPUState *cpu) { run_on_cpu(cpu, do_mshv_cpu_synchronize_post_init, RUN_ON_CPU_NULL); } static void mshv_cpu_synchronize_post_reset(CPUState *cpu) { int ret = mshv_arch_store_vcpu_state(cpu); if (ret) { error_report("Failed to put registers after reset: %s", strerror(-ret)); cpu_dump_state(cpu, stderr, CPU_DUMP_CODE); vm_stop(RUN_STATE_INTERNAL_ERROR); } cpu->vcpu_dirty = false; } static void do_mshv_cpu_synchronize_pre_loadvm(CPUState *cpu, run_on_cpu_data arg) { cpu->vcpu_dirty = true; } static void mshv_cpu_synchronize_pre_loadvm(CPUState *cpu) { run_on_cpu(cpu, do_mshv_cpu_synchronize_pre_loadvm, RUN_ON_CPU_NULL); } static void do_mshv_cpu_synchronize(CPUState *cpu, run_on_cpu_data arg) { if (!cpu->vcpu_dirty) { int ret = mshv_arch_load_vcpu_state(cpu); if (ret < 0) { error_report("Failed to load registers for vcpu %d", cpu->cpu_index); cpu_dump_state(cpu, stderr, CPU_DUMP_CODE); vm_stop(RUN_STATE_INTERNAL_ERROR); } cpu->vcpu_dirty = true; } } static void mshv_cpu_synchronize(CPUState *cpu) { if (!cpu->vcpu_dirty) { run_on_cpu(cpu, do_mshv_cpu_synchronize, RUN_ON_CPU_NULL); } } static bool mshv_cpus_are_resettable(void) { return false; } static void mshv_accel_class_init(ObjectClass *oc, const void *data) { AccelClass *ac = ACCEL_CLASS(oc); ac->name = "MSHV"; ac->init_machine = mshv_init; ac->allowed = &mshv_allowed; } static void mshv_accel_instance_init(Object *obj) { MshvState *s = MSHV_STATE(obj); s->vm = 0; } static const TypeInfo mshv_accel_type = { .name = TYPE_MSHV_ACCEL, .parent = TYPE_ACCEL, .instance_init = mshv_accel_instance_init, .class_init = mshv_accel_class_init, .instance_size = sizeof(MshvState), }; /* * MSHV manages secondary processors in the hypervisor. SIPI for x86 and * PSCI for Arm are handled internally. Halted vCPUs must still enter * mshv_cpu_exec() so that MSHV_RUN_VP is called and the hypervisor will * wake APs. */ static bool mshv_vcpu_thread_is_idle(CPUState *cpu) { return false; } static void mshv_accel_ops_class_init(ObjectClass *oc, const void *data) { AccelOpsClass *ops = ACCEL_OPS_CLASS(oc); ops->create_vcpu_thread = mshv_start_vcpu_thread; ops->cpu_thread_is_idle = mshv_vcpu_thread_is_idle; ops->synchronize_post_init = mshv_cpu_synchronize_post_init; ops->synchronize_post_reset = mshv_cpu_synchronize_post_reset; 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; } static const TypeInfo mshv_accel_ops_type = { .name = ACCEL_OPS_NAME("mshv"), .parent = TYPE_ACCEL_OPS, .class_init = mshv_accel_ops_class_init, .abstract = true, }; static void mshv_type_init(void) { type_register_static(&mshv_accel_type); type_register_static(&mshv_accel_ops_type); } type_init(mshv_type_init);