Skip to content

Commit

Permalink
ps3flash: Cache the last accessed FLASH chunk
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Geert Uytterhoeven authored and Benjamin Herrenschmidt committed Jun 15, 2009
1 parent a4e623f commit 6bd57f2
Showing 1 changed file with 104 additions and 105 deletions.
209 changes: 104 additions & 105 deletions drivers/char/ps3flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -48,33 +51,55 @@ static ssize_t ps3flash_read_write_sectors(struct ps3_storage_device *dev,
__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;

res = ps3flash_read_write_sectors(dev, dev->bounce_lpar, priv->tag,
priv->chunk_sectors, 1);
if (res)
return res;

lpar = dev->bounce_lpar + sector_offset * dev->blk_size;
return ps3flash_read_write_sectors(dev, lpar, start_sector, sectors,
0);
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)
Expand Down Expand Up @@ -109,8 +134,8 @@ static ssize_t ps3flash_read(char __user *userbuf, void *kernelbuf,
{
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;

Expand All @@ -130,30 +155,29 @@ static ssize_t ps3flash_read(char __user *userbuf, void *kernelbuf,
}

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;
Expand All @@ -167,26 +191,25 @@ static ssize_t ps3flash_read(char __user *userbuf, void *kernelbuf,

*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,
const void *kernelbuf, size_t count, loff_t *pos)
{
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,
Expand All @@ -204,71 +227,23 @@ static ssize_t ps3flash_write(const char __user *userbuf,
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);
Expand All @@ -284,16 +259,14 @@ static ssize_t ps3flash_write(const char __user *userbuf,
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);

Expand Down Expand Up @@ -324,9 +297,31 @@ static ssize_t ps3flash_kernel_read(void *buf, size_t count, loff_t pos)
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)
{
Expand Down Expand Up @@ -356,6 +351,8 @@ static const struct file_operations ps3flash_fops = {
.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 = {
Expand Down Expand Up @@ -411,9 +408,11 @@ static int __devinit ps3flash_probe(struct ps3_system_bus_device *_dev)

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)
Expand Down

0 comments on commit 6bd57f2

Please sign in to comment.