aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Brownell <dbrownell@users.sourceforge.net>2008-06-06 01:45:50 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2008-06-06 14:29:08 -0400
commitb2c8daddcbe03a22402ecf943bb88302601c6835 (patch)
tree326282263d7ef47c18e02fee77ffd586457c69e8
parent39b945a37bac2b692773a470890c8ba301485b15 (diff)
spi: fix refcount-related spidev oops-on-rmmod
This addresses other oopsing paths in "spidev" by changing how it manages refcounting. It decouples the lifecycle of the per-device data from the class device (not just the spi device): - Use class_{create,destroy} not class_{register,unregister}. - Use device_{create,destroy} not device_{register,unregister}. - Free the per-device data only when TWO conditions are true: * Driver is unbound from underlying SPI device, and * Device is no longer open (new) Also, spi_{get,set}_drvdata not dev_{get,set}_drvdata for simpler code. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Sebastian Siewior <bigeasy@tglx.de> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--drivers/spi/spidev.c64
1 files changed, 34 insertions, 30 deletions
diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index 41620c0fb046..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,11 +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
69struct spidev_data { 70struct spidev_data {
70 struct device dev; 71 dev_t devt;
71 spinlock_t spi_lock; 72 spinlock_t spi_lock;
72 struct spi_device *spi; 73 struct spi_device *spi;
73 struct list_head device_entry; 74 struct list_head device_entry;
74 75
76 /* buffer is NULL unless this device is open (users > 0) */
75 struct mutex buf_lock; 77 struct mutex buf_lock;
76 unsigned users; 78 unsigned users;
77 u8 *buffer; 79 u8 *buffer;
@@ -467,7 +469,7 @@ static int spidev_open(struct inode *inode, struct file *filp)
467 mutex_lock(&device_list_lock); 469 mutex_lock(&device_list_lock);
468 470
469 list_for_each_entry(spidev, &device_list, device_entry) { 471 list_for_each_entry(spidev, &device_list, device_entry) {
470 if (spidev->dev.devt == inode->i_rdev) { 472 if (spidev->devt == inode->i_rdev) {
471 status = 0; 473 status = 0;
472 break; 474 break;
473 } 475 }
@@ -500,10 +502,22 @@ static int spidev_release(struct inode *inode, struct file *filp)
500 mutex_lock(&device_list_lock); 502 mutex_lock(&device_list_lock);
501 spidev = filp->private_data; 503 spidev = filp->private_data;
502 filp->private_data = NULL; 504 filp->private_data = NULL;
505
506 /* last close? */
503 spidev->users--; 507 spidev->users--;
504 if (!spidev->users) { 508 if (!spidev->users) {
509 int dofree;
510
505 kfree(spidev->buffer); 511 kfree(spidev->buffer);
506 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);
507 } 521 }
508 mutex_unlock(&device_list_lock); 522 mutex_unlock(&device_list_lock);
509 523
@@ -530,19 +544,7 @@ static struct file_operations spidev_fops = {
530 * It also simplifies memory management. 544 * It also simplifies memory management.
531 */ 545 */
532 546
533static void spidev_classdev_release(struct device *dev) 547static struct class *spidev_class;
534{
535 struct spidev_data *spidev;
536
537 spidev = container_of(dev, struct spidev_data, dev);
538 kfree(spidev);
539}
540
541static struct class spidev_class = {
542 .name = "spidev",
543 .owner = THIS_MODULE,
544 .dev_release = spidev_classdev_release,
545};
546 548
547/*-------------------------------------------------------------------------*/ 549/*-------------------------------------------------------------------------*/
548 550
@@ -570,20 +572,20 @@ static int spidev_probe(struct spi_device *spi)
570 mutex_lock(&device_list_lock); 572 mutex_lock(&device_list_lock);
571 minor = find_first_zero_bit(minors, N_SPI_MINORS); 573 minor = find_first_zero_bit(minors, N_SPI_MINORS);
572 if (minor < N_SPI_MINORS) { 574 if (minor < N_SPI_MINORS) {
573 spidev->dev.parent = &spi->dev; 575 struct device *dev;
574 spidev->dev.class = &spidev_class; 576
575 spidev->dev.devt = MKDEV(SPIDEV_MAJOR, minor); 577 spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
576 snprintf(spidev->dev.bus_id, sizeof spidev->dev.bus_id, 578 dev = device_create(spidev_class, &spi->dev, spidev->devt,
577 "spidev%d.%d", 579 "spidev%d.%d",
578 spi->master->bus_num, spi->chip_select); 580 spi->master->bus_num, spi->chip_select);
579 status = device_register(&spidev->dev); 581 status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
580 } else { 582 } else {
581 dev_dbg(&spi->dev, "no minor number available!\n"); 583 dev_dbg(&spi->dev, "no minor number available!\n");
582 status = -ENODEV; 584 status = -ENODEV;
583 } 585 }
584 if (status == 0) { 586 if (status == 0) {
585 set_bit(minor, minors); 587 set_bit(minor, minors);
586 dev_set_drvdata(&spi->dev, spidev); 588 spi_set_drvdata(spi, spidev);
587 list_add(&spidev->device_entry, &device_list); 589 list_add(&spidev->device_entry, &device_list);
588 } 590 }
589 mutex_unlock(&device_list_lock); 591 mutex_unlock(&device_list_lock);
@@ -596,19 +598,21 @@ static int spidev_probe(struct spi_device *spi)
596 598
597static int spidev_remove(struct spi_device *spi) 599static int spidev_remove(struct spi_device *spi)
598{ 600{
599 struct spidev_data *spidev = dev_get_drvdata(&spi->dev); 601 struct spidev_data *spidev = spi_get_drvdata(spi);
600 602
601 /* make sure ops on existing fds can abort cleanly */ 603 /* make sure ops on existing fds can abort cleanly */
602 spin_lock_irq(&spidev->spi_lock); 604 spin_lock_irq(&spidev->spi_lock);
603 spidev->spi = NULL; 605 spidev->spi = NULL;
606 spi_set_drvdata(spi, NULL);
604 spin_unlock_irq(&spidev->spi_lock); 607 spin_unlock_irq(&spidev->spi_lock);
605 608
606 /* prevent new opens */ 609 /* prevent new opens */
607 mutex_lock(&device_list_lock); 610 mutex_lock(&device_list_lock);
608 list_del(&spidev->device_entry); 611 list_del(&spidev->device_entry);
609 dev_set_drvdata(&spi->dev, NULL); 612 device_destroy(spidev_class, spidev->devt);
610 clear_bit(MINOR(spidev->dev.devt), minors); 613 clear_bit(MINOR(spidev->devt), minors);
611 device_unregister(&spidev->dev); 614 if (spidev->users == 0)
615 kfree(spidev);
612 mutex_unlock(&device_list_lock); 616 mutex_unlock(&device_list_lock);
613 617
614 return 0; 618 return 0;
@@ -644,15 +648,15 @@ static int __init spidev_init(void)
644 if (status < 0) 648 if (status < 0)
645 return status; 649 return status;
646 650
647 status = class_register(&spidev_class); 651 spidev_class = class_create(THIS_MODULE, "spidev");
648 if (status < 0) { 652 if (IS_ERR(spidev_class)) {
649 unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); 653 unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
650 return status; 654 return PTR_ERR(spidev_class);
651 } 655 }
652 656
653 status = spi_register_driver(&spidev_spi); 657 status = spi_register_driver(&spidev_spi);
654 if (status < 0) { 658 if (status < 0) {
655 class_unregister(&spidev_class); 659 class_destroy(spidev_class);
656 unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); 660 unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
657 } 661 }
658 return status; 662 return status;
@@ -662,7 +666,7 @@ module_init(spidev_init);
662static void __exit spidev_exit(void) 666static void __exit spidev_exit(void)
663{ 667{
664 spi_unregister_driver(&spidev_spi); 668 spi_unregister_driver(&spidev_spi);
665 class_unregister(&spidev_class); 669 class_destroy(spidev_class);
666 unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); 670 unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
667} 671}
668module_exit(spidev_exit); 672module_exit(spidev_exit);