diff options
author | Geert Uytterhoeven <Geert.Uytterhoeven@sonycom.com> | 2007-07-21 07:37:48 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-21 20:49:16 -0400 |
commit | f96526354bb0824f3ce550a028606d2f94435b92 (patch) | |
tree | 9bc2730a69a4b3fba1750e75e7d6ee6474e6f6b2 /drivers/char/ps3flash.c | |
parent | 9aea8cbf2866c5680e30ff473341b7c5e93f7442 (diff) |
ps3: FLASH ROM Storage Driver
Add a FLASH ROM Storage Driver for the PS3:
- Implemented as a misc character device driver
- Uses a fixed 256 KiB buffer allocated from boot memory as the hypervisor
requires the writing of aligned 256 KiB blocks
Cc: Geoff Levand <geoffrey.levand@am.sony.com>
Signed-off-by: Geert Uytterhoeven <Geert.Uytterhoeven@sonycom.com>
Cc: Jens Axboe <jens.axboe@oracle.com>
Cc: James Bottomley <James.Bottomley@steeleye.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/char/ps3flash.c')
-rw-r--r-- | drivers/char/ps3flash.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/drivers/char/ps3flash.c b/drivers/char/ps3flash.c new file mode 100644 index 000000000000..79b6f461be75 --- /dev/null +++ b/drivers/char/ps3flash.c | |||
@@ -0,0 +1,440 @@ | |||
1 | /* | ||
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 | |||
34 | struct ps3flash_private { | ||
35 | struct mutex mutex; /* Bounce buffer mutex */ | ||
36 | }; | ||
37 | |||
38 | static struct ps3_storage_device *ps3flash_dev; | ||
39 | |||
40 | static 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) { | ||
47 | dev_err(&dev->sbd.core, "%s:%u: %s failed 0x%lx\n", __func__, | ||
48 | __LINE__, write ? "write" : "read", res); | ||
49 | return -EIO; | ||
50 | } | ||
51 | return sectors; | ||
52 | } | ||
53 | |||
54 | static 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) { | ||
62 | dev_dbg(&dev->sbd.core, "%s:%u Limiting sectors to %lu\n", | ||
63 | __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 | |||
72 | static 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 | |||
80 | static 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 | |||
102 | out: | ||
103 | mutex_unlock(&file->f_mapping->host->i_mutex); | ||
104 | return res; | ||
105 | } | ||
106 | |||
107 | static ssize_t ps3flash_read(struct file *file, char __user *buf, size_t count, | ||
108 | loff_t *pos) | ||
109 | { | ||
110 | struct ps3_storage_device *dev = ps3flash_dev; | ||
111 | struct ps3flash_private *priv = dev->sbd.core.driver_data; | ||
112 | u64 size, start_sector, end_sector, offset; | ||
113 | ssize_t sectors_read; | ||
114 | size_t remaining, n; | ||
115 | |||
116 | dev_dbg(&dev->sbd.core, | ||
117 | "%s:%u: Reading %zu bytes at position %lld to user 0x%p\n", | ||
118 | __func__, __LINE__, count, *pos, buf); | ||
119 | |||
120 | size = dev->regions[dev->region_idx].size*dev->blk_size; | ||
121 | if (*pos >= size || !count) | ||
122 | return 0; | ||
123 | |||
124 | if (*pos + count > size) { | ||
125 | dev_dbg(&dev->sbd.core, | ||
126 | "%s:%u Truncating count from %zu to %llu\n", __func__, | ||
127 | __LINE__, count, size - *pos); | ||
128 | count = size - *pos; | ||
129 | } | ||
130 | |||
131 | start_sector = *pos / dev->blk_size; | ||
132 | offset = *pos % dev->blk_size; | ||
133 | end_sector = DIV_ROUND_UP(*pos + count, dev->blk_size); | ||
134 | |||
135 | remaining = count; | ||
136 | do { | ||
137 | mutex_lock(&priv->mutex); | ||
138 | |||
139 | sectors_read = ps3flash_read_sectors(dev, start_sector, | ||
140 | end_sector-start_sector, | ||
141 | 0); | ||
142 | if (sectors_read < 0) { | ||
143 | mutex_unlock(&priv->mutex); | ||
144 | goto fail; | ||
145 | } | ||
146 | |||
147 | n = min(remaining, sectors_read*dev->blk_size-offset); | ||
148 | dev_dbg(&dev->sbd.core, | ||
149 | "%s:%u: copy %lu bytes from 0x%p to user 0x%p\n", | ||
150 | __func__, __LINE__, n, dev->bounce_buf+offset, buf); | ||
151 | if (copy_to_user(buf, dev->bounce_buf+offset, n)) { | ||
152 | mutex_unlock(&priv->mutex); | ||
153 | sectors_read = -EFAULT; | ||
154 | goto fail; | ||
155 | } | ||
156 | |||
157 | mutex_unlock(&priv->mutex); | ||
158 | |||
159 | *pos += n; | ||
160 | buf += n; | ||
161 | remaining -= n; | ||
162 | start_sector += sectors_read; | ||
163 | offset = 0; | ||
164 | } while (remaining > 0); | ||
165 | |||
166 | return count; | ||
167 | |||
168 | fail: | ||
169 | return sectors_read; | ||
170 | } | ||
171 | |||
172 | static ssize_t ps3flash_write(struct file *file, const char __user *buf, | ||
173 | size_t count, loff_t *pos) | ||
174 | { | ||
175 | struct ps3_storage_device *dev = ps3flash_dev; | ||
176 | struct ps3flash_private *priv = dev->sbd.core.driver_data; | ||
177 | u64 size, chunk_sectors, start_write_sector, end_write_sector, | ||
178 | end_read_sector, start_read_sector, head, tail, offset; | ||
179 | ssize_t res; | ||
180 | size_t remaining, n; | ||
181 | unsigned int sec_off; | ||
182 | |||
183 | dev_dbg(&dev->sbd.core, | ||
184 | "%s:%u: Writing %zu bytes at position %lld from user 0x%p\n", | ||
185 | __func__, __LINE__, count, *pos, buf); | ||
186 | |||
187 | size = dev->regions[dev->region_idx].size*dev->blk_size; | ||
188 | if (*pos >= size || !count) | ||
189 | return 0; | ||
190 | |||
191 | if (*pos + count > size) { | ||
192 | dev_dbg(&dev->sbd.core, | ||
193 | "%s:%u Truncating count from %zu to %llu\n", __func__, | ||
194 | __LINE__, count, size - *pos); | ||
195 | count = size - *pos; | ||
196 | } | ||
197 | |||
198 | chunk_sectors = dev->bounce_size / dev->blk_size; | ||
199 | |||
200 | start_write_sector = *pos / dev->bounce_size * chunk_sectors; | ||
201 | offset = *pos % dev->bounce_size; | ||
202 | end_write_sector = DIV_ROUND_UP(*pos + count, dev->bounce_size) * | ||
203 | chunk_sectors; | ||
204 | |||
205 | end_read_sector = DIV_ROUND_UP(*pos, dev->blk_size); | ||
206 | start_read_sector = (*pos + count) / dev->blk_size; | ||
207 | |||
208 | /* | ||
209 | * As we have to write in 256 KiB chunks, while we can read in blk_size | ||
210 | * (usually 512 bytes) chunks, we perform the following steps: | ||
211 | * 1. Read from start_write_sector to end_read_sector ("head") | ||
212 | * 2. Read from start_read_sector to end_write_sector ("tail") | ||
213 | * 3. Copy data to buffer | ||
214 | * 4. Write from start_write_sector to end_write_sector | ||
215 | * All of this is complicated by using only one 256 KiB bounce buffer. | ||
216 | */ | ||
217 | |||
218 | head = end_read_sector - start_write_sector; | ||
219 | tail = end_write_sector - start_read_sector; | ||
220 | |||
221 | remaining = count; | ||
222 | do { | ||
223 | mutex_lock(&priv->mutex); | ||
224 | |||
225 | if (end_read_sector >= start_read_sector) { | ||
226 | /* Merge head and tail */ | ||
227 | dev_dbg(&dev->sbd.core, | ||
228 | "Merged head and tail: %lu sectors at %lu\n", | ||
229 | chunk_sectors, start_write_sector); | ||
230 | res = ps3flash_read_sectors(dev, start_write_sector, | ||
231 | chunk_sectors, 0); | ||
232 | if (res < 0) | ||
233 | goto fail; | ||
234 | } else { | ||
235 | if (head) { | ||
236 | /* Read head */ | ||
237 | dev_dbg(&dev->sbd.core, | ||
238 | "head: %lu sectors at %lu\n", head, | ||
239 | start_write_sector); | ||
240 | res = ps3flash_read_sectors(dev, | ||
241 | start_write_sector, | ||
242 | head, 0); | ||
243 | if (res < 0) | ||
244 | goto fail; | ||
245 | } | ||
246 | if (start_read_sector < | ||
247 | start_write_sector+chunk_sectors) { | ||
248 | /* Read tail */ | ||
249 | dev_dbg(&dev->sbd.core, | ||
250 | "tail: %lu sectors at %lu\n", tail, | ||
251 | start_read_sector); | ||
252 | sec_off = start_read_sector-start_write_sector; | ||
253 | res = ps3flash_read_sectors(dev, | ||
254 | start_read_sector, | ||
255 | tail, sec_off); | ||
256 | if (res < 0) | ||
257 | goto fail; | ||
258 | } | ||
259 | } | ||
260 | |||
261 | n = min(remaining, dev->bounce_size-offset); | ||
262 | dev_dbg(&dev->sbd.core, | ||
263 | "%s:%u: copy %lu bytes from user 0x%p to 0x%p\n", | ||
264 | __func__, __LINE__, n, buf, dev->bounce_buf+offset); | ||
265 | if (copy_from_user(dev->bounce_buf+offset, buf, n)) { | ||
266 | res = -EFAULT; | ||
267 | goto fail; | ||
268 | } | ||
269 | |||
270 | res = ps3flash_write_chunk(dev, start_write_sector); | ||
271 | if (res < 0) | ||
272 | goto fail; | ||
273 | |||
274 | mutex_unlock(&priv->mutex); | ||
275 | |||
276 | *pos += n; | ||
277 | buf += n; | ||
278 | remaining -= n; | ||
279 | start_write_sector += chunk_sectors; | ||
280 | head = 0; | ||
281 | offset = 0; | ||
282 | } while (remaining > 0); | ||
283 | |||
284 | return count; | ||
285 | |||
286 | fail: | ||
287 | mutex_unlock(&priv->mutex); | ||
288 | return res; | ||
289 | } | ||
290 | |||
291 | |||
292 | static irqreturn_t ps3flash_interrupt(int irq, void *data) | ||
293 | { | ||
294 | struct ps3_storage_device *dev = data; | ||
295 | int res; | ||
296 | u64 tag, status; | ||
297 | |||
298 | res = lv1_storage_get_async_status(dev->sbd.dev_id, &tag, &status); | ||
299 | |||
300 | if (tag != dev->tag) | ||
301 | dev_err(&dev->sbd.core, | ||
302 | "%s:%u: tag mismatch, got %lx, expected %lx\n", | ||
303 | __func__, __LINE__, tag, dev->tag); | ||
304 | |||
305 | if (res) { | ||
306 | dev_err(&dev->sbd.core, "%s:%u: res=%d status=0x%lx\n", | ||
307 | __func__, __LINE__, res, status); | ||
308 | } else { | ||
309 | dev->lv1_status = status; | ||
310 | complete(&dev->done); | ||
311 | } | ||
312 | return IRQ_HANDLED; | ||
313 | } | ||
314 | |||
315 | |||
316 | static const struct file_operations ps3flash_fops = { | ||
317 | .owner = THIS_MODULE, | ||
318 | .llseek = ps3flash_llseek, | ||
319 | .read = ps3flash_read, | ||
320 | .write = ps3flash_write, | ||
321 | }; | ||
322 | |||
323 | static struct miscdevice ps3flash_misc = { | ||
324 | .minor = MISC_DYNAMIC_MINOR, | ||
325 | .name = DEVICE_NAME, | ||
326 | .fops = &ps3flash_fops, | ||
327 | }; | ||
328 | |||
329 | static int __devinit ps3flash_probe(struct ps3_system_bus_device *_dev) | ||
330 | { | ||
331 | struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core); | ||
332 | struct ps3flash_private *priv; | ||
333 | int error; | ||
334 | unsigned long tmp; | ||
335 | |||
336 | tmp = dev->regions[dev->region_idx].start*dev->blk_size; | ||
337 | if (tmp % FLASH_BLOCK_SIZE) { | ||
338 | dev_err(&dev->sbd.core, | ||
339 | "%s:%u region start %lu is not aligned\n", __func__, | ||
340 | __LINE__, tmp); | ||
341 | return -EINVAL; | ||
342 | } | ||
343 | tmp = dev->regions[dev->region_idx].size*dev->blk_size; | ||
344 | if (tmp % FLASH_BLOCK_SIZE) { | ||
345 | dev_err(&dev->sbd.core, | ||
346 | "%s:%u region size %lu is not aligned\n", __func__, | ||
347 | __LINE__, tmp); | ||
348 | return -EINVAL; | ||
349 | } | ||
350 | |||
351 | /* use static buffer, kmalloc cannot allocate 256 KiB */ | ||
352 | if (!ps3flash_bounce_buffer.address) | ||
353 | return -ENODEV; | ||
354 | |||
355 | if (ps3flash_dev) { | ||
356 | dev_err(&dev->sbd.core, | ||
357 | "Only one FLASH device is supported\n"); | ||
358 | return -EBUSY; | ||
359 | } | ||
360 | |||
361 | ps3flash_dev = dev; | ||
362 | |||
363 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | ||
364 | if (!priv) { | ||
365 | error = -ENOMEM; | ||
366 | goto fail; | ||
367 | } | ||
368 | |||
369 | dev->sbd.core.driver_data = priv; | ||
370 | mutex_init(&priv->mutex); | ||
371 | |||
372 | dev->bounce_size = ps3flash_bounce_buffer.size; | ||
373 | dev->bounce_buf = ps3flash_bounce_buffer.address; | ||
374 | |||
375 | error = ps3stor_setup(dev, ps3flash_interrupt); | ||
376 | if (error) | ||
377 | goto fail_free_priv; | ||
378 | |||
379 | ps3flash_misc.parent = &dev->sbd.core; | ||
380 | error = misc_register(&ps3flash_misc); | ||
381 | if (error) { | ||
382 | dev_err(&dev->sbd.core, "%s:%u: misc_register failed %d\n", | ||
383 | __func__, __LINE__, error); | ||
384 | goto fail_teardown; | ||
385 | } | ||
386 | |||
387 | dev_info(&dev->sbd.core, "%s:%u: registered misc device %d\n", | ||
388 | __func__, __LINE__, ps3flash_misc.minor); | ||
389 | return 0; | ||
390 | |||
391 | fail_teardown: | ||
392 | ps3stor_teardown(dev); | ||
393 | fail_free_priv: | ||
394 | kfree(priv); | ||
395 | dev->sbd.core.driver_data = NULL; | ||
396 | fail: | ||
397 | ps3flash_dev = NULL; | ||
398 | return error; | ||
399 | } | ||
400 | |||
401 | static int ps3flash_remove(struct ps3_system_bus_device *_dev) | ||
402 | { | ||
403 | struct ps3_storage_device *dev = to_ps3_storage_device(&_dev->core); | ||
404 | |||
405 | misc_deregister(&ps3flash_misc); | ||
406 | ps3stor_teardown(dev); | ||
407 | kfree(dev->sbd.core.driver_data); | ||
408 | dev->sbd.core.driver_data = NULL; | ||
409 | ps3flash_dev = NULL; | ||
410 | return 0; | ||
411 | } | ||
412 | |||
413 | |||
414 | static struct ps3_system_bus_driver ps3flash = { | ||
415 | .match_id = PS3_MATCH_ID_STOR_FLASH, | ||
416 | .core.name = DEVICE_NAME, | ||
417 | .core.owner = THIS_MODULE, | ||
418 | .probe = ps3flash_probe, | ||
419 | .remove = ps3flash_remove, | ||
420 | .shutdown = ps3flash_remove, | ||
421 | }; | ||
422 | |||
423 | |||
424 | static int __init ps3flash_init(void) | ||
425 | { | ||
426 | return ps3_system_bus_driver_register(&ps3flash); | ||
427 | } | ||
428 | |||
429 | static void __exit ps3flash_exit(void) | ||
430 | { | ||
431 | ps3_system_bus_driver_unregister(&ps3flash); | ||
432 | } | ||
433 | |||
434 | module_init(ps3flash_init); | ||
435 | module_exit(ps3flash_exit); | ||
436 | |||
437 | MODULE_LICENSE("GPL"); | ||
438 | MODULE_DESCRIPTION("PS3 FLASH ROM Storage Driver"); | ||
439 | MODULE_AUTHOR("Sony Corporation"); | ||
440 | MODULE_ALIAS(PS3_MODULE_ALIAS_STOR_FLASH); | ||