|
@@ -35,6 +35,8 @@ static DEFINE_IDR(ext_devt_idr);
|
|
|
|
|
|
static struct device_type disk_type;
|
|
|
|
|
|
+static void disk_check_events(struct disk_events *ev,
|
|
|
+ unsigned int *clearing_ptr);
|
|
|
static void disk_alloc_events(struct gendisk *disk);
|
|
|
static void disk_add_events(struct gendisk *disk);
|
|
|
static void disk_del_events(struct gendisk *disk);
|
|
@@ -1549,6 +1551,7 @@ unsigned int disk_clear_events(struct gendisk *disk, unsigned int mask)
|
|
|
const struct block_device_operations *bdops = disk->fops;
|
|
|
struct disk_events *ev = disk->ev;
|
|
|
unsigned int pending;
|
|
|
+ unsigned int clearing = mask;
|
|
|
|
|
|
if (!ev) {
|
|
|
/* for drivers still using the old ->media_changed method */
|
|
@@ -1558,34 +1561,53 @@ unsigned int disk_clear_events(struct gendisk *disk, unsigned int mask)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- /* tell the workfn about the events being cleared */
|
|
|
+ disk_block_events(disk);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * store the union of mask and ev->clearing on the stack so that the
|
|
|
+ * race with disk_flush_events does not cause ambiguity (ev->clearing
|
|
|
+ * can still be modified even if events are blocked).
|
|
|
+ */
|
|
|
spin_lock_irq(&ev->lock);
|
|
|
- ev->clearing |= mask;
|
|
|
+ clearing |= ev->clearing;
|
|
|
+ ev->clearing = 0;
|
|
|
spin_unlock_irq(&ev->lock);
|
|
|
|
|
|
- /* uncondtionally schedule event check and wait for it to finish */
|
|
|
- disk_block_events(disk);
|
|
|
- queue_delayed_work(system_freezable_wq, &ev->dwork, 0);
|
|
|
- flush_delayed_work(&ev->dwork);
|
|
|
- __disk_unblock_events(disk, false);
|
|
|
+ disk_check_events(ev, &clearing);
|
|
|
+ /*
|
|
|
+ * if ev->clearing is not 0, the disk_flush_events got called in the
|
|
|
+ * middle of this function, so we want to run the workfn without delay.
|
|
|
+ */
|
|
|
+ __disk_unblock_events(disk, ev->clearing ? true : false);
|
|
|
|
|
|
/* then, fetch and clear pending events */
|
|
|
spin_lock_irq(&ev->lock);
|
|
|
- WARN_ON_ONCE(ev->clearing & mask); /* cleared by workfn */
|
|
|
pending = ev->pending & mask;
|
|
|
ev->pending &= ~mask;
|
|
|
spin_unlock_irq(&ev->lock);
|
|
|
+ WARN_ON_ONCE(clearing & mask);
|
|
|
|
|
|
return pending;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Separate this part out so that a different pointer for clearing_ptr can be
|
|
|
+ * passed in for disk_clear_events.
|
|
|
+ */
|
|
|
static void disk_events_workfn(struct work_struct *work)
|
|
|
{
|
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
|
struct disk_events *ev = container_of(dwork, struct disk_events, dwork);
|
|
|
+
|
|
|
+ disk_check_events(ev, &ev->clearing);
|
|
|
+}
|
|
|
+
|
|
|
+static void disk_check_events(struct disk_events *ev,
|
|
|
+ unsigned int *clearing_ptr)
|
|
|
+{
|
|
|
struct gendisk *disk = ev->disk;
|
|
|
char *envp[ARRAY_SIZE(disk_uevents) + 1] = { };
|
|
|
- unsigned int clearing = ev->clearing;
|
|
|
+ unsigned int clearing = *clearing_ptr;
|
|
|
unsigned int events;
|
|
|
unsigned long intv;
|
|
|
int nr_events = 0, i;
|
|
@@ -1598,7 +1620,7 @@ static void disk_events_workfn(struct work_struct *work)
|
|
|
|
|
|
events &= ~ev->pending;
|
|
|
ev->pending |= events;
|
|
|
- ev->clearing &= ~clearing;
|
|
|
+ *clearing_ptr &= ~clearing;
|
|
|
|
|
|
intv = disk_events_poll_jiffies(disk);
|
|
|
if (!ev->block && intv)
|