aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorK. Y. Srinivasan <kys@microsoft.com>2015-03-18 15:29:21 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2015-03-25 06:53:53 -0400
commitfde25d25dbd997067058f7d4c2ff31600157e6f2 (patch)
treeb4cc494bf111ccd81d0314f2df1f795c3cedac07
parent0daa7a0afd0fa7aad83cbde0fb341d7fbeac952c (diff)
Drivers: hv: vmbus: Perform device register in the per-channel work element
This patch is a continuation of the rescind handling cleanup work. We cannot block in the global message handling work context especially if we are blocking waiting for the host to wake us up. I would like to thank Dexuan Cui <decui@microsoft.com> for observing this problem. The current char-next branch is broken and this patch fixes the bug. Signed-off-by: K. Y. Srinivasan <kys@microsoft.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/hv/channel_mgmt.c143
-rw-r--r--drivers/hv/connection.c6
-rw-r--r--drivers/hv/hyperv_vmbus.h2
3 files changed, 107 insertions, 44 deletions
diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c
index 611789139f9b..5f8e47bf5ccc 100644
--- a/drivers/hv/channel_mgmt.c
+++ b/drivers/hv/channel_mgmt.c
@@ -23,6 +23,7 @@
23#include <linux/kernel.h> 23#include <linux/kernel.h>
24#include <linux/sched.h> 24#include <linux/sched.h>
25#include <linux/wait.h> 25#include <linux/wait.h>
26#include <linux/delay.h>
26#include <linux/mm.h> 27#include <linux/mm.h>
27#include <linux/slab.h> 28#include <linux/slab.h>
28#include <linux/list.h> 29#include <linux/list.h>
@@ -37,6 +38,10 @@ struct vmbus_channel_message_table_entry {
37 void (*message_handler)(struct vmbus_channel_message_header *msg); 38 void (*message_handler)(struct vmbus_channel_message_header *msg);
38}; 39};
39 40
41struct vmbus_rescind_work {
42 struct work_struct work;
43 struct vmbus_channel *channel;
44};
40 45
41/** 46/**
42 * vmbus_prep_negotiate_resp() - Create default response for Hyper-V Negotiate message 47 * vmbus_prep_negotiate_resp() - Create default response for Hyper-V Negotiate message
@@ -134,20 +139,6 @@ fw_error:
134 139
135EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp); 140EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp);
136 141
137static void vmbus_process_device_unregister(struct work_struct *work)
138{
139 struct device *dev;
140 struct vmbus_channel *channel = container_of(work,
141 struct vmbus_channel,
142 work);
143
144 dev = get_device(&channel->device_obj->device);
145 if (dev) {
146 vmbus_device_unregister(channel->device_obj);
147 put_device(dev);
148 }
149}
150
151static void vmbus_sc_creation_cb(struct work_struct *work) 142static void vmbus_sc_creation_cb(struct work_struct *work)
152{ 143{
153 struct vmbus_channel *newchannel = container_of(work, 144 struct vmbus_channel *newchannel = container_of(work,
@@ -220,6 +211,40 @@ static void free_channel(struct vmbus_channel *channel)
220 queue_work(vmbus_connection.work_queue, &channel->work); 211 queue_work(vmbus_connection.work_queue, &channel->work);
221} 212}
222 213
214static void process_rescind_fn(struct work_struct *work)
215{
216 struct vmbus_rescind_work *rc_work;
217 struct vmbus_channel *channel;
218 struct device *dev;
219
220 rc_work = container_of(work, struct vmbus_rescind_work, work);
221 channel = rc_work->channel;
222
223 /*
224 * We have already acquired a reference on the channel
225 * and so it cannot vanish underneath us.
226 * It is possible (while very unlikely) that we may
227 * get here while the processing of the initial offer
228 * is still not complete. Deal with this situation by
229 * just waiting until the channel is in the correct state.
230 */
231
232 while (channel->work.func != release_channel)
233 msleep(1000);
234
235 if (channel->device_obj) {
236 dev = get_device(&channel->device_obj->device);
237 if (dev) {
238 vmbus_device_unregister(channel->device_obj);
239 put_device(dev);
240 }
241 } else {
242 hv_process_channel_removal(channel,
243 channel->offermsg.child_relid);
244 }
245 kfree(work);
246}
247
223static void percpu_channel_enq(void *arg) 248static void percpu_channel_enq(void *arg)
224{ 249{
225 struct vmbus_channel *channel = arg; 250 struct vmbus_channel *channel = arg;
@@ -282,6 +307,46 @@ void vmbus_free_channels(void)
282 } 307 }
283} 308}
284 309
310static void vmbus_do_device_register(struct work_struct *work)
311{
312 struct hv_device *device_obj;
313 int ret;
314 unsigned long flags;
315 struct vmbus_channel *newchannel = container_of(work,
316 struct vmbus_channel,
317 work);
318
319 ret = vmbus_device_register(newchannel->device_obj);
320 if (ret != 0) {
321 pr_err("unable to add child device object (relid %d)\n",
322 newchannel->offermsg.child_relid);
323 spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
324 list_del(&newchannel->listentry);
325 device_obj = newchannel->device_obj;
326 newchannel->device_obj = NULL;
327 spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
328
329 if (newchannel->target_cpu != get_cpu()) {
330 put_cpu();
331 smp_call_function_single(newchannel->target_cpu,
332 percpu_channel_deq, newchannel, true);
333 } else {
334 percpu_channel_deq(newchannel);
335 put_cpu();
336 }
337
338 kfree(device_obj);
339 if (!newchannel->rescind) {
340 free_channel(newchannel);
341 return;
342 }
343 }
344 /*
345 * The next state for this channel is to be freed.
346 */
347 INIT_WORK(&newchannel->work, release_channel);
348}
349
285/* 350/*
286 * vmbus_process_offer - Process the offer by creating a channel/device 351 * vmbus_process_offer - Process the offer by creating a channel/device
287 * associated with this offer 352 * associated with this offer
@@ -291,7 +356,6 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
291 struct vmbus_channel *channel; 356 struct vmbus_channel *channel;
292 bool fnew = true; 357 bool fnew = true;
293 bool enq = false; 358 bool enq = false;
294 int ret;
295 unsigned long flags; 359 unsigned long flags;
296 360
297 /* Make sure this is a new offer */ 361 /* Make sure this is a new offer */
@@ -393,16 +457,13 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
393 * Add the new device to the bus. This will kick off device-driver 457 * Add the new device to the bus. This will kick off device-driver
394 * binding which eventually invokes the device driver's AddDevice() 458 * binding which eventually invokes the device driver's AddDevice()
395 * method. 459 * method.
460 * Invoke this call on the per-channel work context.
461 * Until we return from this function, rescind offer message
462 * cannot be processed as we are running on the global message
463 * handling work.
396 */ 464 */
397 ret = vmbus_device_register(newchannel->device_obj); 465 INIT_WORK(&newchannel->work, vmbus_do_device_register);
398 if (ret != 0) { 466 queue_work(newchannel->controlwq, &newchannel->work);
399 pr_err("unable to add child device object (relid %d)\n",
400 newchannel->offermsg.child_relid);
401
402 kfree(newchannel->device_obj);
403 goto err_deq_chan;
404 }
405
406 return; 467 return;
407 468
408err_deq_chan: 469err_deq_chan:
@@ -556,33 +617,31 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
556{ 617{
557 struct vmbus_channel_rescind_offer *rescind; 618 struct vmbus_channel_rescind_offer *rescind;
558 struct vmbus_channel *channel; 619 struct vmbus_channel *channel;
559 unsigned long flags; 620 struct vmbus_rescind_work *rc_work;
560 621
561 rescind = (struct vmbus_channel_rescind_offer *)hdr; 622 rescind = (struct vmbus_channel_rescind_offer *)hdr;
562 channel = relid2channel(rescind->child_relid); 623 channel = relid2channel(rescind->child_relid, true);
563 624
564 if (channel == NULL) { 625 if (channel == NULL) {
565 hv_process_channel_removal(NULL, rescind->child_relid); 626 hv_process_channel_removal(NULL, rescind->child_relid);
566 return; 627 return;
567 } 628 }
568 629
569 spin_lock_irqsave(&channel->lock, flags); 630 /*
570 channel->rescind = true; 631 * We have acquired a reference on the channel and have posted
571 spin_unlock_irqrestore(&channel->lock, flags); 632 * the rescind state. Perform further cleanup in a work context
572 633 * that is different from the global work context in which
573 if (channel->device_obj) { 634 * we process messages from the host (we are currently executing
574 /* 635 * on that global context.
575 * We will have to unregister this device from the 636 */
576 * driver core. Do this in the per-channel work context. 637 rc_work = kzalloc(sizeof(struct vmbus_rescind_work), GFP_KERNEL);
577 * Note that we are currently executing on the global 638 if (!rc_work) {
578 * workq for handling messages from the host. 639 pr_err("Unable to allocate memory for rescind processing ");
579 */ 640 return;
580 INIT_WORK(&channel->work, vmbus_process_device_unregister);
581 queue_work(channel->controlwq, &channel->work);
582 } else {
583 hv_process_channel_removal(channel,
584 channel->offermsg.child_relid);
585 } 641 }
642 rc_work->channel = channel;
643 INIT_WORK(&rc_work->work, process_rescind_fn);
644 schedule_work(&rc_work->work);
586} 645}
587 646
588/* 647/*
diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c
index 583d7d42b46d..8bcd3071c84f 100644
--- a/drivers/hv/connection.c
+++ b/drivers/hv/connection.c
@@ -270,7 +270,7 @@ static struct vmbus_channel *pcpu_relid2channel(u32 relid)
270 * relid2channel - Get the channel object given its 270 * relid2channel - Get the channel object given its
271 * child relative id (ie channel id) 271 * child relative id (ie channel id)
272 */ 272 */
273struct vmbus_channel *relid2channel(u32 relid) 273struct vmbus_channel *relid2channel(u32 relid, bool rescind)
274{ 274{
275 struct vmbus_channel *channel; 275 struct vmbus_channel *channel;
276 struct vmbus_channel *found_channel = NULL; 276 struct vmbus_channel *found_channel = NULL;
@@ -282,6 +282,8 @@ struct vmbus_channel *relid2channel(u32 relid)
282 list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) { 282 list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
283 if (channel->offermsg.child_relid == relid) { 283 if (channel->offermsg.child_relid == relid) {
284 found_channel = channel; 284 found_channel = channel;
285 if (rescind)
286 found_channel->rescind = true;
285 break; 287 break;
286 } else if (!list_empty(&channel->sc_list)) { 288 } else if (!list_empty(&channel->sc_list)) {
287 /* 289 /*
@@ -292,6 +294,8 @@ struct vmbus_channel *relid2channel(u32 relid)
292 sc_list); 294 sc_list);
293 if (cur_sc->offermsg.child_relid == relid) { 295 if (cur_sc->offermsg.child_relid == relid) {
294 found_channel = cur_sc; 296 found_channel = cur_sc;
297 if (rescind)
298 found_channel->rescind = true;
295 break; 299 break;
296 } 300 }
297 } 301 }
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
index 88af4ec559c4..63395896baf9 100644
--- a/drivers/hv/hyperv_vmbus.h
+++ b/drivers/hv/hyperv_vmbus.h
@@ -698,7 +698,7 @@ void vmbus_device_unregister(struct hv_device *device_obj);
698/* VmbusChildDeviceDestroy( */ 698/* VmbusChildDeviceDestroy( */
699/* struct hv_device *); */ 699/* struct hv_device *); */
700 700
701struct vmbus_channel *relid2channel(u32 relid); 701struct vmbus_channel *relid2channel(u32 relid, bool rescind);
702 702
703void vmbus_free_channels(void); 703void vmbus_free_channels(void);
704 704