diff options
Diffstat (limited to 'kernel/slow-work.c')
-rw-r--r-- | kernel/slow-work.c | 129 |
1 files changed, 127 insertions, 2 deletions
diff --git a/kernel/slow-work.c b/kernel/slow-work.c index 671cc434532a..f67e1daae93d 100644 --- a/kernel/slow-work.c +++ b/kernel/slow-work.c | |||
@@ -406,11 +406,40 @@ void slow_work_cancel(struct slow_work *work) | |||
406 | bool wait = true, put = false; | 406 | bool wait = true, put = false; |
407 | 407 | ||
408 | set_bit(SLOW_WORK_CANCELLING, &work->flags); | 408 | set_bit(SLOW_WORK_CANCELLING, &work->flags); |
409 | smp_mb(); | ||
410 | |||
411 | /* if the work item is a delayed work item with an active timer, we | ||
412 | * need to wait for the timer to finish _before_ getting the spinlock, | ||
413 | * lest we deadlock against the timer routine | ||
414 | * | ||
415 | * the timer routine will leave DELAYED set if it notices the | ||
416 | * CANCELLING flag in time | ||
417 | */ | ||
418 | if (test_bit(SLOW_WORK_DELAYED, &work->flags)) { | ||
419 | struct delayed_slow_work *dwork = | ||
420 | container_of(work, struct delayed_slow_work, work); | ||
421 | del_timer_sync(&dwork->timer); | ||
422 | } | ||
409 | 423 | ||
410 | spin_lock_irq(&slow_work_queue_lock); | 424 | spin_lock_irq(&slow_work_queue_lock); |
411 | 425 | ||
412 | if (test_bit(SLOW_WORK_PENDING, &work->flags) && | 426 | if (test_bit(SLOW_WORK_DELAYED, &work->flags)) { |
413 | !list_empty(&work->link)) { | 427 | /* the timer routine aborted or never happened, so we are left |
428 | * holding the timer's reference on the item and should just | ||
429 | * drop the pending flag and wait for any ongoing execution to | ||
430 | * finish */ | ||
431 | struct delayed_slow_work *dwork = | ||
432 | container_of(work, struct delayed_slow_work, work); | ||
433 | |||
434 | BUG_ON(timer_pending(&dwork->timer)); | ||
435 | BUG_ON(!list_empty(&work->link)); | ||
436 | |||
437 | clear_bit(SLOW_WORK_DELAYED, &work->flags); | ||
438 | put = true; | ||
439 | clear_bit(SLOW_WORK_PENDING, &work->flags); | ||
440 | |||
441 | } else if (test_bit(SLOW_WORK_PENDING, &work->flags) && | ||
442 | !list_empty(&work->link)) { | ||
414 | /* the link in the pending queue holds a reference on the item | 443 | /* the link in the pending queue holds a reference on the item |
415 | * that we will need to release */ | 444 | * that we will need to release */ |
416 | list_del_init(&work->link); | 445 | list_del_init(&work->link); |
@@ -441,6 +470,102 @@ void slow_work_cancel(struct slow_work *work) | |||
441 | EXPORT_SYMBOL(slow_work_cancel); | 470 | EXPORT_SYMBOL(slow_work_cancel); |
442 | 471 | ||
443 | /* | 472 | /* |
473 | * Handle expiry of the delay timer, indicating that a delayed slow work item | ||
474 | * should now be queued if not cancelled | ||
475 | */ | ||
476 | static void delayed_slow_work_timer(unsigned long data) | ||
477 | { | ||
478 | struct slow_work *work = (struct slow_work *) data; | ||
479 | unsigned long flags; | ||
480 | bool queued = false, put = false; | ||
481 | |||
482 | spin_lock_irqsave(&slow_work_queue_lock, flags); | ||
483 | if (likely(!test_bit(SLOW_WORK_CANCELLING, &work->flags))) { | ||
484 | clear_bit(SLOW_WORK_DELAYED, &work->flags); | ||
485 | |||
486 | if (test_bit(SLOW_WORK_EXECUTING, &work->flags)) { | ||
487 | /* we discard the reference the timer was holding in | ||
488 | * favour of the one the executor holds */ | ||
489 | set_bit(SLOW_WORK_ENQ_DEFERRED, &work->flags); | ||
490 | put = true; | ||
491 | } else { | ||
492 | if (test_bit(SLOW_WORK_VERY_SLOW, &work->flags)) | ||
493 | list_add_tail(&work->link, &vslow_work_queue); | ||
494 | else | ||
495 | list_add_tail(&work->link, &slow_work_queue); | ||
496 | queued = true; | ||
497 | } | ||
498 | } | ||
499 | |||
500 | spin_unlock_irqrestore(&slow_work_queue_lock, flags); | ||
501 | if (put) | ||
502 | slow_work_put_ref(work); | ||
503 | if (queued) | ||
504 | wake_up(&slow_work_thread_wq); | ||
505 | } | ||
506 | |||
507 | /** | ||
508 | * delayed_slow_work_enqueue - Schedule a delayed slow work item for processing | ||
509 | * @dwork: The delayed work item to queue | ||
510 | * @delay: When to start executing the work, in jiffies from now | ||
511 | * | ||
512 | * This is similar to slow_work_enqueue(), but it adds a delay before the work | ||
513 | * is actually queued for processing. | ||
514 | * | ||
515 | * The item can have delayed processing requested on it whilst it is being | ||
516 | * executed. The delay will begin immediately, and if it expires before the | ||
517 | * item finishes executing, the item will be placed back on the queue when it | ||
518 | * has done executing. | ||
519 | */ | ||
520 | int delayed_slow_work_enqueue(struct delayed_slow_work *dwork, | ||
521 | unsigned long delay) | ||
522 | { | ||
523 | struct slow_work *work = &dwork->work; | ||
524 | unsigned long flags; | ||
525 | int ret; | ||
526 | |||
527 | if (delay == 0) | ||
528 | return slow_work_enqueue(&dwork->work); | ||
529 | |||
530 | BUG_ON(slow_work_user_count <= 0); | ||
531 | BUG_ON(!work); | ||
532 | BUG_ON(!work->ops); | ||
533 | |||
534 | if (test_bit(SLOW_WORK_CANCELLING, &work->flags)) | ||
535 | return -ECANCELED; | ||
536 | |||
537 | if (!test_and_set_bit_lock(SLOW_WORK_PENDING, &work->flags)) { | ||
538 | spin_lock_irqsave(&slow_work_queue_lock, flags); | ||
539 | |||
540 | if (test_bit(SLOW_WORK_CANCELLING, &work->flags)) | ||
541 | goto cancelled; | ||
542 | |||
543 | /* the timer holds a reference whilst it is pending */ | ||
544 | ret = work->ops->get_ref(work); | ||
545 | if (ret < 0) | ||
546 | goto cant_get_ref; | ||
547 | |||
548 | if (test_and_set_bit(SLOW_WORK_DELAYED, &work->flags)) | ||
549 | BUG(); | ||
550 | dwork->timer.expires = jiffies + delay; | ||
551 | dwork->timer.data = (unsigned long) work; | ||
552 | dwork->timer.function = delayed_slow_work_timer; | ||
553 | add_timer(&dwork->timer); | ||
554 | |||
555 | spin_unlock_irqrestore(&slow_work_queue_lock, flags); | ||
556 | } | ||
557 | |||
558 | return 0; | ||
559 | |||
560 | cancelled: | ||
561 | ret = -ECANCELED; | ||
562 | cant_get_ref: | ||
563 | spin_unlock_irqrestore(&slow_work_queue_lock, flags); | ||
564 | return ret; | ||
565 | } | ||
566 | EXPORT_SYMBOL(delayed_slow_work_enqueue); | ||
567 | |||
568 | /* | ||
444 | * Schedule a cull of the thread pool at some time in the near future | 569 | * Schedule a cull of the thread pool at some time in the near future |
445 | */ | 570 | */ |
446 | static void slow_work_schedule_cull(void) | 571 | static void slow_work_schedule_cull(void) |