ps3flash: Cache the last accessed FLASH chunk

Add support for caching, to reduce FLASH wear when writing using small
blocksizes. As we also don't care anymore about heads and tails in case of
partial writes, this greatly simplifies the code for handling writes.

Note: We don't bother caching reads smaller than the FLASH chunk size
(256 KiB).

Signed-off-by: Geert Uytterhoeven <Geert.Uytterhoeven@sonycom.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
diff --git a/drivers/char/ps3flash.c b/drivers/char/ps3flash.c
index f7f21f4..d884d31 100644
--- a/drivers/char/ps3flash.c
+++ b/drivers/char/ps3flash.c
@@ -33,13 +33,16 @@
 
 struct ps3flash_private {
 	struct mutex mutex;	/* Bounce buffer mutex */
+	u64 chunk_sectors;
+	int tag;		/* Start sector of buffer, -1 if invalid */
+	bool dirty;
 };
 
 static struct ps3_storage_device *ps3flash_dev;
 
-static ssize_t ps3flash_read_write_sectors(struct ps3_storage_device *dev,
-					   u64 lpar, u64 start_sector,
-					   u64 sectors, int write)
+static int ps3flash_read_write_sectors(struct ps3_storage_device *dev,
+				       u64 lpar, u64 start_sector, u64 sectors,
+				       int write)
 {
 	u64 res = ps3stor_read_write_sectors(dev, lpar, start_sector, sectors,
 					     write);
@@ -48,33 +51,55 @@
 			__LINE__, write ? "write" : "read", res);
 		return -EIO;
 	}
-	return sectors;
+	return 0;
 }
 
-static ssize_t ps3flash_read_sectors(struct ps3_storage_device *dev,
-				     u64 start_sector, u64 sectors,
-				     unsigned int sector_offset)
+static int ps3flash_writeback(struct ps3_storage_device *dev)
 {
-	u64 max_sectors, lpar;
+	struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
+	int res;
 
-	max_sectors = dev->bounce_size / dev->blk_size;
-	if (sectors > max_sectors) {
-		dev_dbg(&dev->sbd.core, "%s:%u Limiting sectors to %llu\n",
-			__func__, __LINE__, max_sectors);
-		sectors = max_sectors;
-	}
+	if (!priv->dirty || priv->tag < 0)
+		return 0;
 
-	lpar = dev->bounce_lpar + sector_offset * dev->blk_size;
-	return ps3flash_read_write_sectors(dev, lpar, start_sector, sectors,
-					   0);
+	res = ps3flash_read_write_sectors(dev, dev->bounce_lpar, priv->tag,
+					  priv->chunk_sectors, 1);
+	if (res)
+		return res;
+
+	priv->dirty = false;
+	return 0;
 }
 
-static ssize_t ps3flash_write_chunk(struct ps3_storage_device *dev,
-				    u64 start_sector)
+static int ps3flash_fetch(struct ps3_storage_device *dev, u64 start_sector,
+			  u64 sectors)
 {
-       u64 sectors = dev->bounce_size / dev->blk_size;
-       return ps3flash_read_write_sectors(dev, dev->bounce_lpar, start_sector,
-					  sectors, 1);
+	struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
+	unsigned int tag, offset;
+	u64 lpar;
+	int res;
+
+	offset = start_sector % priv->chunk_sectors;
+	tag = start_sector - offset;
+	if (tag == priv->tag)
+		return 0;
+
+	res = ps3flash_writeback(dev);
+	if (res)
+		return res;
+
+	priv->tag = -1;
+
+	lpar = dev->bounce_lpar + offset * dev->blk_size;
+	res = ps3flash_read_write_sectors(dev, lpar, start_sector, sectors, 0);
+	if (res)
+		return res;
+
+	/* We don't bother caching reads smaller than the chunk size */
+	if (sectors == priv->chunk_sectors)
+		priv->tag = tag;
+
+	return 0;
 }
 
 static loff_t ps3flash_llseek(struct file *file, loff_t offset, int origin)
@@ -109,8 +134,8 @@
 {
 	struct ps3_storage_device *dev = ps3flash_dev;
 	struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
-	u64 size, start_sector, end_sector, offset;
-	ssize_t sectors_read;
+	u64 size, start_sector, end_sector, offset, sectors;
+	int res;
 	size_t remaining, n;
 	const void *src;
 
@@ -130,30 +155,29 @@
 	}
 
 	start_sector = *pos / dev->blk_size;
-	offset = *pos % dev->blk_size;
+	offset = *pos % dev->bounce_size;
 	end_sector = DIV_ROUND_UP(*pos + count, dev->blk_size);
 
 	remaining = count;
 	do {
+		sectors = min(end_sector - start_sector,
+			      priv->chunk_sectors -
+			      start_sector % priv->chunk_sectors);
+
 		mutex_lock(&priv->mutex);
 
-		sectors_read = ps3flash_read_sectors(dev, start_sector,
-						     end_sector-start_sector,
-						     0);
-		if (sectors_read < 0) {
-			mutex_unlock(&priv->mutex);
+		res = ps3flash_fetch(dev, start_sector, sectors);
+		if (res)
 			goto fail;
-		}
 
-		n = min_t(u64, remaining, sectors_read*dev->blk_size-offset);
-		src = dev->bounce_buf+offset;
+		n = min_t(u64, remaining, dev->bounce_size - offset);
+		src = dev->bounce_buf + offset;
 		dev_dbg(&dev->sbd.core,
 			"%s:%u: copy %lu bytes from 0x%p to U0x%p/K0x%p\n",
 			__func__, __LINE__, n, src, userbuf, kernelbuf);
 		if (userbuf) {
 			if (copy_to_user(userbuf, src, n)) {
-				mutex_unlock(&priv->mutex);
-				sectors_read = -EFAULT;
+				res = -EFAULT;
 				goto fail;
 			}
 			userbuf += n;
@@ -167,14 +191,15 @@
 
 		*pos += n;
 		remaining -= n;
-		start_sector += sectors_read;
+		start_sector += sectors;
 		offset = 0;
 	} while (remaining > 0);
 
 	return count;
 
 fail:
-	return sectors_read;
+	mutex_unlock(&priv->mutex);
+	return res;
 }
 
 static ssize_t ps3flash_write(const char __user *userbuf,
@@ -182,11 +207,9 @@
 {
 	struct ps3_storage_device *dev = ps3flash_dev;
 	struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
-	u64 size, chunk_sectors, start_write_sector, end_write_sector,
-	    end_read_sector, start_read_sector, head, tail, offset;
-	ssize_t res;
+	u64 size, sector, offset;
+	int res = 0;
 	size_t remaining, n;
-	unsigned int sec_off;
 	void *dst;
 
 	dev_dbg(&dev->sbd.core,
@@ -204,71 +227,23 @@
 		count = size - *pos;
 	}
 
-	chunk_sectors = dev->bounce_size / dev->blk_size;
-
-	start_write_sector = *pos / dev->bounce_size * chunk_sectors;
+	sector = *pos / dev->bounce_size * priv->chunk_sectors;
 	offset = *pos % dev->bounce_size;
-	end_write_sector = DIV_ROUND_UP(*pos + count, dev->bounce_size) *
-			   chunk_sectors;
-
-	end_read_sector = DIV_ROUND_UP(*pos, dev->blk_size);
-	start_read_sector = (*pos + count) / dev->blk_size;
-
-	/*
-	 * As we have to write in 256 KiB chunks, while we can read in blk_size
-	 * (usually 512 bytes) chunks, we perform the following steps:
-	 *   1. Read from start_write_sector to end_read_sector ("head")
-	 *   2. Read from start_read_sector to end_write_sector ("tail")
-	 *   3. Copy data to buffer
-	 *   4. Write from start_write_sector to end_write_sector
-	 * All of this is complicated by using only one 256 KiB bounce buffer.
-	 */
-
-	head = end_read_sector - start_write_sector;
-	tail = end_write_sector - start_read_sector;
 
 	remaining = count;
 	do {
+		n = min_t(u64, remaining, dev->bounce_size - offset);
+
 		mutex_lock(&priv->mutex);
 
-		if (end_read_sector >= start_read_sector) {
-			/* Merge head and tail */
-			dev_dbg(&dev->sbd.core,
-				"Merged head and tail: %llu sectors at %llu\n",
-				chunk_sectors, start_write_sector);
-			res = ps3flash_read_sectors(dev, start_write_sector,
-						    chunk_sectors, 0);
-			if (res < 0)
-				goto fail;
-		} else {
-			if (head) {
-				/* Read head */
-				dev_dbg(&dev->sbd.core,
-					"head: %llu sectors at %llu\n", head,
-					start_write_sector);
-				res = ps3flash_read_sectors(dev,
-							    start_write_sector,
-							    head, 0);
-				if (res < 0)
-					goto fail;
-			}
-			if (start_read_sector <
-			    start_write_sector+chunk_sectors) {
-				/* Read tail */
-				dev_dbg(&dev->sbd.core,
-					"tail: %llu sectors at %llu\n", tail,
-					start_read_sector);
-				sec_off = start_read_sector-start_write_sector;
-				res = ps3flash_read_sectors(dev,
-							    start_read_sector,
-							    tail, sec_off);
-				if (res < 0)
-					goto fail;
-			}
-		}
+		if (n != dev->bounce_size)
+			res = ps3flash_fetch(dev, sector, priv->chunk_sectors);
+		else if (sector != priv->tag)
+			res = ps3flash_writeback(dev);
+		if (res)
+			goto fail;
 
-		n = min_t(u64, remaining, dev->bounce_size-offset);
-		dst = dev->bounce_buf+offset;
+		dst = dev->bounce_buf + offset;
 		dev_dbg(&dev->sbd.core,
 			"%s:%u: copy %lu bytes from U0x%p/K0x%p to 0x%p\n",
 			__func__, __LINE__, n, userbuf, kernelbuf, dst);
@@ -284,16 +259,14 @@
 			kernelbuf += n;
 		}
 
-		res = ps3flash_write_chunk(dev, start_write_sector);
-		if (res < 0)
-			goto fail;
+		priv->tag = sector;
+		priv->dirty = true;
 
 		mutex_unlock(&priv->mutex);
 
 		*pos += n;
 		remaining -= n;
-		start_write_sector += chunk_sectors;
-		head = 0;
+		sector += priv->chunk_sectors;
 		offset = 0;
 	} while (remaining > 0);
 
@@ -324,9 +297,31 @@
 static ssize_t ps3flash_kernel_write(const void *buf, size_t count,
 				     loff_t pos)
 {
-	return ps3flash_write(NULL, buf, count, &pos);
+	ssize_t res;
+	int wb;
+
+	res = ps3flash_write(NULL, buf, count, &pos);
+	if (res < 0)
+		return res;
+
+	/* Make kernel writes synchronous */
+	wb = ps3flash_writeback(ps3flash_dev);
+	if (wb)
+		return wb;
+
+	return res;
 }
 
+static int ps3flash_flush(struct file *file, fl_owner_t id)
+{
+	return ps3flash_writeback(ps3flash_dev);
+}
+
+static int ps3flash_fsync(struct file *file, struct dentry *dentry,
+			  int datasync)
+{
+	return ps3flash_writeback(ps3flash_dev);
+}
 
 static irqreturn_t ps3flash_interrupt(int irq, void *data)
 {
@@ -356,6 +351,8 @@
 	.llseek	= ps3flash_llseek,
 	.read	= ps3flash_user_read,
 	.write	= ps3flash_user_write,
+	.flush	= ps3flash_flush,
+	.fsync	= ps3flash_fsync,
 };
 
 static const struct ps3_os_area_flash_ops ps3flash_kernel_ops = {
@@ -411,9 +408,11 @@
 
 	ps3_system_bus_set_drvdata(&dev->sbd, priv);
 	mutex_init(&priv->mutex);
+	priv->tag = -1;
 
 	dev->bounce_size = ps3flash_bounce_buffer.size;
 	dev->bounce_buf = ps3flash_bounce_buffer.address;
+	priv->chunk_sectors = dev->bounce_size / dev->blk_size;
 
 	error = ps3stor_setup(dev, ps3flash_interrupt);
 	if (error)