[RFC 07/12] Drivers: hv: vmbus: Set up irqdomain and irqchip for the VMBus connection

From: mhkelley58
Date: Tue Jun 04 2024 - 01:12:58 EST


From: Michael Kelley <mhklinux@xxxxxxxxxxx>

In preparation for assigning Linux IRQs to VMBus channels, set up an
irqdomain and irqchip for the VMBus connection. The irqdomain is linear,
with the VMBus relid used as the "hwirq" value. A relid is a unique
index assigned by Hyper-V to each VMBus channel, with values ranging
from 1 to 2047. Because these hwirqs don't map to anything in the
architectural hardware, the domain is not part of the domain hierarchy.

VMBus channel interrupts provide minimal management functionality, so
provide only a minimal set of irqchip functions. The set_affinity function
is a place-holder that is populated in a subsequent patch.

Signed-off-by: Michael Kelley <mhklinux@xxxxxxxxxxx>
---
drivers/hv/connection.c | 24 +++++++++-----
drivers/hv/hyperv_vmbus.h | 9 +++++
drivers/hv/vmbus_drv.c | 60 +++++++++++++++++++++++++++++++++-
include/asm-generic/mshyperv.h | 6 ++++
4 files changed, 90 insertions(+), 9 deletions(-)

diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c
index f001ae880e1d..cb01784e5c3b 100644
--- a/drivers/hv/connection.c
+++ b/drivers/hv/connection.c
@@ -21,21 +21,29 @@
#include <linux/export.h>
#include <linux/io.h>
#include <linux/set_memory.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/irqdomain.h>
#include <asm/mshyperv.h>

#include "hyperv_vmbus.h"


struct vmbus_connection vmbus_connection = {
- .conn_state = DISCONNECTED,
- .unload_event = COMPLETION_INITIALIZER(
- vmbus_connection.unload_event),
- .next_gpadl_handle = ATOMIC_INIT(0xE1E10),
-
- .ready_for_suspend_event = COMPLETION_INITIALIZER(
- vmbus_connection.ready_for_suspend_event),
+ .conn_state = DISCONNECTED,
+ .unload_event = COMPLETION_INITIALIZER(
+ vmbus_connection.unload_event),
+ .next_gpadl_handle = ATOMIC_INIT(0xE1E10),
+
+ .vmbus_irq_chip.name = "VMBus",
+ .vmbus_irq_chip.irq_set_affinity = vmbus_irq_set_affinity,
+ .vmbus_irq_chip.irq_mask = vmbus_irq_mask,
+ .vmbus_irq_chip.irq_unmask = vmbus_irq_unmask,
+
+ .ready_for_suspend_event = COMPLETION_INITIALIZER(
+ vmbus_connection.ready_for_suspend_event),
.ready_for_resume_event = COMPLETION_INITIALIZER(
- vmbus_connection.ready_for_resume_event),
+ vmbus_connection.ready_for_resume_event),
};
EXPORT_SYMBOL_GPL(vmbus_connection);

diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
index 76ac5185a01a..95d4d47d34f7 100644
--- a/drivers/hv/hyperv_vmbus.h
+++ b/drivers/hv/hyperv_vmbus.h
@@ -18,7 +18,11 @@
#include <asm/hyperv-tlfs.h>
#include <linux/atomic.h>
#include <linux/hyperv.h>
+#include <linux/fwnode.h>
#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/irqdomain.h>

#include "hv_trace.h"

@@ -258,6 +262,11 @@ struct vmbus_connection {
/* Array of channels */
struct vmbus_channel **channels;

+ /* IRQ domain data */
+ struct fwnode_handle *vmbus_fwnode;
+ struct irq_domain *vmbus_irq_domain;
+ struct irq_chip vmbus_irq_chip;
+
/*
* An offer message is handled first on the work_queue, and then
* is further handled on handle_primary_chan_wq or
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index 291a8358370b..cbccdfad49a2 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c
@@ -36,6 +36,9 @@
#include <linux/syscore_ops.h>
#include <linux/dma-map-ops.h>
#include <linux/pci.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/hardirq.h>
#include <clocksource/hyperv_timer.h>
#include <asm/mshyperv.h>
#include "hyperv_vmbus.h"
@@ -1306,6 +1309,40 @@ static irqreturn_t vmbus_percpu_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}

+int vmbus_irq_set_affinity(struct irq_data *data,
+ const struct cpumask *dest, bool force)
+{
+ return 0;
+}
+
+/*
+ * VMBus channel interrupts do not need to be masked or unmasked, and the
+ * Hyper-V synic doesn't provide any masking functionality anyway. But in the
+ * absence of these irqchip functions, the IRQ subsystem keeps the IRQ marked
+ * as "masked". To prevent any problems associated with staying the "masked"
+ * state, and so that IRQ status shown in debugfs doesn't indicate "masked",
+ * provide null implementations.
+ */
+void vmbus_irq_unmask(struct irq_data *data)
+{
+}
+
+void vmbus_irq_mask(struct irq_data *data)
+{
+}
+
+static int vmbus_irq_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hw)
+{
+ irq_set_chip_and_handler(irq,
+ &vmbus_connection.vmbus_irq_chip, handle_simple_irq);
+ return 0;
+}
+
+static const struct irq_domain_ops vmbus_domain_ops = {
+ .map = vmbus_irq_map,
+};
+
/*
* vmbus_bus_init -Main vmbus driver initialization routine.
*
@@ -1340,6 +1377,7 @@ static int vmbus_bus_init(void)
if (vmbus_irq == -1) {
hv_setup_vmbus_handler(vmbus_isr);
} else {
+ irq_set_handler(vmbus_irq, handle_percpu_demux_irq);
vmbus_evt = alloc_percpu(long);
ret = request_percpu_irq(vmbus_irq, vmbus_percpu_isr,
"Hyper-V VMbus", vmbus_evt);
@@ -1355,6 +1393,20 @@ static int vmbus_bus_init(void)
if (ret)
goto err_alloc;

+ /* Create IRQ domain for VMBus devices */
+ vmbus_connection.vmbus_fwnode = irq_domain_alloc_named_fwnode("hv-vmbus");
+ if (!vmbus_connection.vmbus_fwnode) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+ vmbus_connection.vmbus_irq_domain = irq_domain_create_linear(
+ vmbus_connection.vmbus_fwnode, MAX_CHANNEL_RELIDS,
+ &vmbus_domain_ops, NULL);
+ if (!vmbus_connection.vmbus_irq_domain) {
+ ret = -ENOMEM;
+ goto err_fwnode;
+ }
+
/*
* Initialize the per-cpu interrupt state and stimer state.
* Then connect to the host.
@@ -1362,7 +1414,7 @@ static int vmbus_bus_init(void)
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hyperv/vmbus:online",
hv_synic_init, hv_synic_cleanup);
if (ret < 0)
- goto err_alloc;
+ goto err_domain;
hyperv_cpuhp_online = ret;

ret = vmbus_connect();
@@ -1382,6 +1434,10 @@ static int vmbus_bus_init(void)

err_connect:
cpuhp_remove_state(hyperv_cpuhp_online);
+err_domain:
+ irq_domain_remove(vmbus_connection.vmbus_irq_domain);
+err_fwnode:
+ irq_domain_free_fwnode(vmbus_connection.vmbus_fwnode);
err_alloc:
hv_synic_free();
if (vmbus_irq == -1) {
@@ -2690,6 +2746,8 @@ static void __exit vmbus_exit(void)
hv_debug_rm_all_dir();

vmbus_free_channels();
+ irq_domain_remove(vmbus_connection.vmbus_irq_domain);
+ irq_domain_free_fwnode(vmbus_connection.vmbus_fwnode);
kfree(vmbus_connection.channels);

/*
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index 8fe7aaab2599..0488ff8b511f 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -24,6 +24,7 @@
#include <acpi/acpi_numa.h>
#include <linux/cpumask.h>
#include <linux/nmi.h>
+#include <linux/irq.h>
#include <asm/ptrace.h>
#include <asm/hyperv-tlfs.h>

@@ -187,6 +188,11 @@ void hv_remove_kexec_handler(void);
void hv_setup_crash_handler(void (*handler)(struct pt_regs *regs));
void hv_remove_crash_handler(void);

+extern void vmbus_irq_mask(struct irq_data *data);
+extern void vmbus_irq_unmask(struct irq_data *data);
+extern int vmbus_irq_set_affinity(struct irq_data *data,
+ const struct cpumask *dest, bool force);
+
extern int vmbus_interrupt;
extern int vmbus_irq;

--
2.25.1