From e8b311ffd424d8bfcb8f93c58744b19f6a2e9314 Mon Sep 17 00:00:00 2001
From: Krzysztof Mazur <krzysiek@podlesie.net>
Date: Tue, 21 Dec 2010 23:37:12 +0100
Subject: [PATCH 12/84] lsbd: fix buffer allocation from lsbd_thread

lsbd_thread should not use getblk() and bread() because they can
call write_some_buffers() and call lsbd_make_request(). Now very
simple allocator from loop device is used.

This version works much better under high memory pressure,
cat /dev/zero > /dev/lsbd0 now works without problems.
---
 drivers/block/lsbd.c | 162 +++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 137 insertions(+), 25 deletions(-)

diff --git a/drivers/block/lsbd.c b/drivers/block/lsbd.c
index 45e2292..5933814 100644
--- a/drivers/block/lsbd.c
+++ b/drivers/block/lsbd.c
@@ -237,25 +237,135 @@ static unsigned int lsbd_block_size(struct lsbd *p)
  * @size: number of bytes to read
  *
  * This function NULL on failure; pointer to allocated buffer otherwise.
+ *
+ * This function cannot be called from lsbd_thread.
  */
 struct buffer_head *lsbd_bread(struct lsbd *p, int block, int size)
 {
 	return bread(p->dev, block * p->sectors_per_block, size);
 }
 
+static struct buffer_head *lsbd_get_buffer(struct lsbd *p)
+{
+	struct buffer_head *bh;
+
+	do {
+		bh = kmem_cache_alloc(bh_cachep, SLAB_NOIO);
+		if (bh)
+			break;
+
+		run_task_queue(&tq_disk);
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ / 10);
+	} while (1);
+	memset(bh, 0, sizeof(*bh));
+
+	bh->b_size = p->sector_size;
+	bh->b_dev = p->dev;
+	bh->b_state = (1 << BH_Req) | (1 << BH_Mapped);
+
+	/*
+	 * easy way out, although it does waste some memory for < PAGE_SIZE
+	 * blocks... if highmem bounce buffering can get away with it,
+	 * so can we :-)
+	 */
+	do {
+		bh->b_page = alloc_page(GFP_NOIO);
+		if (bh->b_page)
+			break;
+
+		run_task_queue(&tq_disk);
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ / 10);
+	} while (1);
+
+	bh->b_data = page_address(bh->b_page);
+	init_waitqueue_head(&bh->b_wait);
+	bh->b_rdev = p->dev;
+	atomic_set(&bh->b_count, 1);
+
+	lsbd_debug(p, "allocated buffer %p\n", bh);
+	return bh;
+}
+
+static void lsbd_put_buffer(struct lsbd *p, struct buffer_head *bh)
+{
+	if (bh) {
+		__free_page(bh->b_page);
+		kmem_cache_free(bh_cachep, bh);
+		lsbd_debug(p, "free buffer %p\n", bh);
+	}
+}
+
 /**
- * lsbs_sread - read data sector
+ * lsbs_getblk - allocate LSBD buffer head
+ * @p: LSBD device
+ * @block: block number
+ * @sector: sector offset in block
+ *
+ * This function NULL on failure; pointer to allocated buffer otherwise.
+ */
+struct buffer_head *lsbd_getblk(struct lsbd *p, int block, unsigned int sector)
+{
+	struct buffer_head *bh;
+
+	bh = lsbd_get_buffer(p);
+	bh->b_blocknr = block * p->sectors_per_block + sector;
+	bh->b_rsector = (block * p->sectors_per_block + sector)
+		* (p->sector_size >> 9);
+	return bh;
+}
+
+/*
+ * Default synchronous end-of-IO handler..  Just mark it up-to-date and
+ * unlock the buffer. This is what ll_rw_block uses too.
+ */
+void lsbd_end_buffer_io_sync(struct buffer_head *bh, int uptodate)
+{
+	mark_buffer_uptodate(bh, uptodate);
+	unlock_buffer(bh);
+	put_bh(bh);
+}
+
+/**
+ * lsbs_sread - read data
  * @p: LSBD device
  * @block: block number
  * @sector: sector offset in block
- * @size: number of bytes to read
  *
  * This function NULL on failure; pointer to allocated buffer otherwise.
  */
-struct buffer_head *lsbd_sread(struct lsbd *p, int block, unsigned int sector,
-		int size)
+struct buffer_head *lsbd_sread(struct lsbd *p, int block, unsigned int sector)
 {
-	return bread(p->dev, block * p->sectors_per_block + sector + 1, size);
+	struct buffer_head *bh;
+
+	lsbd_debug(p, "sector %d.%d: getblk\n", block, sector);
+	bh = lsbd_getblk(p, block, sector);
+	if (buffer_uptodate(bh)) {
+		lsbd_debug(p, "sector %d.%d: already uptodate\n",
+				block, sector);
+		return bh;
+	}
+	set_bit(BH_Sync, &bh->b_state);
+	lsbd_debug(p, "sector %d.%d: read\n", block, sector);
+
+	lock_buffer(bh);
+	bh->b_end_io = lsbd_end_buffer_io_sync;
+	set_bit(BH_Req, &bh->b_state);
+	set_bit(BH_Launder, &bh->b_state);
+	get_bh(bh);
+	generic_make_request(READ, bh);
+
+	lsbd_debug(p, "sector %d.%d: wait...\n", block, sector);
+	wait_on_buffer(bh);
+	if (buffer_uptodate(bh)) {
+		lsbd_debug(p, "sector %d.%d: ok\n", block, sector);
+		return bh;
+	}
+	lsbd_debug(p, "sector %d.%d: I/O error\n", block, sector);
+	brelse(bh);
+	lsbd_put_buffer(p, bh);
+	return NULL;
 }
 
 /**
@@ -337,7 +447,7 @@ static int lsbd_find_current_block(struct lsbd *p)
 	 */
 	for (i = 0; i < p->blocks; i++) {
 		reads++;
-		bh = lsbd_bread(p, i, sizeof(*b));
+		bh = lsbd_bread(p, i, p->sector_size);
 		if (bh == NULL)
 			continue;
 
@@ -413,7 +523,7 @@ static int lsbd_find_current_block(struct lsbd *p)
 			p->cur_block, p->epoch, reads);
 	return 0;
 }
-				
+
 /**
  * lsbd_psector_valid - check if physical sector identifier is valid
  * @p: LSBD device
@@ -1031,13 +1141,9 @@ static int lsbd_queue_bh(struct lsbd *p, struct buffer_head *bh,
 		return -EINVAL;
 
 	if (!move) {
-		if (current == p->tsk) {
-			lsbd_info(p, "%s: BUG?\n", current->comm);
-			dump_stack();
-		} else {
-			lsbd_info(p, "%s: waiting on request\n", current->comm);
-			down(&p->req_sem);
-		}
+		BUG_ON(current == p->tsk);
+		lsbd_debug(p, "%s: waiting on request\n", current->comm);
+		down(&p->req_sem);
 	}
 
 	r = kmalloc(sizeof(*r), GFP_NOIO);
@@ -1170,7 +1276,7 @@ void lsbd_clean_block(struct lsbd *p)
 	if (block >= p->blocks)
 		block = 0;
 
-	bh = lsbd_bread(p, block, p->sector_size);
+	bh = lsbd_sread(p, block, 0);
 	if (bh == NULL)
 		goto out;
 	b = (void *) bh->b_data;
@@ -1200,7 +1306,7 @@ void lsbd_clean_block(struct lsbd *p)
 		if (sector_id >= p->lsectors)
 			continue;
 		if (p->lcache[sector_id] == s) {
-			rbh = lsbd_sread(p, block, i, p->sector_size);
+			rbh = lsbd_sread(p, block, i + 1);
 			if (rbh == NULL)
 				continue;
 
@@ -1209,10 +1315,12 @@ void lsbd_clean_block(struct lsbd *p)
 			if (ret) {
 				unlock_buffer(rbh);
 				brelse(rbh);
+				lsbd_put_buffer(p, rbh);
 			}
 		}
 	}
 	brelse(bh);
+	lsbd_put_buffer(p, bh);
 
 out:
 	p->clean_block = block;
@@ -1319,7 +1427,6 @@ static int lsbd_write_block(struct lsbd *p)
 	struct lsbd_block *b;
 	struct lsbd_sect *sects;
 	struct lsbd_request *r;
-	unsigned int base_sector;
 	unsigned int i;
 
 	p->cur_block++;
@@ -1338,11 +1445,8 @@ static int lsbd_write_block(struct lsbd *p)
 		return -ENOMEM;
 	}
 
-	base_sector = p->cur_block * p->sectors_per_block;
 	for (i = 0; i < p->sectors_per_block; i++) {
-		lsbd_debug(p, "sector: %d, block: %d\n", base_sector + i,
-				(base_sector + i));
-		bh[i] = getblk(p->dev, base_sector + i, p->sector_size);
+		bh[i] = lsbd_getblk(p, p->cur_block, i);
 		BUG_ON(bh[i] == NULL);
 		lock_buffer(bh[i]);
 	}
@@ -1376,6 +1480,7 @@ static int lsbd_write_block(struct lsbd *p)
 			if (move) {
 				unlock_buffer(r->bh);
 				brelse(r->bh);
+				lsbd_put_buffer(p, r->bh);
 				r->bh = NULL;
 			}
 			wbh[i + 1] = r->bh;
@@ -1398,15 +1503,20 @@ static int lsbd_write_block(struct lsbd *p)
 	lsbd_block_commit(p, b);
 	for (i = 0; i < p->sectors_per_block; i++) {
 		mark_buffer_uptodate(bh[i], 1);
-		unlock_buffer(bh[i]);
-		mark_buffer_dirty(bh[i]);
+//		unlock_buffer(bh[i]);
+//		mark_buffer_dirty(bh[i]);
+		bh[i]->b_end_io = lsbd_end_buffer_io_sync;
+		set_bit(BH_Req, &bh[i]->b_state);
+		set_bit(BH_Launder, &bh[i]->b_state);
+		get_bh(bh[i]);
+		generic_make_request(WRITE, bh[i]);
 	}
-	ll_rw_block(WRITE, p->sectors_per_block, bh);
 
 	lsbd_debug(p, "waiting for write\n");
 	for (i = 0; i < p->sectors_per_block; i++) {
 		wait_on_buffer(bh[i]);
 		brelse(bh[i]);
+		lsbd_put_buffer(p, bh[i]);
 
 		if (wbh[i] != NULL)
 			wbh[i]->b_end_io(wbh[i], 1);
@@ -1466,7 +1576,9 @@ static int lsbd_thread(void *data)
 		current->state = TASK_RUNNING;
 		remove_wait_queue(&p->wqueue_wait, &wait);
 
-		lsbd_debug(p, "lsbd_thread: reqs %d\n", len);
+		lsbd_debug(p, "lsbd_thread: %s %s\n",
+				want_write ? "want_write" : "",
+				want_clean ? "want_clean" : "");
 
 		if (signal_pending(current)) {
 			spin_lock(&current->sigmask_lock);
-- 
1.8.4.652.g0d6e0ce

