859 lines
22 KiB
C
859 lines
22 KiB
C
/*
|
|
* QEMU MSHV support
|
|
*
|
|
* Copyright Microsoft, Corp. 2025
|
|
*
|
|
* Authors:
|
|
* Ziqiao Zhou <[email protected]>
|
|
* Magnus Kulke <[email protected]>
|
|
* Jinank Jain <[email protected]>
|
|
* Wei Liu <[email protected]>
|
|
*
|
|
* 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);
|