diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c
index adbe184e5197..da42aaae994e 100644
--- a/drivers/hv/channel_mgmt.c
+++ b/drivers/hv/channel_mgmt.c
@@ -10,6 +10,9 @@
 
 #include <linux/kernel.h>
 #include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/irqdomain.h>
 #include <linux/sched.h>
 #include <linux/wait.h>
 #include <linux/mm.h>
@@ -410,7 +413,10 @@ static void free_channel(struct vmbus_channel *channel)
 
 void vmbus_channel_map_relid(struct vmbus_channel *channel)
 {
-	if (WARN_ON(channel->offermsg.child_relid >= MAX_CHANNEL_RELIDS))
+	int res;
+	u32 relid = channel->offermsg.child_relid;
+
+	if (WARN_ON(relid >= MAX_CHANNEL_RELIDS))
 		return;
 	/*
 	 * The mapping of the channel's relid is visible from the CPUs that
@@ -437,18 +443,33 @@ void vmbus_channel_map_relid(struct vmbus_channel *channel)
 	 *      of the VMBus driver and vmbus_chan_sched() can not run before
 	 *      vmbus_bus_resume() has completed execution (cf. resume_noirq).
 	 */
-	virt_store_mb(
-		vmbus_connection.channels[channel->offermsg.child_relid],
-		channel);
+
+	channel->irq = irq_create_mapping(vmbus_connection.vmbus_irq_domain, relid);
+	if (!channel->irq) {
+		pr_err("irq_create_mapping failed for relid %d\n", relid);
+		return;
+	}
+
+	res = irq_set_handler_data(channel->irq, channel);
+	if (res) {
+		irq_dispose_mapping(channel->irq);
+		channel->irq = 0;
+		pr_err("irq_set_handler_data failed with %d for relid %d\n",
+				res, relid);
+		return;
+	}
+
+	irq_set_status_flags(channel->irq, IRQ_MOVE_PCNTXT);
 }
 
 void vmbus_channel_unmap_relid(struct vmbus_channel *channel)
 {
-	if (WARN_ON(channel->offermsg.child_relid >= MAX_CHANNEL_RELIDS))
-		return;
-	WRITE_ONCE(
-		vmbus_connection.channels[channel->offermsg.child_relid],
-		NULL);
+	if (channel->irq_requested) {
+		irq_update_affinity_hint(channel->irq, NULL);
+		free_irq(channel->irq, channel);
+	}
+	channel->irq_requested = false;
+	irq_dispose_mapping(channel->irq);
 }
 
 static void vmbus_release_relid(u32 relid)
@@ -478,10 +499,10 @@ void hv_process_channel_removal(struct vmbus_channel *channel)
 		!is_hvsock_channel(channel));
 
 	/*
-	 * Upon suspend, an in-use hv_sock channel is removed from the array of
-	 * channels and the relid is invalidated.  After hibernation, when the
+	 * Upon suspend, an in-use hv_sock channel is removed from the IRQ
+	 * map and the relid is invalidated. After hibernation, when the
 	 * user-space application destroys the channel, it's unnecessary and
-	 * unsafe to remove the channel from the array of channels.  See also
+	 * unsafe to remove the channel from the IRQ map. See also
 	 * the inline comments before the call of vmbus_release_relid() below.
 	 */
 	if (channel->offermsg.child_relid != INVALID_RELID)
@@ -533,6 +554,9 @@ static void vmbus_add_channel_work(struct work_struct *work)
 	struct vmbus_channel *primary_channel = newchannel->primary_channel;
 	int ret;
 
+	if (!newchannel->irq)
+		goto err_deq_chan;
+
 	/*
 	 * This state is used to indicate a successful open
 	 * so that when we do close the channel normally, we
@@ -1144,7 +1168,7 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr)
 			vmbus_setup_channel_state(oldchannel, offer);
 		}
 
-		/* Add the channel back to the array of channels. */
+		/* Re-establish the channel's IRQ mapping using the new relid */
 		vmbus_channel_map_relid(oldchannel);
 		check_ready_for_resume_event();
 
@@ -1225,7 +1249,7 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
 	}
 
 	mutex_lock(&vmbus_connection.channel_mutex);
-	channel = relid2channel(rescind->child_relid);
+	channel = relid2channel(rescind->child_relid, NULL);
 	if (channel != NULL) {
 		/*
 		 * Guarantee that no other instance of vmbus_onoffer_rescind()
diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c
index cb01784e5c3b..a105eecdeec2 100644
--- a/drivers/hv/connection.c
+++ b/drivers/hv/connection.c
@@ -308,14 +308,6 @@ int vmbus_connect(void)
 	pr_info("Vmbus version:%d.%d\n",
 		version >> 16, version & 0xFFFF);
 
-	vmbus_connection.channels = kcalloc(MAX_CHANNEL_RELIDS,
-					    sizeof(struct vmbus_channel *),
-					    GFP_KERNEL);
-	if (vmbus_connection.channels == NULL) {
-		ret = -ENOMEM;
-		goto cleanup;
-	}
-
 	kfree(msginfo);
 	return 0;
 
@@ -373,15 +365,16 @@ void vmbus_disconnect(void)
  * relid2channel - Get the channel object given its
  * child relative id (ie channel id)
  */
-struct vmbus_channel *relid2channel(u32 relid)
+struct vmbus_channel *relid2channel(u32 relid, struct irq_desc **desc_ptr)
 {
-	if (vmbus_connection.channels == NULL) {
-		pr_warn_once("relid2channel: relid=%d: No channels mapped!\n", relid);
-		return NULL;
-	}
-	if (WARN_ON(relid >= MAX_CHANNEL_RELIDS))
+	struct irq_desc *desc;
+
+	desc = irq_resolve_mapping(vmbus_connection.vmbus_irq_domain, relid);
+	if (!desc)
 		return NULL;
-	return READ_ONCE(vmbus_connection.channels[relid]);
+	if (desc_ptr)
+		*desc_ptr = desc;
+	return irq_desc_get_handler_data(desc);
 }
 
 /*
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
index 95d4d47d34f7..bf35bb40c55e 100644
--- a/drivers/hv/hyperv_vmbus.h
+++ b/drivers/hv/hyperv_vmbus.h
@@ -259,9 +259,6 @@ struct vmbus_connection {
 	struct list_head chn_list;
 	struct mutex channel_mutex;
 
-	/* Array of channels */
-	struct vmbus_channel **channels;
-
 	/* IRQ domain data */
 	struct fwnode_handle *vmbus_fwnode;
 	struct irq_domain *vmbus_irq_domain;
@@ -364,7 +361,7 @@ void vmbus_remove_channel_attr_group(struct vmbus_channel *channel);
 void vmbus_channel_map_relid(struct vmbus_channel *channel);
 void vmbus_channel_unmap_relid(struct vmbus_channel *channel);
 
-struct vmbus_channel *relid2channel(u32 relid);
+struct vmbus_channel *relid2channel(u32 relid, struct irq_desc **desc);
 
 void vmbus_free_channels(void);
 
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index cbccdfad49a2..8fd03d41e71a 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c
@@ -1219,6 +1219,7 @@ static void vmbus_chan_sched(struct hv_per_cpu_context *hv_cpu)
 	for_each_set_bit(relid, recv_int_page, maxbits) {
 		void (*callback_fn)(void *context);
 		struct vmbus_channel *channel;
+		struct irq_desc *desc;
 
 		if (!sync_test_and_clear_bit(relid, recv_int_page))
 			continue;
@@ -1236,7 +1237,7 @@ static void vmbus_chan_sched(struct hv_per_cpu_context *hv_cpu)
 		rcu_read_lock();
 
 		/* Find channel based on relid */
-		channel = relid2channel(relid);
+		channel = relid2channel(relid, &desc);
 		if (channel == NULL)
 			goto sched_unlock_rcu;
 
@@ -2466,10 +2467,10 @@ static int vmbus_bus_suspend(struct device *dev)
 
 	list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
 		/*
-		 * Remove the channel from the array of channels and invalidate
+		 * Remove the channel from the IRQ map and invalidate
 		 * the channel's relid.  Upon resume, vmbus_onoffer() will fix
 		 * up the relid (and other fields, if necessary) and add the
-		 * channel back to the array.
+		 * channel back to the IRQ map.
 		 */
 		vmbus_channel_unmap_relid(channel);
 		channel->offermsg.child_relid = INVALID_RELID;
@@ -2748,7 +2749,6 @@ static void __exit vmbus_exit(void)
 	vmbus_free_channels();
 	irq_domain_remove(vmbus_connection.vmbus_irq_domain);
 	irq_domain_free_fwnode(vmbus_connection.vmbus_fwnode);
-	kfree(vmbus_connection.channels);
 
 	/*
 	 * The vmbus panic notifier is always registered, hence we should
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index 897d96fa19d4..d545b1b635e5 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -1072,6 +1072,9 @@ struct vmbus_channel {
 	/* The max size of a packet on this channel */
 	u32 max_pkt_size;
 
+	/* The Linux IRQ for the channel in the "hv-vmbus" IRQ domain */
+	u32 irq;
+	bool irq_requested;
 	char irq_name[VMBUS_CHAN_IRQ_NAME_MAX];
 };
 
