mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:46:10 +00:00
We reserve 24 ioapic routes using the new functions that operate on the mshv apic state. commit/add_msi_routing() fn's can be removed now. Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com> Link: https://lore.kernel.org/r/20260417105618.3621-13-magnuskulke@linux.microsoft.com Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
378 lines
9.6 KiB
C
378 lines
9.6 KiB
C
/*
|
|
* QEMU MSHV support
|
|
*
|
|
* Copyright Microsoft, Corp. 2025
|
|
*
|
|
* Authors: Ziqiao Zhou <ziqiaozhou@microsoft.com>
|
|
* Magnus Kulke <magnuskulke@microsoft.com>
|
|
* Stanislav Kinsburskii <skinsburskii@microsoft.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "linux/mshv.h"
|
|
#include "qemu/error-report.h"
|
|
#include "hw/hyperv/hvhdk_mini.h"
|
|
#include "hw/hyperv/hvgdk_mini.h"
|
|
#include "hw/intc/ioapic.h"
|
|
#include "hw/pci/msi.h"
|
|
#include "system/mshv.h"
|
|
#include "system/mshv_int.h"
|
|
#include "trace.h"
|
|
#include <sys/ioctl.h>
|
|
|
|
#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;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
int ret;
|
|
struct mshv_user_irqfd arg = {
|
|
.fd = fd,
|
|
.resamplefd = resample_fd,
|
|
.gsi = gsi,
|
|
.flags = flags,
|
|
};
|
|
|
|
ret = ioctl(vm_fd, MSHV_IRQFD, &arg);
|
|
if (ret < 0) {
|
|
error_report("Failed to set irqfd: gsi=%u, fd=%d", gsi, fd);
|
|
return -1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int register_irqfd(int vm_fd, int event_fd, uint32_t gsi)
|
|
{
|
|
int ret;
|
|
|
|
trace_mshv_register_irqfd(vm_fd, event_fd, gsi);
|
|
|
|
ret = irqfd(vm_fd, event_fd, 0, gsi, 0);
|
|
if (ret < 0) {
|
|
error_report("Failed to register irqfd: gsi=%u", gsi);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int register_irqfd_with_resample(int vm_fd, int event_fd,
|
|
int resample_fd, uint32_t gsi)
|
|
{
|
|
int ret;
|
|
uint32_t flags = MSHV_IRQFD_RESAMPLE_FLAG;
|
|
|
|
ret = irqfd(vm_fd, event_fd, resample_fd, gsi, flags);
|
|
if (ret < 0) {
|
|
error_report("Failed to register irqfd with resample: gsi=%u", gsi);
|
|
return -errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int unregister_irqfd(int vm_fd, int event_fd, uint32_t gsi)
|
|
{
|
|
int ret;
|
|
uint32_t flags = MSHV_IRQFD_BIT_DEASSIGN_FLAG;
|
|
|
|
ret = irqfd(vm_fd, event_fd, 0, gsi, flags);
|
|
if (ret < 0) {
|
|
error_report("Failed to unregister irqfd: gsi=%u", gsi);
|
|
return -errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int irqchip_update_irqfd_notifier_gsi(const EventNotifier *event,
|
|
const EventNotifier *resample,
|
|
int virq, bool add)
|
|
{
|
|
int fd = event_notifier_get_fd(event);
|
|
int rfd = resample ? event_notifier_get_fd(resample) : -1;
|
|
int vm_fd = mshv_state->vm;
|
|
|
|
trace_mshv_irqchip_update_irqfd_notifier_gsi(fd, rfd, virq, add);
|
|
|
|
if (!add) {
|
|
return unregister_irqfd(vm_fd, fd, virq);
|
|
}
|
|
|
|
if (rfd > 0) {
|
|
return register_irqfd_with_resample(vm_fd, fd, rfd, virq);
|
|
}
|
|
|
|
return register_irqfd(vm_fd, fd, virq);
|
|
}
|
|
|
|
static int irqchip_allocate_gsi(MshvState *s, int *gsi)
|
|
{
|
|
int next_gsi;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
*gsi = next_gsi;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void irqchip_release_gsi(MshvState *s, int gsi)
|
|
{
|
|
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 = update_routing_entry(mshv_state, &entry);
|
|
if (ret < 0) {
|
|
error_report("Failed to set msi routing for gsi %d", virq);
|
|
abort();
|
|
}
|
|
|
|
trace_mshv_set_msi_routing(virq, msg.address, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, uint32_t vector,
|
|
uint32_t vp_index, bool logical_dest_mode,
|
|
bool level_triggered)
|
|
{
|
|
int ret;
|
|
int vm_fd = mshv_state->vm;
|
|
|
|
if (vector == 0) {
|
|
warn_report("Ignoring request for interrupt vector 0");
|
|
return 0;
|
|
}
|
|
|
|
union hv_interrupt_control control = {
|
|
.interrupt_type = interrupt_type,
|
|
.level_triggered = level_triggered,
|
|
.logical_dest_mode = logical_dest_mode,
|
|
.rsvd = 0,
|
|
};
|
|
|
|
struct hv_input_assert_virtual_interrupt arg = {0};
|
|
arg.control = control;
|
|
arg.dest_addr = (uint64_t)vp_index;
|
|
arg.vector = vector;
|
|
|
|
struct mshv_root_hvcall args = {0};
|
|
args.code = HVCALL_ASSERT_VIRTUAL_INTERRUPT;
|
|
args.in_sz = sizeof(arg);
|
|
args.in_ptr = (uint64_t)&arg;
|
|
|
|
ret = mshv_hvcall(vm_fd, &args);
|
|
if (ret < 0) {
|
|
error_report("Failed to request interrupt");
|
|
return -errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void mshv_irqchip_commit_routes(MshvState *s)
|
|
{
|
|
int ret;
|
|
int vm_fd = s->vm;
|
|
|
|
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,
|
|
const EventNotifier *resample,
|
|
int virq)
|
|
{
|
|
return irqchip_update_irqfd_notifier_gsi(event, resample, virq, true);
|
|
}
|
|
|
|
int mshv_irqchip_remove_irqfd_notifier_gsi(const EventNotifier *event,
|
|
int virq)
|
|
{
|
|
return irqchip_update_irqfd_notifier_gsi(event, NULL, virq, false);
|
|
}
|
|
|
|
static int mshv_reserve_ioapic_msi_routes(MshvState *s)
|
|
{
|
|
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 (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: %s", gsi, strerror(-ret));
|
|
return -1;
|
|
}
|
|
blank_entry.gsi = gsi;
|
|
add_routing_entry(s, &blank_entry);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|