mmc: block: handle complete_work on separate workqueue
The kblockd workqueue is created with the WQ_MEM_RECLAIM flag set.
This generates a rescuer thread for that queue that will trigger when
the CPU is under heavy load and collect the uncompleted work.
In the case of mmc, this creates the possibility of a deadlock when
there are multiple partitions on the device as other blk-mq work is
also run on the same queue. For example:
- worker 0 claims the mmc host to work on partition 1
- worker 1 attempts to claim the host for partition 2 but has to wait
for worker 0 to finish
- worker 0 schedules complete_work to release the host
- rescuer thread is triggered after time-out and collects the dangling
work
- rescuer thread attempts to complete the work in order starting with
claim host
- the task to release host is now blocked by a task to claim it and
will never be called
The above results in multiple hung tasks that lead to failures to
mount partitions.
Handling complete_work on a separate workqueue avoids this by keeping
the work completion tasks separate from the other blk-mq work. This
allows the host to be released without getting blocked by other tasks
attempting to claim the host.
Signed-off-by: Zachary Hays <zhays@lexmark.com>
Fixes: 81196976ed
("mmc: block: Add blk-mq support")
Cc: <stable@vger.kernel.org>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
parent
d6f11e7d91
commit
dcf6e2e38a
|
@ -2112,7 +2112,7 @@ static void mmc_blk_mq_req_done(struct mmc_request *mrq)
|
||||||
if (waiting)
|
if (waiting)
|
||||||
wake_up(&mq->wait);
|
wake_up(&mq->wait);
|
||||||
else
|
else
|
||||||
kblockd_schedule_work(&mq->complete_work);
|
queue_work(mq->card->complete_wq, &mq->complete_work);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2924,6 +2924,13 @@ static int mmc_blk_probe(struct mmc_card *card)
|
||||||
|
|
||||||
mmc_fixup_device(card, mmc_blk_fixups);
|
mmc_fixup_device(card, mmc_blk_fixups);
|
||||||
|
|
||||||
|
card->complete_wq = alloc_workqueue("mmc_complete",
|
||||||
|
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
|
||||||
|
if (unlikely(!card->complete_wq)) {
|
||||||
|
pr_err("Failed to create mmc completion workqueue");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
md = mmc_blk_alloc(card);
|
md = mmc_blk_alloc(card);
|
||||||
if (IS_ERR(md))
|
if (IS_ERR(md))
|
||||||
return PTR_ERR(md);
|
return PTR_ERR(md);
|
||||||
|
@ -2987,6 +2994,7 @@ static void mmc_blk_remove(struct mmc_card *card)
|
||||||
pm_runtime_put_noidle(&card->dev);
|
pm_runtime_put_noidle(&card->dev);
|
||||||
mmc_blk_remove_req(md);
|
mmc_blk_remove_req(md);
|
||||||
dev_set_drvdata(&card->dev, NULL);
|
dev_set_drvdata(&card->dev, NULL);
|
||||||
|
destroy_workqueue(card->complete_wq);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _mmc_blk_suspend(struct mmc_card *card)
|
static int _mmc_blk_suspend(struct mmc_card *card)
|
||||||
|
|
|
@ -308,6 +308,7 @@ struct mmc_card {
|
||||||
unsigned int nr_parts;
|
unsigned int nr_parts;
|
||||||
|
|
||||||
unsigned int bouncesz; /* Bounce buffer size */
|
unsigned int bouncesz; /* Bounce buffer size */
|
||||||
|
struct workqueue_struct *complete_wq; /* Private workqueue */
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline bool mmc_large_sector(struct mmc_card *card)
|
static inline bool mmc_large_sector(struct mmc_card *card)
|
||||||
|
|
Loading…
Reference in New Issue