aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorReece R. Pollack2015-02-02 11:38:59 -0600
committerReece R. Pollack2015-02-16 16:28:03 -0600
commitacd51e14f5268d363db29a450b285629fa14f75a (patch)
tree55952cf14ef5817fb92bea137f37ca302e546a1a
parent0c06814366ca9d2c0cf100549f0507e5257f6718 (diff)
downloadlinux-acd51e14f5268d363db29a450b285629fa14f75a.tar.gz
linux-acd51e14f5268d363db29a450b285629fa14f75a.tar.xz
linux-acd51e14f5268d363db29a450b285629fa14f75a.zip
hwqueue: Fix race in disabling interrupt notifications
Prior to this patch, there existed a race condition between disabling of notifications in hwqueue_disable_notifier() and hwqueue_notify(). In this race, the delivery of an interrupt after the notifier was marked disabled but before disabling the interrupt would result in the interrupt service routine not calling the notifier function. Thus the interrupt persists which prevents or severely delays the disabling of the interrupt. This patch synchronizes the entire notifier disable sequence against the delivery of an interrupt notification by the use of a spinlock. Synchronization is also provided against the enable sequence. Note that the use of atomic operations does not suffice in this situation. Signed-off-by: Reece R. Pollack <x0183204@ti.com>
-rw-r--r--drivers/hwqueue/hwqueue_core.c26
-rw-r--r--drivers/hwqueue/hwqueue_internal.h3
2 files changed, 25 insertions, 4 deletions
diff --git a/drivers/hwqueue/hwqueue_core.c b/drivers/hwqueue/hwqueue_core.c
index c6d3d54e31e..204c8391d4d 100644
--- a/drivers/hwqueue/hwqueue_core.c
+++ b/drivers/hwqueue/hwqueue_core.c
@@ -134,6 +134,7 @@ int hwqueue_device_register(struct hwqueue_device *hdev)
134 setup_timer(&inst->poll_timer, __hwqueue_poll, 134 setup_timer(&inst->poll_timer, __hwqueue_poll,
135 (unsigned long)inst); 135 (unsigned long)inst);
136 init_waitqueue_head(&inst->wait); 136 init_waitqueue_head(&inst->wait);
137 spin_lock_init(&inst->lock);
137 } 138 }
138 139
139 list_add(&hdev->list, &hwqueue_devices); 140 list_add(&hdev->list, &hwqueue_devices);
@@ -492,6 +493,7 @@ int hwqueue_enable_notifier(struct hwqueue *qh)
492 struct hwqueue_instance *inst = qh->inst; 493 struct hwqueue_instance *inst = qh->inst;
493 struct hwqueue_device *hdev = inst->hdev; 494 struct hwqueue_device *hdev = inst->hdev;
494 bool first; 495 bool first;
496 unsigned long flags;
495 497
496 if (!hwqueue_is_readable(qh)) 498 if (!hwqueue_is_readable(qh))
497 return -EINVAL; 499 return -EINVAL;
@@ -499,16 +501,21 @@ int hwqueue_enable_notifier(struct hwqueue *qh)
499 if (WARN_ON(!qh->notifier_fn)) 501 if (WARN_ON(!qh->notifier_fn))
500 return -EINVAL; 502 return -EINVAL;
501 503
504 /* Protect against interrupt delivery */
505 spin_lock_irqsave(&inst->lock, flags);
506
502 /* Adjust the per handle notifier count */ 507 /* Adjust the per handle notifier count */
503 first = (atomic_inc_return(&qh->notifier_enabled) == 1); 508 first = (atomic_inc_return(&qh->notifier_enabled) == 1);
504 if (!first) 509 if (!first)
505 return 0; /* nothing to do */ 510 goto unlock; /* nothing to do */
506 511
507 /* Now adjust the per instance notifier count */ 512 /* Now adjust the per instance notifier count */
508 first = (atomic_inc_return(&inst->num_notifiers) == 1); 513 first = (atomic_inc_return(&inst->num_notifiers) == 1);
509 if (first) 514 if (first)
510 hdev->ops->set_notify(inst, true); 515 hdev->ops->set_notify(inst, true);
511 516
517unlock:
518 spin_unlock_irqrestore(&inst->lock, flags);
512 return 0; 519 return 0;
513} 520}
514EXPORT_SYMBOL(hwqueue_enable_notifier); 521EXPORT_SYMBOL(hwqueue_enable_notifier);
@@ -524,18 +531,24 @@ int hwqueue_disable_notifier(struct hwqueue *qh)
524 struct hwqueue_instance *inst = qh->inst; 531 struct hwqueue_instance *inst = qh->inst;
525 struct hwqueue_device *hdev = inst->hdev; 532 struct hwqueue_device *hdev = inst->hdev;
526 bool last; 533 bool last;
534 unsigned long flags;
527 535
528 if (!hwqueue_is_readable(qh)) 536 if (!hwqueue_is_readable(qh))
529 return -EINVAL; 537 return -EINVAL;
530 538
539 /* Protect against interrupt delivery */
540 spin_lock_irqsave(&inst->lock, flags);
541
531 last = (atomic_dec_return(&qh->notifier_enabled) == 0); 542 last = (atomic_dec_return(&qh->notifier_enabled) == 0);
532 if (!last) 543 if (!last)
533 return 0; /* nothing to do */ 544 goto unlock; /* nothing to do */
534 545
535 last = (atomic_dec_return(&inst->num_notifiers) == 0); 546 last = (atomic_dec_return(&inst->num_notifiers) == 0);
536 if (last) 547 if (last)
537 hdev->ops->set_notify(inst, false); 548 hdev->ops->set_notify(inst, false);
538 549
550unlock:
551 spin_unlock_irqrestore(&inst->lock, flags);
539 return 0; 552 return 0;
540} 553}
541EXPORT_SYMBOL(hwqueue_disable_notifier); 554EXPORT_SYMBOL(hwqueue_disable_notifier);
@@ -619,11 +632,18 @@ EXPORT_SYMBOL(__hwqueue_pop_slow);
619void hwqueue_notify(struct hwqueue_instance *inst) 632void hwqueue_notify(struct hwqueue_instance *inst)
620{ 633{
621 struct hwqueue *qh; 634 struct hwqueue *qh;
635 unsigned long flags;
636 bool enabled;
622 637
623 rcu_read_lock(); 638 rcu_read_lock();
624 639
625 for_each_handle_rcu(qh, inst) { 640 for_each_handle_rcu(qh, inst) {
626 if (atomic_read(&qh->notifier_enabled) <= 0) 641 /* Synchronize against enable/disable notifier */
642 spin_lock_irqsave(&inst->lock, flags);
643 enabled = atomic_read(&qh->notifier_enabled) > 0;
644 spin_unlock_irqrestore(&inst->lock, flags);
645
646 if (!enabled)
627 continue; 647 continue;
628 if (WARN_ON(!qh->notifier_fn)) 648 if (WARN_ON(!qh->notifier_fn))
629 continue; 649 continue;
diff --git a/drivers/hwqueue/hwqueue_internal.h b/drivers/hwqueue/hwqueue_internal.h
index ce99b54495e..35d4f46d493 100644
--- a/drivers/hwqueue/hwqueue_internal.h
+++ b/drivers/hwqueue/hwqueue_internal.h
@@ -27,12 +27,13 @@
27struct hwqueue_instance { 27struct hwqueue_instance {
28 struct list_head handles; 28 struct list_head handles;
29 struct hwqueue_device *hdev; 29 struct hwqueue_device *hdev;
30 spinlock_t lock;
30 struct timer_list poll_timer; 31 struct timer_list poll_timer;
31 wait_queue_head_t wait; 32 wait_queue_head_t wait;
32 void *priv; 33 void *priv;
33 char name[32];
34 atomic_t num_notifiers; 34 atomic_t num_notifiers;
35 struct hwqueue_inst_ops *ops; 35 struct hwqueue_inst_ops *ops;
36 char name[32];
36}; 37};
37 38
38struct hwqueue_device_ops { 39struct hwqueue_device_ops {