aboutsummaryrefslogtreecommitdiffstats
path: root/block/genhd.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2011-06-09 14:43:59 -0400
committerJens Axboe <jaxboe@fusionio.com>2011-06-09 14:43:59 -0400
commitfdd514e16bb2531c0c61ae8a1f87740ce217f630 (patch)
tree5d7b6d4f9112b0b6f93f7ce939045fb634abc3fa /block/genhd.c
parentc3af54afbac3675337cedf326b7b127ffa7f7327 (diff)
block: make disk_block_events() properly wait for work cancellation
disk_block_events() should guarantee that the event work is not in flight on return and once blocked it shouldn't issue further cancellations. Because there was no synchronization between the first blocker doing cancel_delayed_work_sync() and the following blockers, the following blockers could finish before cancellation was complete, which broke both guarantees - event work could be in flight and cancellation could happen after return. This bug triggered WARN_ON_ONCE() in disk_clear_events() reported in bug#34662. https://bugzilla.kernel.org/show_bug.cgi?id=34662 Fix it by adding an outer mutex which protects both block count manipulation and work cancellation. -v2: Use outer mutex instead of bit waitqueue per Linus. Signed-off-by: Tejun Heo <tj@kernel.org> Tested-by: Sitsofe Wheeler <sitsofe@yahoo.com> Reported-by: Sitsofe Wheeler <sitsofe@yahoo.com> Reported-by: Borislav Petkov <bp@alien8.de> Reported-by: Meelis Roos <mroos@linux.ee> Reported-by: Linus Torvalds <torvalds@linux-foundation.org> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Jens Axboe <axboe@kernel.dk> Cc: Kay Sievers <kay.sievers@vrfy.org> Signed-off-by: Jens Axboe <jaxboe@fusionio.com>
Diffstat (limited to 'block/genhd.c')
-rw-r--r--block/genhd.c10
1 files changed, 10 insertions, 0 deletions
diff --git a/block/genhd.c b/block/genhd.c
index ab0731d8976d..3608289c8ecd 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -1371,6 +1371,7 @@ struct disk_events {
1371 struct gendisk *disk; /* the associated disk */ 1371 struct gendisk *disk; /* the associated disk */
1372 spinlock_t lock; 1372 spinlock_t lock;
1373 1373
1374 struct mutex block_mutex; /* protects blocking */
1374 int block; /* event blocking depth */ 1375 int block; /* event blocking depth */
1375 unsigned int pending; /* events already sent out */ 1376 unsigned int pending; /* events already sent out */
1376 unsigned int clearing; /* events being cleared */ 1377 unsigned int clearing; /* events being cleared */
@@ -1438,12 +1439,20 @@ void disk_block_events(struct gendisk *disk)
1438 if (!ev) 1439 if (!ev)
1439 return; 1440 return;
1440 1441
1442 /*
1443 * Outer mutex ensures that the first blocker completes canceling
1444 * the event work before further blockers are allowed to finish.
1445 */
1446 mutex_lock(&ev->block_mutex);
1447
1441 spin_lock_irqsave(&ev->lock, flags); 1448 spin_lock_irqsave(&ev->lock, flags);
1442 cancel = !ev->block++; 1449 cancel = !ev->block++;
1443 spin_unlock_irqrestore(&ev->lock, flags); 1450 spin_unlock_irqrestore(&ev->lock, flags);
1444 1451
1445 if (cancel) 1452 if (cancel)
1446 cancel_delayed_work_sync(&disk->ev->dwork); 1453 cancel_delayed_work_sync(&disk->ev->dwork);
1454
1455 mutex_unlock(&ev->block_mutex);
1447} 1456}
1448 1457
1449static void __disk_unblock_events(struct gendisk *disk, bool check_now) 1458static void __disk_unblock_events(struct gendisk *disk, bool check_now)
@@ -1751,6 +1760,7 @@ static void disk_add_events(struct gendisk *disk)
1751 INIT_LIST_HEAD(&ev->node); 1760 INIT_LIST_HEAD(&ev->node);
1752 ev->disk = disk; 1761 ev->disk = disk;
1753 spin_lock_init(&ev->lock); 1762 spin_lock_init(&ev->lock);
1763 mutex_init(&ev->block_mutex);
1754 ev->block = 1; 1764 ev->block = 1;
1755 ev->poll_msecs = -1; 1765 ev->poll_msecs = -1;
1756 INIT_DELAYED_WORK(&ev->dwork, disk_events_workfn); 1766 INIT_DELAYED_WORK(&ev->dwork, disk_events_workfn);