blob: f7f21f47ea02f7fd6192e580c06a5951ccfa5a2d [file] [log] [blame]
Geert Uytterhoevenf9652632007-07-21 04:37:48 -07001/*
2 * PS3 FLASH ROM Storage Driver
3 *
4 * Copyright (C) 2007 Sony Computer Entertainment Inc.
5 * Copyright 2007 Sony Corp.
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published
9 * by the Free Software Foundation; version 2 of the License.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21#include <linux/fs.h>
22#include <linux/miscdevice.h>
23#include <linux/uaccess.h>
24
25#include <asm/lv1call.h>
26#include <asm/ps3stor.h>
27
28
29#define DEVICE_NAME "ps3flash"
30
31#define FLASH_BLOCK_SIZE (256*1024)
32
33
34struct ps3flash_private {
35 struct mutex mutex; /* Bounce buffer mutex */
36};
37
38static struct ps3_storage_device *ps3flash_dev;
39
40static ssize_t ps3flash_read_write_sectors(struct ps3_storage_device *dev,
41 u64 lpar, u64 start_sector,
42 u64 sectors, int write)
43{
44 u64 res = ps3stor_read_write_sectors(dev, lpar, start_sector, sectors,
45 write);
46 if (res) {
Stephen Rothwell4c33d2d2009-01-13 20:06:02 +000047 dev_err(&dev->sbd.core, "%s:%u: %s failed 0x%llx\n", __func__,
Geert Uytterhoevenf9652632007-07-21 04:37:48 -070048 __LINE__, write ? "write" : "read", res);
49 return -EIO;
50 }
51 return sectors;
52}
53
54static ssize_t ps3flash_read_sectors(struct ps3_storage_device *dev,
55 u64 start_sector, u64 sectors,
56 unsigned int sector_offset)
57{
58 u64 max_sectors, lpar;
59
60 max_sectors = dev->bounce_size / dev->blk_size;
61 if (sectors > max_sectors) {
Stephen Rothwell4c33d2d2009-01-13 20:06:02 +000062 dev_dbg(&dev->sbd.core, "%s:%u Limiting sectors to %llu\n",
Geert Uytterhoevenf9652632007-07-21 04:37:48 -070063 __func__, __LINE__, max_sectors);
64 sectors = max_sectors;
65 }
66
67 lpar = dev->bounce_lpar + sector_offset * dev->blk_size;
68 return ps3flash_read_write_sectors(dev, lpar, start_sector, sectors,
69 0);
70}
71
72static ssize_t ps3flash_write_chunk(struct ps3_storage_device *dev,
73 u64 start_sector)
74{
75 u64 sectors = dev->bounce_size / dev->blk_size;
76 return ps3flash_read_write_sectors(dev, dev->bounce_lpar, start_sector,
77 sectors, 1);
78}
79
80static loff_t ps3flash_llseek(struct file *file, loff_t offset, int origin)
81{
82 struct ps3_storage_device *dev = ps3flash_dev;
83 loff_t res;
84
85 mutex_lock(&file->f_mapping->host->i_mutex);
86 switch (origin) {
87 case 1:
88 offset += file->f_pos;
89 break;
90 case 2:
91 offset += dev->regions[dev->region_idx].size*dev->blk_size;
92 break;
93 }
94 if (offset < 0) {
95 res = -EINVAL;
96 goto out;
97 }
98
99 file->f_pos = offset;
100 res = file->f_pos;
101
102out:
103 mutex_unlock(&file->f_mapping->host->i_mutex);
104 return res;
105}
106
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000107static ssize_t ps3flash_read(char __user *userbuf, void *kernelbuf,
108 size_t count, loff_t *pos)
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700109{
110 struct ps3_storage_device *dev = ps3flash_dev;
Geert Uytterhoeven559dc87f52009-06-10 04:38:55 +0000111 struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700112 u64 size, start_sector, end_sector, offset;
113 ssize_t sectors_read;
114 size_t remaining, n;
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000115 const void *src;
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700116
117 dev_dbg(&dev->sbd.core,
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000118 "%s:%u: Reading %zu bytes at position %lld to U0x%p/K0x%p\n",
119 __func__, __LINE__, count, *pos, userbuf, kernelbuf);
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700120
121 size = dev->regions[dev->region_idx].size*dev->blk_size;
122 if (*pos >= size || !count)
123 return 0;
124
125 if (*pos + count > size) {
126 dev_dbg(&dev->sbd.core,
127 "%s:%u Truncating count from %zu to %llu\n", __func__,
128 __LINE__, count, size - *pos);
129 count = size - *pos;
130 }
131
132 start_sector = *pos / dev->blk_size;
133 offset = *pos % dev->blk_size;
134 end_sector = DIV_ROUND_UP(*pos + count, dev->blk_size);
135
136 remaining = count;
137 do {
138 mutex_lock(&priv->mutex);
139
140 sectors_read = ps3flash_read_sectors(dev, start_sector,
141 end_sector-start_sector,
142 0);
143 if (sectors_read < 0) {
144 mutex_unlock(&priv->mutex);
145 goto fail;
146 }
147
Stephen Rothwell4c33d2d2009-01-13 20:06:02 +0000148 n = min_t(u64, remaining, sectors_read*dev->blk_size-offset);
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000149 src = dev->bounce_buf+offset;
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700150 dev_dbg(&dev->sbd.core,
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000151 "%s:%u: copy %lu bytes from 0x%p to U0x%p/K0x%p\n",
152 __func__, __LINE__, n, src, userbuf, kernelbuf);
153 if (userbuf) {
154 if (copy_to_user(userbuf, src, n)) {
155 mutex_unlock(&priv->mutex);
156 sectors_read = -EFAULT;
157 goto fail;
158 }
159 userbuf += n;
160 }
161 if (kernelbuf) {
162 memcpy(kernelbuf, src, n);
163 kernelbuf += n;
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700164 }
165
166 mutex_unlock(&priv->mutex);
167
168 *pos += n;
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700169 remaining -= n;
170 start_sector += sectors_read;
171 offset = 0;
172 } while (remaining > 0);
173
174 return count;
175
176fail:
177 return sectors_read;
178}
179
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000180static ssize_t ps3flash_write(const char __user *userbuf,
181 const void *kernelbuf, size_t count, loff_t *pos)
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700182{
183 struct ps3_storage_device *dev = ps3flash_dev;
Geert Uytterhoeven559dc87f52009-06-10 04:38:55 +0000184 struct ps3flash_private *priv = ps3_system_bus_get_drvdata(&dev->sbd);
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700185 u64 size, chunk_sectors, start_write_sector, end_write_sector,
186 end_read_sector, start_read_sector, head, tail, offset;
187 ssize_t res;
188 size_t remaining, n;
189 unsigned int sec_off;
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000190 void *dst;
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700191
192 dev_dbg(&dev->sbd.core,
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000193 "%s:%u: Writing %zu bytes at position %lld from U0x%p/K0x%p\n",
194 __func__, __LINE__, count, *pos, userbuf, kernelbuf);
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700195
196 size = dev->regions[dev->region_idx].size*dev->blk_size;
197 if (*pos >= size || !count)
198 return 0;
199
200 if (*pos + count > size) {
201 dev_dbg(&dev->sbd.core,
202 "%s:%u Truncating count from %zu to %llu\n", __func__,
203 __LINE__, count, size - *pos);
204 count = size - *pos;
205 }
206
207 chunk_sectors = dev->bounce_size / dev->blk_size;
208
209 start_write_sector = *pos / dev->bounce_size * chunk_sectors;
210 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
230 remaining = count;
231 do {
232 mutex_lock(&priv->mutex);
233
234 if (end_read_sector >= start_read_sector) {
235 /* Merge head and tail */
236 dev_dbg(&dev->sbd.core,
Stephen Rothwell4c33d2d2009-01-13 20:06:02 +0000237 "Merged head and tail: %llu sectors at %llu\n",
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700238 chunk_sectors, start_write_sector);
239 res = ps3flash_read_sectors(dev, start_write_sector,
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,
Stephen Rothwell4c33d2d2009-01-13 20:06:02 +0000247 "head: %llu sectors at %llu\n", head,
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700248 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,
Stephen Rothwell4c33d2d2009-01-13 20:06:02 +0000259 "tail: %llu sectors at %llu\n", tail,
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700260 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
Stephen Rothwell4c33d2d2009-01-13 20:06:02 +0000270 n = min_t(u64, remaining, dev->bounce_size-offset);
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000271 dst = dev->bounce_buf+offset;
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700272 dev_dbg(&dev->sbd.core,
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000273 "%s:%u: copy %lu bytes from U0x%p/K0x%p to 0x%p\n",
274 __func__, __LINE__, n, userbuf, kernelbuf, dst);
275 if (userbuf) {
276 if (copy_from_user(dst, userbuf, n)) {
277 res = -EFAULT;
278 goto fail;
279 }
280 userbuf += n;
281 }
282 if (kernelbuf) {
283 memcpy(dst, kernelbuf, n);
284 kernelbuf += n;
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700285 }
286
287 res = ps3flash_write_chunk(dev, start_write_sector);
288 if (res < 0)
289 goto fail;
290
291 mutex_unlock(&priv->mutex);
292
293 *pos += n;
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700294 remaining -= n;
295 start_write_sector += chunk_sectors;
296 head = 0;
297 offset = 0;
298 } while (remaining > 0);
299
300 return count;
301
302fail:
303 mutex_unlock(&priv->mutex);
304 return res;
305}
306
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000307static ssize_t ps3flash_user_read(struct file *file, char __user *buf,
308 size_t count, loff_t *pos)
309{
310 return ps3flash_read(buf, NULL, count, pos);
311}
312
313static ssize_t ps3flash_user_write(struct file *file, const char __user *buf,
314 size_t count, loff_t *pos)
315{
316 return ps3flash_write(buf, NULL, count, pos);
317}
318
319static ssize_t ps3flash_kernel_read(void *buf, size_t count, loff_t pos)
320{
321 return ps3flash_read(NULL, buf, count, &pos);
322}
323
324static ssize_t ps3flash_kernel_write(const void *buf, size_t count,
325 loff_t pos)
326{
327 return ps3flash_write(NULL, buf, count, &pos);
328}
329
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700330
331static irqreturn_t ps3flash_interrupt(int irq, void *data)
332{
333 struct ps3_storage_device *dev = data;
334 int res;
335 u64 tag, status;
336
337 res = lv1_storage_get_async_status(dev->sbd.dev_id, &tag, &status);
338
339 if (tag != dev->tag)
340 dev_err(&dev->sbd.core,
Stephen Rothwell4c33d2d2009-01-13 20:06:02 +0000341 "%s:%u: tag mismatch, got %llx, expected %llx\n",
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700342 __func__, __LINE__, tag, dev->tag);
343
344 if (res) {
Stephen Rothwell4c33d2d2009-01-13 20:06:02 +0000345 dev_err(&dev->sbd.core, "%s:%u: res=%d status=0x%llx\n",
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700346 __func__, __LINE__, res, status);
347 } else {
348 dev->lv1_status = status;
349 complete(&dev->done);
350 }
351 return IRQ_HANDLED;
352}
353
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700354static const struct file_operations ps3flash_fops = {
355 .owner = THIS_MODULE,
356 .llseek = ps3flash_llseek,
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000357 .read = ps3flash_user_read,
358 .write = ps3flash_user_write,
359};
360
361static const struct ps3_os_area_flash_ops ps3flash_kernel_ops = {
362 .read = ps3flash_kernel_read,
363 .write = ps3flash_kernel_write,
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700364};
365
366static struct miscdevice ps3flash_misc = {
367 .minor = MISC_DYNAMIC_MINOR,
368 .name = DEVICE_NAME,
369 .fops = &ps3flash_fops,
370};
371
372static int __devinit ps3flash_probe(struct ps3_system_bus_device *_dev)
373{
374 struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core);
375 struct ps3flash_private *priv;
376 int error;
377 unsigned long tmp;
378
379 tmp = dev->regions[dev->region_idx].start*dev->blk_size;
380 if (tmp % FLASH_BLOCK_SIZE) {
381 dev_err(&dev->sbd.core,
382 "%s:%u region start %lu is not aligned\n", __func__,
383 __LINE__, tmp);
384 return -EINVAL;
385 }
386 tmp = dev->regions[dev->region_idx].size*dev->blk_size;
387 if (tmp % FLASH_BLOCK_SIZE) {
388 dev_err(&dev->sbd.core,
389 "%s:%u region size %lu is not aligned\n", __func__,
390 __LINE__, tmp);
391 return -EINVAL;
392 }
393
394 /* use static buffer, kmalloc cannot allocate 256 KiB */
395 if (!ps3flash_bounce_buffer.address)
396 return -ENODEV;
397
398 if (ps3flash_dev) {
399 dev_err(&dev->sbd.core,
400 "Only one FLASH device is supported\n");
401 return -EBUSY;
402 }
403
404 ps3flash_dev = dev;
405
406 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
407 if (!priv) {
408 error = -ENOMEM;
409 goto fail;
410 }
411
Geert Uytterhoeven559dc87f52009-06-10 04:38:55 +0000412 ps3_system_bus_set_drvdata(&dev->sbd, priv);
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700413 mutex_init(&priv->mutex);
414
415 dev->bounce_size = ps3flash_bounce_buffer.size;
416 dev->bounce_buf = ps3flash_bounce_buffer.address;
417
418 error = ps3stor_setup(dev, ps3flash_interrupt);
419 if (error)
420 goto fail_free_priv;
421
422 ps3flash_misc.parent = &dev->sbd.core;
423 error = misc_register(&ps3flash_misc);
424 if (error) {
425 dev_err(&dev->sbd.core, "%s:%u: misc_register failed %d\n",
426 __func__, __LINE__, error);
427 goto fail_teardown;
428 }
429
430 dev_info(&dev->sbd.core, "%s:%u: registered misc device %d\n",
431 __func__, __LINE__, ps3flash_misc.minor);
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000432
433 ps3_os_area_flash_register(&ps3flash_kernel_ops);
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700434 return 0;
435
436fail_teardown:
437 ps3stor_teardown(dev);
438fail_free_priv:
439 kfree(priv);
Geert Uytterhoeven559dc87f52009-06-10 04:38:55 +0000440 ps3_system_bus_set_drvdata(&dev->sbd, NULL);
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700441fail:
442 ps3flash_dev = NULL;
443 return error;
444}
445
446static int ps3flash_remove(struct ps3_system_bus_device *_dev)
447{
448 struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core);
449
Geert Uytterhoevena4e623f2009-06-10 04:39:06 +0000450 ps3_os_area_flash_register(NULL);
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700451 misc_deregister(&ps3flash_misc);
452 ps3stor_teardown(dev);
Geert Uytterhoeven559dc87f52009-06-10 04:38:55 +0000453 kfree(ps3_system_bus_get_drvdata(&dev->sbd));
454 ps3_system_bus_set_drvdata(&dev->sbd, NULL);
Geert Uytterhoevenf9652632007-07-21 04:37:48 -0700455 ps3flash_dev = NULL;
456 return 0;
457}
458
459
460static struct ps3_system_bus_driver ps3flash = {
461 .match_id = PS3_MATCH_ID_STOR_FLASH,
462 .core.name = DEVICE_NAME,
463 .core.owner = THIS_MODULE,
464 .probe = ps3flash_probe,
465 .remove = ps3flash_remove,
466 .shutdown = ps3flash_remove,
467};
468
469
470static int __init ps3flash_init(void)
471{
472 return ps3_system_bus_driver_register(&ps3flash);
473}
474
475static void __exit ps3flash_exit(void)
476{
477 ps3_system_bus_driver_unregister(&ps3flash);
478}
479
480module_init(ps3flash_init);
481module_exit(ps3flash_exit);
482
483MODULE_LICENSE("GPL");
484MODULE_DESCRIPTION("PS3 FLASH ROM Storage Driver");
485MODULE_AUTHOR("Sony Corporation");
486MODULE_ALIAS(PS3_MODULE_ALIAS_STOR_FLASH);