diff --git a/accel/accel-irq.c b/accel/accel-irq.c index 7f864e35c4..830be27c7f 100644 --- a/accel/accel-irq.c +++ b/accel/accel-irq.c @@ -10,19 +10,18 @@ */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "hw/pci/msi.h" #include "system/kvm.h" #include "system/mshv.h" #include "system/accel-irq.h" -int accel_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) +int accel_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { - return mshv_irqchip_add_msi_route(vector, dev); + return mshv_irqchip_add_msi_route(c, vector, dev); } -#endif if (kvm_enabled()) { return kvm_irqchip_add_msi_route(c, vector, dev); } @@ -31,36 +30,28 @@ int accel_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) int accel_irqchip_update_msi_route(int vector, MSIMessage msg, PCIDevice *dev) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { return mshv_irqchip_update_msi_route(vector, msg, dev); } -#endif if (kvm_enabled()) { return kvm_irqchip_update_msi_route(kvm_state, vector, msg, dev); } return -ENOSYS; } -void accel_irqchip_commit_route_changes(KVMRouteChange *c) +void accel_irqchip_commit_route_changes(AccelRouteChange *c) { -#ifdef CONFIG_MSHV_IS_POSSIBLE - if (mshv_msi_via_irqfd_enabled()) { - mshv_irqchip_commit_routes(); - } -#endif - if (kvm_enabled()) { - kvm_irqchip_commit_route_changes(c); + if (c->changes) { + accel_irqchip_commit_routes(); + c->changes = 0; } } void accel_irqchip_commit_routes(void) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { - mshv_irqchip_commit_routes(); + mshv_irqchip_commit_routes(mshv_state); } -#endif if (kvm_enabled()) { kvm_irqchip_commit_routes(kvm_state); } @@ -68,11 +59,9 @@ void accel_irqchip_commit_routes(void) void accel_irqchip_release_virq(int virq) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { - mshv_irqchip_release_virq(virq); + mshv_irqchip_release_virq(mshv_state, virq); } -#endif if (kvm_enabled()) { kvm_irqchip_release_virq(kvm_state, virq); } @@ -81,11 +70,9 @@ void accel_irqchip_release_virq(int virq) int accel_irqchip_add_irqfd_notifier_gsi(EventNotifier *n, EventNotifier *rn, int virq) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { return mshv_irqchip_add_irqfd_notifier_gsi(n, rn, virq); } -#endif if (kvm_enabled()) { return kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, n, rn, virq); } @@ -94,13 +81,29 @@ int accel_irqchip_add_irqfd_notifier_gsi(EventNotifier *n, EventNotifier *rn, int accel_irqchip_remove_irqfd_notifier_gsi(EventNotifier *n, int virq) { -#ifdef CONFIG_MSHV_IS_POSSIBLE if (mshv_msi_via_irqfd_enabled()) { return mshv_irqchip_remove_irqfd_notifier_gsi(n, virq); } -#endif if (kvm_enabled()) { return kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, n, virq); } return -ENOSYS; } + +inline AccelRouteChange accel_irqchip_begin_route_changes(void) +{ + if (mshv_msi_via_irqfd_enabled()) { + return (AccelRouteChange) { + .accel = ACCEL(mshv_state), + .changes = 0, + }; + } + if (kvm_enabled()) { + return (AccelRouteChange) { + .accel = ACCEL(kvm_state), + .changes = 0, + }; + } + error_report("can't initiate route change, no accel irqchip available"); + abort(); +} diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index 8ddfbaff46..5d55cb45cf 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -2359,11 +2359,11 @@ int kvm_irqchip_send_msi(KVMState *s, MSIMessage msg) return kvm_vm_ioctl(s, KVM_SIGNAL_MSI, &msi); } -int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) +int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { struct kvm_irq_routing_entry kroute = {}; int virq; - KVMState *s = c->s; + KVMState *s = KVM_STATE(c->accel); MSIMessage msg = {0, 0}; if (pci_available && dev) { @@ -2506,7 +2506,7 @@ int kvm_irqchip_send_msi(KVMState *s, MSIMessage msg) abort(); } -int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) +int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { return -ENOSYS; } diff --git a/accel/mshv/irq.c b/accel/mshv/irq.c index 3c238c33c3..4828ac51ac 100644 --- a/accel/mshv/irq.c +++ b/accel/mshv/irq.c @@ -25,177 +25,6 @@ #define MSHV_IRQFD_RESAMPLE_FLAG (1 << MSHV_IRQFD_BIT_RESAMPLE) #define MSHV_IRQFD_BIT_DEASSIGN_FLAG (1 << MSHV_IRQFD_BIT_DEASSIGN) -static MshvMsiControl *msi_control; -static QemuMutex msi_control_mutex; - -void mshv_init_msicontrol(void) -{ - qemu_mutex_init(&msi_control_mutex); - msi_control = g_new0(MshvMsiControl, 1); - msi_control->gsi_routes = g_hash_table_new(g_direct_hash, g_direct_equal); - msi_control->updated = false; -} - -static int set_msi_routing(uint32_t gsi, uint64_t addr, uint32_t data) -{ - struct mshv_user_irq_entry *entry; - uint32_t high_addr = addr >> 32; - uint32_t low_addr = addr & 0xFFFFFFFF; - GHashTable *gsi_routes; - - trace_mshv_set_msi_routing(gsi, addr, data); - - if (gsi >= MSHV_MAX_MSI_ROUTES) { - error_report("gsi >= MSHV_MAX_MSI_ROUTES"); - return -1; - } - - assert(msi_control); - - WITH_QEMU_LOCK_GUARD(&msi_control_mutex) { - gsi_routes = msi_control->gsi_routes; - entry = g_hash_table_lookup(gsi_routes, GINT_TO_POINTER(gsi)); - - if (entry - && entry->address_hi == high_addr - && entry->address_lo == low_addr - && entry->data == data) - { - /* nothing to update */ - return 0; - } - - /* free old entry */ - g_free(entry); - - /* create new entry */ - entry = g_new0(struct mshv_user_irq_entry, 1); - entry->gsi = gsi; - entry->address_hi = high_addr; - entry->address_lo = low_addr; - entry->data = data; - - g_hash_table_insert(gsi_routes, GINT_TO_POINTER(gsi), entry); - msi_control->updated = true; - } - - return 0; -} - -static int add_msi_routing(uint64_t addr, uint32_t data) -{ - struct mshv_user_irq_entry *route_entry; - uint32_t high_addr = addr >> 32; - uint32_t low_addr = addr & 0xFFFFFFFF; - int gsi; - GHashTable *gsi_routes; - - trace_mshv_add_msi_routing(addr, data); - - assert(msi_control); - - WITH_QEMU_LOCK_GUARD(&msi_control_mutex) { - /* find an empty slot */ - gsi = 0; - gsi_routes = msi_control->gsi_routes; - while (gsi < MSHV_MAX_MSI_ROUTES) { - route_entry = g_hash_table_lookup(gsi_routes, GINT_TO_POINTER(gsi)); - if (!route_entry) { - break; - } - gsi++; - } - if (gsi >= MSHV_MAX_MSI_ROUTES) { - error_report("No empty gsi slot available"); - return -1; - } - - /* create new entry */ - route_entry = g_new0(struct mshv_user_irq_entry, 1); - route_entry->gsi = gsi; - route_entry->address_hi = high_addr; - route_entry->address_lo = low_addr; - route_entry->data = data; - - g_hash_table_insert(gsi_routes, GINT_TO_POINTER(gsi), route_entry); - msi_control->updated = true; - } - - return gsi; -} - -static int commit_msi_routing_table(int vm_fd) -{ - guint len; - int i, ret; - size_t table_size; - struct mshv_user_irq_table *table; - GHashTableIter iter; - gpointer key, value; - - assert(msi_control); - - WITH_QEMU_LOCK_GUARD(&msi_control_mutex) { - if (!msi_control->updated) { - /* nothing to update */ - return 0; - } - - /* Calculate the size of the table */ - len = g_hash_table_size(msi_control->gsi_routes); - table_size = sizeof(struct mshv_user_irq_table) - + len * sizeof(struct mshv_user_irq_entry); - table = g_malloc0(table_size); - - g_hash_table_iter_init(&iter, msi_control->gsi_routes); - i = 0; - while (g_hash_table_iter_next(&iter, &key, &value)) { - struct mshv_user_irq_entry *entry = value; - table->entries[i] = *entry; - i++; - } - table->nr = i; - - trace_mshv_commit_msi_routing_table(vm_fd, len); - - ret = ioctl(vm_fd, MSHV_SET_MSI_ROUTING, table); - g_free(table); - if (ret < 0) { - error_report("Failed to commit msi routing table"); - return -1; - } - msi_control->updated = false; - } - return 0; -} - -static int remove_msi_routing(uint32_t gsi) -{ - struct mshv_user_irq_entry *route_entry; - GHashTable *gsi_routes; - - trace_mshv_remove_msi_routing(gsi); - - if (gsi >= MSHV_MAX_MSI_ROUTES) { - error_report("Invalid GSI: %u", gsi); - return -1; - } - - assert(msi_control); - - WITH_QEMU_LOCK_GUARD(&msi_control_mutex) { - gsi_routes = msi_control->gsi_routes; - route_entry = g_hash_table_lookup(gsi_routes, GINT_TO_POINTER(gsi)); - if (route_entry) { - g_hash_table_remove(gsi_routes, GINT_TO_POINTER(gsi)); - g_free(route_entry); - msi_control->updated = true; - } - } - - return 0; -} - /* Pass an eventfd which is to be used for injecting interrupts from userland */ static int irqfd(int vm_fd, int fd, int resample_fd, uint32_t gsi, uint32_t flags) @@ -278,35 +107,155 @@ static int irqchip_update_irqfd_notifier_gsi(const EventNotifier *event, return register_irqfd(vm_fd, fd, virq); } - -int mshv_irqchip_add_msi_route(int vector, PCIDevice *dev) +static int irqchip_allocate_gsi(MshvState *s, int *gsi) { - MSIMessage msg = { 0, 0 }; - int virq = 0; + int next_gsi; - if (pci_available && dev) { - msg = pci_get_msi_message(dev, vector); - virq = add_msi_routing(msg.address, le32_to_cpu(msg.data)); + /* Return the lowest unused GSI in the bitmap */ + next_gsi = find_first_zero_bit(s->used_gsi_bitmap, s->gsi_count); + if (next_gsi >= s->gsi_count) { + return -ENOSPC; } - return virq; + *gsi = next_gsi; + + return 0; } -void mshv_irqchip_release_virq(int virq) +static void irqchip_release_gsi(MshvState *s, int gsi) { - remove_msi_routing(virq); + clear_bit(gsi, s->used_gsi_bitmap); +} + +static void add_routing_entry(MshvState *s, struct mshv_user_irq_entry *entry) +{ + struct mshv_user_irq_entry *new; + int n, size; + + if (s->irq_routes->nr == s->nr_allocated_irq_routes) { + n = s->nr_allocated_irq_routes * 2; + if (n < MSHV_MIN_ALLOCATED_MSI_ROUTES) { + n = MSHV_MIN_ALLOCATED_MSI_ROUTES; + } + size = sizeof(struct mshv_user_irq_table); + size += n * sizeof(*new); + s->irq_routes = g_realloc(s->irq_routes, size); + s->nr_allocated_irq_routes = n; + } + + n = s->irq_routes->nr; + s->irq_routes->nr++; + new = &s->irq_routes->entries[n]; + + *new = *entry; + + set_bit(entry->gsi, s->used_gsi_bitmap); + + trace_mshv_add_msi_routing(entry->address_lo | entry->address_hi, + entry->data); +} + +int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) +{ + struct mshv_user_irq_entry entry = { 0 }; + MSIMessage msg = { 0 }; + uint32_t data, high_addr, low_addr; + int gsi, ret; + MshvState *s = MSHV_STATE(c->accel); + + if (!pci_available || !dev) { + return 0; + } + + msg = pci_get_msi_message(dev, vector); + + ret = irqchip_allocate_gsi(mshv_state, &gsi); + if (ret < 0) { + error_report("Could not allocate GSI for MSI route"); + return -1; + } + high_addr = msg.address >> 32; + low_addr = msg.address & 0xFFFFFFFF; + data = le32_to_cpu(msg.data); + + entry.gsi = gsi; + entry.address_hi = high_addr; + entry.address_lo = low_addr; + entry.data = data; + + if (s->irq_routes->nr < s->gsi_count) { + add_routing_entry(s, &entry); + c->changes++; + } else { + irqchip_release_gsi(s, gsi); + return -ENOSPC; + } + + return gsi; +} + +void mshv_irqchip_release_virq(MshvState *s, int virq) +{ + struct mshv_user_irq_entry *e; + int i; + + for (i = 0; i < s->irq_routes->nr; i++) { + e = &s->irq_routes->entries[i]; + if (e->gsi == virq) { + s->irq_routes->nr--; + *e = s->irq_routes->entries[s->irq_routes->nr]; + } + } + irqchip_release_gsi(s, virq); + + trace_mshv_remove_msi_routing(virq); +} + +static int update_routing_entry(MshvState *s, + struct mshv_user_irq_entry *new_entry) +{ + struct mshv_user_irq_entry *entry; + int n; + + for (n = 0; n < s->irq_routes->nr; n++) { + entry = &s->irq_routes->entries[n]; + if (entry->gsi != new_entry->gsi) { + continue; + } + + if (!memcmp(entry, new_entry, sizeof *entry)) { + return 0; + } + + *entry = *new_entry; + + return 0; + } + + return -ESRCH; } int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev) { + uint32_t addr_hi = msg.address >> 32; + uint32_t addr_lo = msg.address & 0xFFFFFFFF; + uint32_t data = le32_to_cpu(msg.data); + struct mshv_user_irq_entry entry = { + .gsi = virq, + .address_hi = addr_hi, + .address_lo = addr_lo, + .data = data, + }; int ret; - ret = set_msi_routing(virq, msg.address, le32_to_cpu(msg.data)); + ret = update_routing_entry(mshv_state, &entry); if (ret < 0) { - error_report("Failed to set msi routing"); - return -1; + error_report("Failed to set msi routing for gsi %d", virq); + abort(); } + trace_mshv_set_msi_routing(virq, msg.address, data); + return 0; } @@ -347,16 +296,17 @@ int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, uint3 return 0; } -void mshv_irqchip_commit_routes(void) +void mshv_irqchip_commit_routes(MshvState *s) { int ret; - int vm_fd = mshv_state->vm; + int vm_fd = s->vm; - ret = commit_msi_routing_table(vm_fd); + ret = ioctl(vm_fd, MSHV_SET_MSI_ROUTING, s->irq_routes); if (ret < 0) { error_report("Failed to commit msi routing table"); abort(); } + trace_mshv_commit_msi_routing_table(vm_fd, s->irq_routes->nr); } int mshv_irqchip_add_irqfd_notifier_gsi(const EventNotifier *event, @@ -372,27 +322,45 @@ int mshv_irqchip_remove_irqfd_notifier_gsi(const EventNotifier *event, return irqchip_update_irqfd_notifier_gsi(event, NULL, virq, false); } -int mshv_reserve_ioapic_msi_routes(int vm_fd) +static int mshv_reserve_ioapic_msi_routes(MshvState *s) { - int ret, gsi; + int ret, i; + int gsi = 0; + struct mshv_user_irq_entry blank_entry = { 0 }; /* * Reserve GSI 0-23 for IOAPIC pins, to avoid conflicts of legacy * peripherals with MSI-X devices */ - for (gsi = 0; gsi < IOAPIC_NUM_PINS; gsi++) { - ret = add_msi_routing(0, 0); + for (i = 0; i < IOAPIC_NUM_PINS; i++) { + /* ret = add_msi_routing(0, 0); */ + ret = irqchip_allocate_gsi(s, &gsi); if (ret < 0) { - error_report("Failed to reserve GSI %d", gsi); + error_report("Failed to reserve GSI %d: %s", gsi, strerror(-ret)); return -1; } + blank_entry.gsi = gsi; + add_routing_entry(s, &blank_entry); } - ret = commit_msi_routing_table(vm_fd); - if (ret < 0) { - error_report("Failed to commit reserved IOAPIC MSI routes"); - return -1; - } + mshv_irqchip_commit_routes(s); return 0; } + +void mshv_init_irq_routing(MshvState *s) +{ + int ret; + int gsi_count = MSHV_MAX_MSI_ROUTES; + + s->irq_routes = g_malloc0(sizeof(*s->irq_routes)); + s->nr_allocated_irq_routes = 0; + s->gsi_count = gsi_count; + s->used_gsi_bitmap = bitmap_new(gsi_count); + + ret = mshv_reserve_ioapic_msi_routes(s); + if (ret < 0) { + error_report("Failed to reserve IOAPIC MSI routes"); + abort(); + } +} diff --git a/accel/mshv/mem.c b/accel/mshv/mem.c index e55c38d4db..820f87ef0c 100644 --- a/accel/mshv/mem.c +++ b/accel/mshv/mem.c @@ -12,10 +12,13 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qapi/error.h" #include "linux/mshv.h" #include "system/address-spaces.h" #include "system/mshv.h" #include "system/mshv_int.h" +#include "hw/hyperv/hvhdk_mini.h" +#include "system/physmem.h" #include "exec/memattrs.h" #include #include "trace.h" @@ -211,3 +214,211 @@ void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section, abort(); } } + +static int enable_dirty_page_tracking(int vm_fd) +{ + int ret; + struct hv_input_set_partition_property in = {0}; + struct mshv_root_hvcall args = {0}; + + in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING; + in.property_value = 1; + + args.code = HVCALL_SET_PARTITION_PROPERTY; + args.in_sz = sizeof(in); + args.in_ptr = (uint64_t)∈ + + ret = mshv_hvcall(vm_fd, &args); + if (ret < 0) { + error_report("Failed to enable dirty page tracking: %s", + strerror(errno)); + return -1; + } + + return 0; +} + +/* + * Retrieve dirty page bitmap for a GPA range, clearing the dirty bits + * atomically. Large ranges are handled in batches. + */ +static int get_dirty_log(int vm_fd, uint64_t base_pfn, uint64_t page_count, + unsigned long *bitmap, size_t bitmap_size) +{ + uint64_t batch, bitmap_offset, completed = 0; + struct mshv_gpap_access_bitmap args = {0}; + int ret; + + QEMU_BUILD_BUG_ON(MSHV_DIRTY_PAGES_BATCH_SIZE % BITS_PER_LONG != 0); + assert(bitmap_size >= ROUND_UP(page_count, BITS_PER_LONG) / 8); + + while (completed < page_count) { + batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed); + bitmap_offset = completed / BITS_PER_LONG; + + args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY; + args.access_op = MSHV_GPAP_ACCESS_OP_CLEAR; + args.page_count = batch; + args.gpap_base = base_pfn + completed; + args.bitmap_ptr = (uint64_t)(bitmap + bitmap_offset); + + ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args); + if (ret < 0) { + error_report("Failed to get dirty log (base_pfn=0x%" PRIx64 + " batch=%" PRIu64 "): %s", + base_pfn + completed, batch, strerror(errno)); + return -1; + } + completed += batch; + } + + return 0; +} + +bool mshv_log_global_start(MemoryListener *listener, Error **errp) +{ + int ret; + + ret = enable_dirty_page_tracking(mshv_state->vm); + if (ret < 0) { + error_setg_errno(errp, -ret, "Failed to enable dirty page tracking"); + return false; + } + return true; +} + +static int disable_dirty_page_tracking(int vm_fd) +{ + int ret; + struct hv_input_set_partition_property in = {0}; + struct mshv_root_hvcall args = {0}; + + in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING; + in.property_value = 0; + + args.code = HVCALL_SET_PARTITION_PROPERTY; + args.in_sz = sizeof(in); + args.in_ptr = (uint64_t)∈ + + ret = mshv_hvcall(vm_fd, &args); + if (ret < 0) { + error_report("Failed to disable dirty page tracking: %s", + strerror(errno)); + return -1; + } + + return 0; +} + +static int set_dirty_pages(int vm_fd, uint64_t base_pfn, uint64_t page_count) +{ + uint64_t batch, completed = 0; + unsigned long bitmap[MSHV_DIRTY_PAGES_BATCH_SIZE / BITS_PER_LONG]; + struct mshv_gpap_access_bitmap args = {0}; + int ret; + + while (completed < page_count) { + batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed); + + args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY; + args.access_op = MSHV_GPAP_ACCESS_OP_SET; + args.page_count = batch; + args.gpap_base = base_pfn + completed; + args.bitmap_ptr = (uint64_t)bitmap; + + ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args); + if (ret < 0) { + error_report("Failed to set dirty pages (base_pfn=0x%" PRIx64 + " batch=%" PRIu64 "): %s", + base_pfn + completed, batch, strerror(errno)); + return -1; + } + completed += batch; + } + + return 0; +} + +static bool set_dirty_bits_cb(Int128 start, Int128 len, const MemoryRegion *mr, + hwaddr offset_in_region, void *opaque) +{ + int ret, *errp = opaque; + hwaddr gpa, size; + uint64_t page_count, base_pfn; + + gpa = int128_get64(start); + size = int128_get64(len); + page_count = size >> MSHV_PAGE_SHIFT; + base_pfn = gpa >> MSHV_PAGE_SHIFT; + + if (!mr->ram || mr->readonly) { + return false; + } + + if (page_count == 0) { + return false; + } + + ret = set_dirty_pages(mshv_state->vm, base_pfn, page_count); + + /* true aborts the iteration, which is what we want if there's an error */ + if (ret < 0) { + *errp = ret; + return true; + } + + return false; +} + +void mshv_log_global_stop(MemoryListener *listener) +{ + int err = 0; + /* MSHV requires all dirty bits to be set before disabling tracking. */ + FlatView *fv = address_space_to_flatview(&address_space_memory); + flatview_for_each_range(fv, set_dirty_bits_cb, &err); + + if (err < 0) { + error_report("Failed to set dirty bits before disabling tracking"); + } + + disable_dirty_page_tracking(mshv_state->vm); +} + +void mshv_log_sync(MemoryListener *listener, MemoryRegionSection *section) +{ + hwaddr size, start_addr, mr_offset; + uint64_t page_count, base_pfn; + size_t bitmap_size; + unsigned long *bitmap; + ram_addr_t ram_addr; + int ret; + MemoryRegion *mr = section->mr; + + if (!memory_region_is_ram(mr) || memory_region_is_rom(mr)) { + return; + } + + size = align_section(section, &start_addr); + if (!size) { + return; + } + + page_count = size >> MSHV_PAGE_SHIFT; + base_pfn = start_addr >> MSHV_PAGE_SHIFT; + bitmap_size = ROUND_UP(page_count, BITS_PER_LONG) / 8; + bitmap = g_malloc0(bitmap_size); + + ret = get_dirty_log(mshv_state->vm, base_pfn, page_count, bitmap, + bitmap_size); + if (ret < 0) { + g_free(bitmap); + return; + } + + mr_offset = section->offset_within_region + start_addr - + section->offset_within_address_space; + ram_addr = memory_region_get_ram_addr(mr) + mr_offset; + + physical_memory_set_dirty_lebitmap(bitmap, ram_addr, page_count); + g_free(bitmap); +} diff --git a/accel/mshv/meson.build b/accel/mshv/meson.build index c1b1787c5e..e433187cde 100644 --- a/accel/mshv/meson.build +++ b/accel/mshv/meson.build @@ -1,6 +1,5 @@ system_ss.add(when: 'CONFIG_MSHV', if_true: files( 'irq.c', 'mem.c', - 'msr.c', 'mshv-all.c' )) diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c index 58d8638c0c..9452504ac2 100644 --- a/accel/mshv/mshv-all.c +++ b/accel/mshv/mshv-all.c @@ -43,10 +43,6 @@ #include #include -#define TYPE_MSHV_ACCEL ACCEL_CLASS_NAME("mshv") - -DECLARE_INSTANCE_CHECKER(MshvState, MSHV_STATE, TYPE_MSHV_ACCEL) - bool mshv_allowed; MshvState *mshv_state; @@ -110,21 +106,136 @@ static int resume_vm(int vm_fd) 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[0].as_uint64); + 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[1].as_uint64); + 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; - struct mshv_create_partition args = {0}; + 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 */ - uint64_t pt_flags = (1ULL << MSHV_PT_BIT_LAPIC) | - (1ULL << MSHV_PT_BIT_X2APIC) | - (1ULL << MSHV_PT_BIT_GPA_SUPER_PAGES); + 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); - /* Set default isolation type */ - uint64_t pt_isolation = MSHV_PT_ISOLATION_NONE; + /* enable all */ + disabled_xsave_features.as_uint64 = 0; + /* + * 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 = pt_isolation; + 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) { @@ -204,11 +315,6 @@ static int create_vm(int mshv_fd, int *vm_fd) return -1; } - ret = mshv_reserve_ioapic_msi_routes(*vm_fd); - if (ret < 0) { - return -1; - } - ret = mshv_arch_post_init_vm(*vm_fd); if (ret < 0) { return -1; @@ -371,6 +477,9 @@ static MemoryListener mshv_memory_listener = { .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 = { @@ -415,13 +524,13 @@ static int mshv_init_vcpu(CPUState *cpu) int ret; cpu->accel = g_new0(AccelCPUState, 1); - mshv_arch_init_vcpu(cpu); ret = mshv_create_vcpu(vm_fd, vp_index, &cpu->accel->cpufd); if (ret < 0) { return -1; } + mshv_arch_init_vcpu(cpu); cpu->accel->dirty = true; return 0; @@ -450,8 +559,6 @@ static int mshv_init(AccelState *as, MachineState *ms) mshv_init_mmio_emu(); - mshv_init_msicontrol(); - ret = create_vm(mshv_fd, &vm_fd); if (ret < 0) { close(mshv_fd); @@ -467,11 +574,19 @@ static int mshv_init(AccelState *as, MachineState *ms) 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); @@ -503,7 +618,7 @@ static int mshv_cpu_exec(CPUState *cpu) do { if (cpu->accel->dirty) { - ret = mshv_arch_put_registers(cpu); + ret = mshv_arch_store_vcpu_state(cpu); if (ret) { error_report("Failed to put registers after init: %s", strerror(-ret)); @@ -625,7 +740,7 @@ static void mshv_start_vcpu_thread(CPUState *cpu) static void do_mshv_cpu_synchronize_post_init(CPUState *cpu, run_on_cpu_data arg) { - int ret = mshv_arch_put_registers(cpu); + int ret = mshv_arch_store_vcpu_state(cpu); if (ret < 0) { error_report("Failed to put registers after init: %s", strerror(-ret)); abort(); @@ -641,7 +756,7 @@ static void mshv_cpu_synchronize_post_init(CPUState *cpu) static void mshv_cpu_synchronize_post_reset(CPUState *cpu) { - int ret = mshv_arch_put_registers(cpu); + int ret = mshv_arch_store_vcpu_state(cpu); if (ret) { error_report("Failed to put registers after reset: %s", strerror(-ret)); @@ -665,7 +780,7 @@ static void mshv_cpu_synchronize_pre_loadvm(CPUState *cpu) static void do_mshv_cpu_synchronize(CPUState *cpu, run_on_cpu_data arg) { if (!cpu->accel->dirty) { - int ret = mshv_load_regs(cpu); + int ret = mshv_arch_load_vcpu_state(cpu); if (ret < 0) { error_report("Failed to load registers for vcpu %d", cpu->cpu_index); diff --git a/accel/mshv/msr.c b/accel/mshv/msr.c deleted file mode 100644 index e6e5baef50..0000000000 --- a/accel/mshv/msr.c +++ /dev/null @@ -1,375 +0,0 @@ -/* - * QEMU MSHV support - * - * Copyright Microsoft, Corp. 2025 - * - * Authors: Magnus Kulke - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "qemu/osdep.h" -#include "system/mshv.h" -#include "system/mshv_int.h" -#include "hw/hyperv/hvgdk_mini.h" -#include "linux/mshv.h" -#include "qemu/error-report.h" - -static uint32_t supported_msrs[64] = { - IA32_MSR_TSC, - IA32_MSR_EFER, - IA32_MSR_KERNEL_GS_BASE, - IA32_MSR_APIC_BASE, - IA32_MSR_PAT, - IA32_MSR_SYSENTER_CS, - IA32_MSR_SYSENTER_ESP, - IA32_MSR_SYSENTER_EIP, - IA32_MSR_STAR, - IA32_MSR_LSTAR, - IA32_MSR_CSTAR, - IA32_MSR_SFMASK, - IA32_MSR_MTRR_DEF_TYPE, - IA32_MSR_MTRR_PHYSBASE0, - IA32_MSR_MTRR_PHYSMASK0, - IA32_MSR_MTRR_PHYSBASE1, - IA32_MSR_MTRR_PHYSMASK1, - IA32_MSR_MTRR_PHYSBASE2, - IA32_MSR_MTRR_PHYSMASK2, - IA32_MSR_MTRR_PHYSBASE3, - IA32_MSR_MTRR_PHYSMASK3, - IA32_MSR_MTRR_PHYSBASE4, - IA32_MSR_MTRR_PHYSMASK4, - IA32_MSR_MTRR_PHYSBASE5, - IA32_MSR_MTRR_PHYSMASK5, - IA32_MSR_MTRR_PHYSBASE6, - IA32_MSR_MTRR_PHYSMASK6, - IA32_MSR_MTRR_PHYSBASE7, - IA32_MSR_MTRR_PHYSMASK7, - IA32_MSR_MTRR_FIX64K_00000, - IA32_MSR_MTRR_FIX16K_80000, - IA32_MSR_MTRR_FIX16K_A0000, - IA32_MSR_MTRR_FIX4K_C0000, - IA32_MSR_MTRR_FIX4K_C8000, - IA32_MSR_MTRR_FIX4K_D0000, - IA32_MSR_MTRR_FIX4K_D8000, - IA32_MSR_MTRR_FIX4K_E0000, - IA32_MSR_MTRR_FIX4K_E8000, - IA32_MSR_MTRR_FIX4K_F0000, - IA32_MSR_MTRR_FIX4K_F8000, - IA32_MSR_TSC_AUX, - IA32_MSR_DEBUG_CTL, - HV_X64_MSR_GUEST_OS_ID, - HV_X64_MSR_SINT0, - HV_X64_MSR_SINT1, - HV_X64_MSR_SINT2, - HV_X64_MSR_SINT3, - HV_X64_MSR_SINT4, - HV_X64_MSR_SINT5, - HV_X64_MSR_SINT6, - HV_X64_MSR_SINT7, - HV_X64_MSR_SINT8, - HV_X64_MSR_SINT9, - HV_X64_MSR_SINT10, - HV_X64_MSR_SINT11, - HV_X64_MSR_SINT12, - HV_X64_MSR_SINT13, - HV_X64_MSR_SINT14, - HV_X64_MSR_SINT15, - HV_X64_MSR_SCONTROL, - HV_X64_MSR_SIEFP, - HV_X64_MSR_SIMP, - HV_X64_MSR_REFERENCE_TSC, - HV_X64_MSR_EOM, -}; -static const size_t msr_count = ARRAY_SIZE(supported_msrs); - -static int compare_msr_index(const void *a, const void *b) -{ - return *(uint32_t *)a - *(uint32_t *)b; -} - -__attribute__((constructor)) -static void init_sorted_msr_map(void) -{ - qsort(supported_msrs, msr_count, sizeof(uint32_t), compare_msr_index); -} - -static int mshv_is_supported_msr(uint32_t msr) -{ - return bsearch(&msr, supported_msrs, msr_count, sizeof(uint32_t), - compare_msr_index) != NULL; -} - -static int mshv_msr_to_hv_reg_name(uint32_t msr, uint32_t *hv_reg) -{ - switch (msr) { - case IA32_MSR_TSC: - *hv_reg = HV_X64_REGISTER_TSC; - return 0; - case IA32_MSR_EFER: - *hv_reg = HV_X64_REGISTER_EFER; - return 0; - case IA32_MSR_KERNEL_GS_BASE: - *hv_reg = HV_X64_REGISTER_KERNEL_GS_BASE; - return 0; - case IA32_MSR_APIC_BASE: - *hv_reg = HV_X64_REGISTER_APIC_BASE; - return 0; - case IA32_MSR_PAT: - *hv_reg = HV_X64_REGISTER_PAT; - return 0; - case IA32_MSR_SYSENTER_CS: - *hv_reg = HV_X64_REGISTER_SYSENTER_CS; - return 0; - case IA32_MSR_SYSENTER_ESP: - *hv_reg = HV_X64_REGISTER_SYSENTER_ESP; - return 0; - case IA32_MSR_SYSENTER_EIP: - *hv_reg = HV_X64_REGISTER_SYSENTER_EIP; - return 0; - case IA32_MSR_STAR: - *hv_reg = HV_X64_REGISTER_STAR; - return 0; - case IA32_MSR_LSTAR: - *hv_reg = HV_X64_REGISTER_LSTAR; - return 0; - case IA32_MSR_CSTAR: - *hv_reg = HV_X64_REGISTER_CSTAR; - return 0; - case IA32_MSR_SFMASK: - *hv_reg = HV_X64_REGISTER_SFMASK; - return 0; - case IA32_MSR_MTRR_CAP: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_CAP; - return 0; - case IA32_MSR_MTRR_DEF_TYPE: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_DEF_TYPE; - return 0; - case IA32_MSR_MTRR_PHYSBASE0: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0; - return 0; - case IA32_MSR_MTRR_PHYSMASK0: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0; - return 0; - case IA32_MSR_MTRR_PHYSBASE1: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE1; - return 0; - case IA32_MSR_MTRR_PHYSMASK1: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK1; - return 0; - case IA32_MSR_MTRR_PHYSBASE2: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE2; - return 0; - case IA32_MSR_MTRR_PHYSMASK2: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK2; - return 0; - case IA32_MSR_MTRR_PHYSBASE3: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE3; - return 0; - case IA32_MSR_MTRR_PHYSMASK3: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK3; - return 0; - case IA32_MSR_MTRR_PHYSBASE4: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE4; - return 0; - case IA32_MSR_MTRR_PHYSMASK4: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK4; - return 0; - case IA32_MSR_MTRR_PHYSBASE5: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE5; - return 0; - case IA32_MSR_MTRR_PHYSMASK5: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK5; - return 0; - case IA32_MSR_MTRR_PHYSBASE6: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE6; - return 0; - case IA32_MSR_MTRR_PHYSMASK6: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK6; - return 0; - case IA32_MSR_MTRR_PHYSBASE7: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE7; - return 0; - case IA32_MSR_MTRR_PHYSMASK7: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK7; - return 0; - case IA32_MSR_MTRR_FIX64K_00000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX64K00000; - return 0; - case IA32_MSR_MTRR_FIX16K_80000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX16K80000; - return 0; - case IA32_MSR_MTRR_FIX16K_A0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX16KA0000; - return 0; - case IA32_MSR_MTRR_FIX4K_C0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KC0000; - return 0; - case IA32_MSR_MTRR_FIX4K_C8000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KC8000; - return 0; - case IA32_MSR_MTRR_FIX4K_D0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KD0000; - return 0; - case IA32_MSR_MTRR_FIX4K_D8000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KD8000; - return 0; - case IA32_MSR_MTRR_FIX4K_E0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KE0000; - return 0; - case IA32_MSR_MTRR_FIX4K_E8000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KE8000; - return 0; - case IA32_MSR_MTRR_FIX4K_F0000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KF0000; - return 0; - case IA32_MSR_MTRR_FIX4K_F8000: - *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KF8000; - return 0; - case IA32_MSR_TSC_AUX: - *hv_reg = HV_X64_REGISTER_TSC_AUX; - return 0; - case IA32_MSR_BNDCFGS: - *hv_reg = HV_X64_REGISTER_BNDCFGS; - return 0; - case IA32_MSR_DEBUG_CTL: - *hv_reg = HV_X64_REGISTER_DEBUG_CTL; - return 0; - case IA32_MSR_TSC_ADJUST: - *hv_reg = HV_X64_REGISTER_TSC_ADJUST; - return 0; - case IA32_MSR_SPEC_CTRL: - *hv_reg = HV_X64_REGISTER_SPEC_CTRL; - return 0; - case HV_X64_MSR_GUEST_OS_ID: - *hv_reg = HV_REGISTER_GUEST_OS_ID; - return 0; - case HV_X64_MSR_SINT0: - *hv_reg = HV_REGISTER_SINT0; - return 0; - case HV_X64_MSR_SINT1: - *hv_reg = HV_REGISTER_SINT1; - return 0; - case HV_X64_MSR_SINT2: - *hv_reg = HV_REGISTER_SINT2; - return 0; - case HV_X64_MSR_SINT3: - *hv_reg = HV_REGISTER_SINT3; - return 0; - case HV_X64_MSR_SINT4: - *hv_reg = HV_REGISTER_SINT4; - return 0; - case HV_X64_MSR_SINT5: - *hv_reg = HV_REGISTER_SINT5; - return 0; - case HV_X64_MSR_SINT6: - *hv_reg = HV_REGISTER_SINT6; - return 0; - case HV_X64_MSR_SINT7: - *hv_reg = HV_REGISTER_SINT7; - return 0; - case HV_X64_MSR_SINT8: - *hv_reg = HV_REGISTER_SINT8; - return 0; - case HV_X64_MSR_SINT9: - *hv_reg = HV_REGISTER_SINT9; - return 0; - case HV_X64_MSR_SINT10: - *hv_reg = HV_REGISTER_SINT10; - return 0; - case HV_X64_MSR_SINT11: - *hv_reg = HV_REGISTER_SINT11; - return 0; - case HV_X64_MSR_SINT12: - *hv_reg = HV_REGISTER_SINT12; - return 0; - case HV_X64_MSR_SINT13: - *hv_reg = HV_REGISTER_SINT13; - return 0; - case HV_X64_MSR_SINT14: - *hv_reg = HV_REGISTER_SINT14; - return 0; - case HV_X64_MSR_SINT15: - *hv_reg = HV_REGISTER_SINT15; - return 0; - case IA32_MSR_MISC_ENABLE: - *hv_reg = HV_X64_REGISTER_MSR_IA32_MISC_ENABLE; - return 0; - case HV_X64_MSR_SCONTROL: - *hv_reg = HV_REGISTER_SCONTROL; - return 0; - case HV_X64_MSR_SIEFP: - *hv_reg = HV_REGISTER_SIEFP; - return 0; - case HV_X64_MSR_SIMP: - *hv_reg = HV_REGISTER_SIMP; - return 0; - case HV_X64_MSR_REFERENCE_TSC: - *hv_reg = HV_REGISTER_REFERENCE_TSC; - return 0; - case HV_X64_MSR_EOM: - *hv_reg = HV_REGISTER_EOM; - return 0; - default: - error_report("failed to map MSR %u to HV register name", msr); - return -1; - } -} - -static int set_msrs(const CPUState *cpu, GList *msrs) -{ - size_t n_msrs; - GList *entries; - MshvMsrEntry *entry; - enum hv_register_name name; - struct hv_register_assoc *assoc; - int ret; - size_t i = 0; - - n_msrs = g_list_length(msrs); - hv_register_assoc *assocs = g_new0(hv_register_assoc, n_msrs); - - entries = msrs; - for (const GList *elem = entries; elem != NULL; elem = elem->next) { - entry = elem->data; - ret = mshv_msr_to_hv_reg_name(entry->index, &name); - if (ret < 0) { - g_free(assocs); - return ret; - } - assoc = &assocs[i]; - assoc->name = name; - /* the union has been initialized to 0 */ - assoc->value.reg64 = entry->data; - i++; - } - ret = mshv_set_generic_regs(cpu, assocs, n_msrs); - g_free(assocs); - if (ret < 0) { - error_report("failed to set msrs"); - return -1; - } - return 0; -} - - -int mshv_configure_msr(const CPUState *cpu, const MshvMsrEntry *msrs, - size_t n_msrs) -{ - GList *valid_msrs = NULL; - uint32_t msr_index; - int ret; - - for (size_t i = 0; i < n_msrs; i++) { - msr_index = msrs[i].index; - /* check whether index of msrs is in SUPPORTED_MSRS */ - if (mshv_is_supported_msr(msr_index)) { - valid_msrs = g_list_append(valid_msrs, (void *) &msrs[i]); - } - } - - ret = set_msrs(cpu, valid_msrs); - g_list_free(valid_msrs); - - return ret; -} diff --git a/accel/stubs/kvm-stub.c b/accel/stubs/kvm-stub.c index c4617caac6..32b4b07403 100644 --- a/accel/stubs/kvm-stub.c +++ b/accel/stubs/kvm-stub.c @@ -44,7 +44,7 @@ int kvm_on_sigbus(int code, void *addr) return 1; } -int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev) +int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { return -ENOSYS; } diff --git a/accel/stubs/mshv-stub.c b/accel/stubs/mshv-stub.c index e499b199d9..566a17ff9e 100644 --- a/accel/stubs/mshv-stub.c +++ b/accel/stubs/mshv-stub.c @@ -13,13 +13,14 @@ #include "system/mshv.h" bool mshv_allowed; +MshvState *mshv_state; -int mshv_irqchip_add_msi_route(int vector, PCIDevice *dev) +int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) { return -ENOSYS; } -void mshv_irqchip_release_virq(int virq) +void mshv_irqchip_release_virq(MshvState *s, int virq) { } @@ -28,7 +29,7 @@ int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev) return -ENOSYS; } -void mshv_irqchip_commit_routes(void) +void mshv_irqchip_commit_routes(MshvState *s) { } diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index 97750f5edc..cf3e1a007c 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -249,6 +249,13 @@ The RISC-V ``shakti_c`` machine hasn't had meaningful contributions since 2021 and is currently unmaintained. The machine is scheduled to be removed as it appears to have no users. +``memory-encryption`` machine property (since 11.1) +''''''''''''''''''''''''''''''''''''''''''''''''''' + +Use ``confidential-guest-support`` instead. The ``memory-encryption`` object +was an early implementation of memory encryption support in QEMU, but it has +been superseded by the more comprehensive ``confidential-guest-support`` +object. Backend options --------------- diff --git a/hw/core/machine.c b/hw/core/machine.c index 9a10e45aab..5b97df6db9 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -497,6 +497,8 @@ static void machine_set_memory_encryption(Object *obj, const char *value, Object *cgs = object_resolve_path_component(object_get_objects_root(), value); + warn_report("memory-encryption is deprecated, use confidential-guest-support instead"); + if (!cgs) { error_setg(errp, "No such memory encryption object '%s'", value); return; diff --git a/hw/misc/ivshmem-pci.c b/hw/misc/ivshmem-pci.c index c987eebb98..536475e9de 100644 --- a/hw/misc/ivshmem-pci.c +++ b/hw/misc/ivshmem-pci.c @@ -26,7 +26,7 @@ #include "hw/core/qdev-properties-system.h" #include "hw/pci/msi.h" #include "hw/pci/msix.h" -#include "system/kvm.h" +#include "system/accel-irq.h" #include "migration/blocker.h" #include "migration/vmstate.h" #include "qemu/error-report.h" @@ -424,19 +424,19 @@ static void ivshmem_add_kvm_msi_virq(IVShmemState *s, int vector, Error **errp) { PCIDevice *pdev = PCI_DEVICE(s); - KVMRouteChange c; + AccelRouteChange c; int ret; IVSHMEM_DPRINTF("ivshmem_add_kvm_msi_virq vector:%d\n", vector); assert(!s->msi_vectors[vector].pdev); - c = kvm_irqchip_begin_route_changes(kvm_state); + c = accel_irqchip_begin_route_changes(); ret = kvm_irqchip_add_msi_route(&c, vector, pdev); if (ret < 0) { error_setg(errp, "kvm_irqchip_add_msi_route failed"); return; } - kvm_irqchip_commit_route_changes(&c); + accel_irqchip_commit_route_changes(&c); s->msi_vectors[vector].virq = ret; s->msi_vectors[vector].pdev = pdev; diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 7d57eb4f47..4d822b96b5 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -38,6 +38,7 @@ #include "qemu/module.h" #include "qemu/range.h" #include "qemu/units.h" +#include "system/accel-irq.h" #include "system/kvm.h" #include "system/runstate.h" #include "pci.h" @@ -50,7 +51,7 @@ #include "vfio-helpers.h" /* Protected by BQL */ -static KVMRouteChange vfio_route_change; +static AccelRouteChange vfio_route_change; static void vfio_disable_interrupts(VFIOPCIDevice *vdev); static void vfio_mmap_set_enabled(VFIOPCIDevice *vdev, bool enabled); @@ -688,9 +689,9 @@ static int vfio_msix_vector_do_use(PCIDevice *pdev, unsigned int nr, if (vdev->defer_kvm_irq_routing) { vfio_pci_add_kvm_msi_virq(vdev, vector, nr, true); } else { - vfio_route_change = kvm_irqchip_begin_route_changes(kvm_state); + vfio_route_change = accel_irqchip_begin_route_changes(); vfio_pci_add_kvm_msi_virq(vdev, vector, nr, true); - kvm_irqchip_commit_route_changes(&vfio_route_change); + accel_irqchip_commit_route_changes(&vfio_route_change); vfio_connect_kvm_msi_virq(vector, nr); } } @@ -789,7 +790,7 @@ void vfio_pci_prepare_kvm_msi_virq_batch(VFIOPCIDevice *vdev) { assert(!vdev->defer_kvm_irq_routing); vdev->defer_kvm_irq_routing = true; - vfio_route_change = kvm_irqchip_begin_route_changes(kvm_state); + vfio_route_change = accel_irqchip_begin_route_changes(); } void vfio_pci_commit_kvm_msi_virq_batch(VFIOPCIDevice *vdev) @@ -799,7 +800,7 @@ void vfio_pci_commit_kvm_msi_virq_batch(VFIOPCIDevice *vdev) assert(vdev->defer_kvm_irq_routing); vdev->defer_kvm_irq_routing = false; - kvm_irqchip_commit_route_changes(&vfio_route_change); + accel_irqchip_commit_route_changes(&vfio_route_change); for (i = 0; i < vdev->nr_vectors; i++) { vfio_connect_kvm_msi_virq(&vdev->msi_vectors[i], i); diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c index fd9719547f..6f5db5fc42 100644 --- a/hw/virtio/virtio-pci.c +++ b/hw/virtio/virtio-pci.c @@ -869,7 +869,7 @@ static int kvm_virtio_pci_vq_vector_use(VirtIOPCIProxy *proxy, int ret; if (irqfd->users == 0) { - KVMRouteChange c = kvm_irqchip_begin_route_changes(kvm_state); + AccelRouteChange c = accel_irqchip_begin_route_changes(); ret = accel_irqchip_add_msi_route(&c, vector, &proxy->pci_dev); if (ret < 0) { return ret; @@ -2669,4 +2669,3 @@ static void virtio_pci_register_types(void) } type_init(virtio_pci_register_types) - diff --git a/include/accel/accel-route.h b/include/accel/accel-route.h new file mode 100644 index 0000000000..07fac27e2a --- /dev/null +++ b/include/accel/accel-route.h @@ -0,0 +1,17 @@ +/* + * Accelerator MSI route change tracking + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ACCEL_ROUTE_H +#define ACCEL_ROUTE_H + +#include "qemu/accel.h" + +typedef struct AccelRouteChange { + AccelState *accel; + int changes; +} AccelRouteChange; + +#endif /* ACCEL_ROUTE_H */ diff --git a/include/hw/hyperv/hvgdk_mini.h b/include/hw/hyperv/hvgdk_mini.h index c3a8f33280..f8838a31bb 100644 --- a/include/hw/hyperv/hvgdk_mini.h +++ b/include/hw/hyperv/hvgdk_mini.h @@ -9,9 +9,25 @@ #define MSHV_IOCTL 0xB8 +/* Hyper-V specific model specific registers (MSRs) */ + +/* HV_X64_SYNTHETIC_MSR */ +#define HV_X64_MSR_GUEST_OS_ID 0x40000000 +#define HV_X64_MSR_HYPERCALL 0x40000001 +#define HV_X64_MSR_VP_INDEX 0x40000002 +#define HV_X64_MSR_RESET 0x40000003 +#define HV_X64_MSR_VP_RUNTIME 0x40000010 +#define HV_X64_MSR_TIME_REF_COUNT 0x40000020 +#define HV_X64_MSR_REFERENCE_TSC 0x40000021 +#define HV_X64_MSR_TSC_FREQUENCY 0x40000022 +#define HV_X64_MSR_APIC_FREQUENCY 0x40000023 + typedef enum hv_register_name { /* Pending Interruption Register */ HV_REGISTER_PENDING_INTERRUPTION = 0x00010002, + HV_REGISTER_INTERRUPT_STATE = 0x00010003, + HV_REGISTER_PENDING_EVENT0 = 0x00010004, + HV_REGISTER_PENDING_EVENT1 = 0x00010005, /* X64 User-Mode Registers */ HV_X64_REGISTER_RAX = 0x00020000, @@ -154,10 +170,22 @@ typedef enum hv_register_name { HV_X64_REGISTER_SPEC_CTRL = 0x00080084, HV_X64_REGISTER_TSC_ADJUST = 0x00080096, + /* CET / Shadow Stack */ + HV_X64_REGISTER_U_XSS = 0x0008008B, + HV_X64_REGISTER_U_CET = 0x0008008C, + HV_X64_REGISTER_S_CET = 0x0008008D, + HV_X64_REGISTER_SSP = 0x0008008E, + HV_X64_REGISTER_PL0_SSP = 0x0008008F, + HV_X64_REGISTER_PL1_SSP = 0x00080090, + HV_X64_REGISTER_PL2_SSP = 0x00080091, + HV_X64_REGISTER_PL3_SSP = 0x00080092, + HV_X64_REGISTER_INTERRUPT_SSP_TABLE_ADDR = 0x00080093, + /* Other MSRs */ HV_X64_REGISTER_MSR_IA32_MISC_ENABLE = 0x000800A0, /* Misc */ + HV_X64_REGISTER_HYPERCALL = 0x00090001, HV_REGISTER_GUEST_OS_ID = 0x00090002, HV_REGISTER_REFERENCE_TSC = 0x00090017, @@ -454,6 +482,8 @@ typedef struct hv_input_set_vp_registers { struct hv_register_assoc elements[]; } QEMU_PACKED hv_input_set_vp_registers; +#define MSHV_VP_MAX_REGISTERS 128 + union hv_interrupt_control { uint64_t as_uint64; struct { @@ -474,6 +504,109 @@ struct hv_input_assert_virtual_interrupt { uint16_t rsvd_z1; } QEMU_PACKED; +/* Flags for dirty mask of hv_vp_register_page */ +enum hv_x64_register_class_type { + HV_X64_REGISTER_CLASS_GENERAL = 0, + HV_X64_REGISTER_CLASS_IP = 1, + HV_X64_REGISTER_CLASS_XMM = 2, + HV_X64_REGISTER_CLASS_SEGMENT = 3, + HV_X64_REGISTER_CLASS_FLAGS = 4, +}; + +#define HV_VP_REGISTER_PAGE_MAX_VECTOR_COUNT 7 + +union hv_vp_register_page_interrupt_vectors { + uint64_t as_uint64; + struct { + uint8_t vector_count; + uint8_t vector[HV_VP_REGISTER_PAGE_MAX_VECTOR_COUNT]; + }; +}; + +struct hv_vp_register_page { + uint16_t version; + uint8_t isvalid; + uint8_t rsvdz; + uint32_t dirty; + + union { + struct { + /* General purpose registers (HV_X64_REGISTER_CLASS_GENERAL) */ + union { + struct { + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rbx; + uint64_t rsp; + uint64_t rbp; + uint64_t rsi; + uint64_t rdi; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + } QEMU_PACKED; + + uint64_t gp_registers[16]; + }; + /* Instruction pointer (HV_X64_REGISTER_CLASS_IP) */ + uint64_t rip; + /* Flags (HV_X64_REGISTER_CLASS_FLAGS) */ + uint64_t rflags; + } QEMU_PACKED; + + uint64_t registers[18]; + }; + uint8_t reserved[8]; + /* Volatile XMM registers (HV_X64_REGISTER_CLASS_XMM) */ + union { + struct { + struct hv_u128 xmm0; + struct hv_u128 xmm1; + struct hv_u128 xmm2; + struct hv_u128 xmm3; + struct hv_u128 xmm4; + struct hv_u128 xmm5; + } QEMU_PACKED; + + struct hv_u128 xmm_registers[6]; + }; + /* Segment registers (HV_X64_REGISTER_CLASS_SEGMENT) */ + union { + struct { + struct hv_x64_segment_register es; + struct hv_x64_segment_register cs; + struct hv_x64_segment_register ss; + struct hv_x64_segment_register ds; + struct hv_x64_segment_register fs; + struct hv_x64_segment_register gs; + } QEMU_PACKED; + + struct hv_x64_segment_register segment_registers[6]; + }; + /* Misc. control registers (cannot be set via this interface) */ + uint64_t cr0; + uint64_t cr3; + uint64_t cr4; + uint64_t cr8; + uint64_t efer; + uint64_t dr7; + union hv_x64_pending_interruption_register pending_interruption; + union hv_x64_interrupt_state_register interrupt_state; + uint64_t instruction_emulation_hints; + uint64_t xfem; + + uint8_t reserved1[0x100]; + + /* Interrupts injected as part of HvCallDispatchVp. */ + union hv_vp_register_page_interrupt_vectors interrupt_vectors; +} QEMU_PACKED; + /* /dev/mshv */ #define MSHV_CREATE_PARTITION _IOW(MSHV_IOCTL, 0x00, struct mshv_create_partition) #define MSHV_CREATE_VP _IOW(MSHV_IOCTL, 0x01, struct mshv_create_vp) diff --git a/include/hw/hyperv/hvhdk.h b/include/hw/hyperv/hvhdk.h index 41af743847..9e6dcb22f6 100644 --- a/include/hw/hyperv/hvhdk.h +++ b/include/hw/hyperv/hvhdk.h @@ -11,6 +11,16 @@ #define HV_PARTITION_SYNTHETIC_PROCESSOR_FEATURES_BANKS 1 +struct hv_input_get_partition_property { + uint64_t partition_id; + uint32_t property_code; /* enum hv_partition_property_code */ + uint32_t padding; +} QEMU_PACKED; + +struct hv_output_get_partition_property { + uint64_t property_value; +} QEMU_PACKED; + struct hv_input_set_partition_property { uint64_t partition_id; uint32_t property_code; /* enum hv_partition_property_code */ @@ -161,6 +171,191 @@ union hv_partition_synthetic_processor_features { }; }; +union hv_partition_processor_xsave_features { + struct { + uint64_t xsave_support:1; + uint64_t xsaveopt_support:1; + uint64_t avx_support:1; + uint64_t avx2_support:1; + uint64_t fma_support:1; + uint64_t mpx_support:1; + uint64_t avx512_support:1; + uint64_t avx512_dq_support:1; + uint64_t avx512_cd_support:1; + uint64_t avx512_bw_support:1; + uint64_t avx512_vl_support:1; + uint64_t xsave_comp_support:1; + uint64_t xsave_supervisor_support:1; + uint64_t xcr1_support:1; + uint64_t avx512_bitalg_support:1; + uint64_t avx512_i_fma_support:1; + uint64_t avx512_v_bmi_support:1; + uint64_t avx512_v_bmi2_support:1; + uint64_t avx512_vnni_support:1; + uint64_t gfni_support:1; + uint64_t vaes_support:1; + uint64_t avx512_v_popcntdq_support:1; + uint64_t vpclmulqdq_support:1; + uint64_t avx512_bf16_support:1; + uint64_t avx512_vp2_intersect_support:1; + uint64_t avx512_fp16_support:1; + uint64_t xfd_support:1; + uint64_t amx_tile_support:1; + uint64_t amx_bf16_support:1; + uint64_t amx_int8_support:1; + uint64_t avx_vnni_support:1; + uint64_t avx_ifma_support:1; + uint64_t avx_ne_convert_support:1; + uint64_t avx_vnni_int8_support:1; + uint64_t avx_vnni_int16_support:1; + uint64_t avx10_1_256_support:1; + uint64_t avx10_1_512_support:1; + uint64_t amx_fp16_support:1; + uint64_t reserved1:26; + }; + uint64_t as_uint64; +}; + +#define HV_PARTITION_PROCESSOR_FEATURES_BANKS 2 +#define HV_PARTITION_PROCESSOR_FEATURES_RESERVEDBANK1_BITFIELD_COUNT 4 + + +union hv_partition_processor_features { + uint64_t as_uint64[HV_PARTITION_PROCESSOR_FEATURES_BANKS]; + struct { + uint64_t sse3_support:1; + uint64_t lahf_sahf_support:1; + uint64_t ssse3_support:1; + uint64_t sse4_1_support:1; + uint64_t sse4_2_support:1; + uint64_t sse4a_support:1; + uint64_t xop_support:1; + uint64_t pop_cnt_support:1; + uint64_t cmpxchg16b_support:1; + uint64_t altmovcr8_support:1; + uint64_t lzcnt_support:1; + uint64_t mis_align_sse_support:1; + uint64_t mmx_ext_support:1; + uint64_t amd3dnow_support:1; + uint64_t extended_amd3dnow_support:1; + uint64_t page_1gb_support:1; + uint64_t aes_support:1; + uint64_t pclmulqdq_support:1; + uint64_t pcid_support:1; + uint64_t fma4_support:1; + uint64_t f16c_support:1; + uint64_t rd_rand_support:1; + uint64_t rd_wr_fs_gs_support:1; + uint64_t smep_support:1; + uint64_t enhanced_fast_string_support:1; + uint64_t bmi1_support:1; + uint64_t bmi2_support:1; + uint64_t hle_support_deprecated:1; + uint64_t rtm_support_deprecated:1; + uint64_t movbe_support:1; + uint64_t npiep1_support:1; + uint64_t dep_x87_fpu_save_support:1; + uint64_t rd_seed_support:1; + uint64_t adx_support:1; + uint64_t intel_prefetch_support:1; + uint64_t smap_support:1; + uint64_t hle_support:1; + uint64_t rtm_support:1; + uint64_t rdtscp_support:1; + uint64_t clflushopt_support:1; + uint64_t clwb_support:1; + uint64_t sha_support:1; + uint64_t x87_pointers_saved_support:1; + uint64_t invpcid_support:1; + uint64_t ibrs_support:1; + uint64_t stibp_support:1; + uint64_t ibpb_support:1; + uint64_t unrestricted_guest_support:1; + uint64_t mdd_support:1; + uint64_t fast_short_rep_mov_support:1; + uint64_t l1dcache_flush_support:1; + uint64_t rdcl_no_support:1; + uint64_t ibrs_all_support:1; + uint64_t skip_l1df_support:1; + uint64_t ssb_no_support:1; + uint64_t rsb_a_no_support:1; + uint64_t virt_spec_ctrl_support:1; + uint64_t rd_pid_support:1; + uint64_t umip_support:1; + uint64_t mbs_no_support:1; + uint64_t mb_clear_support:1; + uint64_t taa_no_support:1; + uint64_t tsx_ctrl_support:1; + uint64_t reserved_bank0:1; + + /* N.B. Begin bank 1 processor features. */ + uint64_t a_count_m_count_support:1; + uint64_t tsc_invariant_support:1; + uint64_t cl_zero_support:1; + uint64_t rdpru_support:1; + uint64_t la57_support:1; + uint64_t mbec_support:1; + uint64_t nested_virt_support:1; + uint64_t psfd_support:1; + uint64_t cet_ss_support:1; + uint64_t cet_ibt_support:1; + uint64_t vmx_exception_inject_support:1; + uint64_t enqcmd_support:1; + uint64_t umwait_tpause_support:1; + uint64_t movdiri_support:1; + uint64_t movdir64b_support:1; + uint64_t cldemote_support:1; + uint64_t serialize_support:1; + uint64_t tsc_deadline_tmr_support:1; + uint64_t tsc_adjust_support:1; + uint64_t fzl_rep_movsb:1; + uint64_t fs_rep_stosb:1; + uint64_t fs_rep_cmpsb:1; + uint64_t tsx_ld_trk_support:1; + uint64_t vmx_ins_outs_exit_info_support:1; + uint64_t hlat_support:1; + uint64_t sbdr_ssdp_no_support:1; + uint64_t fbsdp_no_support:1; + uint64_t psdp_no_support:1; + uint64_t fb_clear_support:1; + uint64_t btc_no_support:1; + uint64_t ibpb_rsb_flush_support:1; + uint64_t stibp_always_on_support:1; + uint64_t perf_global_ctrl_support:1; + uint64_t npt_execute_only_support:1; + uint64_t npt_ad_flags_support:1; + uint64_t npt1_gb_page_support:1; + uint64_t amd_processor_topology_node_id_support:1; + uint64_t local_machine_check_support:1; + uint64_t extended_topology_leaf_fp256_amd_support:1; + uint64_t gds_no_support:1; + uint64_t cmpccxadd_support:1; + uint64_t tsc_aux_virtualization_support:1; + uint64_t rmp_query_support:1; + uint64_t bhi_no_support:1; + uint64_t bhi_dis_support:1; + uint64_t prefetch_i_support:1; + uint64_t sha512_support:1; + uint64_t mitigation_ctrl_support:1; + uint64_t rfds_no_support:1; + uint64_t rfds_clear_support:1; + uint64_t sm3_support:1; + uint64_t sm4_support:1; + uint64_t secure_avic_support:1; + uint64_t guest_intercept_ctrl_support:1; + uint64_t sbpb_supported:1; + uint64_t ibpb_br_type_supported:1; + uint64_t srso_no_supported:1; + uint64_t srso_user_kernel_no_supported:1; + uint64_t vrew_clear_supported:1; + uint64_t tsa_l1_no_supported:1; + uint64_t tsa_sq_no_supported:1; + uint64_t lass_support:1; + uint64_t idle_hlt_intercept_support:1; + uint64_t msr_list_support:1; + } QEMU_PACKED; +}; + enum hv_translate_gva_result_code { HV_TRANSLATE_GVA_SUCCESS = 0, diff --git a/include/system/accel-irq.h b/include/system/accel-irq.h index a2caa06f54..fc94c54264 100644 --- a/include/system/accel-irq.h +++ b/include/system/accel-irq.h @@ -25,9 +25,11 @@ static inline bool accel_irqchip_is_split(void) return mshv_msi_via_irqfd_enabled() || kvm_irqchip_is_split(); } -int accel_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev); +AccelRouteChange accel_irqchip_begin_route_changes(void); +int accel_irqchip_add_msi_route(AccelRouteChange *c, int vector, + PCIDevice *dev); int accel_irqchip_update_msi_route(int vector, MSIMessage msg, PCIDevice *dev); -void accel_irqchip_commit_route_changes(KVMRouteChange *c); +void accel_irqchip_commit_route_changes(AccelRouteChange *c); void accel_irqchip_commit_routes(void); void accel_irqchip_release_virq(int virq); int accel_irqchip_add_irqfd_notifier_gsi(EventNotifier *n, EventNotifier *rn, diff --git a/include/system/kvm.h b/include/system/kvm.h index 5fa33eddda..cdd1856ac5 100644 --- a/include/system/kvm.h +++ b/include/system/kvm.h @@ -18,6 +18,7 @@ #include "exec/memattrs.h" #include "qemu/accel.h" +#include "accel/accel-route.h" #include "qom/object.h" #ifdef COMPILING_PER_TARGET @@ -183,11 +184,6 @@ extern KVMState *kvm_state; typedef struct Notifier Notifier; typedef struct NotifierWithReturn NotifierWithReturn; -typedef struct KVMRouteChange { - KVMState *s; - int changes; -} KVMRouteChange; - /* external API */ unsigned int kvm_get_max_memslots(void); @@ -466,7 +462,7 @@ void kvm_init_cpu_signals(CPUState *cpu); /** * kvm_irqchip_add_msi_route - Add MSI route for specific vector - * @c: KVMRouteChange instance. + * @c: AccelRouteChange instance. * @vector: which vector to add. This can be either MSI/MSIX * vector. The function will automatically detect whether * MSI/MSIX is enabled, and fetch corresponding MSI @@ -475,24 +471,11 @@ void kvm_init_cpu_signals(CPUState *cpu); * as @NULL, an empty MSI message will be inited. * @return: virq (>=0) when success, errno (<0) when failed. */ -int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev); +int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev); int kvm_irqchip_update_msi_route(KVMState *s, int virq, MSIMessage msg, PCIDevice *dev); void kvm_irqchip_commit_routes(KVMState *s); -static inline KVMRouteChange kvm_irqchip_begin_route_changes(KVMState *s) -{ - return (KVMRouteChange) { .s = s, .changes = 0 }; -} - -static inline void kvm_irqchip_commit_route_changes(KVMRouteChange *c) -{ - if (c->changes) { - kvm_irqchip_commit_routes(c->s); - c->changes = 0; - } -} - int kvm_irqchip_get_virq(KVMState *s); void kvm_irqchip_release_virq(KVMState *s, int virq); diff --git a/include/system/mshv.h b/include/system/mshv.h index 75286baf16..46fe3fcebc 100644 --- a/include/system/mshv.h +++ b/include/system/mshv.h @@ -21,6 +21,7 @@ #include "qapi/qapi-types-common.h" #include "system/memory.h" #include "accel/accel-ops.h" +#include "accel/accel-route.h" #ifdef COMPILING_PER_TARGET #ifdef CONFIG_MSHV @@ -32,6 +33,7 @@ #endif #define MSHV_MAX_MSI_ROUTES 4096 +#define MSHV_MIN_ALLOCATED_MSI_ROUTES 64 #define MSHV_PAGE_SHIFT 12 @@ -44,7 +46,13 @@ extern bool mshv_allowed; #define mshv_msi_via_irqfd_enabled() mshv_enabled() #endif +#define TYPE_MSHV_ACCEL ACCEL_CLASS_NAME("mshv") + typedef struct MshvState MshvState; + +DECLARE_INSTANCE_CHECKER(MshvState, MSHV_STATE, + TYPE_MSHV_ACCEL) + extern MshvState *mshv_state; /* interrupt */ @@ -52,12 +60,16 @@ int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, uint3 uint32_t vp_index, bool logical_destination_mode, bool level_triggered); -int mshv_irqchip_add_msi_route(int vector, PCIDevice *dev); +int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev); int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev); -void mshv_irqchip_commit_routes(void); -void mshv_irqchip_release_virq(int virq); +void mshv_irqchip_release_virq(MshvState *s, int virq); +void mshv_irqchip_commit_routes(MshvState *s); int mshv_irqchip_add_irqfd_notifier_gsi(const EventNotifier *n, const EventNotifier *rn, int virq); int mshv_irqchip_remove_irqfd_notifier_gsi(const EventNotifier *n, int virq); +void mshv_init_irq_routing(MshvState *s); + +/* cpuid */ +uint32_t mshv_get_supported_cpuid(uint32_t func, uint32_t idx, int reg); #endif diff --git a/include/system/mshv_int.h b/include/system/mshv_int.h index 35386c422f..ba3073888a 100644 --- a/include/system/mshv_int.h +++ b/include/system/mshv_int.h @@ -14,8 +14,25 @@ #ifndef QEMU_MSHV_INT_H #define QEMU_MSHV_INT_H +#include "hw/hyperv/hvhdk.h" + #define MSHV_MSR_ENTRIES_COUNT 64 +/* + * Interruption-type encoding, used by the hypervisor in + * hv_x64_pending_interruption_register.interruption_type + * See TLFS 6.0 section 7.9.2, p55 + * https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/tlfs + */ +#define MSHV_HV_INTERRUPTION_TYPE_EXT_INT 0 +#define MSHV_HV_INTERRUPTION_TYPE_NMI 2 +#define MSHV_HV_INTERRUPTION_TYPE_HW_EXC 3 +#define MSHV_HV_INTERRUPTION_TYPE_SW_INT 4 +#define MSHV_HV_INTERRUPTION_TYPE_PRIV_SW_EXC 5 +#define MSHV_HV_INTERRUPTION_TYPE_SW_EXC 6 + +#define MSHV_DIRTY_PAGES_BATCH_SIZE 0x10000 + typedef struct hyperv_message hv_message; typedef struct MshvHvCallArgs { @@ -48,6 +65,12 @@ struct MshvState { int nr_as; MshvAddressSpace *as; int fd; + /* irqchip routing */ + struct mshv_user_irq_table *irq_routes; + int nr_allocated_irq_routes; + unsigned long *used_gsi_bitmap; + unsigned int gsi_count; + union hv_partition_processor_features processor_features; }; typedef struct MshvMsiControl { @@ -58,19 +81,6 @@ typedef struct MshvMsiControl { #define mshv_vcpufd(cpu) (cpu->accel->cpufd) /* cpu */ -typedef struct MshvFPU { - uint8_t fpr[8][16]; - uint16_t fcw; - uint16_t fsw; - uint8_t ftwx; - uint8_t pad1; - uint16_t last_opcode; - uint64_t last_ip; - uint64_t last_dp; - uint8_t xmm[16][16]; - uint32_t mxcsr; - uint32_t pad2; -} MshvFPU; typedef enum MshvVmExit { MshvVmExitIgnore = 0, @@ -81,19 +91,20 @@ typedef enum MshvVmExit { void mshv_init_mmio_emu(void); int mshv_create_vcpu(int vm_fd, uint8_t vp_index, int *cpu_fd); void mshv_remove_vcpu(int vm_fd, int cpu_fd); -int mshv_configure_vcpu(const CPUState *cpu, const MshvFPU *fpu, uint64_t xcr0); -int mshv_get_standard_regs(CPUState *cpu); -int mshv_get_special_regs(CPUState *cpu); +int mshv_configure_vcpu(const CPUState *cpu); int mshv_run_vcpu(int vm_fd, CPUState *cpu, hv_message *msg, MshvVmExit *exit); -int mshv_load_regs(CPUState *cpu); -int mshv_store_regs(CPUState *cpu); int mshv_set_generic_regs(const CPUState *cpu, const hv_register_assoc *assocs, size_t n_regs); -int mshv_arch_put_registers(const CPUState *cpu); +int mshv_get_generic_regs(CPUState *cpu, hv_register_assoc *assocs, + size_t n_regs); +int mshv_arch_store_vcpu_state(const CPUState *cpu); +int mshv_arch_load_vcpu_state(CPUState *cpu); void mshv_arch_init_vcpu(CPUState *cpu); void mshv_arch_destroy_vcpu(CPUState *cpu); void mshv_arch_amend_proc_features( union hv_partition_synthetic_processor_features *features); +void mshv_arch_disable_partition_proc_features( + union hv_partition_processor_features *disabled_features); int mshv_arch_post_init_vm(int vm_fd); typedef struct mshv_root_hvcall mshv_root_hvcall; @@ -113,24 +124,13 @@ int mshv_guest_mem_write(uint64_t gpa, const uint8_t *data, uintptr_t size, bool is_secure_mode); void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section, bool add); +void mshv_log_sync(MemoryListener *listener, MemoryRegionSection *section); +bool mshv_log_global_start(MemoryListener *listener, Error **errp); +void mshv_log_global_stop(MemoryListener *listener); /* msr */ -typedef struct MshvMsrEntry { - uint32_t index; - uint32_t reserved; - uint64_t data; -} MshvMsrEntry; - -typedef struct MshvMsrEntries { - MshvMsrEntry entries[MSHV_MSR_ENTRIES_COUNT]; - uint32_t nmsrs; -} MshvMsrEntries; - -int mshv_configure_msr(const CPUState *cpu, const MshvMsrEntry *msrs, - size_t n_msrs); - -/* interrupt */ -void mshv_init_msicontrol(void); -int mshv_reserve_ioapic_msi_routes(int vm_fd); +int mshv_init_msrs(const CPUState *cpu); +int mshv_get_msrs(CPUState *cpu); +int mshv_set_msrs(const CPUState *cpu); #endif diff --git a/qemu-options.hx b/qemu-options.hx index c799286153..f1874a0591 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -36,7 +36,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ " dea-key-wrap=on|off controls support for DEA key wrapping (default=on)\n" " suppress-vmdesc=on|off disables self-describing migration (default=off)\n" " nvdimm=on|off controls NVDIMM support (default=off)\n" - " memory-encryption= memory encryption object to use (default=none)\n" + " confidential-guest-support= specifies confidential guest support object (default=none)\n" + " memory-encryption= (memory-encryption is the alias of confidential-guest-support, recommend to use confidential-guest-support)\n" " hmat=on|off controls ACPI HMAT support (default=off)\n" " spcr=on|off controls ACPI SPCR support (default=on)\n" #ifdef CONFIG_POSIX @@ -100,8 +101,12 @@ SRST ``nvdimm=on|off`` Enables or disables NVDIMM support. The default is off. + ``confidential-guest-support=`` + confidential guest support object to use. The default is none. + ``memory-encryption=`` - Memory encryption object to use. The default is none. + The alias of ``confidential-guest-support``. Recommend to use + confidential-guest-support. ``hmat=on|off`` Enables or disables ACPI Heterogeneous Memory Attribute Table @@ -6364,9 +6369,50 @@ SRST # |qemu_system_x86| \\ ...... \\ -object sev-guest,id=sev0,cbitpos=47,reduced-phys-bits=1 \\ - -machine ...,memory-encryption=sev0 \\ + -machine ...,confidential-guest-support=sev0 \\ ..... + ``-object tdx-guest,id=id,[attributes=attrs,sept-ve-disable=on|off,mrconfigid=sha384_digest,mrowner=sha384_digest,mrownerconfig=sha384_digest,quote-generation-socket=socketaddr]`` + Create an Intel Trusted Domain eXtensions (TDX) guest object, which is + the type of ``confidential-guest-support`` object. When pass the object + ID to machine's ``confidential-guest-support`` property, it can create + a TDX guest. + + The ``attributes`` property is a 64-bit integer, which specifies the + TD attributes of the TD. + + The ``sept-ve-disable`` property controls the bit 28 of TD attributes + specifically. When it's on, the EPT violation conversion to #VE on + guest access of PENDING pages is disabled. Some guest OS (e.g., Linux + TD guest) may require this to be set, otherwise they refuse to boot. + The default value is on. + + The ``mrconfigid`` property is base64 encoded SHA384 digest, which + provides the ID for non-owner-defined configuration of the guest TD, + e.g., run-time or OS configuration. The default value is all zeros. + + The ``mrowner`` property is base64 encoded SHA384 digest, which + provides the ID for guest TD's owner. The default value is all zeros. + + The ``mrownerconfig`` property is base64 encoded SHA384 digest, which + provides the ID for owner-defined configuration of the guest TD, e.g., + the configuration specific to the workload rather than the run-time of + OS. The default value is all zeros. + + The ``quote-generation-socket`` property specifies the socket address + of the Quote Generation Service (QGS). QGS is a daemon running on the + host. QEMU forwards the request from TD guest to QGS and + sents the reply (which contains generated QUOTE on success) from QGS + to guest TD. + + .. parsed-literal:: + + # |qemu_system_x86| \\ + ...... \\ + -object '{"qom-type":"tdx-guest","id":"tdx","quote-generation-socket":{"type":"unix","path":"/var/run/qgs.socket"}}' \\ + -machine ...,confidential-guest-support=tdx \\ + ...... + ``-object igvm-cfg,file=file`` Create an IGVM configuration object that defines the initial state of the guest using a file in that conforms to the Independent Guest diff --git a/system/runstate.c b/system/runstate.c index 18e585be47..08acf801b0 100644 --- a/system/runstate.c +++ b/system/runstate.c @@ -706,7 +706,7 @@ void qemu_system_guest_panicked(GuestPanicInformation *info) " error code: 0x%" PRIx32 " error message:\"%s\"\n", info->u.tdx.error_code, message); g_free(message); - if (info->u.tdx.gpa != -1ull) { + if (info->u.tdx.has_gpa) { qemu_log_mask(LOG_GUEST_ERROR, "Additional error information " "can be found at gpa page: 0x%" PRIx64 "\n", info->u.tdx.gpa); diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 8929a75c7c..5805d33ab9 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -26,6 +26,7 @@ #include "tcg/helper-tcg.h" #include "exec/translation-block.h" #include "system/hvf.h" +#include "system/mshv.h" #include "system/whpx.h" #include "whpx/whpx-i386.h" #include "hvf/hvf-i386.h" @@ -8233,6 +8234,13 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w) r = hvf_get_supported_cpuid(wi->cpuid.eax, wi->cpuid.ecx, wi->cpuid.reg); + } else if (mshv_enabled()) { + if (wi->type != CPUID_FEATURE_WORD) { + return 0; + } + r = mshv_get_supported_cpuid(wi->cpuid.eax, + wi->cpuid.ecx, + wi->cpuid.reg); } else if (whpx_enabled()) { switch (wi->type) { case CPUID_FEATURE_WORD: diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 67e2ecf325..e6a197602d 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1983,6 +1983,9 @@ typedef struct CPUCaches { CPUCacheInfo *l3_cache; } CPUCaches; +struct kvm_msrs; +struct hv_vp_register_page; + typedef struct CPUArchState { /* standard registers */ target_ulong regs[CPU_NB_EREGS]; @@ -2289,6 +2292,10 @@ typedef struct CPUArchState { QEMUTimer *xen_periodic_timer; QemuMutex xen_timers_lock; #endif +#if defined(CONFIG_MSHV) + /* Shared register page */ + struct hv_vp_register_page *regs_page; +#endif #if defined(CONFIG_HVF) || defined(CONFIG_MSHV) || defined(CONFIG_WHPX) void *emu_mmio_buf; #endif @@ -2315,8 +2322,6 @@ typedef struct CPUArchState { DECLARE_BITMAP(avail_cpu_topo, CPU_TOPOLOGY_LEVEL__MAX); } CPUX86State; -struct kvm_msrs; - /** * X86CPU: * @env: #CPUX86State diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 9e352882c8..64cc421abe 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -32,6 +32,7 @@ #include "vmsr_energy.h" #include "system/system.h" #include "system/hw_accel.h" +#include "system/accel-irq.h" #include "system/kvm_int.h" #include "system/runstate.h" #include "system/ramblock.h" @@ -6678,7 +6679,7 @@ void kvm_arch_init_irq_routing(KVMState *s) kvm_gsi_routing_allowed = true; if (kvm_irqchip_is_split()) { - KVMRouteChange c = kvm_irqchip_begin_route_changes(s); + AccelRouteChange c = accel_irqchip_begin_route_changes(); int i; /* If the ioapic is in QEMU and the lapics are in KVM, reserve @@ -6689,7 +6690,7 @@ void kvm_arch_init_irq_routing(KVMState *s) exit(1); } } - kvm_irqchip_commit_route_changes(&c); + accel_irqchip_commit_route_changes(&c); } } diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c index dfad469112..c9c3b05d5f 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -560,19 +560,29 @@ typedef struct TdxXFAMDep { } TdxXFAMDep; /* - * Note, only the CPUID bits whose virtualization type are "XFAM & Native" are - * defiend here. + * Note, usually the CPUID bits whose virtualization type are "XFAM & Native" + * are defined here while "XFAM & Configured & Native" are not. Because the + * latter are reported as configurable bits by KVM when they are supported. + * And they are not supported when not in the configurable bits list from KVM + * even if the corresponding XFAM bit is supported. * - * For those whose virtualization type are "XFAM & Configured & Native", they - * are reported as configurable bits. And they are not supported if not in the - * configureable bits list from KVM even if the corresponding XFAM bit is - * supported. + * Special cases: + * + * - AMX alias bits, their type is "CPUID_Enabled & Native" which means their + * value is determined by the CPUID bit they are aliased to. + * + * - AVX10_VL_MASK, their type is "XFAM & CPUID_Enabled & Native" which means + * their value is determined by both the corresponding XFAM bit and CPUID bit. + * + * For simplicity, relax the dependency to related XFAM bit. + * tdx_check_features() will eventually catch the unsupported configurations. */ TdxXFAMDep tdx_xfam_deps[] = { { XSTATE_YMM_BIT, { FEAT_1_ECX, CPUID_EXT_FMA } }, { XSTATE_YMM_BIT, { FEAT_7_0_EBX, CPUID_7_0_EBX_AVX2 } }, { XSTATE_OPMASK_BIT, { FEAT_7_0_ECX, CPUID_7_0_ECX_AVX512_VBMI } }, { XSTATE_OPMASK_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AVX512_FP16 } }, + { XSTATE_OPMASK_BIT, { FEAT_24_0_EBX, CPUID_24_0_EBX_AVX10_VL_MASK } }, { XSTATE_PT_BIT, { FEAT_7_0_EBX, CPUID_7_0_EBX_INTEL_PT } }, { XSTATE_PKRU_BIT, { FEAT_7_0_ECX, CPUID_7_0_ECX_PKU } }, { XSTATE_CET_U_BIT, { FEAT_7_0_ECX, CPUID_7_0_ECX_CET_SHSTK } }, @@ -580,6 +590,10 @@ TdxXFAMDep tdx_xfam_deps[] = { { XSTATE_XTILE_CFG_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AMX_BF16 } }, { XSTATE_XTILE_CFG_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AMX_TILE } }, { XSTATE_XTILE_CFG_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AMX_INT8 } }, + { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_INT8_ALIAS } }, + { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_BF16_ALIAS } }, + { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_COMPLEX_ALIAS } }, + { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_FP16_ALIAS } }, }; static struct kvm_cpuid_entry2 *find_in_supported_entry(uint32_t function, @@ -1403,7 +1417,7 @@ int tdx_handle_report_fatal_error(X86CPU *cpu, struct kvm_run *run) uint64_t reg_mask = run->system_event.data[R_ECX]; char *message = NULL; uint64_t *tmp; - uint64_t gpa = -1ull; + uint64_t gpa = 0; bool has_gpa = false; if (error_code & 0xffff) { diff --git a/target/i386/meson.build b/target/i386/meson.build index 80062d1d0d..b4fbf8a537 100644 --- a/target/i386/meson.build +++ b/target/i386/meson.build @@ -12,7 +12,7 @@ i386_ss.add(when: 'CONFIG_KVM', if_true: files('xsave_helper.c', 'host-cpu.c')) i386_ss.add(when: 'CONFIG_HVF', if_true: files('xsave_helper.c', 'host-cpu.c')) i386_ss.add(when: 'CONFIG_WHPX', if_true: files('xsave_helper.c', 'host-cpu.c')) i386_ss.add(when: 'CONFIG_NVMM', if_true: files('host-cpu.c')) -i386_ss.add(when: 'CONFIG_MSHV', if_true: files('host-cpu.c')) +i386_ss.add(when: 'CONFIG_MSHV', if_true: files('xsave_helper.c', 'host-cpu.c')) i386_system_ss = ss.source_set() i386_system_ss.add(files( diff --git a/target/i386/mshv/meson.build b/target/i386/mshv/meson.build index 49f28d4a5b..6091c21887 100644 --- a/target/i386/mshv/meson.build +++ b/target/i386/mshv/meson.build @@ -2,6 +2,7 @@ i386_mshv_ss = ss.source_set() i386_mshv_ss.add(files( 'mshv-cpu.c', + 'msr.c', )) i386_system_ss.add_all(when: 'CONFIG_MSHV', if_true: i386_mshv_ss) diff --git a/target/i386/mshv/mshv-cpu.c b/target/i386/mshv/mshv-cpu.c index 4ed6e7548f..126ca40b48 100644 --- a/target/i386/mshv/mshv-cpu.c +++ b/target/i386/mshv/mshv-cpu.c @@ -24,10 +24,13 @@ #include "hw/i386/apic_internal.h" #include "cpu.h" +#include "host-cpu.h" #include "emulate/x86_decode.h" #include "emulate/x86_emu.h" #include "emulate/x86_flags.h" +#include "accel/accel-cpu-target.h" + #include "trace-accel_mshv.h" #include "trace.h" @@ -107,6 +110,175 @@ static enum hv_register_name FPU_REGISTER_NAMES[26] = { HV_X64_REGISTER_XMM_CONTROL_STATUS, }; +static int set_special_regs(const CPUState *cpu); + +static int get_xsave_state(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + int cpu_fd = mshv_vcpufd(cpu); + int ret; + void *xsavec_buf; + const size_t page = HV_HYP_PAGE_SIZE; + size_t xsavec_buf_len = page; + + /* TODO: should properly determine xsavec size based on CPUID */ + xsavec_buf = qemu_memalign(page, xsavec_buf_len); + memset(xsavec_buf, 0, xsavec_buf_len); + + struct mshv_get_set_vp_state args = { + .type = MSHV_VP_STATE_XSAVE, + .buf_sz = xsavec_buf_len, + .buf_ptr = (uintptr_t)xsavec_buf, + }; + + ret = ioctl(cpu_fd, MSHV_GET_VP_STATE, &args); + if (ret < 0) { + error_report("failed to get xsave state: %s", strerror(errno)); + return -errno; + } + + ret = decompact_xsave_area(xsavec_buf, xsavec_buf_len, env); + g_free(xsavec_buf); + if (ret < 0) { + error_report("failed to decompact xsave area"); + return ret; + } + x86_cpu_xrstor_all_areas(x86cpu, env->xsave_buf, env->xsave_buf_len); + + return 0; +} + +static int set_xsave_state(const CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + int cpu_fd = mshv_vcpufd(cpu); + int ret; + void *xsavec_buf; + size_t page = HV_HYP_PAGE_SIZE, xsavec_buf_len; + + /* allocate and populate compacted buffer */ + xsavec_buf = qemu_memalign(page, page); + xsavec_buf_len = page; + + /* save registers to standard format buffer */ + x86_cpu_xsave_all_areas(x86cpu, env->xsave_buf, env->xsave_buf_len); + + /* store compacted version of xsave area in xsavec_buf */ + compact_xsave_area(env, xsavec_buf, xsavec_buf_len); + + struct mshv_get_set_vp_state args = { + .type = MSHV_VP_STATE_XSAVE, + .buf_sz = xsavec_buf_len, + .buf_ptr = (uintptr_t)xsavec_buf, + }; + + ret = ioctl(cpu_fd, MSHV_SET_VP_STATE, &args); + g_free(xsavec_buf); + if (ret < 0) { + error_report("failed to set xsave state: %s", strerror(errno)); + return -errno; + } + + return 0; +} + +static void populate_fpu(const hv_register_assoc *assocs, X86CPU *x86cpu) +{ + union hv_register_value value; + const union hv_x64_fp_control_status_register *ctrl_status; + const union hv_x64_xmm_control_status_register *xmm_ctrl; + CPUX86State *env = &x86cpu->env; + size_t i, fp_i; + bool valid; + + /* first 16 registers are xmm0-xmm15 */ + for (i = 0; i < 16; i++) { + value = assocs[i].value; + env->xmm_regs[i].ZMM_Q(0) = value.reg128.low_part; + env->xmm_regs[i].ZMM_Q(1) = value.reg128.high_part; + } + + /* next 8 registers are fp_mmx0-fp_mmx7 */ + for (i = 16; i < 24; i++) { + fp_i = i - 16; + value = assocs[i].value; + env->fpregs[fp_i].d.low = value.fp.mantissa; + env->fpregs[fp_i].d.high = (value.fp.sign << 15) + | (value.fp.biased_exponent & 0x7FFF); + } + + /* last two registers are fp_control_status and xmm_control_status */ + ctrl_status = &assocs[24].value.fp_control_status; + env->fpuc = ctrl_status->fp_control; + + env->fpus = ctrl_status->fp_status & ~0x3800; + /* bits 11,12,13 are the top of stack pointer */ + env->fpstt = (ctrl_status->fp_status >> 11) & 0x7; + + for (i = 0; i < 8; i++) { + valid = ctrl_status->fp_tag & (1 << i); + env->fptags[i] = valid ? 0 : 1; + } + + env->fpop = ctrl_status->last_fp_op; + env->fpip = ctrl_status->last_fp_rip; + + xmm_ctrl = &assocs[25].value.xmm_control_status; + env->mxcsr = xmm_ctrl->xmm_status_control; + env->fpdp = xmm_ctrl->last_fp_rdp; +} + +static int get_fpu(CPUState *cpu) +{ + struct hv_register_assoc assocs[ARRAY_SIZE(FPU_REGISTER_NAMES)]; + int ret; + X86CPU *x86cpu = X86_CPU(cpu); + size_t n_regs = ARRAY_SIZE(FPU_REGISTER_NAMES); + + for (size_t i = 0; i < n_regs; i++) { + assocs[i].name = FPU_REGISTER_NAMES[i]; + } + ret = mshv_get_generic_regs(cpu, assocs, n_regs); + if (ret < 0) { + error_report("failed to get special registers"); + return -errno; + } + + populate_fpu(assocs, x86cpu); + + return 0; +} + +static int get_xc_reg(CPUState *cpu) +{ + int ret; + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + struct hv_register_assoc assocs[1]; + + assocs[0].name = HV_X64_REGISTER_XFEM; + + ret = mshv_get_generic_regs(cpu, assocs, 1); + if (ret < 0) { + error_report("failed to get xcr0"); + return -1; + } + env->xcr0 = assocs[0].value.reg64; + + return 0; +} + +static enum hv_register_name NON_VP_PAGE_REGISTER_NAMES[6] = { + HV_X64_REGISTER_TR, + HV_X64_REGISTER_LDTR, + HV_X64_REGISTER_GDTR, + HV_X64_REGISTER_IDTR, + HV_X64_REGISTER_CR2, + HV_X64_REGISTER_APIC_BASE, +}; + static int translate_gva(const CPUState *cpu, uint64_t gva, uint64_t *gpa, uint64_t flags) { @@ -190,8 +362,8 @@ int mshv_set_generic_regs(const CPUState *cpu, const hv_register_assoc *assocs, return 0; } -static int get_generic_regs(CPUState *cpu, hv_register_assoc *assocs, - size_t n_regs) +int mshv_get_generic_regs(CPUState *cpu, hv_register_assoc *assocs, + size_t n_regs) { int cpu_fd = mshv_vcpufd(cpu); int vp_index = cpu->cpu_index; @@ -285,14 +457,56 @@ static int set_standard_regs(const CPUState *cpu) return 0; } -int mshv_store_regs(CPUState *cpu) +static void mshv_set_standard_regs_vp_page(CPUState *cpu) { + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + + env->regs_page->rax = env->regs[R_EAX]; + env->regs_page->rbx = env->regs[R_EBX]; + env->regs_page->rcx = env->regs[R_ECX]; + env->regs_page->rdx = env->regs[R_EDX]; + env->regs_page->rsi = env->regs[R_ESI]; + env->regs_page->rdi = env->regs[R_EDI]; + env->regs_page->rsp = env->regs[R_ESP]; + env->regs_page->rbp = env->regs[R_EBP]; + env->regs_page->r8 = env->regs[R_R8]; + env->regs_page->r9 = env->regs[R_R9]; + env->regs_page->r10 = env->regs[R_R10]; + env->regs_page->r11 = env->regs[R_R11]; + env->regs_page->r12 = env->regs[R_R12]; + env->regs_page->r13 = env->regs[R_R13]; + env->regs_page->r14 = env->regs[R_R14]; + env->regs_page->r15 = env->regs[R_R15]; + env->regs_page->rip = env->eip; + lflags_to_rflags(env); + env->regs_page->rflags = env->eflags; + + env->regs_page->dirty |= (1u << HV_X64_REGISTER_CLASS_GENERAL) + | (1u << HV_X64_REGISTER_CLASS_IP) + | (1u << HV_X64_REGISTER_CLASS_FLAGS); +} + +static int store_regs(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; int ret; - ret = set_standard_regs(cpu); + /* Use register vp page to optimize registers access */ + if (env->regs_page && env->regs_page->isvalid != 0) { + mshv_set_standard_regs_vp_page(cpu); + } else { + ret = set_standard_regs(cpu); + if (ret < 0) { + return ret; + } + } + + ret = set_special_regs(cpu); if (ret < 0) { - error_report("Failed to store standard registers"); - return -1; + error_report("Failed to store speical registers"); + return ret; } return 0; @@ -323,7 +537,7 @@ static void populate_standard_regs(const hv_register_assoc *assocs, rflags_to_lflags(env); } -int mshv_get_standard_regs(CPUState *cpu) +static int get_standard_regs(CPUState *cpu) { struct hv_register_assoc assocs[ARRAY_SIZE(STANDARD_REGISTER_NAMES)]; int ret; @@ -334,7 +548,7 @@ int mshv_get_standard_regs(CPUState *cpu) for (size_t i = 0; i < n_regs; i++) { assocs[i].name = STANDARD_REGISTER_NAMES[i]; } - ret = get_generic_regs(cpu, assocs, n_regs); + ret = mshv_get_generic_regs(cpu, assocs, n_regs); if (ret < 0) { error_report("failed to get standard registers"); return -1; @@ -401,8 +615,107 @@ static void populate_special_regs(const hv_register_assoc *assocs, cpu_set_apic_base(x86cpu->apic_state, assocs[16].value.reg64); } +static void mshv_get_standard_regs_vp_page(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; -int mshv_get_special_regs(CPUState *cpu) + /* General Purpose Registers */ + env->regs[R_EAX] = env->regs_page->rax; + env->regs[R_EBX] = env->regs_page->rbx; + env->regs[R_ECX] = env->regs_page->rcx; + env->regs[R_EDX] = env->regs_page->rdx; + env->regs[R_ESI] = env->regs_page->rsi; + env->regs[R_EDI] = env->regs_page->rdi; + env->regs[R_ESP] = env->regs_page->rsp; + env->regs[R_EBP] = env->regs_page->rbp; + env->regs[R_R8] = env->regs_page->r8; + env->regs[R_R9] = env->regs_page->r9; + env->regs[R_R10] = env->regs_page->r10; + env->regs[R_R11] = env->regs_page->r11; + env->regs[R_R12] = env->regs_page->r12; + env->regs[R_R13] = env->regs_page->r13; + env->regs[R_R14] = env->regs_page->r14; + env->regs[R_R15] = env->regs_page->r15; + + env->eip = env->regs_page->rip; + env->eflags = env->regs_page->rflags; + rflags_to_lflags(env); +} + +static int mshv_get_special_regs_vp_page(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + struct hv_register_assoc assocs[ARRAY_SIZE(NON_VP_PAGE_REGISTER_NAMES)]; + int ret; + size_t n_regs = ARRAY_SIZE(NON_VP_PAGE_REGISTER_NAMES); + hv_x64_segment_register seg; + + /* Populate special registers that are in the VP register page */ + env->cr[0] = env->regs_page->cr0; + env->cr[3] = env->regs_page->cr3; + env->cr[4] = env->regs_page->cr4; + env->efer = env->regs_page->efer; + cpu_set_apic_tpr(x86cpu->apic_state, env->regs_page->cr8); + + /* Segment Registers - copy from packed struct to avoid unaligned access */ + memcpy(&seg, &env->regs_page->es, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_ES]); + memcpy(&seg, &env->regs_page->cs, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_CS]); + memcpy(&seg, &env->regs_page->ss, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_SS]); + memcpy(&seg, &env->regs_page->ds, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_DS]); + memcpy(&seg, &env->regs_page->fs, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_FS]); + memcpy(&seg, &env->regs_page->gs, sizeof(hv_x64_segment_register)); + populate_segment_reg(&seg, &env->segs[R_GS]); + + /* The rest of the special registers that are not in the VP register page */ + for (size_t i = 0; i < n_regs; i++) { + assocs[i].name = NON_VP_PAGE_REGISTER_NAMES[i]; + } + + ret = mshv_get_generic_regs(cpu, assocs, n_regs); + if (ret < 0) { + error_report("failed to get non-vp-page special registers"); + return -1; + } + + /* Non-VP page registers - TR, LDTR, GDTR, IDTR, CR2, APIC_BASE */ + populate_segment_reg(&assocs[0].value.segment, &env->tr); + populate_segment_reg(&assocs[1].value.segment, &env->ldt); + + populate_table_reg(&assocs[2].value.table, &env->gdt); + populate_table_reg(&assocs[3].value.table, &env->idt); + env->cr[2] = assocs[4].value.reg64; + + cpu_set_apic_base(x86cpu->apic_state, assocs[5].value.reg64); + + return ret; +} + +static int mshv_get_registers_vp_page(CPUState *cpu) +{ + int ret; + + /* General Purpose Registers */ + mshv_get_standard_regs_vp_page(cpu); + + /* Special Registers - makes a hypercall */ + ret = mshv_get_special_regs_vp_page(cpu); + if (ret < 0) { + error_report("failed to get special registers for vp page"); + return -1; + } + + return 0; +} + + +static int get_special_regs(CPUState *cpu) { struct hv_register_assoc assocs[ARRAY_SIZE(SPECIAL_REGISTER_NAMES)]; int ret; @@ -412,7 +725,7 @@ int mshv_get_special_regs(CPUState *cpu) for (size_t i = 0; i < n_regs; i++) { assocs[i].name = SPECIAL_REGISTER_NAMES[i]; } - ret = get_generic_regs(cpu, assocs, n_regs); + ret = mshv_get_generic_regs(cpu, assocs, n_regs); if (ret < 0) { error_report("failed to get special registers"); return -errno; @@ -422,26 +735,245 @@ int mshv_get_special_regs(CPUState *cpu) return 0; } -int mshv_load_regs(CPUState *cpu) +static int load_regs(CPUState *cpu) { + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; int ret; - ret = mshv_get_standard_regs(cpu); + /* Use register vp page to optimize registers access */ + if (env->regs_page && env->regs_page->isvalid != 0) { + ret = mshv_get_registers_vp_page(cpu); + return ret; + } + + ret = get_standard_regs(cpu); if (ret < 0) { - error_report("Failed to load standard registers"); + return ret; + } + + ret = get_special_regs(cpu); + if (ret < 0) { + return ret; + } + + return 0; +} + +static int get_vcpu_events(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + struct hv_register_assoc assocs[] = { + { .name = HV_REGISTER_PENDING_INTERRUPTION }, + { .name = HV_REGISTER_INTERRUPT_STATE }, + { .name = HV_REGISTER_PENDING_EVENT0 }, + }; + union hv_x64_pending_interruption_register pending_int; + union hv_x64_interrupt_state_register int_state; + union hv_x64_pending_exception_event pending_exc; + int ret; + + ret = mshv_get_generic_regs(cpu, assocs, ARRAY_SIZE(assocs)); + if (ret < 0) { + error_report("failed to get vcpu event registers"); return -1; } - ret = mshv_get_special_regs(cpu); + pending_int.as_uint64 = assocs[0].value.reg64; + int_state.as_uint64 = assocs[1].value.reg64; + pending_exc = assocs[2].value.pending_exception_event; + + /* Clear previous state. injected ints/excs are blanked w/ -1 */ + env->interrupt_injected = -1; + env->soft_interrupt = 0; + env->exception_injected = 0; + env->exception_pending = 0; + env->exception_nr = -1; + env->has_error_code = 0; + env->error_code = 0; + env->exception_has_payload = 0; + env->exception_payload = 0; + env->nmi_injected = 0; + + if (pending_int.interruption_pending) { + switch (pending_int.interruption_type) { + case MSHV_HV_INTERRUPTION_TYPE_EXT_INT: + env->interrupt_injected = pending_int.interruption_vector; + break; + case MSHV_HV_INTERRUPTION_TYPE_NMI: + env->nmi_injected = 1; + break; + case MSHV_HV_INTERRUPTION_TYPE_HW_EXC: + env->exception_injected = 1; + env->exception_nr = pending_int.interruption_vector; + env->has_error_code = pending_int.deliver_error_code; + env->error_code = pending_int.error_code; + break; + case MSHV_HV_INTERRUPTION_TYPE_SW_INT: + env->interrupt_injected = pending_int.interruption_vector; + env->soft_interrupt = 1; + break; + case MSHV_HV_INTERRUPTION_TYPE_SW_EXC: + case MSHV_HV_INTERRUPTION_TYPE_PRIV_SW_EXC: + env->exception_injected = 1; + env->exception_nr = pending_int.interruption_vector; + env->has_error_code = pending_int.deliver_error_code; + env->error_code = pending_int.error_code; + break; + default: + error_report("unknown interruption type %u", + pending_int.interruption_type); + return -EINVAL; + } + } + + /* disabled for one instr after STI, MOV/POP SS, see hvf_store_events() */ + if (int_state.interrupt_shadow) { + env->hflags |= HF_INHIBIT_IRQ_MASK; + } else { + env->hflags &= ~HF_INHIBIT_IRQ_MASK; + } + + /* see kvm_get_vcpu_events(), hvf_store_events() */ + if (int_state.nmi_masked) { + env->hflags2 |= HF2_NMI_MASK; + } else { + env->hflags2 &= ~HF2_NMI_MASK; + } + + /* HV_REGISTER_PENDING_EVENT0: pending exception not yet injected */ + if (pending_exc.event_pending) { + env->exception_pending = 1; + env->exception_nr = pending_exc.vector; + env->has_error_code = pending_exc.deliver_error_code; + env->error_code = pending_exc.error_code; + env->exception_has_payload = (pending_exc.exception_parameter != 0); + env->exception_payload = pending_exc.exception_parameter; + } + + /* + * Ignoring HV_REGISTER_PENDING_EVENT1, virtualization fault events, MSHV + * does not support nested virtualization. + */ + + return 0; +} + +static int set_vcpu_events(const CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + union hv_x64_pending_interruption_register pending_int = { 0 }; + union hv_x64_interrupt_state_register int_state = { 0 }; + union hv_x64_pending_exception_event pending_exc = { 0 }; + struct hv_register_assoc assocs[3]; + int ret; + + /* build pending_int from CPUX86State */ + if (env->exception_injected) { + pending_int.interruption_pending = 1; + pending_int.interruption_type = MSHV_HV_INTERRUPTION_TYPE_HW_EXC; + pending_int.interruption_vector = env->exception_nr; + pending_int.deliver_error_code = env->has_error_code; + pending_int.error_code = env->error_code; + } else if (env->nmi_injected) { + pending_int.interruption_pending = 1; + pending_int.interruption_type = MSHV_HV_INTERRUPTION_TYPE_NMI; + pending_int.interruption_vector = EXCP02_NMI; + } else if (env->interrupt_injected >= 0) { + pending_int.interruption_pending = 1; + pending_int.interruption_type = env->soft_interrupt + ? MSHV_HV_INTERRUPTION_TYPE_SW_INT + : MSHV_HV_INTERRUPTION_TYPE_EXT_INT; + pending_int.interruption_vector = env->interrupt_injected; + } + + /* build int_state, normalize to bool */ + int_state.interrupt_shadow = !!(env->hflags & HF_INHIBIT_IRQ_MASK); + int_state.nmi_masked = !!(env->hflags2 & HF2_NMI_MASK); + + /* build pending_exc */ + if (env->exception_pending) { + pending_exc.event_pending = 1; + pending_exc.vector = env->exception_nr; + pending_exc.deliver_error_code = env->has_error_code; + pending_exc.error_code = env->error_code; + pending_exc.exception_parameter = env->exception_payload; + } + + assocs[0].name = HV_REGISTER_PENDING_INTERRUPTION; + assocs[0].value.reg64 = pending_int.as_uint64; + assocs[1].name = HV_REGISTER_INTERRUPT_STATE; + assocs[1].value.reg64 = int_state.as_uint64; + assocs[2].name = HV_REGISTER_PENDING_EVENT0; + assocs[2].value.pending_exception_event = pending_exc; + + ret = mshv_set_generic_regs(cpu, assocs, ARRAY_SIZE(assocs)); if (ret < 0) { - error_report("Failed to load special registers"); + error_report("failed to set vcpu event registers"); return -1; } return 0; } -static void add_cpuid_entry(GList *cpuid_entries, +static int update_hflags(CPUState *cpu) +{ + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + + x86_update_hflags(env); + + return 0; +} + +int mshv_arch_load_vcpu_state(CPUState *cpu) +{ + int ret; + + ret = get_standard_regs(cpu); + if (ret < 0) { + return ret; + } + + ret = get_special_regs(cpu); + if (ret < 0) { + return ret; + } + + /* INVARIANT: hflags are derived from regs+sregs, need to get both first */ + update_hflags(cpu); + + ret = get_xc_reg(cpu); + if (ret < 0) { + return ret; + } + + ret = get_fpu(cpu); + if (ret < 0) { + return ret; + } + + ret = mshv_get_msrs(cpu); + if (ret < 0) { + return ret; + } + + ret = get_xsave_state(cpu); + if (ret < 0) { + return ret; + } + + ret = get_vcpu_events(cpu); + if (ret < 0) { + return ret; + } + + return 0; +} + +static void add_cpuid_entry(GList **cpuid_entries, uint32_t function, uint32_t index, uint32_t eax, uint32_t ebx, uint32_t ecx, uint32_t edx) @@ -456,23 +988,26 @@ static void add_cpuid_entry(GList *cpuid_entries, entry->ecx = ecx; entry->edx = edx; - cpuid_entries = g_list_append(cpuid_entries, entry); + *cpuid_entries = g_list_append(*cpuid_entries, entry); } -static void collect_cpuid_entries(const CPUState *cpu, GList *cpuid_entries) +static void collect_cpuid_entries(const CPUState *cpu, GList **cpuid_entries) { X86CPU *x86_cpu = X86_CPU(cpu); CPUX86State *env = &x86_cpu->env; uint32_t eax, ebx, ecx, edx; uint32_t leaf, subleaf; - size_t max_leaf = 0x1F; - size_t max_subleaf = 0x20; - - uint32_t leaves_with_subleaves[] = {0x4, 0x7, 0xD, 0xF, 0x10}; + uint32_t max_basic_leaf, max_extended_leaf; + uint32_t max_subleaf = 0x20; + uint32_t leaves_with_subleaves[] = {0x04, 0x07, 0x0d, 0x0f, 0x10}; int n_subleaf_leaves = ARRAY_SIZE(leaves_with_subleaves); - /* Regular leaves without subleaves */ - for (leaf = 0; leaf <= max_leaf; leaf++) { + /* Get maximum basic and and extended CPUID leaves */ + cpu_x86_cpuid(env, 0, 0, &max_basic_leaf, &ebx, &ecx, &edx); + cpu_x86_cpuid(env, 0x80000000, 0, &max_extended_leaf, &ebx, &ecx, &edx); + + /* Collect basic leaves (0x0 to max_basic_leaf) */ + for (leaf = 0; leaf <= max_basic_leaf; leaf++) { bool has_subleaves = false; for (int i = 0; i < n_subleaf_leaves; i++) { if (leaf == leaves_with_subleaves[i]) { @@ -483,32 +1018,46 @@ static void collect_cpuid_entries(const CPUState *cpu, GList *cpuid_entries) if (!has_subleaves) { cpu_x86_cpuid(env, leaf, 0, &eax, &ebx, &ecx, &edx); - if (eax == 0 && ebx == 0 && ecx == 0 && edx == 0) { - /* all zeroes indicates no more leaves */ - continue; - } - add_cpuid_entry(cpuid_entries, leaf, 0, eax, ebx, ecx, edx); continue; } + /* + * Valid XSAVE components can exist at a higher index se we need to set + * all subleaves for leaf 0x0d, even if we encounter an empty one. + */ + if (leaf == 0x0d) { + for (subleaf = 0; subleaf <= 63; subleaf++) { + cpu_x86_cpuid(env, leaf, subleaf, &eax, &ebx, &ecx, &edx); + add_cpuid_entry(cpuid_entries, leaf, subleaf, + eax, ebx, ecx, edx); + } + continue; + } + subleaf = 0; while (subleaf < max_subleaf) { cpu_x86_cpuid(env, leaf, subleaf, &eax, &ebx, &ecx, &edx); if (eax == 0 && ebx == 0 && ecx == 0 && edx == 0) { - /* all zeroes indicates no more leaves */ break; } - add_cpuid_entry(cpuid_entries, leaf, 0, eax, ebx, ecx, edx); + add_cpuid_entry(cpuid_entries, leaf, subleaf, eax, ebx, ecx, edx); subleaf++; } } + + /* Collect extended leaves (0x80000000 to max_extended_leaf) */ + for (leaf = 0x80000000; leaf <= max_extended_leaf; leaf++) { + cpu_x86_cpuid(env, leaf, 0, &eax, &ebx, &ecx, &edx); + add_cpuid_entry(cpuid_entries, leaf, 0, eax, ebx, ecx, edx); + } } static int register_intercept_result_cpuid_entry(const CPUState *cpu, uint8_t subleaf_specific, uint8_t always_override, + uint32_t ebx_mask, struct hv_cpuid_entry *entry) { int ret; @@ -522,22 +1071,18 @@ static int register_intercept_result_cpuid_entry(const CPUState *cpu, .input.always_override = always_override, .input.padding = 0, /* - * With regard to masks - these are to specify bits to be overwritten - * The current CpuidEntry structure wouldn't allow to carry the masks - * in addition to the actual register values. For this reason, the - * masks are set to the exact values of the corresponding register bits - * to be registered for an overwrite. To view resulting values the - * hypervisor would return, HvCallGetVpCpuidValues hypercall can be - * used. + * Masks specify which bits to override. Set to 0xFFFFFFFF to + * override all bits with the values from the QEMU CPU model. + * A mask of 0 lets the hypervisor supply its own value. */ .result.eax = entry->eax, - .result.eax_mask = entry->eax, + .result.eax_mask = 0xFFFFFFFF, .result.ebx = entry->ebx, - .result.ebx_mask = entry->ebx, + .result.ebx_mask = ebx_mask, .result.ecx = entry->ecx, - .result.ecx_mask = entry->ecx, + .result.ecx_mask = 0xFFFFFFFF, .result.edx = entry->edx, - .result.edx_mask = entry->edx, + .result.edx_mask = 0xFFFFFFFF, }; union hv_register_intercept_result_parameters parameters = { .cpuid = cpuid_params, @@ -568,6 +1113,7 @@ static int register_intercept_result_cpuid(const CPUState *cpu, int ret = 0, entry_ret; struct hv_cpuid_entry *entry; uint8_t subleaf_specific, always_override; + uint32_t ebx_mask; for (size_t i = 0; i < cpuid->nent; i++) { entry = &cpuid->entries[i]; @@ -575,29 +1121,62 @@ static int register_intercept_result_cpuid(const CPUState *cpu, /* set defaults */ subleaf_specific = 0; always_override = 1; + ebx_mask = 0xFFFFFFFF; - /* Intel */ - /* 0xb - Extended Topology Enumeration Leaf */ - /* 0x1f - V2 Extended Topology Enumeration Leaf */ - /* AMD */ - /* 0x8000_001e - Processor Topology Information */ - /* 0x8000_0026 - Extended CPU Topology */ - if (entry->function == 0xb - || entry->function == 0x1f - || entry->function == 0x8000001e - || entry->function == 0x80000026) { + /* + * Intel + * 0xb - Extended Topology Enumeration Leaf + * 0x1f - V2 Extended Topology Enumeration Leaf + * AMD + * 0x8000_001e - Processor Topology Information + * 0x8000_0026 - Extended CPU Topology + */ + if (entry->function == 0xb || + entry->function == 0x1f || + entry->function == 0x8000001e || + entry->function == 0x80000026) { subleaf_specific = 1; always_override = 1; - } else if (entry->function == 0x00000001 - || entry->function == 0x80000000 - || entry->function == 0x80000001 - || entry->function == 0x80000008) { + /* + * Feature enumeration leaves (subleaf-specific) + * 0x04: Deterministic Cache Parameters + * 0x07: Structured Extended Feature Flags + * 0x0D: Processor Extended State Enumeration + * 0x0F: Platform QoS Monitoring + * 0x10: Platform QoS Enforcement + */ + } else if (entry->function == 0x04 || + entry->function == 0x07 || + entry->function == 0x0d || + entry->function == 0x0f || + entry->function == 0x10) { + subleaf_specific = 1; + always_override = 1; + /* Basic feature leaves (no subleaves) */ + } else if (entry->function == 0x00000001 || + entry->function == 0x80000000 || + entry->function == 0x80000001 || + entry->function == 0x80000008) { subleaf_specific = 0; always_override = 1; } - entry_ret = register_intercept_result_cpuid_entry(cpu, subleaf_specific, + /* + * CPUID[0xD,0].EBX and CPUID[0xD,1].EBX report the XSAVE area + * size based on features currently enabled in XCR0/XSS. These + * values are dynamic and must not be overridden with static + * results from the QEMU CPU model. Setting ebx_mask to 0 lets + * the hypervisor supply EBX based on the guest's actual state. + */ + if (entry->function == 0x0d && + (entry->index == 0 || entry->index == 1)) { + ebx_mask = 0; + } + + entry_ret = register_intercept_result_cpuid_entry(cpu, + subleaf_specific, always_override, + ebx_mask, entry); if ((entry_ret < 0) && (ret == 0)) { ret = entry_ret; @@ -607,7 +1186,7 @@ static int register_intercept_result_cpuid(const CPUState *cpu, return ret; } -static int set_cpuid2(const CPUState *cpu) +static int init_cpuid2(const CPUState *cpu) { int ret; size_t n_entries, cpuid_size; @@ -615,7 +1194,7 @@ static int set_cpuid2(const CPUState *cpu) struct hv_cpuid_entry *entry; GList *entries = NULL; - collect_cpuid_entries(cpu, entries); + collect_cpuid_entries(cpu, &entries); n_entries = g_list_length(entries); cpuid_size = sizeof(struct hv_cpuid) @@ -710,48 +1289,65 @@ static int set_special_regs(const CPUState *cpu) return 0; } -static int set_fpu(const CPUState *cpu, const struct MshvFPU *regs) +static int set_fpu(const CPUState *cpu) { struct hv_register_assoc assocs[ARRAY_SIZE(FPU_REGISTER_NAMES)]; union hv_register_value *value; - size_t fp_i; union hv_x64_fp_control_status_register *ctrl_status; union hv_x64_xmm_control_status_register *xmm_ctrl_status; int ret; size_t n_regs = ARRAY_SIZE(FPU_REGISTER_NAMES); + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + size_t i, fp_i; + bool valid; /* first 16 registers are xmm0-xmm15 */ - for (size_t i = 0; i < 16; i++) { + for (i = 0; i < 16; i++) { assocs[i].name = FPU_REGISTER_NAMES[i]; value = &assocs[i].value; - memcpy(&value->reg128, ®s->xmm[i], 16); + value->reg128.low_part = env->xmm_regs[i].ZMM_Q(0); + value->reg128.high_part = env->xmm_regs[i].ZMM_Q(1); } /* next 8 registers are fp_mmx0-fp_mmx7 */ - for (size_t i = 16; i < 24; i++) { - assocs[i].name = FPU_REGISTER_NAMES[i]; + for (i = 16; i < 24; i++) { fp_i = (i - 16); + assocs[i].name = FPU_REGISTER_NAMES[i]; value = &assocs[i].value; - memcpy(&value->reg128, ®s->fpr[fp_i], 16); + value->fp.mantissa = env->fpregs[fp_i].d.low; + value->fp.biased_exponent = env->fpregs[fp_i].d.high & 0x7FFF; + value->fp.sign = (env->fpregs[fp_i].d.high >> 15) & 0x1; + value->fp.reserved = 0; } /* last two registers are fp_control_status and xmm_control_status */ assocs[24].name = FPU_REGISTER_NAMES[24]; value = &assocs[24].value; ctrl_status = &value->fp_control_status; - ctrl_status->fp_control = regs->fcw; - ctrl_status->fp_status = regs->fsw; - ctrl_status->fp_tag = regs->ftwx; + + ctrl_status->fp_control = env->fpuc; + /* bits 11,12,13 are the top of stack pointer */ + ctrl_status->fp_status = (env->fpus & ~0x3800) | ((env->fpstt & 0x7) << 11); + + ctrl_status->fp_tag = 0; + for (i = 0; i < 8; i++) { + valid = (env->fptags[i] == 0); + if (valid) { + ctrl_status->fp_tag |= (1u << i); + } + } + ctrl_status->reserved = 0; - ctrl_status->last_fp_op = regs->last_opcode; - ctrl_status->last_fp_rip = regs->last_ip; + ctrl_status->last_fp_op = env->fpop; + ctrl_status->last_fp_rip = env->fpip; assocs[25].name = FPU_REGISTER_NAMES[25]; value = &assocs[25].value; xmm_ctrl_status = &value->xmm_control_status; - xmm_ctrl_status->xmm_status_control = regs->mxcsr; - xmm_ctrl_status->xmm_status_control_mask = 0; - xmm_ctrl_status->last_fp_rdp = regs->last_dp; + xmm_ctrl_status->xmm_status_control = env->mxcsr; + xmm_ctrl_status->xmm_status_control_mask = 0x0000ffff; + xmm_ctrl_status->last_fp_rdp = env->fpdp; ret = mshv_set_generic_regs(cpu, assocs, n_regs); if (ret < 0) { @@ -762,12 +1358,15 @@ static int set_fpu(const CPUState *cpu, const struct MshvFPU *regs) return 0; } -static int set_xc_reg(const CPUState *cpu, uint64_t xcr0) +static int set_xc_reg(const CPUState *cpu) { int ret; + X86CPU *x86cpu = X86_CPU(cpu); + CPUX86State *env = &x86cpu->env; + struct hv_register_assoc assoc = { .name = HV_X64_REGISTER_XFEM, - .value.reg64 = xcr0, + .value.reg64 = env->xcr0, }; ret = mshv_set_generic_regs(cpu, &assoc, 1); @@ -778,30 +1377,6 @@ static int set_xc_reg(const CPUState *cpu, uint64_t xcr0) return 0; } -static int set_cpu_state(const CPUState *cpu, const MshvFPU *fpu_regs, - uint64_t xcr0) -{ - int ret; - - ret = set_standard_regs(cpu); - if (ret < 0) { - return ret; - } - ret = set_special_regs(cpu); - if (ret < 0) { - return ret; - } - ret = set_fpu(cpu, fpu_regs); - if (ret < 0) { - return ret; - } - ret = set_xc_reg(cpu, xcr0); - if (ret < 0) { - return ret; - } - return 0; -} - static int get_vp_state(int cpu_fd, struct mshv_get_set_vp_state *state) { int ret; @@ -815,7 +1390,7 @@ static int get_vp_state(int cpu_fd, struct mshv_get_set_vp_state *state) return 0; } -static int get_lapic(int cpu_fd, +static int get_lapic(const CPUState *cpu, struct hv_local_interrupt_controller_state *state) { int ret; @@ -823,6 +1398,7 @@ static int get_lapic(int cpu_fd, /* 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; @@ -859,7 +1435,7 @@ static int set_vp_state(int cpu_fd, const struct mshv_get_set_vp_state *state) return 0; } -static int set_lapic(int cpu_fd, +static int set_lapic(const CPUState *cpu, const struct hv_local_interrupt_controller_state *state) { int ret; @@ -867,6 +1443,7 @@ static int set_lapic(int cpu_fd, /* 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"); @@ -888,13 +1465,13 @@ static int set_lapic(int cpu_fd, return 0; } -static int set_lint(int cpu_fd) +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_fd, &lapic_state); + ret = get_lapic(cpu, &lapic_state); if (ret < 0) { return ret; } @@ -907,167 +1484,46 @@ static int set_lint(int cpu_fd) /* TODO: should we skip setting lapic if the values are the same? */ - return set_lapic(cpu_fd, &lapic_state); + return set_lapic(cpu, &lapic_state); } -static int setup_msrs(const CPUState *cpu) +int mshv_arch_store_vcpu_state(const CPUState *cpu) { int ret; - uint64_t default_type = MSR_MTRR_ENABLE | MSR_MTRR_MEM_TYPE_WB; - /* boot msr entries */ - MshvMsrEntry msrs[9] = { - { .index = IA32_MSR_SYSENTER_CS, .data = 0x0, }, - { .index = IA32_MSR_SYSENTER_ESP, .data = 0x0, }, - { .index = IA32_MSR_SYSENTER_EIP, .data = 0x0, }, - { .index = IA32_MSR_STAR, .data = 0x0, }, - { .index = IA32_MSR_CSTAR, .data = 0x0, }, - { .index = IA32_MSR_LSTAR, .data = 0x0, }, - { .index = IA32_MSR_KERNEL_GS_BASE, .data = 0x0, }, - { .index = IA32_MSR_SFMASK, .data = 0x0, }, - { .index = IA32_MSR_MTRR_DEF_TYPE, .data = default_type, }, - }; - - ret = mshv_configure_msr(cpu, msrs, 9); + ret = set_standard_regs(cpu); if (ret < 0) { - error_report("failed to setup msrs"); - return -1; - } - - return 0; -} - -/* - * TODO: populate topology info: - * - * X86CPU *x86cpu = X86_CPU(cpu); - * CPUX86State *env = &x86cpu->env; - * X86CPUTopoInfo *topo_info = &env->topo_info; - */ -int mshv_configure_vcpu(const CPUState *cpu, const struct MshvFPU *fpu, - uint64_t xcr0) -{ - int ret; - int cpu_fd = mshv_vcpufd(cpu); - - ret = set_cpuid2(cpu); - if (ret < 0) { - error_report("failed to set cpuid"); - return -1; - } - - ret = setup_msrs(cpu); - if (ret < 0) { - error_report("failed to setup msrs"); - return -1; - } - - ret = set_cpu_state(cpu, fpu, xcr0); - if (ret < 0) { - error_report("failed to set cpu state"); - return -1; - } - - ret = set_lint(cpu_fd); - if (ret < 0) { - error_report("failed to set lpic int"); - return -1; - } - - return 0; -} - -static int put_regs(const CPUState *cpu) -{ - X86CPU *x86cpu = X86_CPU(cpu); - CPUX86State *env = &x86cpu->env; - MshvFPU fpu = {0}; - int ret; - - memset(&fpu, 0, sizeof(fpu)); - - ret = mshv_configure_vcpu(cpu, &fpu, env->xcr0); - if (ret < 0) { - error_report("failed to configure vcpu"); return ret; } - return 0; -} - -struct MsrPair { - uint32_t index; - uint64_t value; -}; - -static int put_msrs(const CPUState *cpu) -{ - int ret = 0; - X86CPU *x86cpu = X86_CPU(cpu); - CPUX86State *env = &x86cpu->env; - MshvMsrEntries *msrs = g_malloc0(sizeof(MshvMsrEntries)); - - struct MsrPair pairs[] = { - { MSR_IA32_SYSENTER_CS, env->sysenter_cs }, - { MSR_IA32_SYSENTER_ESP, env->sysenter_esp }, - { MSR_IA32_SYSENTER_EIP, env->sysenter_eip }, - { MSR_EFER, env->efer }, - { MSR_PAT, env->pat }, - { MSR_STAR, env->star }, - { MSR_CSTAR, env->cstar }, - { MSR_LSTAR, env->lstar }, - { MSR_KERNELGSBASE, env->kernelgsbase }, - { MSR_FMASK, env->fmask }, - { MSR_MTRRdefType, env->mtrr_deftype }, - { MSR_VM_HSAVE_PA, env->vm_hsave }, - { MSR_SMI_COUNT, env->msr_smi_count }, - { MSR_IA32_PKRS, env->pkrs }, - { MSR_IA32_BNDCFGS, env->msr_bndcfgs }, - { MSR_IA32_XSS, env->xss }, - { MSR_IA32_UMWAIT_CONTROL, env->umwait }, - { MSR_IA32_TSX_CTRL, env->tsx_ctrl }, - { MSR_AMD64_TSC_RATIO, env->amd_tsc_scale_msr }, - { MSR_TSC_AUX, env->tsc_aux }, - { MSR_TSC_ADJUST, env->tsc_adjust }, - { MSR_IA32_SMBASE, env->smbase }, - { MSR_IA32_SPEC_CTRL, env->spec_ctrl }, - { MSR_VIRT_SSBD, env->virt_ssbd }, - }; - - if (ARRAY_SIZE(pairs) > MSHV_MSR_ENTRIES_COUNT) { - error_report("MSR entries exceed maximum size"); - g_free(msrs); - return -1; - } - - for (size_t i = 0; i < ARRAY_SIZE(pairs); i++) { - MshvMsrEntry *entry = &msrs->entries[i]; - entry->index = pairs[i].index; - entry->reserved = 0; - entry->data = pairs[i].value; - msrs->nmsrs++; - } - - ret = mshv_configure_msr(cpu, &msrs->entries[0], msrs->nmsrs); - g_free(msrs); - return ret; -} - - -int mshv_arch_put_registers(const CPUState *cpu) -{ - int ret; - - ret = put_regs(cpu); + ret = set_special_regs(cpu); if (ret < 0) { - error_report("Failed to put registers"); - return -1; + return ret; } - ret = put_msrs(cpu); + ret = set_xc_reg(cpu); if (ret < 0) { - error_report("Failed to put msrs"); - return -1; + return ret; + } + + ret = set_fpu(cpu); + if (ret < 0) { + return ret; + } + + ret = mshv_set_msrs(cpu); + if (ret < 0) { + return ret; + } + + ret = set_xsave_state(cpu); + if (ret < 0) { + return ret; + } + + ret = set_vcpu_events(cpu); + if (ret < 0) { + return ret; } return 0; @@ -1079,6 +1535,12 @@ void mshv_arch_amend_proc_features( features->access_guest_idle_reg = 1; } +void mshv_arch_disable_partition_proc_features( + union hv_partition_processor_features *disabled_features) +{ + disabled_features->la57_support = 1; +} + static int set_memory_info(const struct hyperv_message *msg, struct hv_x64_memory_intercept_message *info) { @@ -1103,16 +1565,16 @@ static int emulate_instruction(CPUState *cpu, int ret; x86_insn_stream stream = { .bytes = insn_bytes, .len = insn_len }; - ret = mshv_load_regs(cpu); + ret = load_regs(cpu); if (ret < 0) { - error_report("failed to load registers"); + error_report("Failed to load registers"); return -1; } decode_instruction_stream(env, &decode, &stream); exec_instruction(env, &decode); - ret = mshv_store_regs(cpu); + ret = store_regs(cpu); if (ret < 0) { error_report("failed to store registers"); return -1; @@ -1291,25 +1753,6 @@ static int handle_pio_non_str(const CPUState *cpu, return 0; } -static int fetch_guest_state(CPUState *cpu) -{ - int ret; - - ret = mshv_get_standard_regs(cpu); - if (ret < 0) { - error_report("Failed to get standard registers"); - return -1; - } - - ret = mshv_get_special_regs(cpu); - if (ret < 0) { - error_report("Failed to get special registers"); - return -1; - } - - return 0; -} - static int read_memory(const CPUState *cpu, uint64_t initial_gva, uint64_t initial_gpa, uint64_t gva, uint8_t *data, size_t len) @@ -1429,9 +1872,9 @@ static int handle_pio_str(CPUState *cpu, hv_x64_io_port_intercept_message *info) X86CPU *x86_cpu = X86_CPU(cpu); CPUX86State *env = &x86_cpu->env; - ret = fetch_guest_state(cpu); + ret = load_regs(cpu); if (ret < 0) { - error_report("Failed to fetch guest state"); + error_report("Failed to load registers"); return -1; } @@ -1462,7 +1905,7 @@ static int handle_pio_str(CPUState *cpu, hv_x64_io_port_intercept_message *info) ret = set_x64_registers(cpu, reg_names, reg_values); if (ret < 0) { - error_report("Failed to set x64 registers"); + error_report("Failed to set RIP and RAX registers"); return -1; } @@ -1605,8 +2048,10 @@ void mshv_arch_init_vcpu(CPUState *cpu) X86CPU *x86_cpu = X86_CPU(cpu); CPUX86State *env = &x86_cpu->env; AccelCPUState *state = cpu->accel; - size_t page = HV_HYP_PAGE_SIZE; + size_t page = HV_HYP_PAGE_SIZE, xsave_len; void *mem = qemu_memalign(page, 2 * page); + int ret; + X86XSaveHeader *header; /* sanity check, to make sure we don't overflow the page */ QEMU_BUILD_BUG_ON((MAX_REGISTER_COUNT @@ -1614,11 +2059,48 @@ void mshv_arch_init_vcpu(CPUState *cpu) + sizeof(hv_input_get_vp_registers) > HV_HYP_PAGE_SIZE)); + /* mmap the registers page */ + void *rp = mmap(NULL, page, PROT_READ | PROT_WRITE, + MAP_SHARED, mshv_vcpufd(cpu), + MSHV_VP_MMAP_OFFSET_REGISTERS * page); + if (rp == MAP_FAILED) { + warn_report("register page mmap failed, falling back to hypercalls: %s", + strerror(errno)); + env->regs_page = NULL; + } else { + env->regs_page = (struct hv_vp_register_page *) rp; + } + state->hvcall_args.base = mem; state->hvcall_args.input_page = mem; state->hvcall_args.output_page = (uint8_t *)mem + page; env->emu_mmio_buf = g_new(char, 4096); + + /* Initialize XSAVE buffer page-aligned */ + /* TODO: pick proper size based on CPUID */ + xsave_len = page; + env->xsave_buf = qemu_memalign(page, xsave_len); + env->xsave_buf_len = xsave_len; + memset(env->xsave_buf, 0, env->xsave_buf_len); + + /* we need to set the compacted format bit in xsave header for mshv */ + header = (X86XSaveHeader *)(env->xsave_buf + sizeof(X86LegacyXSaveArea)); + header->xcomp_bv = header->xstate_bv | (1ULL << 63); + + /* + * TODO: populate topology info: + * X86CPUTopoInfo *topo_info = &env->topo_info; + */ + + ret = init_cpuid2(cpu); + assert(ret == 0); + + ret = mshv_init_msrs(cpu); + assert(ret == 0); + + ret = init_lint(cpu); + assert(ret == 0); } void mshv_arch_destroy_vcpu(CPUState *cpu) @@ -1627,9 +2109,55 @@ void mshv_arch_destroy_vcpu(CPUState *cpu) CPUX86State *env = &x86_cpu->env; AccelCPUState *state = cpu->accel; + /* Unmap the register page */ + if (env->regs_page) { + munmap(env->regs_page, HV_HYP_PAGE_SIZE); + env->regs_page = NULL; + } g_free(state->hvcall_args.base); state->hvcall_args = (MshvHvCallArgs){0}; g_clear_pointer(&env->emu_mmio_buf, g_free); + + qemu_vfree(env->xsave_buf); + env->xsave_buf = NULL; + env->xsave_buf_len = 0; +} + +uint32_t mshv_get_supported_cpuid(uint32_t func, uint32_t idx, int reg) +{ + uint32_t eax, ebx, ecx, edx; + uint32_t ret = 0; + + host_cpuid(func, idx, &eax, &ebx, &ecx, &edx); + switch (reg) { + case R_EAX: + ret = eax; break; + case R_EBX: + ret = ebx; break; + case R_ECX: + ret = ecx; break; + case R_EDX: + ret = edx; break; + } + + /* Disable nested virtualization features not yet supported by MSHV */ + if (func == 0x80000001 && reg == R_ECX) { + ret &= ~CPUID_EXT3_SVM; + } + if (func == 0x01 && reg == R_ECX) { + ret &= ~CPUID_EXT_VMX; + } + + if (func == 0x07 && idx == 0 && reg == R_ECX) { + /* + * LA57 (5-level paging) causes incorrect GVA=>GPA translations + * in the instruction decoder/emulator. Disable until page table + * walk in x86_mmu.c works w/ 5-level paging. + */ + ret &= ~CPUID_7_0_ECX_LA57; + } + + return ret; } /* @@ -1671,3 +2199,62 @@ int mshv_arch_post_init_vm(int vm_fd) return ret; } + +static void mshv_cpu_xsave_init(void) +{ + static bool first = true; + uint32_t eax, ebx, ecx, edx; + int i; + + if (!first) { + return; + } + first = false; + + /* x87 and SSE states are in the legacy region of the XSAVE area. */ + x86_ext_save_areas[XSTATE_FP_BIT].offset = 0; + x86_ext_save_areas[XSTATE_SSE_BIT].offset = 0; + + for (i = XSTATE_SSE_BIT + 1; i < XSAVE_STATE_AREA_COUNT; i++) { + ExtSaveArea *esa = &x86_ext_save_areas[i]; + + if (!esa->size) { + continue; + } + host_cpuid(0xd, i, &eax, &ebx, &ecx, &edx); + if (eax != 0) { + assert(esa->size == eax); + esa->offset = ebx; + esa->ecx = ecx; + } + } +} + +static void mshv_cpu_instance_init(CPUState *cs) +{ + X86CPU *cpu = X86_CPU(cs); + + host_cpu_instance_init(cpu); + mshv_cpu_xsave_init(); +} + +static void mshv_cpu_accel_class_init(ObjectClass *oc, const void *data) +{ + AccelCPUClass *acc = ACCEL_CPU_CLASS(oc); + + acc->cpu_instance_init = mshv_cpu_instance_init; +} + +static const TypeInfo mshv_cpu_accel_type_info = { + .name = ACCEL_CPU_NAME("mshv"), + .parent = TYPE_ACCEL_CPU, + .class_init = mshv_cpu_accel_class_init, + .abstract = true, +}; + +static void mshv_cpu_accel_register_types(void) +{ + type_register_static(&mshv_cpu_accel_type_info); +} + +type_init(mshv_cpu_accel_register_types); diff --git a/target/i386/mshv/msr.c b/target/i386/mshv/msr.c new file mode 100644 index 0000000000..8c220a9942 --- /dev/null +++ b/target/i386/mshv/msr.c @@ -0,0 +1,425 @@ +/* + * QEMU MSHV support + * + * Copyright Microsoft, Corp. 2025 + * + * Authors: Magnus Kulke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "system/mshv.h" +#include "system/mshv_int.h" +#include "hw/hyperv/hvgdk_mini.h" +#include "linux/mshv.h" +#include "qemu/error-report.h" +#include "cpu.h" + +#define MSHV_ENV_FIELD(env, offset) (*(uint64_t *)((char *)(env) + (offset))) + +typedef struct MshvMsrEnvMap { + uint32_t msr_index; + uint32_t hv_name; + ptrdiff_t env_offset; +} MshvMsrEnvMap; + +/* We assert that 64bit access to sysenter_cs is safe because of padding */ +QEMU_BUILD_BUG_ON(offsetof(CPUX86State, sysenter_esp) - + offsetof(CPUX86State, sysenter_cs) + < sizeof(uint64_t)); + +/* Those MSRs have a direct mapping to fields in CPUX86State */ +static const MshvMsrEnvMap msr_env_map[] = { + /* Architectural */ + { IA32_MSR_EFER, HV_X64_REGISTER_EFER, offsetof(CPUX86State, efer) }, + { IA32_MSR_PAT, HV_X64_REGISTER_PAT, offsetof(CPUX86State, pat) }, + + /* Syscall */ + { IA32_MSR_SYSENTER_CS, HV_X64_REGISTER_SYSENTER_CS, + offsetof(CPUX86State, sysenter_cs) }, + { IA32_MSR_SYSENTER_ESP, HV_X64_REGISTER_SYSENTER_ESP, + offsetof(CPUX86State, sysenter_esp) }, + { IA32_MSR_SYSENTER_EIP, HV_X64_REGISTER_SYSENTER_EIP, + offsetof(CPUX86State, sysenter_eip) }, + { IA32_MSR_STAR, HV_X64_REGISTER_STAR, + offsetof(CPUX86State, star) }, + { IA32_MSR_LSTAR, HV_X64_REGISTER_LSTAR, + offsetof(CPUX86State, lstar) }, + { IA32_MSR_CSTAR, HV_X64_REGISTER_CSTAR, + offsetof(CPUX86State, cstar) }, + { IA32_MSR_SFMASK, HV_X64_REGISTER_SFMASK, + offsetof(CPUX86State, fmask) }, + { IA32_MSR_KERNEL_GS_BASE, HV_X64_REGISTER_KERNEL_GS_BASE, + offsetof(CPUX86State, kernelgsbase) }, + + /* TSC-related */ + { IA32_MSR_TSC, HV_X64_REGISTER_TSC, + offsetof(CPUX86State, tsc) }, + { IA32_MSR_TSC_AUX, HV_X64_REGISTER_TSC_AUX, + offsetof(CPUX86State, tsc_aux) }, + { IA32_MSR_TSC_ADJUST, HV_X64_REGISTER_TSC_ADJUST, + offsetof(CPUX86State, tsc_adjust) }, + + /* Hyper-V per-partition MSRs */ + { HV_X64_MSR_HYPERCALL, HV_X64_REGISTER_HYPERCALL, + offsetof(CPUX86State, msr_hv_hypercall) }, + { HV_X64_MSR_GUEST_OS_ID, HV_REGISTER_GUEST_OS_ID, + offsetof(CPUX86State, msr_hv_guest_os_id) }, + { HV_X64_MSR_REFERENCE_TSC, HV_REGISTER_REFERENCE_TSC, + offsetof(CPUX86State, msr_hv_tsc) }, + + /* Hyper-V MSRs (non-SINT) */ + { HV_X64_MSR_SCONTROL, HV_REGISTER_SCONTROL, + offsetof(CPUX86State, msr_hv_synic_control) }, + { HV_X64_MSR_SIEFP, HV_REGISTER_SIEFP, + offsetof(CPUX86State, msr_hv_synic_evt_page) }, + { HV_X64_MSR_SIMP, HV_REGISTER_SIMP, + offsetof(CPUX86State, msr_hv_synic_msg_page) }, + + /* MTRR default type */ + { IA32_MSR_MTRR_DEF_TYPE, HV_X64_REGISTER_MSR_MTRR_DEF_TYPE, + offsetof(CPUX86State, mtrr_deftype) }, + + /* CET / Shadow Stack */ + { MSR_IA32_U_CET, HV_X64_REGISTER_U_CET, + offsetof(CPUX86State, u_cet) }, + { MSR_IA32_S_CET, HV_X64_REGISTER_S_CET, + offsetof(CPUX86State, s_cet) }, + { MSR_IA32_PL0_SSP, HV_X64_REGISTER_PL0_SSP, + offsetof(CPUX86State, pl0_ssp) }, + { MSR_IA32_PL1_SSP, HV_X64_REGISTER_PL1_SSP, + offsetof(CPUX86State, pl1_ssp) }, + { MSR_IA32_PL2_SSP, HV_X64_REGISTER_PL2_SSP, + offsetof(CPUX86State, pl2_ssp) }, + { MSR_IA32_PL3_SSP, HV_X64_REGISTER_PL3_SSP, + offsetof(CPUX86State, pl3_ssp) }, + { MSR_IA32_INT_SSP_TAB, HV_X64_REGISTER_INTERRUPT_SSP_TABLE_ADDR, + offsetof(CPUX86State, int_ssp_table) }, + + /* XSAVE Supervisor State */ + { MSR_IA32_XSS, HV_X64_REGISTER_U_XSS, + offsetof(CPUX86State, xss) }, + + /* Other */ + + /* TODO: find out processor features that correlate to unsupported MSRs. */ + /* { IA32_MSR_MISC_ENABLE, HV_X64_REGISTER_MSR_IA32_MISC_ENABLE, */ + /* offsetof(CPUX86State, msr_ia32_misc_enable) }, */ + /* { IA32_MSR_BNDCFGS, HV_X64_REGISTER_BNDCFGS, */ + /* offsetof(CPUX86State, msr_bndcfgs) }, */ + { IA32_MSR_SPEC_CTRL, HV_X64_REGISTER_SPEC_CTRL, + offsetof(CPUX86State, spec_ctrl) }, +}; + +/* + * The assocs have to be set according to this schema: + * 8 entries for 0-7 mtrr_base + * 8 entries for mtrr_mask 0-7 + * 11 entries for 1 x 64k, 2 x 16k, 8 x 4k fixed MTRR + * 27 total entries + */ + +#define MSHV_MTRR_MSR_COUNT 27 +#define MSHV_MSR_TOTAL_COUNT (ARRAY_SIZE(msr_env_map) + MSHV_MTRR_MSR_COUNT) + +static void store_in_env_mtrr_phys(CPUState *cpu, + const struct hv_register_assoc *assocs, + size_t n_assocs) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + size_t i, fixed_offset; + hv_register_name hv_name; + uint64_t base, mask; + + assert(n_assocs == MSHV_MTRR_MSR_COUNT); + + for (i = 0; i < MSR_MTRRcap_VCNT; i++) { + hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 + i; + assert(assocs[i].name == hv_name); + hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 + i; + assert(assocs[i + MSR_MTRRcap_VCNT].name == hv_name); + + base = assocs[i].value.reg64; + mask = assocs[i + MSR_MTRRcap_VCNT].value.reg64; + env->mtrr_var[i].base = base; + env->mtrr_var[i].mask = mask; + } + + /* fixed 1x 64, 2x 16, 8x 4 kB */ + fixed_offset = MSR_MTRRcap_VCNT * 2; + for (i = 0; i < 11; i++) { + hv_name = HV_X64_REGISTER_MSR_MTRR_FIX64K00000 + i; + assert(assocs[fixed_offset + i].name == hv_name); + env->mtrr_fixed[i] = assocs[fixed_offset + i].value.reg64; + } +} + +/* + * The assocs have to be set according to this schema: + * 8 entries for 0-7 mtrr_base + * 8 entries for mtrr_mask 0-7 + * 11 entries for 1 x 64k, 2 x 16k, 8 x 4k fixed MTRR + * 27 total entries + */ +static void load_from_env_mtrr_phys(const CPUState *cpu, + struct hv_register_assoc *assocs, + size_t n_assocs) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + size_t i, fixed_offset; + uint64_t base, mask, fixed_value; + hv_register_name base_name, mask_name, fixed_name; + hv_register_assoc *assoc; + + assert(n_assocs == MSHV_MTRR_MSR_COUNT); + + for (i = 0; i < MSR_MTRRcap_VCNT; i++) { + base = env->mtrr_var[i].base; + mask = env->mtrr_var[i].mask; + + base_name = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 + i; + mask_name = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 + i; + + assoc = &assocs[i]; + assoc->name = base_name; + assoc->value.reg64 = base; + + assoc = &assocs[i + MSR_MTRRcap_VCNT]; + assoc->name = mask_name; + assoc->value.reg64 = mask; + } + + /* fixed 1x 64, 2x 16, 8x 4 kB */ + fixed_offset = MSR_MTRRcap_VCNT * 2; + for (i = 0; i < 11; i++) { + fixed_name = HV_X64_REGISTER_MSR_MTRR_FIX64K00000 + i; + fixed_value = env->mtrr_fixed[i]; + + assoc = &assocs[fixed_offset + i]; + assoc->name = fixed_name; + assoc->value.reg64 = fixed_value; + } +} + +int mshv_init_msrs(const CPUState *cpu) +{ + int ret; + uint64_t d_t = MSR_MTRR_ENABLE | MSR_MTRR_MEM_TYPE_WB; + + const struct hv_register_assoc assocs[] = { + { .name = HV_X64_REGISTER_SYSENTER_CS, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_SYSENTER_ESP, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_SYSENTER_EIP, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_STAR, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_CSTAR, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_LSTAR, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_KERNEL_GS_BASE, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_SFMASK, .value.reg64 = 0x0 }, + { .name = HV_X64_REGISTER_MSR_MTRR_DEF_TYPE, .value.reg64 = d_t }, + }; + + ret = mshv_set_generic_regs(cpu, assocs, ARRAY_SIZE(assocs)); + if (ret < 0) { + error_report("failed to put msrs"); + return -1; + } + + return 0; +} + + +/* + * INVARIANT: this fn expects assocs in the same order as they appear in + * msr_env_map. + */ +static void store_in_env(CPUState *cpu, const struct hv_register_assoc *assocs, + size_t n_assocs) +{ + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + size_t i, j; + const MshvMsrEnvMap *mapping; + union hv_register_value hv_value; + ptrdiff_t offset; + uint32_t hv_name; + size_t mtrr_index; + + assert(n_assocs <= MSHV_MSR_TOTAL_COUNT); + + for (i = 0, j = 0; i < ARRAY_SIZE(msr_env_map); i++) { + hv_name = assocs[j].name; + mapping = &msr_env_map[i]; + if (hv_name != mapping->hv_name) { + continue; + } + + hv_value = assocs[j].value; + offset = mapping->env_offset; + MSHV_ENV_FIELD(env, offset) = hv_value.reg64; + j++; + } + + mtrr_index = j; + store_in_env_mtrr_phys(cpu, &assocs[mtrr_index], MSHV_MTRR_MSR_COUNT); +} + +static void set_hv_name_in_assocs(struct hv_register_assoc *assocs, + size_t n_assocs) +{ + size_t i; + size_t mtrr_offset, mtrr_fixed_offset; + hv_register_name hv_name; + + assert(n_assocs == MSHV_MSR_TOTAL_COUNT); + + for (i = 0; i < ARRAY_SIZE(msr_env_map); i++) { + assocs[i].name = msr_env_map[i].hv_name; + } + + mtrr_offset = ARRAY_SIZE(msr_env_map); + for (i = 0; i < MSR_MTRRcap_VCNT; i++) { + hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 + i; + assocs[mtrr_offset + i].name = hv_name; + hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 + i; + assocs[mtrr_offset + MSR_MTRRcap_VCNT + i].name = hv_name; + } + + /* fixed 1x 64, 2x 16, 8x 4 kB */ + mtrr_fixed_offset = mtrr_offset + MSR_MTRRcap_VCNT * 2; + for (i = 0; i < 11; i++) { + hv_name = HV_X64_REGISTER_MSR_MTRR_FIX64K00000 + i; + assocs[mtrr_fixed_offset + i].name = hv_name; + } +} + +static bool msr_supported(uint32_t name) +{ + /* + * This check is not done comprehensively, it's meant to avoid hvcall + * failures for certain MSRs on architectures that don't support them. + */ + + switch (name) { + case HV_X64_REGISTER_SPEC_CTRL: + return mshv_state->processor_features.ibrs_support; + case HV_X64_REGISTER_TSC_ADJUST: + return mshv_state->processor_features.tsc_adjust_support; + case HV_X64_REGISTER_U_CET: + case HV_X64_REGISTER_S_CET: + case HV_X64_REGISTER_PL0_SSP: + case HV_X64_REGISTER_PL1_SSP: + case HV_X64_REGISTER_PL2_SSP: + case HV_X64_REGISTER_PL3_SSP: + case HV_X64_REGISTER_INTERRUPT_SSP_TABLE_ADDR: + case HV_X64_REGISTER_U_XSS: + return mshv_state->processor_features.cet_ss_support || + mshv_state->processor_features.cet_ibt_support; + } + + return true; +} + +int mshv_get_msrs(CPUState *cpu) +{ + int ret = 0; + size_t n_assocs = MSHV_MSR_TOTAL_COUNT; + struct hv_register_assoc assocs[MSHV_MSR_TOTAL_COUNT]; + size_t i, j; + uint32_t name; + + set_hv_name_in_assocs(assocs, n_assocs); + + /* Filter out MSRs that cannot be read */ + for (i = 0, j = 0; i < n_assocs; i++) { + name = assocs[i].name; + + if (!msr_supported(name)) { + continue; + } + + if (j != i) { + assocs[j] = assocs[i]; + } + j++; + } + n_assocs = j; + + ret = mshv_get_generic_regs(cpu, assocs, n_assocs); + if (ret < 0) { + error_report("Failed to get MSRs"); + return -errno; + } + + store_in_env(cpu, assocs, n_assocs); + + return 0; +} + +static void load_from_env(const CPUState *cpu, struct hv_register_assoc *assocs, + size_t n_assocs) +{ + size_t i; + const MshvMsrEnvMap *mapping; + X86CPU *x86_cpu = X86_CPU(cpu); + CPUX86State *env = &x86_cpu->env; + ptrdiff_t offset; + union hv_register_value *hv_value; + size_t mtrr_offset; + + assert(n_assocs == MSHV_MSR_TOTAL_COUNT); + + for (i = 0; i < ARRAY_SIZE(msr_env_map); i++) { + mapping = &msr_env_map[i]; + offset = mapping->env_offset; + assocs[i].name = mapping->hv_name; + hv_value = &assocs[i].value; + hv_value->reg64 = MSHV_ENV_FIELD(env, offset); + } + + mtrr_offset = ARRAY_SIZE(msr_env_map); + load_from_env_mtrr_phys(cpu, &assocs[mtrr_offset], MSHV_MTRR_MSR_COUNT); +} + +int mshv_set_msrs(const CPUState *cpu) +{ + size_t n_assocs = MSHV_MSR_TOTAL_COUNT; + struct hv_register_assoc assocs[MSHV_MSR_TOTAL_COUNT]; + int ret; + size_t i, j; + + load_from_env(cpu, assocs, n_assocs); + + /* Filter out MSRs that cannot be written */ + for (i = 0, j = 0; i < n_assocs; i++) { + uint32_t name = assocs[i].name; + + /* Partition-wide MSRs: only write on vCPU 0 */ + if (cpu->cpu_index != 0 && + (name == HV_X64_REGISTER_HYPERCALL || + name == HV_REGISTER_GUEST_OS_ID || + name == HV_REGISTER_REFERENCE_TSC)) { + continue; + } + + if (!msr_supported(name)) { + continue; + } + + if (j != i) { + assocs[j] = assocs[i]; + } + j++; + } + n_assocs = j; + + ret = mshv_set_generic_regs(cpu, assocs, n_assocs); + if (ret < 0) { + error_report("Failed to set MSRs"); + return -errno; + } + + return 0; +} diff --git a/target/i386/sev.c b/target/i386/sev.c index 99cf30806b..debe6344ab 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -160,10 +160,6 @@ struct SevCommonStateClass { * * The SevGuestState object is used for creating and managing a SEV * guest. - * - * # $QEMU \ - * -object sev-guest,id=sev0 \ - * -machine ...,memory-encryption=sev0 */ struct SevGuestState { SevCommonState parent_obj;