summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Jones <davej@suse.de>2002-05-30 20:47:43 -0700
committerLinus Torvalds <torvalds@penguin.transmeta.com>2002-05-30 20:47:43 -0700
commit60d2ad5f92ed8b8aab58f18a1808a2987fbd6c2e (patch)
tree5985341d7a1e89ba4f49e205729e0cb659d78a6f
parent1b3c4f5a3db018d8915e3c5876f29f4baab37972 (diff)
[PATCH] Fix deadlock in nbd
Variant of a patch in 2.4 by Steven Whitehouse.
-rw-r--r--drivers/block/nbd.c125
-rw-r--r--include/linux/nbd.h9
2 files changed, 79 insertions, 55 deletions
diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c
index d64aa828ac51..a7e00395ebac 100644
--- a/drivers/block/nbd.c
+++ b/drivers/block/nbd.c
@@ -24,6 +24,8 @@
* 01-3-11 Make nbd work with new Linux block layer code. It now supports
* plugging like all the other block devices. Also added in MSG_MORE to
* reduce number of partial TCP segments sent. <steve@chygwyn.com>
+ * 01-12-6 Fix deadlock condition by making queue locks independant of
+ * the transmit lock. <steve@chygwyn.com>
*
* possible FIXME: make set_sock / set_blksize / set_size / do_it one syscall
* why not: would need verify_area and friends, would share yet another
@@ -148,27 +150,28 @@ static int nbd_xmit(int send, struct socket *sock, char *buf, int size, int msg_
#define FAIL( s ) { printk( KERN_ERR "NBD: " s "(result %d)\n", result ); goto error_out; }
-void nbd_send_req(struct socket *sock, struct request *req)
+void nbd_send_req(struct nbd_device *lo, struct request *req)
{
- int result, rw, i, flags;
+ int result, i, flags;
struct nbd_request request;
unsigned long size = req->nr_sectors << 9;
+ struct socket *sock = lo->sock;
DEBUG("NBD: sending control, ");
- rw = rq_data_dir(req);
-
request.magic = htonl(NBD_REQUEST_MAGIC);
- request.type = htonl((rw & WRITE) ? 1 : 0);
+ request.type = htonl(nbd_cmd(req));
request.from = cpu_to_be64( (u64) req->sector << 9);
request.len = htonl(size);
memcpy(request.handle, &req, sizeof(req));
- result = nbd_xmit(1, sock, (char *) &request, sizeof(request), rw & WRITE ? MSG_MORE : 0);
+ down(&lo->tx_lock);
+
+ result = nbd_xmit(1, sock, (char *) &request, sizeof(request), nbd_cmd(req) == NBD_CMD_WRITE ? MSG_MORE : 0);
if (result <= 0)
FAIL("Sendmsg failed for control.");
- if (rw & WRITE) {
+ if (nbd_cmd(req) == NBD_CMD_WRITE) {
struct bio *bio;
/*
* we are really probing at internals to determine
@@ -187,37 +190,59 @@ void nbd_send_req(struct socket *sock, struct request *req)
}
}
}
+ up(&lo->tx_lock);
return;
error_out:
+ up(&lo->tx_lock);
req->errors++;
}
+static struct request *nbd_find_request(struct nbd_device *lo, char *handle)
+{
+ struct request *req;
+ struct list_head *tmp;
+ struct request *xreq;
+
+ memcpy(&xreq, handle, sizeof(xreq));
+
+ spin_lock(&lo->queue_lock);
+ list_for_each(tmp, &lo->queue_head) {
+ req = list_entry(tmp, struct request, queuelist);
+ if (req != xreq)
+ continue;
+ list_del(&req->queuelist);
+ spin_unlock(&lo->queue_lock);
+ return req;
+ }
+ spin_unlock(&lo->queue_lock);
+ return NULL;
+}
+
#define HARDFAIL( s ) { printk( KERN_ERR "NBD: " s "(result %d)\n", result ); lo->harderror = result; return NULL; }
struct request *nbd_read_stat(struct nbd_device *lo)
/* NULL returned = something went wrong, inform userspace */
{
int result;
struct nbd_reply reply;
- struct request *xreq, *req;
+ struct request *req;
DEBUG("reading control, ");
reply.magic = 0;
result = nbd_xmit(0, lo->sock, (char *) &reply, sizeof(reply), MSG_WAITALL);
if (result <= 0)
HARDFAIL("Recv control failed.");
- memcpy(&xreq, reply.handle, sizeof(xreq));
- req = blkdev_entry_to_request(lo->queue_head.prev);
-
- if (xreq != req)
- FAIL("Unexpected handle received.\n");
+ req = nbd_find_request(lo, reply.handle);
+ if (req == NULL)
+ HARDFAIL("Unexpected reply");
DEBUG("ok, ");
if (ntohl(reply.magic) != NBD_REPLY_MAGIC)
HARDFAIL("Not enough magic.");
if (ntohl(reply.error))
FAIL("Other side returned error.");
- if (rq_data_dir(req) == READ) {
+
+ if (nbd_cmd(req) == NBD_CMD_READ) {
struct bio *bio = req->bio;
DEBUG("data, ");
do {
@@ -240,20 +265,14 @@ void nbd_do_it(struct nbd_device *lo)
{
struct request *req;
- down (&lo->tx_lock);
while (1) {
- up (&lo->tx_lock);
req = nbd_read_stat(lo);
- down (&lo->tx_lock);
if (!req) {
printk(KERN_ALERT "req should never be null\n" );
goto out;
}
#ifdef PARANOIA
- if (req != blkdev_entry_to_request(lo->queue_head.prev)) {
- printk(KERN_ALERT "NBD: I have problem...\n");
- }
if (lo != &nbd_dev[minor(req->rq_dev)]) {
printk(KERN_ALERT "NBD: request corrupted!\n");
continue;
@@ -263,15 +282,10 @@ void nbd_do_it(struct nbd_device *lo)
goto out;
}
#endif
- blkdev_dequeue_request(req);
- up (&lo->tx_lock);
-
nbd_end_request(req);
- down (&lo->tx_lock);
}
out:
- up (&lo->tx_lock);
}
void nbd_clear_que(struct nbd_device *lo)
@@ -285,26 +299,19 @@ void nbd_clear_que(struct nbd_device *lo)
}
#endif
- while (!list_empty(&lo->queue_head)) {
- req = blkdev_entry_to_request(lo->queue_head.prev);
-#ifdef PARANOIA
- if (!req) {
- printk( KERN_ALERT "NBD: panic, panic, panic\n" );
- break;
+ do {
+ req = NULL;
+ spin_lock(&lo->queue_lock);
+ if (!list_empty(&lo->queue_head)) {
+ req = list_entry(lo->queue_head.next, struct request, queuelist);
+ list_del(&req->queuelist);
}
- if (lo != &nbd_dev[minor(req->rq_dev)]) {
- printk(KERN_ALERT "NBD: request corrupted when clearing!\n");
- continue;
+ spin_unlock(&lo->queue_lock);
+ if (req) {
+ req->errors++;
+ nbd_end_request(req);
}
-#endif
- req->errors++;
- blkdev_dequeue_request(req);
- up(&lo->tx_lock);
-
- nbd_end_request(req);
-
- down(&lo->tx_lock);
- }
+ } while(req);
}
/*
@@ -340,8 +347,12 @@ static void do_nbd_request(request_queue_t * q)
lo = &nbd_dev[dev];
if (!lo->file)
FAIL("Request when not-ready.");
- if ((rq_data_dir(req) == WRITE) && (lo->flags & NBD_READ_ONLY))
- FAIL("Write on read-only");
+ nbd_cmd(req) = NBD_CMD_READ;
+ if (rq_data_dir(req) == WRITE) {
+ nbd_cmd(req) = NBD_CMD_WRITE;
+ if (lo->flags & NBD_READ_ONLY)
+ FAIL("Write on read-only");
+ }
#ifdef PARANOIA
if (lo->magic != LO_MAGIC)
FAIL("nbd[] is not magical!");
@@ -351,10 +362,11 @@ static void do_nbd_request(request_queue_t * q)
blkdev_dequeue_request(req);
spin_unlock_irq(q->queue_lock);
- down (&lo->tx_lock);
+ spin_lock(&lo->queue_lock);
list_add(&req->queuelist, &lo->queue_head);
- nbd_send_req(lo->sock, req); /* Why does this block? */
- up (&lo->tx_lock);
+ spin_unlock(&lo->queue_lock);
+
+ nbd_send_req(lo, req);
spin_lock_irq(q->queue_lock);
continue;
@@ -389,21 +401,23 @@ static int nbd_ioctl(struct inode *inode, struct file *file,
lo = &nbd_dev[dev];
switch (cmd) {
case NBD_DISCONNECT:
- printk("NBD_DISCONNECT\n") ;
- sreq.flags = REQ_SPECIAL; /* FIXME: interpet as shutdown cmd */
- if (!lo->sock) return -EINVAL ;
- nbd_send_req(lo->sock,&sreq) ;
+ printk(KERN_INFO "NBD_DISCONNECT\n");
+ sreq.flags = REQ_SPECIAL;
+ nbd_cmd(&sreq) = NBD_CMD_DISC;
+ if (!lo->sock)
+ return -EINVAL;
+ nbd_send_req(lo, &sreq);
return 0 ;
case NBD_CLEAR_SOCK:
- down(&lo->tx_lock);
nbd_clear_que(lo);
+ spin_lock(&lo->queue_lock);
if (!list_empty(&lo->queue_head)) {
- up(&lo->tx_lock);
+ spin_unlock(&lo->queue_lock);
printk(KERN_ERR "nbd: Some requests are in progress -> can not turn off.\n");
return -EBUSY;
}
- up(&lo->tx_lock);
+ spin_unlock(&lo->queue_lock);
file = lo->file;
if (!file)
return -EINVAL;
@@ -525,6 +539,7 @@ static int __init nbd_init(void)
nbd_dev[i].file = NULL;
nbd_dev[i].magic = LO_MAGIC;
nbd_dev[i].flags = 0;
+ spin_lock_init(&nbd_dev[i].queue_lock);
INIT_LIST_HEAD(&nbd_dev[i].queue_head);
init_MUTEX(&nbd_dev[i].tx_lock);
nbd_blksizes[i] = 1024;
diff --git a/include/linux/nbd.h b/include/linux/nbd.h
index 556b847804ca..eae4f5bbb65a 100644
--- a/include/linux/nbd.h
+++ b/include/linux/nbd.h
@@ -20,6 +20,12 @@
#define NBD_SET_SIZE_BLOCKS _IO( 0xab, 7 )
#define NBD_DISCONNECT _IO( 0xab, 8 )
+enum {
+ NBD_CMD_READ = 0,
+ NBD_CMD_WRITE = 1,
+ NBD_CMD_DISC = 2
+};
+
#ifdef MAJOR_NR
#include <asm/semaphore.h>
@@ -33,6 +39,8 @@ extern int requests_in;
extern int requests_out;
#endif
+#define nbd_cmd(req) ((req)->cmd[0])
+
static void
nbd_end_request(struct request *req)
{
@@ -68,6 +76,7 @@ struct nbd_device {
struct socket * sock;
struct file * file; /* If == NULL, device is not ready, yet */
int magic; /* FIXME: not if debugging is off */
+ spinlock_t queue_lock;
struct list_head queue_head; /* Requests are added here... */
struct semaphore tx_lock;
};