diff options
-rw-r--r-- | drivers/hv/channel_mgmt.c | 85 | ||||
-rw-r--r-- | drivers/hv/connection.c | 2 | ||||
-rw-r--r-- | drivers/hv/hyperv_vmbus.h | 14 | ||||
-rw-r--r-- | drivers/hv/vmbus_drv.c | 17 | ||||
-rw-r--r-- | include/linux/hyperv.h | 3 |
5 files changed, 101 insertions, 20 deletions
diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 5518d031f62a..8eb167540b4f 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c | |||
@@ -407,7 +407,15 @@ void hv_process_channel_removal(struct vmbus_channel *channel) | |||
407 | cpumask_clear_cpu(channel->target_cpu, | 407 | cpumask_clear_cpu(channel->target_cpu, |
408 | &primary_channel->alloced_cpus_in_node); | 408 | &primary_channel->alloced_cpus_in_node); |
409 | 409 | ||
410 | vmbus_release_relid(channel->offermsg.child_relid); | 410 | /* |
411 | * Upon suspend, an in-use hv_sock channel is marked as "rescinded" and | ||
412 | * the relid is invalidated; after hibernation, when the user-space app | ||
413 | * destroys the channel, the relid is INVALID_RELID, and in this case | ||
414 | * it's unnecessary and unsafe to release the old relid, since the same | ||
415 | * relid can refer to a completely different channel now. | ||
416 | */ | ||
417 | if (channel->offermsg.child_relid != INVALID_RELID) | ||
418 | vmbus_release_relid(channel->offermsg.child_relid); | ||
411 | 419 | ||
412 | free_channel(channel); | 420 | free_channel(channel); |
413 | } | 421 | } |
@@ -851,6 +859,36 @@ void vmbus_initiate_unload(bool crash) | |||
851 | vmbus_wait_for_unload(); | 859 | vmbus_wait_for_unload(); |
852 | } | 860 | } |
853 | 861 | ||
862 | static void check_ready_for_resume_event(void) | ||
863 | { | ||
864 | /* | ||
865 | * If all the old primary channels have been fixed up, then it's safe | ||
866 | * to resume. | ||
867 | */ | ||
868 | if (atomic_dec_and_test(&vmbus_connection.nr_chan_fixup_on_resume)) | ||
869 | complete(&vmbus_connection.ready_for_resume_event); | ||
870 | } | ||
871 | |||
872 | static void vmbus_setup_channel_state(struct vmbus_channel *channel, | ||
873 | struct vmbus_channel_offer_channel *offer) | ||
874 | { | ||
875 | /* | ||
876 | * Setup state for signalling the host. | ||
877 | */ | ||
878 | channel->sig_event = VMBUS_EVENT_CONNECTION_ID; | ||
879 | |||
880 | if (vmbus_proto_version != VERSION_WS2008) { | ||
881 | channel->is_dedicated_interrupt = | ||
882 | (offer->is_dedicated_interrupt != 0); | ||
883 | channel->sig_event = offer->connection_id; | ||
884 | } | ||
885 | |||
886 | memcpy(&channel->offermsg, offer, | ||
887 | sizeof(struct vmbus_channel_offer_channel)); | ||
888 | channel->monitor_grp = (u8)offer->monitorid / 32; | ||
889 | channel->monitor_bit = (u8)offer->monitorid % 32; | ||
890 | } | ||
891 | |||
854 | /* | 892 | /* |
855 | * find_primary_channel_by_offer - Get the channel object given the new offer. | 893 | * find_primary_channel_by_offer - Get the channel object given the new offer. |
856 | * This is only used in the resume path of hibernation. | 894 | * This is only used in the resume path of hibernation. |
@@ -902,14 +940,29 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) | |||
902 | atomic_dec(&vmbus_connection.offer_in_progress); | 940 | atomic_dec(&vmbus_connection.offer_in_progress); |
903 | 941 | ||
904 | /* | 942 | /* |
905 | * We're resuming from hibernation: we expect the host to send | 943 | * We're resuming from hibernation: all the sub-channel and |
906 | * exactly the same offers that we had before the hibernation. | 944 | * hv_sock channels we had before the hibernation should have |
945 | * been cleaned up, and now we must be seeing a re-offered | ||
946 | * primary channel that we had before the hibernation. | ||
907 | */ | 947 | */ |
948 | |||
949 | WARN_ON(oldchannel->offermsg.child_relid != INVALID_RELID); | ||
950 | /* Fix up the relid. */ | ||
951 | oldchannel->offermsg.child_relid = offer->child_relid; | ||
952 | |||
908 | offer_sz = sizeof(*offer); | 953 | offer_sz = sizeof(*offer); |
909 | if (memcmp(offer, &oldchannel->offermsg, offer_sz) == 0) | 954 | if (memcmp(offer, &oldchannel->offermsg, offer_sz) == 0) { |
955 | check_ready_for_resume_event(); | ||
910 | return; | 956 | return; |
957 | } | ||
911 | 958 | ||
912 | pr_debug("Mismatched offer from the host (relid=%d)\n", | 959 | /* |
960 | * This is not an error, since the host can also change the | ||
961 | * other field(s) of the offer, e.g. on WS RS5 (Build 17763), | ||
962 | * the offer->connection_id of the Mellanox VF vmbus device | ||
963 | * can change when the host reoffers the device upon resume. | ||
964 | */ | ||
965 | pr_debug("vmbus offer changed: relid=%d\n", | ||
913 | offer->child_relid); | 966 | offer->child_relid); |
914 | 967 | ||
915 | print_hex_dump_debug("Old vmbus offer: ", DUMP_PREFIX_OFFSET, | 968 | print_hex_dump_debug("Old vmbus offer: ", DUMP_PREFIX_OFFSET, |
@@ -917,6 +970,12 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) | |||
917 | false); | 970 | false); |
918 | print_hex_dump_debug("New vmbus offer: ", DUMP_PREFIX_OFFSET, | 971 | print_hex_dump_debug("New vmbus offer: ", DUMP_PREFIX_OFFSET, |
919 | 16, 4, offer, offer_sz, false); | 972 | 16, 4, offer, offer_sz, false); |
973 | |||
974 | /* Fix up the old channel. */ | ||
975 | vmbus_setup_channel_state(oldchannel, offer); | ||
976 | |||
977 | check_ready_for_resume_event(); | ||
978 | |||
920 | return; | 979 | return; |
921 | } | 980 | } |
922 | 981 | ||
@@ -929,21 +988,7 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) | |||
929 | return; | 988 | return; |
930 | } | 989 | } |
931 | 990 | ||
932 | /* | 991 | vmbus_setup_channel_state(newchannel, offer); |
933 | * Setup state for signalling the host. | ||
934 | */ | ||
935 | newchannel->sig_event = VMBUS_EVENT_CONNECTION_ID; | ||
936 | |||
937 | if (vmbus_proto_version != VERSION_WS2008) { | ||
938 | newchannel->is_dedicated_interrupt = | ||
939 | (offer->is_dedicated_interrupt != 0); | ||
940 | newchannel->sig_event = offer->connection_id; | ||
941 | } | ||
942 | |||
943 | memcpy(&newchannel->offermsg, offer, | ||
944 | sizeof(struct vmbus_channel_offer_channel)); | ||
945 | newchannel->monitor_grp = (u8)offer->monitorid / 32; | ||
946 | newchannel->monitor_bit = (u8)offer->monitorid % 32; | ||
947 | 992 | ||
948 | vmbus_process_offer(newchannel); | 993 | vmbus_process_offer(newchannel); |
949 | } | 994 | } |
diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 99851ea682eb..6e4c015783ff 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c | |||
@@ -29,6 +29,8 @@ struct vmbus_connection vmbus_connection = { | |||
29 | 29 | ||
30 | .ready_for_suspend_event= COMPLETION_INITIALIZER( | 30 | .ready_for_suspend_event= COMPLETION_INITIALIZER( |
31 | vmbus_connection.ready_for_suspend_event), | 31 | vmbus_connection.ready_for_suspend_event), |
32 | .ready_for_resume_event = COMPLETION_INITIALIZER( | ||
33 | vmbus_connection.ready_for_resume_event), | ||
32 | }; | 34 | }; |
33 | EXPORT_SYMBOL_GPL(vmbus_connection); | 35 | EXPORT_SYMBOL_GPL(vmbus_connection); |
34 | 36 | ||
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 974b747ca1fc..f7a5f5615f34 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h | |||
@@ -272,6 +272,20 @@ struct vmbus_connection { | |||
272 | * drop to zero. | 272 | * drop to zero. |
273 | */ | 273 | */ |
274 | struct completion ready_for_suspend_event; | 274 | struct completion ready_for_suspend_event; |
275 | |||
276 | /* | ||
277 | * The number of primary channels that should be "fixed up" | ||
278 | * upon resume: these channels are re-offered upon resume, and some | ||
279 | * fields of the channel offers (i.e. child_relid and connection_id) | ||
280 | * can change, so the old offermsg must be fixed up, before the resume | ||
281 | * callbacks of the VSC drivers start to further touch the channels. | ||
282 | */ | ||
283 | atomic_t nr_chan_fixup_on_resume; | ||
284 | /* | ||
285 | * vmbus_bus_resume() waits for "nr_chan_fixup_on_resume" to | ||
286 | * drop to zero. | ||
287 | */ | ||
288 | struct completion ready_for_resume_event; | ||
275 | }; | 289 | }; |
276 | 290 | ||
277 | 291 | ||
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 32ec951d334f..391f0b225c9a 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c | |||
@@ -2164,9 +2164,17 @@ static int vmbus_bus_suspend(struct device *dev) | |||
2164 | if (atomic_read(&vmbus_connection.nr_chan_close_on_suspend) > 0) | 2164 | if (atomic_read(&vmbus_connection.nr_chan_close_on_suspend) > 0) |
2165 | wait_for_completion(&vmbus_connection.ready_for_suspend_event); | 2165 | wait_for_completion(&vmbus_connection.ready_for_suspend_event); |
2166 | 2166 | ||
2167 | WARN_ON(atomic_read(&vmbus_connection.nr_chan_fixup_on_resume) != 0); | ||
2168 | |||
2167 | mutex_lock(&vmbus_connection.channel_mutex); | 2169 | mutex_lock(&vmbus_connection.channel_mutex); |
2168 | 2170 | ||
2169 | list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) { | 2171 | list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) { |
2172 | /* | ||
2173 | * Invalidate the field. Upon resume, vmbus_onoffer() will fix | ||
2174 | * up the field, and the other fields (if necessary). | ||
2175 | */ | ||
2176 | channel->offermsg.child_relid = INVALID_RELID; | ||
2177 | |||
2170 | if (is_hvsock_channel(channel)) { | 2178 | if (is_hvsock_channel(channel)) { |
2171 | if (!channel->rescind) { | 2179 | if (!channel->rescind) { |
2172 | pr_err("hv_sock channel not rescinded!\n"); | 2180 | pr_err("hv_sock channel not rescinded!\n"); |
@@ -2181,6 +2189,8 @@ static int vmbus_bus_suspend(struct device *dev) | |||
2181 | WARN_ON_ONCE(1); | 2189 | WARN_ON_ONCE(1); |
2182 | } | 2190 | } |
2183 | spin_unlock_irqrestore(&channel->lock, flags); | 2191 | spin_unlock_irqrestore(&channel->lock, flags); |
2192 | |||
2193 | atomic_inc(&vmbus_connection.nr_chan_fixup_on_resume); | ||
2184 | } | 2194 | } |
2185 | 2195 | ||
2186 | mutex_unlock(&vmbus_connection.channel_mutex); | 2196 | mutex_unlock(&vmbus_connection.channel_mutex); |
@@ -2189,6 +2199,9 @@ static int vmbus_bus_suspend(struct device *dev) | |||
2189 | 2199 | ||
2190 | vmbus_connection.conn_state = DISCONNECTED; | 2200 | vmbus_connection.conn_state = DISCONNECTED; |
2191 | 2201 | ||
2202 | /* Reset the event for the next resume. */ | ||
2203 | reinit_completion(&vmbus_connection.ready_for_resume_event); | ||
2204 | |||
2192 | return 0; | 2205 | return 0; |
2193 | } | 2206 | } |
2194 | 2207 | ||
@@ -2223,8 +2236,12 @@ static int vmbus_bus_resume(struct device *dev) | |||
2223 | if (ret != 0) | 2236 | if (ret != 0) |
2224 | return ret; | 2237 | return ret; |
2225 | 2238 | ||
2239 | WARN_ON(atomic_read(&vmbus_connection.nr_chan_fixup_on_resume) == 0); | ||
2240 | |||
2226 | vmbus_request_offers(); | 2241 | vmbus_request_offers(); |
2227 | 2242 | ||
2243 | wait_for_completion(&vmbus_connection.ready_for_resume_event); | ||
2244 | |||
2228 | /* Reset the event for the next suspend. */ | 2245 | /* Reset the event for the next suspend. */ |
2229 | reinit_completion(&vmbus_connection.ready_for_suspend_event); | 2246 | reinit_completion(&vmbus_connection.ready_for_suspend_event); |
2230 | 2247 | ||
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 8a60e7766037..a3aa9e9ef6f2 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h | |||
@@ -426,6 +426,9 @@ enum vmbus_channel_message_type { | |||
426 | CHANNELMSG_COUNT | 426 | CHANNELMSG_COUNT |
427 | }; | 427 | }; |
428 | 428 | ||
429 | /* Hyper-V supports about 2048 channels, and the RELIDs start with 1. */ | ||
430 | #define INVALID_RELID U32_MAX | ||
431 | |||
429 | struct vmbus_channel_message_header { | 432 | struct vmbus_channel_message_header { |
430 | enum vmbus_channel_message_type msgtype; | 433 | enum vmbus_channel_message_type msgtype; |
431 | u32 padding; | 434 | u32 padding; |