| /* |
| * QEMU MSHV support |
| * |
| * Copyright Microsoft, Corp. 2025 |
| * |
| * Authors: |
| * Ziqiao Zhou <ziqiaozhou@microsoft.com> |
| * Magnus Kulke <magnuskulke@microsoft.com> |
| * Jinank Jain <jinankjain@microsoft.com> |
| * Wei Liu <liuwe@microsoft.com> |
| * |
| * 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 <err.h> |
| #include <sys/ioctl.h> |
| |
| 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); |