Merge tag 'pinctrl-v5.4-2' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw...
[linux-2.6-block.git] / drivers / hv / channel_mgmt.c
index addcef50df7ace897120a23c5b4236c360a886d3..8eb167540b4f66e658e7977858635f6dc8139700 100644 (file)
@@ -407,7 +407,15 @@ void hv_process_channel_removal(struct vmbus_channel *channel)
                cpumask_clear_cpu(channel->target_cpu,
                                  &primary_channel->alloced_cpus_in_node);
 
-       vmbus_release_relid(channel->offermsg.child_relid);
+       /*
+        * Upon suspend, an in-use hv_sock channel is marked as "rescinded" and
+        * the relid is invalidated; after hibernation, when the user-space app
+        * destroys the channel, the relid is INVALID_RELID, and in this case
+        * it's unnecessary and unsafe to release the old relid, since the same
+        * relid can refer to a completely different channel now.
+        */
+       if (channel->offermsg.child_relid != INVALID_RELID)
+               vmbus_release_relid(channel->offermsg.child_relid);
 
        free_channel(channel);
 }
@@ -545,6 +553,10 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
 
        mutex_lock(&vmbus_connection.channel_mutex);
 
+       /* Remember the channels that should be cleaned up upon suspend. */
+       if (is_hvsock_channel(newchannel) || is_sub_channel(newchannel))
+               atomic_inc(&vmbus_connection.nr_chan_close_on_suspend);
+
        /*
         * Now that we have acquired the channel_mutex,
         * we can release the potentially racing rescind thread.
@@ -847,6 +859,67 @@ void vmbus_initiate_unload(bool crash)
                vmbus_wait_for_unload();
 }
 
+static void check_ready_for_resume_event(void)
+{
+       /*
+        * If all the old primary channels have been fixed up, then it's safe
+        * to resume.
+        */
+       if (atomic_dec_and_test(&vmbus_connection.nr_chan_fixup_on_resume))
+               complete(&vmbus_connection.ready_for_resume_event);
+}
+
+static void vmbus_setup_channel_state(struct vmbus_channel *channel,
+                                     struct vmbus_channel_offer_channel *offer)
+{
+       /*
+        * Setup state for signalling the host.
+        */
+       channel->sig_event = VMBUS_EVENT_CONNECTION_ID;
+
+       if (vmbus_proto_version != VERSION_WS2008) {
+               channel->is_dedicated_interrupt =
+                               (offer->is_dedicated_interrupt != 0);
+               channel->sig_event = offer->connection_id;
+       }
+
+       memcpy(&channel->offermsg, offer,
+              sizeof(struct vmbus_channel_offer_channel));
+       channel->monitor_grp = (u8)offer->monitorid / 32;
+       channel->monitor_bit = (u8)offer->monitorid % 32;
+}
+
+/*
+ * find_primary_channel_by_offer - Get the channel object given the new offer.
+ * This is only used in the resume path of hibernation.
+ */
+static struct vmbus_channel *
+find_primary_channel_by_offer(const struct vmbus_channel_offer_channel *offer)
+{
+       struct vmbus_channel *channel = NULL, *iter;
+       const guid_t *inst1, *inst2;
+
+       /* Ignore sub-channel offers. */
+       if (offer->offer.sub_channel_index != 0)
+               return NULL;
+
+       mutex_lock(&vmbus_connection.channel_mutex);
+
+       list_for_each_entry(iter, &vmbus_connection.chn_list, listentry) {
+               inst1 = &iter->offermsg.offer.if_instance;
+               inst2 = &offer->offer.if_instance;
+
+               if (guid_equal(inst1, inst2)) {
+                       channel = iter;
+                       break;
+               }
+       }
+
+       mutex_unlock(&vmbus_connection.channel_mutex);
+
+       return channel;
+}
+
 /*
  * vmbus_onoffer - Handler for channel offers from vmbus in parent partition.
  *
@@ -854,12 +927,58 @@ void vmbus_initiate_unload(bool crash)
 static void vmbus_onoffer(struct vmbus_channel_message_header *hdr)
 {
        struct vmbus_channel_offer_channel *offer;
-       struct vmbus_channel *newchannel;
+       struct vmbus_channel *oldchannel, *newchannel;
+       size_t offer_sz;
 
        offer = (struct vmbus_channel_offer_channel *)hdr;
 
        trace_vmbus_onoffer(offer);
 
+       oldchannel = find_primary_channel_by_offer(offer);
+
+       if (oldchannel != NULL) {
+               atomic_dec(&vmbus_connection.offer_in_progress);
+
+               /*
+                * We're resuming from hibernation: all the sub-channel and
+                * hv_sock channels we had before the hibernation should have
+                * been cleaned up, and now we must be seeing a re-offered
+                * primary channel that we had before the hibernation.
+                */
+
+               WARN_ON(oldchannel->offermsg.child_relid != INVALID_RELID);
+               /* Fix up the relid. */
+               oldchannel->offermsg.child_relid = offer->child_relid;
+
+               offer_sz = sizeof(*offer);
+               if (memcmp(offer, &oldchannel->offermsg, offer_sz) == 0) {
+                       check_ready_for_resume_event();
+                       return;
+               }
+
+               /*
+                * This is not an error, since the host can also change the
+                * other field(s) of the offer, e.g. on WS RS5 (Build 17763),
+                * the offer->connection_id of the Mellanox VF vmbus device
+                * can change when the host reoffers the device upon resume.
+                */
+               pr_debug("vmbus offer changed: relid=%d\n",
+                        offer->child_relid);
+
+               print_hex_dump_debug("Old vmbus offer: ", DUMP_PREFIX_OFFSET,
+                                    16, 4, &oldchannel->offermsg, offer_sz,
+                                    false);
+               print_hex_dump_debug("New vmbus offer: ", DUMP_PREFIX_OFFSET,
+                                    16, 4, offer, offer_sz, false);
+
+               /* Fix up the old channel. */
+               vmbus_setup_channel_state(oldchannel, offer);
+
+               check_ready_for_resume_event();
+
+               return;
+       }
+
        /* Allocate the channel object and save this offer. */
        newchannel = alloc_channel();
        if (!newchannel) {
@@ -869,25 +988,21 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr)
                return;
        }
 
-       /*
-        * Setup state for signalling the host.
-        */
-       newchannel->sig_event = VMBUS_EVENT_CONNECTION_ID;
-
-       if (vmbus_proto_version != VERSION_WS2008) {
-               newchannel->is_dedicated_interrupt =
-                               (offer->is_dedicated_interrupt != 0);
-               newchannel->sig_event = offer->connection_id;
-       }
-
-       memcpy(&newchannel->offermsg, offer,
-              sizeof(struct vmbus_channel_offer_channel));
-       newchannel->monitor_grp = (u8)offer->monitorid / 32;
-       newchannel->monitor_bit = (u8)offer->monitorid % 32;
+       vmbus_setup_channel_state(newchannel, offer);
 
        vmbus_process_offer(newchannel);
 }
 
+static void check_ready_for_suspend_event(void)
+{
+       /*
+        * If all the sub-channels or hv_sock channels have been cleaned up,
+        * then it's safe to suspend.
+        */
+       if (atomic_dec_and_test(&vmbus_connection.nr_chan_close_on_suspend))
+               complete(&vmbus_connection.ready_for_suspend_event);
+}
+
 /*
  * vmbus_onoffer_rescind - Rescind offer handler.
  *
@@ -898,6 +1013,7 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
        struct vmbus_channel_rescind_offer *rescind;
        struct vmbus_channel *channel;
        struct device *dev;
+       bool clean_up_chan_for_suspend;
 
        rescind = (struct vmbus_channel_rescind_offer *)hdr;
 
@@ -937,6 +1053,8 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
                return;
        }
 
+       clean_up_chan_for_suspend = is_hvsock_channel(channel) ||
+                                   is_sub_channel(channel);
        /*
         * Before setting channel->rescind in vmbus_rescind_cleanup(), we
         * should make sure the channel callback is not running any more.
@@ -962,6 +1080,10 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
        if (channel->device_obj) {
                if (channel->chn_rescind_callback) {
                        channel->chn_rescind_callback(channel);
+
+                       if (clean_up_chan_for_suspend)
+                               check_ready_for_suspend_event();
+
                        return;
                }
                /*
@@ -994,6 +1116,11 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
                }
                mutex_unlock(&vmbus_connection.channel_mutex);
        }
+
+       /* The "channel" may have been freed. Do not access it any longer. */
+
+       if (clean_up_chan_for_suspend)
+               check_ready_for_suspend_event();
 }
 
 void vmbus_hvsock_device_unregister(struct vmbus_channel *channel)