diff options
author | Geert Uytterhoeven <Geert.Uytterhoeven@sonycom.com> | 2009-06-10 00:39:07 -0400 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2009-06-15 02:47:27 -0400 |
commit | 6bd57f2e5db408e0dfdb3bf052d58c4e7b18ed3c (patch) | |
tree | 768510bab8b596ff499f796ea8c08077074eb5d6 /drivers/char | |
parent | a4e623fbc9b201930abcf78df6db5e49aa8e00cb (diff) |
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>
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/ps3flash.c | 209 |
1 files changed, 104 insertions, 105 deletions
diff --git a/drivers/char/ps3flash.c b/drivers/char/ps3flash.c index f7f21f47ea02..d884d31491d2 100644 --- a/drivers/char/ps3flash.c +++ b/drivers/char/ps3flash.c | |||
@@ -33,13 +33,16 @@ | |||
33 | 33 | ||
34 | struct ps3flash_private { | 34 | struct ps3flash_private { |
35 | struct mutex mutex; /* Bounce buffer mutex */ | 35 | struct mutex mutex; /* Bounce buffer mutex */ |
36 | u64 chunk_sectors; | ||
37 | int tag; /* Start sector of buffer, -1 if invalid */ | ||
38 | bool dirty; | ||
36 | }; | 39 | }; |
37 | 40 | ||
38 | static struct ps3_storage_device *ps3flash_dev; | 41 | static struct ps3_storage_device *ps3flash_dev; |
39 | 42 | ||
40 | static ssize_t ps3flash_read_write_sectors(struct ps3_storage_device *dev, | 43 | static int ps3flash_read_write_sectors(struct ps3_storage_device *dev, |
41 | u64 lpar, u64 start_sector, | 44 | u64 lpar, u64 start_sector, u64 sectors, |
42 | u64 sectors, int write) | 45 | int write) |
43 | { | 46 | { |
44 | u64 res = ps3stor_read_write_sectors(dev, lpar, start_sector, sectors, | 47 | u64 res = ps3stor_read_write_sectors(dev, lpar, start_sector, sectors, |
45 | write); | 48 | write); |
@@ -48,33 +51,55 @@ static ssize_t ps3flash_read_write_sectors(struct ps3_storage_device *dev, | |||
48 | __LINE__, write ? "write" : "read", res); | 51 | __LINE__, write ? "write" : "read", res); |
49 | return -EIO; | 52 | return -EIO; |
50 | } | 53 | } |
51 | return sectors; | 54 | return 0; |
52 | } | 55 | } |
53 | 56 | ||
54 | static ssize_t ps3flash_read_sectors(struct ps3_storage_device *dev, | 57 | static int ps3flash_writeback(struct ps3_storage_device *dev) |
55 | u64 start_sector, u64 sectors, | ||
56 | unsigned int sector_offset) | ||
57 | { | 58 | { |
58 | u64 max_sectors, lpar; | 59 | struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); |
60 | int res; | ||
59 | 61 | ||
60 | max_sectors = dev->bounce_size / dev->blk_size; | 62 | if (!priv->dirty || priv->tag < 0) |
61 | if (sectors > max_sectors) { | 63 | return 0; |
62 | dev_dbg(&dev->sbd.core, "%s:%u Limiting sectors to %llu\n", | 64 | |
63 | __func__, __LINE__, max_sectors); | 65 | res = ps3flash_read_write_sectors(dev, dev->bounce_lpar, priv->tag, |
64 | sectors = max_sectors; | 66 | priv->chunk_sectors, 1); |
65 | } | 67 | if (res) |
68 | return res; | ||
66 | 69 | ||
67 | lpar = dev->bounce_lpar + sector_offset * dev->blk_size; | 70 | priv->dirty = false; |
68 | return ps3flash_read_write_sectors(dev, lpar, start_sector, sectors, | 71 | return 0; |
69 | 0); | ||
70 | } | 72 | } |
71 | 73 | ||
72 | static ssize_t ps3flash_write_chunk(struct ps3_storage_device *dev, | 74 | static int ps3flash_fetch(struct ps3_storage_device *dev, u64 start_sector, |
73 | u64 start_sector) | 75 | u64 sectors) |
74 | { | 76 | { |
75 | u64 sectors = dev->bounce_size / dev->blk_size; | 77 | struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); |
76 | return ps3flash_read_write_sectors(dev, dev->bounce_lpar, start_sector, | 78 | unsigned int tag, offset; |
77 | sectors, 1); | 79 | u64 lpar; |
80 | int res; | ||
81 | |||
82 | offset = start_sector % priv->chunk_sectors; | ||
83 | tag = start_sector - offset; | ||
84 | if (tag == priv->tag) | ||
85 | return 0; | ||
86 | |||
87 | res = ps3flash_writeback(dev); | ||
88 | if (res) | ||
89 | return res; | ||
90 | |||
91 | priv->tag = -1; | ||
92 | |||
93 | lpar = dev->bounce_lpar + offset * dev->blk_size; | ||
94 | res = ps3flash_read_write_sectors(dev, lpar, start_sector, sectors, 0); | ||
95 | if (res) | ||
96 | return res; | ||
97 | |||
98 | /* We don't bother caching reads smaller than the chunk size */ | ||
99 | if (sectors == priv->chunk_sectors) | ||
100 | priv->tag = tag; | ||
101 | |||
102 | return 0; | ||
78 | } | 103 | } |
79 | 104 | ||
80 | static loff_t ps3flash_llseek(struct file *file, loff_t offset, int origin) | 105 | static loff_t ps3flash_llseek(struct file *file, loff_t offset, int origin) |
@@ -109,8 +134,8 @@ static ssize_t ps3flash_read(char __user *userbuf, void *kernelbuf, | |||
109 | { | 134 | { |
110 | struct ps3_storage_device *dev = ps3flash_dev; | 135 | struct ps3_storage_device *dev = ps3flash_dev; |
111 | struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); | 136 | struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); |
112 | u64 size, start_sector, end_sector, offset; | 137 | u64 size, start_sector, end_sector, offset, sectors; |
113 | ssize_t sectors_read; | 138 | int res; |
114 | size_t remaining, n; | 139 | size_t remaining, n; |
115 | const void *src; | 140 | const void *src; |
116 | 141 | ||
@@ -130,30 +155,29 @@ static ssize_t ps3flash_read(char __user *userbuf, void *kernelbuf, | |||
130 | } | 155 | } |
131 | 156 | ||
132 | start_sector = *pos / dev->blk_size; | 157 | start_sector = *pos / dev->blk_size; |
133 | offset = *pos % dev->blk_size; | 158 | offset = *pos % dev->bounce_size; |
134 | end_sector = DIV_ROUND_UP(*pos + count, dev->blk_size); | 159 | end_sector = DIV_ROUND_UP(*pos + count, dev->blk_size); |
135 | 160 | ||
136 | remaining = count; | 161 | remaining = count; |
137 | do { | 162 | do { |
163 | sectors = min(end_sector - start_sector, | ||
164 | priv->chunk_sectors - | ||
165 | start_sector % priv->chunk_sectors); | ||
166 | |||
138 | mutex_lock(&priv->mutex); | 167 | mutex_lock(&priv->mutex); |
139 | 168 | ||
140 | sectors_read = ps3flash_read_sectors(dev, start_sector, | 169 | res = ps3flash_fetch(dev, start_sector, sectors); |
141 | end_sector-start_sector, | 170 | if (res) |
142 | 0); | ||
143 | if (sectors_read < 0) { | ||
144 | mutex_unlock(&priv->mutex); | ||
145 | goto fail; | 171 | goto fail; |
146 | } | ||
147 | 172 | ||
148 | n = min_t(u64, remaining, sectors_read*dev->blk_size-offset); | 173 | n = min_t(u64, remaining, dev->bounce_size - offset); |
149 | src = dev->bounce_buf+offset; | 174 | src = dev->bounce_buf + offset; |
150 | dev_dbg(&dev->sbd.core, | 175 | dev_dbg(&dev->sbd.core, |
151 | "%s:%u: copy %lu bytes from 0x%p to U0x%p/K0x%p\n", | 176 | "%s:%u: copy %lu bytes from 0x%p to U0x%p/K0x%p\n", |
152 | __func__, __LINE__, n, src, userbuf, kernelbuf); | 177 | __func__, __LINE__, n, src, userbuf, kernelbuf); |
153 | if (userbuf) { | 178 | if (userbuf) { |
154 | if (copy_to_user(userbuf, src, n)) { | 179 | if (copy_to_user(userbuf, src, n)) { |
155 | mutex_unlock(&priv->mutex); | 180 | res = -EFAULT; |
156 | sectors_read = -EFAULT; | ||
157 | goto fail; | 181 | goto fail; |
158 | } | 182 | } |
159 | userbuf += n; | 183 | userbuf += n; |
@@ -167,14 +191,15 @@ static ssize_t ps3flash_read(char __user *userbuf, void *kernelbuf, | |||
167 | 191 | ||
168 | *pos += n; | 192 | *pos += n; |
169 | remaining -= n; | 193 | remaining -= n; |
170 | start_sector += sectors_read; | 194 | start_sector += sectors; |
171 | offset = 0; | 195 | offset = 0; |
172 | } while (remaining > 0); | 196 | } while (remaining > 0); |
173 | 197 | ||
174 | return count; | 198 | return count; |
175 | 199 | ||
176 | fail: | 200 | fail: |
177 | return sectors_read; | 201 | mutex_unlock(&priv->mutex); |
202 | return res; | ||
178 | } | 203 | } |
179 | 204 | ||
180 | static ssize_t ps3flash_write(const char __user *userbuf, | 205 | static ssize_t ps3flash_write(const char __user *userbuf, |
@@ -182,11 +207,9 @@ static ssize_t ps3flash_write(const char __user *userbuf, | |||
182 | { | 207 | { |
183 | struct ps3_storage_device *dev = ps3flash_dev; | 208 | struct ps3_storage_device *dev = ps3flash_dev; |
184 | struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); | 209 | struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd); |
185 | u64 size, chunk_sectors, start_write_sector, end_write_sector, | 210 | u64 size, sector, offset; |
186 | end_read_sector, start_read_sector, head, tail, offset; | 211 | int res = 0; |
187 | ssize_t res; | ||
188 | size_t remaining, n; | 212 | size_t remaining, n; |
189 | unsigned int sec_off; | ||
190 | void *dst; | 213 | void *dst; |
191 | 214 | ||
192 | dev_dbg(&dev->sbd.core, | 215 | dev_dbg(&dev->sbd.core, |
@@ -204,71 +227,23 @@ static ssize_t ps3flash_write(const char __user *userbuf, | |||
204 | count = size - *pos; | 227 | count = size - *pos; |
205 | } | 228 | } |
206 | 229 | ||
207 | chunk_sectors = dev->bounce_size / dev->blk_size; | 230 | sector = *pos / dev->bounce_size * priv->chunk_sectors; |
208 | |||
209 | start_write_sector = *pos / dev->bounce_size * chunk_sectors; | ||
210 | offset = *pos % dev->bounce_size; | 231 | offset = *pos % dev->bounce_size; |
211 | end_write_sector = DIV_ROUND_UP(*pos + count, dev->bounce_size) * | ||
212 | chunk_sectors; | ||
213 | |||
214 | end_read_sector = DIV_ROUND_UP(*pos, dev->blk_size); | ||
215 | start_read_sector = (*pos + count) / dev->blk_size; | ||
216 | |||
217 | /* | ||
218 | * As we have to write in 256 KiB chunks, while we can read in blk_size | ||
219 | * (usually 512 bytes) chunks, we perform the following steps: | ||
220 | * 1. Read from start_write_sector to end_read_sector ("head") | ||
221 | * 2. Read from start_read_sector to end_write_sector ("tail") | ||
222 | * 3. Copy data to buffer | ||
223 | * 4. Write from start_write_sector to end_write_sector | ||
224 | * All of this is complicated by using only one 256 KiB bounce buffer. | ||
225 | */ | ||
226 | |||
227 | head = end_read_sector - start_write_sector; | ||
228 | tail = end_write_sector - start_read_sector; | ||
229 | 232 | ||
230 | remaining = count; | 233 | remaining = count; |
231 | do { | 234 | do { |
235 | n = min_t(u64, remaining, dev->bounce_size - offset); | ||
236 | |||
232 | mutex_lock(&priv->mutex); | 237 | mutex_lock(&priv->mutex); |
233 | 238 | ||
234 | if (end_read_sector >= start_read_sector) { | 239 | if (n != dev->bounce_size) |
235 | /* Merge head and tail */ | 240 | res = ps3flash_fetch(dev, sector, priv->chunk_sectors); |
236 | dev_dbg(&dev->sbd.core, | 241 | else if (sector != priv->tag) |
237 | "Merged head and tail: %llu sectors at %llu\n", | 242 | res = ps3flash_writeback(dev); |
238 | chunk_sectors, start_write_sector); | 243 | if (res) |
239 | res = ps3flash_read_sectors(dev, start_write_sector, | 244 | goto fail; |
240 | chunk_sectors, 0); | ||
241 | if (res < 0) | ||
242 | goto fail; | ||
243 | } else { | ||
244 | if (head) { | ||
245 | /* Read head */ | ||
246 | dev_dbg(&dev->sbd.core, | ||
247 | "head: %llu sectors at %llu\n", head, | ||
248 | start_write_sector); | ||
249 | res = ps3flash_read_sectors(dev, | ||
250 | start_write_sector, | ||
251 | head, 0); | ||
252 | if (res < 0) | ||
253 | goto fail; | ||
254 | } | ||
255 | if (start_read_sector < | ||
256 | start_write_sector+chunk_sectors) { | ||
257 | /* Read tail */ | ||
258 | dev_dbg(&dev->sbd.core, | ||
259 | "tail: %llu sectors at %llu\n", tail, | ||
260 | start_read_sector); | ||
261 | sec_off = start_read_sector-start_write_sector; | ||
262 | res = ps3flash_read_sectors(dev, | ||
263 | start_read_sector, | ||
264 | tail, sec_off); | ||
265 | if (res < 0) | ||
266 | goto fail; | ||
267 | } | ||
268 | } | ||
269 | 245 | ||
270 | n = min_t(u64, remaining, dev->bounce_size-offset); | 246 | dst = dev->bounce_buf + offset; |
271 | dst = dev->bounce_buf+offset; | ||
272 | dev_dbg(&dev->sbd.core, | 247 | dev_dbg(&dev->sbd.core, |
273 | "%s:%u: copy %lu bytes from U0x%p/K0x%p to 0x%p\n", | 248 | "%s:%u: copy %lu bytes from U0x%p/K0x%p to 0x%p\n", |
274 | __func__, __LINE__, n, userbuf, kernelbuf, dst); | 249 | __func__, __LINE__, n, userbuf, kernelbuf, dst); |
@@ -284,16 +259,14 @@ static ssize_t ps3flash_write(const char __user *userbuf, | |||
284 | kernelbuf += n; | 259 | kernelbuf += n; |
285 | } | 260 | } |
286 | 261 | ||
287 | res = ps3flash_write_chunk(dev, start_write_sector); | 262 | priv->tag = sector; |
288 | if (res < 0) | 263 | priv->dirty = true; |
289 | goto fail; | ||
290 | 264 | ||
291 | mutex_unlock(&priv->mutex); | 265 | mutex_unlock(&priv->mutex); |
292 | 266 | ||
293 | *pos += n; | 267 | *pos += n; |
294 | remaining -= n; | 268 | remaining -= n; |
295 | start_write_sector += chunk_sectors; | 269 | sector += priv->chunk_sectors; |
296 | head = 0; | ||
297 | offset = 0; | 270 | offset = 0; |
298 | } while (remaining > 0); | 271 | } while (remaining > 0); |
299 | 272 | ||
@@ -324,9 +297,31 @@ static ssize_t ps3flash_kernel_read(void *buf, size_t count, loff_t pos) | |||
324 | static ssize_t ps3flash_kernel_write(const void *buf, size_t count, | 297 | static ssize_t ps3flash_kernel_write(const void *buf, size_t count, |
325 | loff_t pos) | 298 | loff_t pos) |
326 | { | 299 | { |
327 | return ps3flash_write(NULL, buf, count, &pos); | 300 | ssize_t res; |
301 | int wb; | ||
302 | |||
303 | res = ps3flash_write(NULL, buf, count, &pos); | ||
304 | if (res < 0) | ||
305 | return res; | ||
306 | |||
307 | /* Make kernel writes synchronous */ | ||
308 | wb = ps3flash_writeback(ps3flash_dev); | ||
309 | if (wb) | ||
310 | return wb; | ||
311 | |||
312 | return res; | ||
328 | } | 313 | } |
329 | 314 | ||
315 | static int ps3flash_flush(struct file *file, fl_owner_t id) | ||
316 | { | ||
317 | return ps3flash_writeback(ps3flash_dev); | ||
318 | } | ||
319 | |||
320 | static int ps3flash_fsync(struct file *file, struct dentry *dentry, | ||
321 | int datasync) | ||
322 | { | ||
323 | return ps3flash_writeback(ps3flash_dev); | ||
324 | } | ||
330 | 325 | ||
331 | static irqreturn_t ps3flash_interrupt(int irq, void *data) | 326 | static irqreturn_t ps3flash_interrupt(int irq, void *data) |
332 | { | 327 | { |
@@ -356,6 +351,8 @@ static const struct file_operations ps3flash_fops = { | |||
356 | .llseek = ps3flash_llseek, | 351 | .llseek = ps3flash_llseek, |
357 | .read = ps3flash_user_read, | 352 | .read = ps3flash_user_read, |
358 | .write = ps3flash_user_write, | 353 | .write = ps3flash_user_write, |
354 | .flush = ps3flash_flush, | ||
355 | .fsync = ps3flash_fsync, | ||
359 | }; | 356 | }; |
360 | 357 | ||
361 | static const struct ps3_os_area_flash_ops ps3flash_kernel_ops = { | 358 | static const struct ps3_os_area_flash_ops ps3flash_kernel_ops = { |
@@ -411,9 +408,11 @@ static int __devinit ps3flash_probe(struct ps3_system_bus_device *_dev) | |||
411 | 408 | ||
412 | ps3_system_bus_set_drvdata(&dev->sbd, priv); | 409 | ps3_system_bus_set_drvdata(&dev->sbd, priv); |
413 | mutex_init(&priv->mutex); | 410 | mutex_init(&priv->mutex); |
411 | priv->tag = -1; | ||
414 | 412 | ||
415 | dev->bounce_size = ps3flash_bounce_buffer.size; | 413 | dev->bounce_size = ps3flash_bounce_buffer.size; |
416 | dev->bounce_buf = ps3flash_bounce_buffer.address; | 414 | dev->bounce_buf = ps3flash_bounce_buffer.address; |
415 | priv->chunk_sectors = dev->bounce_size / dev->blk_size; | ||
417 | 416 | ||
418 | error = ps3stor_setup(dev, ps3flash_interrupt); | 417 | error = ps3stor_setup(dev, ps3flash_interrupt); |
419 | if (error) | 418 | if (error) |