aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hv/channel_mgmt.c
diff options
context:
space:
mode:
authorVitaly Kuznetsov <vkuznets@redhat.com>2015-01-20 10:45:06 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2015-01-25 12:18:01 -0500
commitd7f2fbafb4f84306436277664cf28042beaf252a (patch)
tree4d27864cf7fa8e9c3f9e4c6a4f83d02cfc0a00f8 /drivers/hv/channel_mgmt.c
parent67fae053bfc6e84144150e4c6c62670abb215c33 (diff)
Drivers: hv: vmbus: serialize Offer and Rescind offer
Commit 4b2f9abea52a ("staging: hv: convert channel_mgmt.c to not call osd_schedule_callback")' was written under an assumption that we never receive Rescind offer while we're still processing the initial Offer request. However, the issue we fixed in 04a258c162a8 could be caused by this assumption not always being true. In particular, we need to protect against the following: 1) Receiving a Rescind offer after we do queue_work() for processing an Offer request and before we actually enter vmbus_process_offer(). work.func points to vmbus_process_offer() at this moment and in vmbus_onoffer_rescind() we do another queue_work() without a check so we'll enter vmbus_process_offer() twice. 2) Receiving a Rescind offer after we enter vmbus_process_offer() and especially after we set >state = CHANNEL_OPEN_STATE. Many things can go wrong in that case, e.g. we can call free_channel() while we're still using it. Implement the required protection by changing work->func at the very end of vmbus_process_offer() and checking work->func in vmbus_onoffer_rescind(). In case we receive rescind offer during or before vmbus_process_offer() is done we set rescind flag to true and we check it at the end of vmbus_process_offer() so such offer will not get lost. Suggested-by: Radim Krčmář <rkrcmar@redhat.com> Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com> Acked-by: Jason Wang <jasowang@redhat.com> Signed-off-by: K. Y. Srinivasan <kys@microsoft.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/hv/channel_mgmt.c')
-rw-r--r--drivers/hv/channel_mgmt.c30
1 files changed, 22 insertions, 8 deletions
diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c
index 1e0b996ed643..3736f71bdec5 100644
--- a/drivers/hv/channel_mgmt.c
+++ b/drivers/hv/channel_mgmt.c
@@ -279,9 +279,6 @@ static void vmbus_process_offer(struct work_struct *work)
279 int ret; 279 int ret;
280 unsigned long flags; 280 unsigned long flags;
281 281
282 /* The next possible work is rescind handling */
283 INIT_WORK(&newchannel->work, vmbus_process_rescind_offer);
284
285 /* Make sure this is a new offer */ 282 /* Make sure this is a new offer */
286 spin_lock_irqsave(&vmbus_connection.channel_lock, flags); 283 spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
287 284
@@ -341,7 +338,7 @@ static void vmbus_process_offer(struct work_struct *work)
341 if (channel->sc_creation_callback != NULL) 338 if (channel->sc_creation_callback != NULL)
342 channel->sc_creation_callback(newchannel); 339 channel->sc_creation_callback(newchannel);
343 340
344 goto out; 341 goto done_init_rescind;
345 } 342 }
346 343
347 goto err_free_chan; 344 goto err_free_chan;
@@ -382,7 +379,14 @@ static void vmbus_process_offer(struct work_struct *work)
382 kfree(newchannel->device_obj); 379 kfree(newchannel->device_obj);
383 goto err_free_chan; 380 goto err_free_chan;
384 } 381 }
385out: 382done_init_rescind:
383 spin_lock_irqsave(&newchannel->lock, flags);
384 /* The next possible work is rescind handling */
385 INIT_WORK(&newchannel->work, vmbus_process_rescind_offer);
386 /* Check if rescind offer was already received */
387 if (newchannel->rescind)
388 queue_work(newchannel->controlwq, &newchannel->work);
389 spin_unlock_irqrestore(&newchannel->lock, flags);
386 return; 390 return;
387err_free_chan: 391err_free_chan:
388 free_channel(newchannel); 392 free_channel(newchannel);
@@ -520,6 +524,7 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
520{ 524{
521 struct vmbus_channel_rescind_offer *rescind; 525 struct vmbus_channel_rescind_offer *rescind;
522 struct vmbus_channel *channel; 526 struct vmbus_channel *channel;
527 unsigned long flags;
523 528
524 rescind = (struct vmbus_channel_rescind_offer *)hdr; 529 rescind = (struct vmbus_channel_rescind_offer *)hdr;
525 channel = relid2channel(rescind->child_relid); 530 channel = relid2channel(rescind->child_relid);
@@ -528,11 +533,20 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
528 /* Just return here, no channel found */ 533 /* Just return here, no channel found */
529 return; 534 return;
530 535
536 spin_lock_irqsave(&channel->lock, flags);
531 channel->rescind = true; 537 channel->rescind = true;
538 /*
539 * channel->work.func != vmbus_process_rescind_offer means we are still
540 * processing offer request and the rescind offer processing should be
541 * postponed. It will be done at the very end of vmbus_process_offer()
542 * as rescind flag is being checked there.
543 */
544 if (channel->work.func == vmbus_process_rescind_offer)
545 /* work is initialized for vmbus_process_rescind_offer() from
546 * vmbus_process_offer() where the channel got created */
547 queue_work(channel->controlwq, &channel->work);
532 548
533 /* work is initialized for vmbus_process_rescind_offer() from 549 spin_unlock_irqrestore(&channel->lock, flags);
534 * vmbus_process_offer() where the channel got created */
535 queue_work(channel->controlwq, &channel->work);
536} 550}
537 551
538/* 552/*