diff options
author | K. Y. Srinivasan <kys@microsoft.com> | 2015-03-18 15:29:21 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-03-25 06:53:53 -0400 |
commit | fde25d25dbd997067058f7d4c2ff31600157e6f2 (patch) | |
tree | b4cc494bf111ccd81d0314f2df1f795c3cedac07 | |
parent | 0daa7a0afd0fa7aad83cbde0fb341d7fbeac952c (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.c | 143 | ||||
-rw-r--r-- | drivers/hv/connection.c | 6 | ||||
-rw-r--r-- | drivers/hv/hyperv_vmbus.h | 2 |
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 | ||
41 | struct 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 | ||
135 | EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp); | 140 | EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp); |
136 | 141 | ||
137 | static 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 | |||
151 | static void vmbus_sc_creation_cb(struct work_struct *work) | 142 | static 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 | ||
214 | static 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 | |||
223 | static void percpu_channel_enq(void *arg) | 248 | static 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 | ||
310 | static 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 | ||
408 | err_deq_chan: | 469 | err_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 | */ |
273 | struct vmbus_channel *relid2channel(u32 relid) | 273 | struct 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 | ||
701 | struct vmbus_channel *relid2channel(u32 relid); | 701 | struct vmbus_channel *relid2channel(u32 relid, bool rescind); |
702 | 702 | ||
703 | void vmbus_free_channels(void); | 703 | void vmbus_free_channels(void); |
704 | 704 | ||