Commit | Line | Data |
---|---|---|
dbcb4a1a CM |
1 | /* |
2 | * Copyright 2011 Tilera Corporation. All Rights Reserved. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation, version 2. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, but | |
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | |
11 | * NON INFRINGEMENT. See the GNU General Public License for | |
12 | * more details. | |
13 | * | |
14 | * SPI Flash ROM driver | |
15 | * | |
16 | * This source code is derived from code provided in "Linux Device | |
17 | * Drivers, Third Edition", by Jonathan Corbet, Alessandro Rubini, and | |
18 | * Greg Kroah-Hartman, published by O'Reilly Media, Inc. | |
19 | */ | |
20 | ||
21 | #include <linux/module.h> | |
22 | #include <linux/moduleparam.h> | |
dbcb4a1a CM |
23 | #include <linux/kernel.h> /* printk() */ |
24 | #include <linux/slab.h> /* kmalloc() */ | |
25 | #include <linux/fs.h> /* everything... */ | |
26 | #include <linux/errno.h> /* error codes */ | |
27 | #include <linux/types.h> /* size_t */ | |
28 | #include <linux/proc_fs.h> | |
29 | #include <linux/fcntl.h> /* O_ACCMODE */ | |
dbcb4a1a CM |
30 | #include <linux/pagemap.h> |
31 | #include <linux/hugetlb.h> | |
32 | #include <linux/uaccess.h> | |
33 | #include <linux/platform_device.h> | |
34 | #include <hv/hypervisor.h> | |
35 | #include <linux/ioctl.h> | |
36 | #include <linux/cdev.h> | |
37 | #include <linux/delay.h> | |
38 | #include <hv/drv_srom_intf.h> | |
39 | ||
40 | /* | |
41 | * Size of our hypervisor I/O requests. We break up large transfers | |
42 | * so that we don't spend large uninterrupted spans of time in the | |
43 | * hypervisor. Erasing an SROM sector takes a significant fraction of | |
44 | * a second, so if we allowed the user to, say, do one I/O to write the | |
45 | * entire ROM, we'd get soft lockup timeouts, or worse. | |
46 | */ | |
47 | #define SROM_CHUNK_SIZE ((size_t)4096) | |
48 | ||
49 | /* | |
50 | * When hypervisor is busy (e.g. erasing), poll the status periodically. | |
51 | */ | |
52 | ||
53 | /* | |
54 | * Interval to poll the state in msec | |
55 | */ | |
56 | #define SROM_WAIT_TRY_INTERVAL 20 | |
57 | ||
58 | /* | |
59 | * Maximum times to poll the state | |
60 | */ | |
61 | #define SROM_MAX_WAIT_TRY_TIMES 1000 | |
62 | ||
63 | struct srom_dev { | |
64 | int hv_devhdl; /* Handle for hypervisor device */ | |
65 | u32 total_size; /* Size of this device */ | |
66 | u32 sector_size; /* Size of a sector */ | |
67 | u32 page_size; /* Size of a page */ | |
68 | struct mutex lock; /* Allow only one accessor at a time */ | |
69 | }; | |
70 | ||
71 | static int srom_major; /* Dynamic major by default */ | |
72 | module_param(srom_major, int, 0); | |
73 | MODULE_AUTHOR("Tilera Corporation"); | |
74 | MODULE_LICENSE("GPL"); | |
75 | ||
76 | static int srom_devs; /* Number of SROM partitions */ | |
77 | static struct cdev srom_cdev; | |
514b82a5 | 78 | static struct platform_device *srom_parent; |
dbcb4a1a CM |
79 | static struct class *srom_class; |
80 | static struct srom_dev *srom_devices; | |
81 | ||
82 | /* | |
83 | * Handle calling the hypervisor and managing EAGAIN/EBUSY. | |
84 | */ | |
85 | ||
86 | static ssize_t _srom_read(int hv_devhdl, void *buf, | |
87 | loff_t off, size_t count) | |
88 | { | |
89 | int retval, retries = SROM_MAX_WAIT_TRY_TIMES; | |
90 | for (;;) { | |
91 | retval = hv_dev_pread(hv_devhdl, 0, (HV_VirtAddr)buf, | |
92 | count, off); | |
93 | if (retval >= 0) | |
94 | return retval; | |
95 | if (retval == HV_EAGAIN) | |
96 | continue; | |
97 | if (retval == HV_EBUSY && --retries > 0) { | |
98 | msleep(SROM_WAIT_TRY_INTERVAL); | |
99 | continue; | |
100 | } | |
101 | pr_err("_srom_read: error %d\n", retval); | |
102 | return -EIO; | |
103 | } | |
104 | } | |
105 | ||
106 | static ssize_t _srom_write(int hv_devhdl, const void *buf, | |
107 | loff_t off, size_t count) | |
108 | { | |
109 | int retval, retries = SROM_MAX_WAIT_TRY_TIMES; | |
110 | for (;;) { | |
111 | retval = hv_dev_pwrite(hv_devhdl, 0, (HV_VirtAddr)buf, | |
112 | count, off); | |
113 | if (retval >= 0) | |
114 | return retval; | |
115 | if (retval == HV_EAGAIN) | |
116 | continue; | |
117 | if (retval == HV_EBUSY && --retries > 0) { | |
118 | msleep(SROM_WAIT_TRY_INTERVAL); | |
119 | continue; | |
120 | } | |
121 | pr_err("_srom_write: error %d\n", retval); | |
122 | return -EIO; | |
123 | } | |
124 | } | |
125 | ||
126 | /** | |
127 | * srom_open() - Device open routine. | |
128 | * @inode: Inode for this device. | |
129 | * @filp: File for this specific open of the device. | |
130 | * | |
131 | * Returns zero, or an error code. | |
132 | */ | |
133 | static int srom_open(struct inode *inode, struct file *filp) | |
134 | { | |
135 | filp->private_data = &srom_devices[iminor(inode)]; | |
136 | return 0; | |
137 | } | |
138 | ||
139 | ||
140 | /** | |
141 | * srom_release() - Device release routine. | |
142 | * @inode: Inode for this device. | |
143 | * @filp: File for this specific open of the device. | |
144 | * | |
145 | * Returns zero, or an error code. | |
146 | */ | |
147 | static int srom_release(struct inode *inode, struct file *filp) | |
148 | { | |
149 | struct srom_dev *srom = filp->private_data; | |
150 | char dummy; | |
151 | ||
152 | /* Make sure we've flushed anything written to the ROM. */ | |
153 | mutex_lock(&srom->lock); | |
154 | if (srom->hv_devhdl >= 0) | |
155 | _srom_write(srom->hv_devhdl, &dummy, SROM_FLUSH_OFF, 1); | |
156 | mutex_unlock(&srom->lock); | |
157 | ||
158 | filp->private_data = NULL; | |
159 | ||
160 | return 0; | |
161 | } | |
162 | ||
163 | ||
164 | /** | |
165 | * srom_read() - Read data from the device. | |
166 | * @filp: File for this specific open of the device. | |
167 | * @buf: User's data buffer. | |
168 | * @count: Number of bytes requested. | |
169 | * @f_pos: File position. | |
170 | * | |
171 | * Returns number of bytes read, or an error code. | |
172 | */ | |
173 | static ssize_t srom_read(struct file *filp, char __user *buf, | |
174 | size_t count, loff_t *f_pos) | |
175 | { | |
176 | int retval = 0; | |
177 | void *kernbuf; | |
178 | struct srom_dev *srom = filp->private_data; | |
179 | ||
180 | kernbuf = kmalloc(SROM_CHUNK_SIZE, GFP_KERNEL); | |
181 | if (!kernbuf) | |
182 | return -ENOMEM; | |
183 | ||
184 | if (mutex_lock_interruptible(&srom->lock)) { | |
185 | retval = -ERESTARTSYS; | |
186 | kfree(kernbuf); | |
187 | return retval; | |
188 | } | |
189 | ||
190 | while (count) { | |
191 | int hv_retval; | |
192 | int bytes_this_pass = min(count, SROM_CHUNK_SIZE); | |
193 | ||
194 | hv_retval = _srom_read(srom->hv_devhdl, kernbuf, | |
195 | *f_pos, bytes_this_pass); | |
16418bb1 | 196 | if (hv_retval <= 0) { |
dbcb4a1a CM |
197 | if (retval == 0) |
198 | retval = hv_retval; | |
199 | break; | |
200 | } | |
201 | ||
16418bb1 CM |
202 | if (copy_to_user(buf, kernbuf, hv_retval) != 0) { |
203 | retval = -EFAULT; | |
204 | break; | |
205 | } | |
206 | ||
dbcb4a1a CM |
207 | retval += hv_retval; |
208 | *f_pos += hv_retval; | |
209 | buf += hv_retval; | |
210 | count -= hv_retval; | |
211 | } | |
212 | ||
213 | mutex_unlock(&srom->lock); | |
214 | kfree(kernbuf); | |
215 | ||
216 | return retval; | |
217 | } | |
218 | ||
219 | /** | |
220 | * srom_write() - Write data to the device. | |
221 | * @filp: File for this specific open of the device. | |
222 | * @buf: User's data buffer. | |
223 | * @count: Number of bytes requested. | |
224 | * @f_pos: File position. | |
225 | * | |
226 | * Returns number of bytes written, or an error code. | |
227 | */ | |
228 | static ssize_t srom_write(struct file *filp, const char __user *buf, | |
229 | size_t count, loff_t *f_pos) | |
230 | { | |
231 | int retval = 0; | |
232 | void *kernbuf; | |
233 | struct srom_dev *srom = filp->private_data; | |
234 | ||
235 | kernbuf = kmalloc(SROM_CHUNK_SIZE, GFP_KERNEL); | |
236 | if (!kernbuf) | |
237 | return -ENOMEM; | |
238 | ||
239 | if (mutex_lock_interruptible(&srom->lock)) { | |
240 | retval = -ERESTARTSYS; | |
241 | kfree(kernbuf); | |
242 | return retval; | |
243 | } | |
244 | ||
245 | while (count) { | |
246 | int hv_retval; | |
247 | int bytes_this_pass = min(count, SROM_CHUNK_SIZE); | |
248 | ||
249 | if (copy_from_user(kernbuf, buf, bytes_this_pass) != 0) { | |
250 | retval = -EFAULT; | |
251 | break; | |
252 | } | |
253 | ||
254 | hv_retval = _srom_write(srom->hv_devhdl, kernbuf, | |
255 | *f_pos, bytes_this_pass); | |
256 | if (hv_retval <= 0) { | |
257 | if (retval == 0) | |
258 | retval = hv_retval; | |
259 | break; | |
260 | } | |
261 | ||
262 | retval += hv_retval; | |
263 | *f_pos += hv_retval; | |
264 | buf += hv_retval; | |
265 | count -= hv_retval; | |
266 | } | |
267 | ||
268 | mutex_unlock(&srom->lock); | |
269 | kfree(kernbuf); | |
270 | ||
271 | return retval; | |
272 | } | |
273 | ||
274 | /* Provide our own implementation so we can use srom->total_size. */ | |
914961aa | 275 | loff_t srom_llseek(struct file *file, loff_t offset, int origin) |
dbcb4a1a | 276 | { |
914961aa AV |
277 | struct srom_dev *srom = file->private_data; |
278 | return fixed_size_llseek(file, offset, origin, srom->total_size); | |
dbcb4a1a CM |
279 | } |
280 | ||
e017a84b GKH |
281 | static ssize_t total_size_show(struct device *dev, |
282 | struct device_attribute *attr, char *buf) | |
dbcb4a1a CM |
283 | { |
284 | struct srom_dev *srom = dev_get_drvdata(dev); | |
285 | return sprintf(buf, "%u\n", srom->total_size); | |
286 | } | |
e017a84b | 287 | static DEVICE_ATTR_RO(total_size); |
dbcb4a1a | 288 | |
e017a84b GKH |
289 | static ssize_t sector_size_show(struct device *dev, |
290 | struct device_attribute *attr, char *buf) | |
dbcb4a1a CM |
291 | { |
292 | struct srom_dev *srom = dev_get_drvdata(dev); | |
293 | return sprintf(buf, "%u\n", srom->sector_size); | |
294 | } | |
e017a84b | 295 | static DEVICE_ATTR_RO(sector_size); |
dbcb4a1a | 296 | |
e017a84b GKH |
297 | static ssize_t page_size_show(struct device *dev, |
298 | struct device_attribute *attr, char *buf) | |
dbcb4a1a CM |
299 | { |
300 | struct srom_dev *srom = dev_get_drvdata(dev); | |
301 | return sprintf(buf, "%u\n", srom->page_size); | |
302 | } | |
e017a84b | 303 | static DEVICE_ATTR_RO(page_size); |
dbcb4a1a | 304 | |
7c42721f | 305 | static struct attribute *srom_dev_attrs[] = { |
e017a84b GKH |
306 | &dev_attr_total_size.attr, |
307 | &dev_attr_sector_size.attr, | |
308 | &dev_attr_page_size.attr, | |
309 | NULL, | |
dbcb4a1a | 310 | }; |
e017a84b | 311 | ATTRIBUTE_GROUPS(srom_dev); |
dbcb4a1a | 312 | |
2c9ede55 | 313 | static char *srom_devnode(struct device *dev, umode_t *mode) |
dbcb4a1a CM |
314 | { |
315 | *mode = S_IRUGO | S_IWUSR; | |
316 | return kasprintf(GFP_KERNEL, "srom/%s", dev_name(dev)); | |
317 | } | |
318 | ||
319 | /* | |
320 | * The fops | |
321 | */ | |
322 | static const struct file_operations srom_fops = { | |
323 | .owner = THIS_MODULE, | |
324 | .llseek = srom_llseek, | |
325 | .read = srom_read, | |
326 | .write = srom_write, | |
327 | .open = srom_open, | |
328 | .release = srom_release, | |
329 | }; | |
330 | ||
331 | /** | |
332 | * srom_setup_minor() - Initialize per-minor information. | |
333 | * @srom: Per-device SROM state. | |
334 | * @index: Device to set up. | |
335 | */ | |
336 | static int srom_setup_minor(struct srom_dev *srom, int index) | |
337 | { | |
338 | struct device *dev; | |
339 | int devhdl = srom->hv_devhdl; | |
340 | ||
341 | mutex_init(&srom->lock); | |
342 | ||
343 | if (_srom_read(devhdl, &srom->total_size, | |
344 | SROM_TOTAL_SIZE_OFF, sizeof(srom->total_size)) < 0) | |
345 | return -EIO; | |
346 | if (_srom_read(devhdl, &srom->sector_size, | |
347 | SROM_SECTOR_SIZE_OFF, sizeof(srom->sector_size)) < 0) | |
348 | return -EIO; | |
349 | if (_srom_read(devhdl, &srom->page_size, | |
350 | SROM_PAGE_SIZE_OFF, sizeof(srom->page_size)) < 0) | |
351 | return -EIO; | |
352 | ||
514b82a5 | 353 | dev = device_create(srom_class, &srom_parent->dev, |
dbcb4a1a | 354 | MKDEV(srom_major, index), srom, "%d", index); |
8c6ffba0 | 355 | return PTR_ERR_OR_ZERO(dev); |
dbcb4a1a CM |
356 | } |
357 | ||
358 | /** srom_init() - Initialize the driver's module. */ | |
359 | static int srom_init(void) | |
360 | { | |
361 | int result, i; | |
362 | dev_t dev = MKDEV(srom_major, 0); | |
363 | ||
364 | /* | |
365 | * Start with a plausible number of partitions; the krealloc() call | |
366 | * below will yield about log(srom_devs) additional allocations. | |
367 | */ | |
368 | srom_devices = kzalloc(4 * sizeof(struct srom_dev), GFP_KERNEL); | |
369 | ||
370 | /* Discover the number of srom partitions. */ | |
371 | for (i = 0; ; i++) { | |
372 | int devhdl; | |
373 | char buf[20]; | |
374 | struct srom_dev *new_srom_devices = | |
375 | krealloc(srom_devices, (i+1) * sizeof(struct srom_dev), | |
376 | GFP_KERNEL | __GFP_ZERO); | |
377 | if (!new_srom_devices) { | |
378 | result = -ENOMEM; | |
379 | goto fail_mem; | |
380 | } | |
381 | srom_devices = new_srom_devices; | |
382 | sprintf(buf, "srom/0/%d", i); | |
383 | devhdl = hv_dev_open((HV_VirtAddr)buf, 0); | |
384 | if (devhdl < 0) { | |
385 | if (devhdl != HV_ENODEV) | |
386 | pr_notice("srom/%d: hv_dev_open failed: %d.\n", | |
387 | i, devhdl); | |
388 | break; | |
389 | } | |
390 | srom_devices[i].hv_devhdl = devhdl; | |
391 | } | |
392 | srom_devs = i; | |
393 | ||
394 | /* Bail out early if we have no partitions at all. */ | |
395 | if (srom_devs == 0) { | |
396 | result = -ENODEV; | |
397 | goto fail_mem; | |
398 | } | |
399 | ||
400 | /* Register our major, and accept a dynamic number. */ | |
401 | if (srom_major) | |
402 | result = register_chrdev_region(dev, srom_devs, "srom"); | |
403 | else { | |
404 | result = alloc_chrdev_region(&dev, 0, srom_devs, "srom"); | |
405 | srom_major = MAJOR(dev); | |
406 | } | |
407 | if (result < 0) | |
408 | goto fail_mem; | |
409 | ||
410 | /* Register a character device. */ | |
411 | cdev_init(&srom_cdev, &srom_fops); | |
412 | srom_cdev.owner = THIS_MODULE; | |
413 | srom_cdev.ops = &srom_fops; | |
414 | result = cdev_add(&srom_cdev, dev, srom_devs); | |
415 | if (result < 0) | |
416 | goto fail_chrdev; | |
417 | ||
514b82a5 PM |
418 | /* Create a parent device */ |
419 | srom_parent = platform_device_register_simple("srom", -1, NULL, 0); | |
420 | if (IS_ERR(srom_parent)) { | |
421 | result = PTR_ERR(srom_parent); | |
422 | goto fail_pdev; | |
423 | } | |
424 | ||
dbcb4a1a CM |
425 | /* Create a sysfs class. */ |
426 | srom_class = class_create(THIS_MODULE, "srom"); | |
427 | if (IS_ERR(srom_class)) { | |
428 | result = PTR_ERR(srom_class); | |
429 | goto fail_cdev; | |
430 | } | |
e017a84b | 431 | srom_class->dev_groups = srom_dev_groups; |
dbcb4a1a CM |
432 | srom_class->devnode = srom_devnode; |
433 | ||
434 | /* Do per-partition initialization */ | |
435 | for (i = 0; i < srom_devs; i++) { | |
436 | result = srom_setup_minor(srom_devices + i, i); | |
437 | if (result < 0) | |
438 | goto fail_class; | |
439 | } | |
440 | ||
441 | return 0; | |
442 | ||
443 | fail_class: | |
444 | for (i = 0; i < srom_devs; i++) | |
445 | device_destroy(srom_class, MKDEV(srom_major, i)); | |
446 | class_destroy(srom_class); | |
447 | fail_cdev: | |
514b82a5 PM |
448 | platform_device_unregister(srom_parent); |
449 | fail_pdev: | |
dbcb4a1a CM |
450 | cdev_del(&srom_cdev); |
451 | fail_chrdev: | |
452 | unregister_chrdev_region(dev, srom_devs); | |
453 | fail_mem: | |
454 | kfree(srom_devices); | |
455 | return result; | |
456 | } | |
457 | ||
458 | /** srom_cleanup() - Clean up the driver's module. */ | |
459 | static void srom_cleanup(void) | |
460 | { | |
461 | int i; | |
462 | for (i = 0; i < srom_devs; i++) | |
463 | device_destroy(srom_class, MKDEV(srom_major, i)); | |
464 | class_destroy(srom_class); | |
465 | cdev_del(&srom_cdev); | |
514b82a5 | 466 | platform_device_unregister(srom_parent); |
dbcb4a1a CM |
467 | unregister_chrdev_region(MKDEV(srom_major, 0), srom_devs); |
468 | kfree(srom_devices); | |
469 | } | |
470 | ||
471 | module_init(srom_init); | |
472 | module_exit(srom_cleanup); |