@@ -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()
@@ -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);
}
/*
@@ -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);
@@ -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
@@ -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];
};