From 94655e1c302fcef917337eaa08138cd8a5722551 Mon Sep 17 00:00:00 2001 From: Brian Behlendorf Date: Fri, 21 May 2010 15:01:12 -0700 Subject: [PATCH] Use __blk_end_request() in zvol_request() to avoid ZVOL deadlock. The unlocked version of blk_end_request() should be used in the zvol_request() error handling to avoid deadlocking the request_queue queue_lock. Things get a little more complicated for older kernel APIs but the compat layer has been updated as well to reflect this. --- module/zfs/include/sys/blkdev.h | 45 +++++++++++++++++++++++---------- module/zfs/zvol.c | 11 ++++---- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/module/zfs/include/sys/blkdev.h b/module/zfs/include/sys/blkdev.h index 78ce3edd6c..53ac24656d 100644 --- a/module/zfs/include/sys/blkdev.h +++ b/module/zfs/include/sys/blkdev.h @@ -61,9 +61,8 @@ blk_requeue_request(request_queue_t *q, struct request *req) #ifndef HAVE_BLK_END_REQUEST static inline bool -blk_end_request(struct request *req, int error, unsigned int nr_bytes) +__blk_end_request(struct request *req, int error, unsigned int nr_bytes) { - struct request_queue *q = req->q; LIST_HEAD(list); /* @@ -79,14 +78,23 @@ blk_end_request(struct request *req, int error, unsigned int nr_bytes) * entire request partial requests are not supported. */ req->hard_cur_sectors = nr_bytes >> 9; - - - spin_lock_irq(q->queue_lock); end_request(req, ((error == 0) ? 1 : error)); - spin_unlock_irq(q->queue_lock); return 0; } + +static inline bool +blk_end_request(struct request *req, int error, unsigned int nr_bytes) +{ + struct request_queue *q = req->q; + bool rc; + + spin_lock_irq(q->queue_lock); + rc = __blk_end_request(req, error, nr_bytes); + spin_unlock_irq(q->queue_lock); + + return rc; +} #else # ifdef HAVE_BLK_END_REQUEST_GPL_ONLY /* @@ -94,25 +102,34 @@ blk_end_request(struct request *req, int error, unsigned int nr_bytes) * GPL-only version of the helper. As of 2.6.31 the helper is available * to non-GPL modules and is not explicitly exported GPL-only. */ -# define blk_end_request ___blk_end_request -static inline bool -___blk_end_request(struct request *req, int error, unsigned int nr_bytes) -{ - struct request_queue *q = req->q; +# define __blk_end_request __blk_end_request_x +# define blk_end_request blk_end_request_x +static inline bool +__blk_end_request_x(struct request *req, int error, unsigned int nr_bytes) +{ /* * The old API required the driver to end each segment and not * the entire request. In our case we always need to end the * entire request partial requests are not supported. */ req->hard_cur_sectors = nr_bytes >> 9; - - spin_lock_irq(q->queue_lock); end_request(req, ((error == 0) ? 1 : error)); - spin_unlock_irq(q->queue_lock); return 0; } +static inline bool +blk_end_request_x(struct request *req, int error, unsigned int nr_bytes) +{ + struct request_queue *q = req->q; + bool rc; + + spin_lock_irq(q->queue_lock); + __blk_end_request_x(req, error, nr_bytes); + spin_unlock_irq(q->queue_lock); + + return rc; +} # endif /* HAVE_BLK_END_REQUEST_GPL_ONLY */ #endif /* HAVE_BLK_END_REQUEST */ diff --git a/module/zfs/zvol.c b/module/zfs/zvol.c index 9235bfb46f..4443e2d46d 100644 --- a/module/zfs/zvol.c +++ b/module/zfs/zvol.c @@ -626,14 +626,14 @@ zvol_request(struct request_queue *q) req->rq_disk->disk_name, (long long unsigned)blk_rq_pos(req), (long unsigned)blk_rq_sectors(req)); - blk_end_request(req, -EIO, size); + __blk_end_request(req, -EIO, size); continue; } if (!blk_fs_request(req)) { printk(KERN_INFO "%s: non-fs cmd\n", req->rq_disk->disk_name); - blk_end_request(req, -EIO, size); + __blk_end_request(req, -EIO, size); continue; } @@ -642,8 +642,9 @@ zvol_request(struct request_queue *q) zvol_dispatch(zvol_read, req); break; case WRITE: - if (unlikely(get_disk_ro(zv->zv_disk))) { - blk_end_request(req, -EROFS, size); + if (unlikely(get_disk_ro(zv->zv_disk)) || + unlikely(zv->zv_mode & DS_MODE_READONLY)) { + __blk_end_request(req, -EROFS, size); break; } @@ -652,7 +653,7 @@ zvol_request(struct request_queue *q) default: printk(KERN_INFO "%s: unknown cmd: %d\n", req->rq_disk->disk_name, (int)rq_data_dir(req)); - blk_end_request(req, -EIO, size); + __blk_end_request(req, -EIO, size); break; } }