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