diff options
author | Derek Basehore <dbasehore@chromium.org> | 2012-12-18 15:27:20 -0500 |
---|---|---|
committer | Jens Axboe <axboe@kernel.dk> | 2012-12-19 14:36:10 -0500 |
commit | 12c2bdb232168511c8dd54d6626549391a228918 (patch) | |
tree | 8b9179df57d7ddd596a57d465cce4e0949795d25 | |
parent | aea24a8bbc81c8b404e4e293fb37aefaf388d35d (diff) |
block: prevent race/cleanup
Remove a race condition which causes a warning in disk_clear_events. This
is a race between disk_clear_events() and disk_flush_events().
ev->clearing will be altered by disk_flush_events() even though we are
blocking event checking through disk_flush_events(). If this happens
after ev->clearing was cleared for disk_clear_events(), this can cause the
WARN_ON_ONCE() in that function to be triggered.
This change also has disk_clear_events() not go through a workqueue.
Since we have to wait for the work to complete, we should just call the
function directly. Also, since this work cannot be put on a freezable
workqueue, it will have to contend with increased demand, so calling the
function directly avoids this.
[akpm@linux-foundation.org: fix spello in comment]
Signed-off-by: Derek Basehore <dbasehore@chromium.org>
Cc: Mandeep Singh Baines <msb@chromium.org>
Cc: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
-rw-r--r-- | block/genhd.c | 45 |
1 files changed, 30 insertions, 15 deletions
diff --git a/block/genhd.c b/block/genhd.c index 33a3b38aa4b2..3993ebf4135f 100644 --- a/block/genhd.c +++ b/block/genhd.c | |||
@@ -35,6 +35,8 @@ static DEFINE_IDR(ext_devt_idr); | |||
35 | 35 | ||
36 | static struct device_type disk_type; | 36 | static struct device_type disk_type; |
37 | 37 | ||
38 | static void disk_check_events(struct disk_events *ev, | ||
39 | unsigned int *clearing_ptr); | ||
38 | static void disk_alloc_events(struct gendisk *disk); | 40 | static void disk_alloc_events(struct gendisk *disk); |
39 | static void disk_add_events(struct gendisk *disk); | 41 | static void disk_add_events(struct gendisk *disk); |
40 | static void disk_del_events(struct gendisk *disk); | 42 | static void disk_del_events(struct gendisk *disk); |
@@ -1549,6 +1551,7 @@ unsigned int disk_clear_events(struct gendisk *disk, unsigned int mask) | |||
1549 | const struct block_device_operations *bdops = disk->fops; | 1551 | const struct block_device_operations *bdops = disk->fops; |
1550 | struct disk_events *ev = disk->ev; | 1552 | struct disk_events *ev = disk->ev; |
1551 | unsigned int pending; | 1553 | unsigned int pending; |
1554 | unsigned int clearing = mask; | ||
1552 | 1555 | ||
1553 | if (!ev) { | 1556 | if (!ev) { |
1554 | /* for drivers still using the old ->media_changed method */ | 1557 | /* for drivers still using the old ->media_changed method */ |
@@ -1558,41 +1561,53 @@ unsigned int disk_clear_events(struct gendisk *disk, unsigned int mask) | |||
1558 | return 0; | 1561 | return 0; |
1559 | } | 1562 | } |
1560 | 1563 | ||
1561 | /* tell the workfn about the events being cleared */ | 1564 | disk_block_events(disk); |
1565 | |||
1566 | /* | ||
1567 | * store the union of mask and ev->clearing on the stack so that the | ||
1568 | * race with disk_flush_events does not cause ambiguity (ev->clearing | ||
1569 | * can still be modified even if events are blocked). | ||
1570 | */ | ||
1562 | spin_lock_irq(&ev->lock); | 1571 | spin_lock_irq(&ev->lock); |
1563 | ev->clearing |= mask; | 1572 | clearing |= ev->clearing; |
1573 | ev->clearing = 0; | ||
1564 | spin_unlock_irq(&ev->lock); | 1574 | spin_unlock_irq(&ev->lock); |
1565 | 1575 | ||
1566 | /* uncondtionally schedule event check and wait for it to finish */ | 1576 | disk_check_events(ev, &clearing); |
1567 | disk_block_events(disk); | ||
1568 | /* | 1577 | /* |
1569 | * We need to put the work on system_nrt_wq here since there is a | 1578 | * if ev->clearing is not 0, the disk_flush_events got called in the |
1570 | * deadlock that happens while probing a usb device while suspending. If | 1579 | * middle of this function, so we want to run the workfn without delay. |
1571 | * we put work on a freezable workqueue here, a usb probe will wait here | ||
1572 | * until the workqueue is unfrozen during suspend. Since suspend waits | ||
1573 | * on all probes to complete, we have a deadlock | ||
1574 | */ | 1580 | */ |
1575 | queue_delayed_work(system_nrt_wq, &ev->dwork, 0); | 1581 | __disk_unblock_events(disk, ev->clearing ? true : false); |
1576 | flush_delayed_work(&ev->dwork); | ||
1577 | __disk_unblock_events(disk, false); | ||
1578 | 1582 | ||
1579 | /* then, fetch and clear pending events */ | 1583 | /* then, fetch and clear pending events */ |
1580 | spin_lock_irq(&ev->lock); | 1584 | spin_lock_irq(&ev->lock); |
1581 | WARN_ON_ONCE(ev->clearing & mask); /* cleared by workfn */ | ||
1582 | pending = ev->pending & mask; | 1585 | pending = ev->pending & mask; |
1583 | ev->pending &= ~mask; | 1586 | ev->pending &= ~mask; |
1584 | spin_unlock_irq(&ev->lock); | 1587 | spin_unlock_irq(&ev->lock); |
1588 | WARN_ON_ONCE(clearing & mask); | ||
1585 | 1589 | ||
1586 | return pending; | 1590 | return pending; |
1587 | } | 1591 | } |
1588 | 1592 | ||
1593 | /* | ||
1594 | * Separate this part out so that a different pointer for clearing_ptr can be | ||
1595 | * passed in for disk_clear_events. | ||
1596 | */ | ||
1589 | static void disk_events_workfn(struct work_struct *work) | 1597 | static void disk_events_workfn(struct work_struct *work) |
1590 | { | 1598 | { |
1591 | struct delayed_work *dwork = to_delayed_work(work); | 1599 | struct delayed_work *dwork = to_delayed_work(work); |
1592 | struct disk_events *ev = container_of(dwork, struct disk_events, dwork); | 1600 | struct disk_events *ev = container_of(dwork, struct disk_events, dwork); |
1601 | |||
1602 | disk_check_events(ev, &ev->clearing); | ||
1603 | } | ||
1604 | |||
1605 | static void disk_check_events(struct disk_events *ev, | ||
1606 | unsigned int *clearing_ptr) | ||
1607 | { | ||
1593 | struct gendisk *disk = ev->disk; | 1608 | struct gendisk *disk = ev->disk; |
1594 | char *envp[ARRAY_SIZE(disk_uevents) + 1] = { }; | 1609 | char *envp[ARRAY_SIZE(disk_uevents) + 1] = { }; |
1595 | unsigned int clearing = ev->clearing; | 1610 | unsigned int clearing = *clearing_ptr; |
1596 | unsigned int events; | 1611 | unsigned int events; |
1597 | unsigned long intv; | 1612 | unsigned long intv; |
1598 | int nr_events = 0, i; | 1613 | int nr_events = 0, i; |
@@ -1605,7 +1620,7 @@ static void disk_events_workfn(struct work_struct *work) | |||
1605 | 1620 | ||
1606 | events &= ~ev->pending; | 1621 | events &= ~ev->pending; |
1607 | ev->pending |= events; | 1622 | ev->pending |= events; |
1608 | ev->clearing &= ~clearing; | 1623 | *clearing_ptr &= ~clearing; |
1609 | 1624 | ||
1610 | intv = disk_events_poll_jiffies(disk); | 1625 | intv = disk_events_poll_jiffies(disk); |
1611 | if (!ev->block && intv) | 1626 | if (!ev->block && intv) |