[PATCH 16/17] mshv: User space controlled MSI irq routing for mshv

From: Vineeth Pillai
Date: Wed Jun 02 2021 - 13:21:57 EST


Implementation of an in-kernel MSI irq routing mechanism for mshv.

Inspired from the KVM irq routing implementation but adapted
only for MSI interrupts.
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=399ec807ddc38ecccf8c06dbde04531cbdc63e11

All credit goes to kvm developers.

Signed-off-by: Vineeth Pillai <viremana@xxxxxxxxxxxxxxxxxxx>
---
drivers/hv/Makefile | 2 +-
drivers/hv/mshv_main.c | 34 ++++++++++
drivers/hv/mshv_msi.c | 127 ++++++++++++++++++++++++++++++++++++++
include/linux/mshv.h | 27 ++++++++
include/uapi/linux/mshv.h | 13 ++++
5 files changed, 202 insertions(+), 1 deletion(-)
create mode 100644 drivers/hv/mshv_msi.c

diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 5cb738c10a2d..370d126252ef 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -14,4 +14,4 @@ hv_vmbus-$(CONFIG_HYPERV_TESTING) += hv_debugfs.o
hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_fcopy.o hv_utils_transport.o

mshv-y += mshv_main.o hv_call.o hv_synic.o hv_portid_table.o \
- hv_eventfd.o
+ hv_eventfd.o mshv_msi.o
diff --git a/drivers/hv/mshv_main.c b/drivers/hv/mshv_main.c
index 0f083447c553..f7ca0f082b75 100644
--- a/drivers/hv/mshv_main.c
+++ b/drivers/hv/mshv_main.c
@@ -852,6 +852,35 @@ mshv_partition_ioctl_irqfd(struct mshv_partition *partition,
return mshv_irqfd(partition, &args);
}

+static long
+mshv_partition_ioctl_set_msi_routing(struct mshv_partition *partition,
+ void __user *user_args)
+{
+ struct mshv_msi_routing_entry *entries = NULL;
+ struct mshv_msi_routing args;
+ long ret;
+
+ if (copy_from_user(&args, user_args, sizeof(args)))
+ return -EFAULT;
+
+ if (args.nr > MSHV_MAX_MSI_ROUTES)
+ return -EINVAL;
+
+ if (args.nr) {
+ struct mshv_msi_routing __user *urouting = user_args;
+
+ entries = vmemdup_user(urouting->entries,
+ array_size(sizeof(*entries),
+ args.nr));
+ if (IS_ERR(entries))
+ return PTR_ERR(entries);
+ }
+ ret = mshv_set_msi_routing(partition, entries, args.nr);
+ kvfree(entries);
+
+ return ret;
+}
+
static long
mshv_partition_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
@@ -898,6 +927,10 @@ mshv_partition_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
ret = mshv_partition_ioctl_ioeventfd(partition,
(void __user *)arg);
break;
+ case MSHV_SET_MSI_ROUTING:
+ ret = mshv_partition_ioctl_set_msi_routing(partition,
+ (void __user *)arg);
+ break;
default:
ret = -ENOTTY;
}
@@ -965,6 +998,7 @@ destroy_partition(struct mshv_partition *partition)
vfree(region->pages);
}

+ mshv_free_msi_routing(partition);
kfree(partition);
}

diff --git a/drivers/hv/mshv_msi.c b/drivers/hv/mshv_msi.c
new file mode 100644
index 000000000000..ae25ed8dfef4
--- /dev/null
+++ b/drivers/hv/mshv_msi.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020, Microsoft Corporation.
+ *
+ * Authors:
+ * Vineeth Remanan Pillai <viremana@xxxxxxxxxxxxxxxxxxx>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/mshv.h>
+#include <linux/mshv_eventfd.h>
+#include <linux/hyperv.h>
+#include <asm/mshyperv.h>
+
+#include "mshv.h"
+
+MODULE_AUTHOR("Microsoft");
+MODULE_LICENSE("GPL");
+
+int mshv_set_msi_routing(struct mshv_partition *partition,
+ const struct mshv_msi_routing_entry *ue,
+ unsigned int nr)
+{
+ struct mshv_msi_routing_table *new = NULL, *old;
+ u32 i, nr_rt_entries = 0;
+ int r = 0;
+
+ if (nr == 0)
+ goto swap_routes;
+
+ for (i = 0; i < nr; i++) {
+ if (ue[i].gsi >= MSHV_MAX_MSI_ROUTES)
+ return -EINVAL;
+
+ if (ue[i].address_hi)
+ return -EINVAL;
+
+ nr_rt_entries = max(nr_rt_entries, ue[i].gsi);
+ }
+ nr_rt_entries += 1;
+
+ new = kzalloc(struct_size(new, entries, nr_rt_entries),
+ GFP_KERNEL_ACCOUNT);
+ if (!new)
+ return -ENOMEM;
+
+ new->nr_rt_entries = nr_rt_entries;
+ for (i = 0; i < nr; i++) {
+ struct mshv_kernel_msi_routing_entry *e;
+
+ e = &new->entries[ue[i].gsi];
+
+ /*
+ * Allow only one to one mapping between GSI and MSI routing.
+ */
+ if (e->gsi != 0) {
+ r = -EINVAL;
+ goto out;
+ }
+
+ e->gsi = ue[i].gsi;
+ e->address_lo = ue[i].address_lo;
+ e->address_hi = ue[i].address_hi;
+ e->data = ue[i].data;
+ e->entry_valid = true;
+ }
+
+swap_routes:
+ spin_lock(&partition->irq_lock);
+ old = rcu_dereference_protected(partition->msi_routing, 1);
+ rcu_assign_pointer(partition->msi_routing, new);
+ spin_unlock(&partition->irq_lock);
+
+ synchronize_srcu_expedited(&partition->irq_srcu);
+ new = old;
+
+out:
+ kfree(new);
+
+ return r;
+}
+
+void mshv_free_msi_routing(struct mshv_partition *partition)
+{
+ /*
+ * Called only during vm destruction.
+ * Nobody can use the pointer at this stage
+ */
+ struct mshv_msi_routing_table *rt = rcu_access_pointer(partition->msi_routing);
+
+ kfree(rt);
+}
+
+struct mshv_kernel_msi_routing_entry
+mshv_msi_map_gsi(struct mshv_partition *partition, u32 gsi)
+{
+ struct mshv_kernel_msi_routing_entry entry = { 0 };
+ struct mshv_msi_routing_table *msi_rt;
+
+ msi_rt = srcu_dereference_check(partition->msi_routing,
+ &partition->irq_srcu,
+ lockdep_is_held(&partition->irq_lock));
+ if (!msi_rt) {
+ pr_warn("No valid routing information found for gsi: %u\n",
+ gsi);
+ entry.gsi = gsi;
+ return entry;
+ }
+
+ return msi_rt->entries[gsi];
+}
+
+void mshv_set_msi_irq(struct mshv_kernel_msi_routing_entry *e,
+ struct mshv_lapic_irq *irq)
+{
+ memset(irq, 0, sizeof(*irq));
+ if (!e || !e->entry_valid)
+ return;
+
+ irq->vector = e->data & 0xFF;
+ irq->apic_id = (e->address_lo >> 12) & 0xFF;
+ irq->control.interrupt_type = (e->data & 0x700) >> 8;
+ irq->control.level_triggered = (e->data >> 15) & 0x1;
+ irq->control.logical_dest_mode = (e->address_lo >> 2) & 0x1;
+}
diff --git a/include/linux/mshv.h b/include/linux/mshv.h
index 5968b49b9c27..ec349be0ba91 100644
--- a/include/linux/mshv.h
+++ b/include/linux/mshv.h
@@ -69,6 +69,7 @@ struct mshv_partition {
spinlock_t lock;
struct list_head items;
} ioeventfds;
+ struct mshv_msi_routing_table __rcu *msi_routing;
};

struct mshv_lapic_irq {
@@ -77,6 +78,32 @@ struct mshv_lapic_irq {
union hv_interrupt_control control;
};

+#define MSHV_MAX_MSI_ROUTES 4096
+
+struct mshv_kernel_msi_routing_entry {
+ u32 entry_valid;
+ u32 gsi;
+ u32 address_lo;
+ u32 address_hi;
+ u32 data;
+};
+
+struct mshv_msi_routing_table {
+ u32 nr_rt_entries;
+ struct mshv_kernel_msi_routing_entry entries[];
+};
+
+int mshv_set_msi_routing(struct mshv_partition *partition,
+ const struct mshv_msi_routing_entry *entries,
+ unsigned int nr);
+void mshv_free_msi_routing(struct mshv_partition *partition);
+
+struct mshv_kernel_msi_routing_entry mshv_msi_map_gsi(
+ struct mshv_partition *partition, u32 gsi);
+
+void mshv_set_msi_irq(struct mshv_kernel_msi_routing_entry *e,
+ struct mshv_lapic_irq *irq);
+
struct hv_synic_pages {
struct hv_message_page *synic_message_page;
struct hv_synic_event_flags_page *synic_event_flags_page;
diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
index 008e68bde56d..ac58f2ded79c 100644
--- a/include/uapi/linux/mshv.h
+++ b/include/uapi/linux/mshv.h
@@ -117,6 +117,18 @@ struct mshv_ioeventfd {
__u8 pad[4];
};

+struct mshv_msi_routing_entry {
+ __u32 gsi;
+ __u32 address_lo;
+ __u32 address_hi;
+ __u32 data;
+};
+
+struct mshv_msi_routing {
+ __u32 nr;
+ __u32 pad;
+ struct mshv_msi_routing_entry entries[0];
+};

#define MSHV_IOCTL 0xB8

@@ -136,6 +148,7 @@ struct mshv_ioeventfd {
_IOWR(MSHV_IOCTL, 0xD, struct mshv_partition_property)
#define MSHV_IRQFD _IOW(MSHV_IOCTL, 0xE, struct mshv_irqfd)
#define MSHV_IOEVENTFD _IOW(MSHV_IOCTL, 0xF, struct mshv_ioeventfd)
+#define MSHV_SET_MSI_ROUTING _IOW(MSHV_IOCTL, 0x11, struct mshv_msi_routing)

/* vp device */
#define MSHV_GET_VP_REGISTERS _IOWR(MSHV_IOCTL, 0x05, struct mshv_vp_registers)
--
2.25.1