diff options
Diffstat (limited to 'drivers/spi/spidev.c')
-rw-r--r-- | drivers/spi/spidev.c | 176 |
1 files changed, 127 insertions, 49 deletions
diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index ab2b769c83ca..ddbe1a5e970e 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c | |||
@@ -25,6 +25,7 @@ | |||
25 | #include <linux/ioctl.h> | 25 | #include <linux/ioctl.h> |
26 | #include <linux/fs.h> | 26 | #include <linux/fs.h> |
27 | #include <linux/device.h> | 27 | #include <linux/device.h> |
28 | #include <linux/err.h> | ||
28 | #include <linux/list.h> | 29 | #include <linux/list.h> |
29 | #include <linux/errno.h> | 30 | #include <linux/errno.h> |
30 | #include <linux/mutex.h> | 31 | #include <linux/mutex.h> |
@@ -68,10 +69,12 @@ static unsigned long minors[N_SPI_MINORS / BITS_PER_LONG]; | |||
68 | | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP) | 69 | | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP) |
69 | 70 | ||
70 | struct spidev_data { | 71 | struct spidev_data { |
71 | struct device dev; | 72 | dev_t devt; |
73 | spinlock_t spi_lock; | ||
72 | struct spi_device *spi; | 74 | struct spi_device *spi; |
73 | struct list_head device_entry; | 75 | struct list_head device_entry; |
74 | 76 | ||
77 | /* buffer is NULL unless this device is open (users > 0) */ | ||
75 | struct mutex buf_lock; | 78 | struct mutex buf_lock; |
76 | unsigned users; | 79 | unsigned users; |
77 | u8 *buffer; | 80 | u8 *buffer; |
@@ -86,12 +89,75 @@ MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); | |||
86 | 89 | ||
87 | /*-------------------------------------------------------------------------*/ | 90 | /*-------------------------------------------------------------------------*/ |
88 | 91 | ||
92 | /* | ||
93 | * We can't use the standard synchronous wrappers for file I/O; we | ||
94 | * need to protect against async removal of the underlying spi_device. | ||
95 | */ | ||
96 | static void spidev_complete(void *arg) | ||
97 | { | ||
98 | complete(arg); | ||
99 | } | ||
100 | |||
101 | static ssize_t | ||
102 | spidev_sync(struct spidev_data *spidev, struct spi_message *message) | ||
103 | { | ||
104 | DECLARE_COMPLETION_ONSTACK(done); | ||
105 | int status; | ||
106 | |||
107 | message->complete = spidev_complete; | ||
108 | message->context = &done; | ||
109 | |||
110 | spin_lock_irq(&spidev->spi_lock); | ||
111 | if (spidev->spi == NULL) | ||
112 | status = -ESHUTDOWN; | ||
113 | else | ||
114 | status = spi_async(spidev->spi, message); | ||
115 | spin_unlock_irq(&spidev->spi_lock); | ||
116 | |||
117 | if (status == 0) { | ||
118 | wait_for_completion(&done); | ||
119 | status = message->status; | ||
120 | if (status == 0) | ||
121 | status = message->actual_length; | ||
122 | } | ||
123 | return status; | ||
124 | } | ||
125 | |||
126 | static inline ssize_t | ||
127 | spidev_sync_write(struct spidev_data *spidev, size_t len) | ||
128 | { | ||
129 | struct spi_transfer t = { | ||
130 | .tx_buf = spidev->buffer, | ||
131 | .len = len, | ||
132 | }; | ||
133 | struct spi_message m; | ||
134 | |||
135 | spi_message_init(&m); | ||
136 | spi_message_add_tail(&t, &m); | ||
137 | return spidev_sync(spidev, &m); | ||
138 | } | ||
139 | |||
140 | static inline ssize_t | ||
141 | spidev_sync_read(struct spidev_data *spidev, size_t len) | ||
142 | { | ||
143 | struct spi_transfer t = { | ||
144 | .rx_buf = spidev->buffer, | ||
145 | .len = len, | ||
146 | }; | ||
147 | struct spi_message m; | ||
148 | |||
149 | spi_message_init(&m); | ||
150 | spi_message_add_tail(&t, &m); | ||
151 | return spidev_sync(spidev, &m); | ||
152 | } | ||
153 | |||
154 | /*-------------------------------------------------------------------------*/ | ||
155 | |||
89 | /* Read-only message with current device setup */ | 156 | /* Read-only message with current device setup */ |
90 | static ssize_t | 157 | static ssize_t |
91 | spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) | 158 | spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) |
92 | { | 159 | { |
93 | struct spidev_data *spidev; | 160 | struct spidev_data *spidev; |
94 | struct spi_device *spi; | ||
95 | ssize_t status = 0; | 161 | ssize_t status = 0; |
96 | 162 | ||
97 | /* chipselect only toggles at start or end of operation */ | 163 | /* chipselect only toggles at start or end of operation */ |
@@ -99,18 +165,17 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) | |||
99 | return -EMSGSIZE; | 165 | return -EMSGSIZE; |
100 | 166 | ||
101 | spidev = filp->private_data; | 167 | spidev = filp->private_data; |
102 | spi = spidev->spi; | ||
103 | 168 | ||
104 | mutex_lock(&spidev->buf_lock); | 169 | mutex_lock(&spidev->buf_lock); |
105 | status = spi_read(spi, spidev->buffer, count); | 170 | status = spidev_sync_read(spidev, count); |
106 | if (status == 0) { | 171 | if (status > 0) { |
107 | unsigned long missing; | 172 | unsigned long missing; |
108 | 173 | ||
109 | missing = copy_to_user(buf, spidev->buffer, count); | 174 | missing = copy_to_user(buf, spidev->buffer, status); |
110 | if (count && missing == count) | 175 | if (missing == status) |
111 | status = -EFAULT; | 176 | status = -EFAULT; |
112 | else | 177 | else |
113 | status = count - missing; | 178 | status = status - missing; |
114 | } | 179 | } |
115 | mutex_unlock(&spidev->buf_lock); | 180 | mutex_unlock(&spidev->buf_lock); |
116 | 181 | ||
@@ -123,7 +188,6 @@ spidev_write(struct file *filp, const char __user *buf, | |||
123 | size_t count, loff_t *f_pos) | 188 | size_t count, loff_t *f_pos) |
124 | { | 189 | { |
125 | struct spidev_data *spidev; | 190 | struct spidev_data *spidev; |
126 | struct spi_device *spi; | ||
127 | ssize_t status = 0; | 191 | ssize_t status = 0; |
128 | unsigned long missing; | 192 | unsigned long missing; |
129 | 193 | ||
@@ -132,14 +196,11 @@ spidev_write(struct file *filp, const char __user *buf, | |||
132 | return -EMSGSIZE; | 196 | return -EMSGSIZE; |
133 | 197 | ||
134 | spidev = filp->private_data; | 198 | spidev = filp->private_data; |
135 | spi = spidev->spi; | ||
136 | 199 | ||
137 | mutex_lock(&spidev->buf_lock); | 200 | mutex_lock(&spidev->buf_lock); |
138 | missing = copy_from_user(spidev->buffer, buf, count); | 201 | missing = copy_from_user(spidev->buffer, buf, count); |
139 | if (missing == 0) { | 202 | if (missing == 0) { |
140 | status = spi_write(spi, spidev->buffer, count); | 203 | status = spidev_sync_write(spidev, count); |
141 | if (status == 0) | ||
142 | status = count; | ||
143 | } else | 204 | } else |
144 | status = -EFAULT; | 205 | status = -EFAULT; |
145 | mutex_unlock(&spidev->buf_lock); | 206 | mutex_unlock(&spidev->buf_lock); |
@@ -154,7 +215,6 @@ static int spidev_message(struct spidev_data *spidev, | |||
154 | struct spi_transfer *k_xfers; | 215 | struct spi_transfer *k_xfers; |
155 | struct spi_transfer *k_tmp; | 216 | struct spi_transfer *k_tmp; |
156 | struct spi_ioc_transfer *u_tmp; | 217 | struct spi_ioc_transfer *u_tmp; |
157 | struct spi_device *spi = spidev->spi; | ||
158 | unsigned n, total; | 218 | unsigned n, total; |
159 | u8 *buf; | 219 | u8 *buf; |
160 | int status = -EFAULT; | 220 | int status = -EFAULT; |
@@ -216,7 +276,7 @@ static int spidev_message(struct spidev_data *spidev, | |||
216 | spi_message_add_tail(k_tmp, &msg); | 276 | spi_message_add_tail(k_tmp, &msg); |
217 | } | 277 | } |
218 | 278 | ||
219 | status = spi_sync(spi, &msg); | 279 | status = spidev_sync(spidev, &msg); |
220 | if (status < 0) | 280 | if (status < 0) |
221 | goto done; | 281 | goto done; |
222 | 282 | ||
@@ -270,8 +330,16 @@ spidev_ioctl(struct inode *inode, struct file *filp, | |||
270 | if (err) | 330 | if (err) |
271 | return -EFAULT; | 331 | return -EFAULT; |
272 | 332 | ||
333 | /* guard against device removal before, or while, | ||
334 | * we issue this ioctl. | ||
335 | */ | ||
273 | spidev = filp->private_data; | 336 | spidev = filp->private_data; |
274 | spi = spidev->spi; | 337 | spin_lock_irq(&spidev->spi_lock); |
338 | spi = spi_dev_get(spidev->spi); | ||
339 | spin_unlock_irq(&spidev->spi_lock); | ||
340 | |||
341 | if (spi == NULL) | ||
342 | return -ESHUTDOWN; | ||
275 | 343 | ||
276 | switch (cmd) { | 344 | switch (cmd) { |
277 | /* read requests */ | 345 | /* read requests */ |
@@ -357,8 +425,10 @@ spidev_ioctl(struct inode *inode, struct file *filp, | |||
357 | default: | 425 | default: |
358 | /* segmented and/or full-duplex I/O request */ | 426 | /* segmented and/or full-duplex I/O request */ |
359 | if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) | 427 | if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) |
360 | || _IOC_DIR(cmd) != _IOC_WRITE) | 428 | || _IOC_DIR(cmd) != _IOC_WRITE) { |
361 | return -ENOTTY; | 429 | retval = -ENOTTY; |
430 | break; | ||
431 | } | ||
362 | 432 | ||
363 | tmp = _IOC_SIZE(cmd); | 433 | tmp = _IOC_SIZE(cmd); |
364 | if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { | 434 | if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { |
@@ -386,6 +456,7 @@ spidev_ioctl(struct inode *inode, struct file *filp, | |||
386 | kfree(ioc); | 456 | kfree(ioc); |
387 | break; | 457 | break; |
388 | } | 458 | } |
459 | spi_dev_put(spi); | ||
389 | return retval; | 460 | return retval; |
390 | } | 461 | } |
391 | 462 | ||
@@ -398,7 +469,7 @@ static int spidev_open(struct inode *inode, struct file *filp) | |||
398 | mutex_lock(&device_list_lock); | 469 | mutex_lock(&device_list_lock); |
399 | 470 | ||
400 | list_for_each_entry(spidev, &device_list, device_entry) { | 471 | list_for_each_entry(spidev, &device_list, device_entry) { |
401 | if (spidev->dev.devt == inode->i_rdev) { | 472 | if (spidev->devt == inode->i_rdev) { |
402 | status = 0; | 473 | status = 0; |
403 | break; | 474 | break; |
404 | } | 475 | } |
@@ -432,10 +503,22 @@ static int spidev_release(struct inode *inode, struct file *filp) | |||
432 | mutex_lock(&device_list_lock); | 503 | mutex_lock(&device_list_lock); |
433 | spidev = filp->private_data; | 504 | spidev = filp->private_data; |
434 | filp->private_data = NULL; | 505 | filp->private_data = NULL; |
506 | |||
507 | /* last close? */ | ||
435 | spidev->users--; | 508 | spidev->users--; |
436 | if (!spidev->users) { | 509 | if (!spidev->users) { |
510 | int dofree; | ||
511 | |||
437 | kfree(spidev->buffer); | 512 | kfree(spidev->buffer); |
438 | spidev->buffer = NULL; | 513 | spidev->buffer = NULL; |
514 | |||
515 | /* ... after we unbound from the underlying device? */ | ||
516 | spin_lock_irq(&spidev->spi_lock); | ||
517 | dofree = (spidev->spi == NULL); | ||
518 | spin_unlock_irq(&spidev->spi_lock); | ||
519 | |||
520 | if (dofree) | ||
521 | kfree(spidev); | ||
439 | } | 522 | } |
440 | mutex_unlock(&device_list_lock); | 523 | mutex_unlock(&device_list_lock); |
441 | 524 | ||
@@ -462,19 +545,7 @@ static struct file_operations spidev_fops = { | |||
462 | * It also simplifies memory management. | 545 | * It also simplifies memory management. |
463 | */ | 546 | */ |
464 | 547 | ||
465 | static void spidev_classdev_release(struct device *dev) | 548 | static struct class *spidev_class; |
466 | { | ||
467 | struct spidev_data *spidev; | ||
468 | |||
469 | spidev = container_of(dev, struct spidev_data, dev); | ||
470 | kfree(spidev); | ||
471 | } | ||
472 | |||
473 | static struct class spidev_class = { | ||
474 | .name = "spidev", | ||
475 | .owner = THIS_MODULE, | ||
476 | .dev_release = spidev_classdev_release, | ||
477 | }; | ||
478 | 549 | ||
479 | /*-------------------------------------------------------------------------*/ | 550 | /*-------------------------------------------------------------------------*/ |
480 | 551 | ||
@@ -491,6 +562,7 @@ static int spidev_probe(struct spi_device *spi) | |||
491 | 562 | ||
492 | /* Initialize the driver data */ | 563 | /* Initialize the driver data */ |
493 | spidev->spi = spi; | 564 | spidev->spi = spi; |
565 | spin_lock_init(&spidev->spi_lock); | ||
494 | mutex_init(&spidev->buf_lock); | 566 | mutex_init(&spidev->buf_lock); |
495 | 567 | ||
496 | INIT_LIST_HEAD(&spidev->device_entry); | 568 | INIT_LIST_HEAD(&spidev->device_entry); |
@@ -501,20 +573,20 @@ static int spidev_probe(struct spi_device *spi) | |||
501 | mutex_lock(&device_list_lock); | 573 | mutex_lock(&device_list_lock); |
502 | minor = find_first_zero_bit(minors, N_SPI_MINORS); | 574 | minor = find_first_zero_bit(minors, N_SPI_MINORS); |
503 | if (minor < N_SPI_MINORS) { | 575 | if (minor < N_SPI_MINORS) { |
504 | spidev->dev.parent = &spi->dev; | 576 | struct device *dev; |
505 | spidev->dev.class = &spidev_class; | 577 | |
506 | spidev->dev.devt = MKDEV(SPIDEV_MAJOR, minor); | 578 | spidev->devt = MKDEV(SPIDEV_MAJOR, minor); |
507 | snprintf(spidev->dev.bus_id, sizeof spidev->dev.bus_id, | 579 | dev = device_create(spidev_class, &spi->dev, spidev->devt, |
508 | "spidev%d.%d", | 580 | "spidev%d.%d", |
509 | spi->master->bus_num, spi->chip_select); | 581 | spi->master->bus_num, spi->chip_select); |
510 | status = device_register(&spidev->dev); | 582 | status = IS_ERR(dev) ? PTR_ERR(dev) : 0; |
511 | } else { | 583 | } else { |
512 | dev_dbg(&spi->dev, "no minor number available!\n"); | 584 | dev_dbg(&spi->dev, "no minor number available!\n"); |
513 | status = -ENODEV; | 585 | status = -ENODEV; |
514 | } | 586 | } |
515 | if (status == 0) { | 587 | if (status == 0) { |
516 | set_bit(minor, minors); | 588 | set_bit(minor, minors); |
517 | dev_set_drvdata(&spi->dev, spidev); | 589 | spi_set_drvdata(spi, spidev); |
518 | list_add(&spidev->device_entry, &device_list); | 590 | list_add(&spidev->device_entry, &device_list); |
519 | } | 591 | } |
520 | mutex_unlock(&device_list_lock); | 592 | mutex_unlock(&device_list_lock); |
@@ -527,15 +599,21 @@ static int spidev_probe(struct spi_device *spi) | |||
527 | 599 | ||
528 | static int spidev_remove(struct spi_device *spi) | 600 | static int spidev_remove(struct spi_device *spi) |
529 | { | 601 | { |
530 | struct spidev_data *spidev = dev_get_drvdata(&spi->dev); | 602 | struct spidev_data *spidev = spi_get_drvdata(spi); |
531 | 603 | ||
532 | mutex_lock(&device_list_lock); | 604 | /* make sure ops on existing fds can abort cleanly */ |
605 | spin_lock_irq(&spidev->spi_lock); | ||
606 | spidev->spi = NULL; | ||
607 | spi_set_drvdata(spi, NULL); | ||
608 | spin_unlock_irq(&spidev->spi_lock); | ||
533 | 609 | ||
610 | /* prevent new opens */ | ||
611 | mutex_lock(&device_list_lock); | ||
534 | list_del(&spidev->device_entry); | 612 | list_del(&spidev->device_entry); |
535 | dev_set_drvdata(&spi->dev, NULL); | 613 | device_destroy(spidev_class, spidev->devt); |
536 | clear_bit(MINOR(spidev->dev.devt), minors); | 614 | clear_bit(MINOR(spidev->devt), minors); |
537 | device_unregister(&spidev->dev); | 615 | if (spidev->users == 0) |
538 | 616 | kfree(spidev); | |
539 | mutex_unlock(&device_list_lock); | 617 | mutex_unlock(&device_list_lock); |
540 | 618 | ||
541 | return 0; | 619 | return 0; |
@@ -571,15 +649,15 @@ static int __init spidev_init(void) | |||
571 | if (status < 0) | 649 | if (status < 0) |
572 | return status; | 650 | return status; |
573 | 651 | ||
574 | status = class_register(&spidev_class); | 652 | spidev_class = class_create(THIS_MODULE, "spidev"); |
575 | if (status < 0) { | 653 | if (IS_ERR(spidev_class)) { |
576 | unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); | 654 | unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); |
577 | return status; | 655 | return PTR_ERR(spidev_class); |
578 | } | 656 | } |
579 | 657 | ||
580 | status = spi_register_driver(&spidev_spi); | 658 | status = spi_register_driver(&spidev_spi); |
581 | if (status < 0) { | 659 | if (status < 0) { |
582 | class_unregister(&spidev_class); | 660 | class_destroy(spidev_class); |
583 | unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); | 661 | unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); |
584 | } | 662 | } |
585 | return status; | 663 | return status; |
@@ -589,7 +667,7 @@ module_init(spidev_init); | |||
589 | static void __exit spidev_exit(void) | 667 | static void __exit spidev_exit(void) |
590 | { | 668 | { |
591 | spi_unregister_driver(&spidev_spi); | 669 | spi_unregister_driver(&spidev_spi); |
592 | class_unregister(&spidev_class); | 670 | class_destroy(spidev_class); |
593 | unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); | 671 | unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); |
594 | } | 672 | } |
595 | module_exit(spidev_exit); | 673 | module_exit(spidev_exit); |