diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c index 71c7a39..1754795 100644 --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -47,20 +47,32 @@ struct i2c_dev { struct list_head list; struct i2c_adapter *adap; struct device *dev; + + wait_queue_head_t crelease; + atomic_t ccount; /* client counter */ + atomic_t detach; /* detach is pending */ }; #define I2C_MINORS 256 static LIST_HEAD(i2c_dev_list); static DEFINE_SPINLOCK(i2c_dev_list_lock); -static struct i2c_dev *i2c_dev_get_by_minor(unsigned index) +#define i2c_dev_get_by_minor(index) __i2c_dev_get_by_minor(index, 0) + +static struct i2c_dev *__i2c_dev_get_by_minor(unsigned index, unsigned detach) { struct i2c_dev *i2c_dev; spin_lock(&i2c_dev_list_lock); list_for_each_entry(i2c_dev, &i2c_dev_list, list) { - if (i2c_dev->adap->nr == index) + if (i2c_dev->adap->nr == index) { + if (detach) { + /* detach is pending */ + atomic_set(&i2c_dev->detach, 1); + } + goto found; + } } i2c_dev = NULL; found: @@ -82,6 +94,9 @@ static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap) if (!i2c_dev) return ERR_PTR(-ENOMEM); i2c_dev->adap = adap; + atomic_set(&i2c_dev->ccount, 0); + atomic_set(&i2c_dev->detach, 0); + init_waitqueue_head(&i2c_dev->crelease); spin_lock(&i2c_dev_list_lock); list_add_tail(&i2c_dev->list, &i2c_dev_list); @@ -94,6 +109,7 @@ static void return_i2c_dev(struct i2c_dev *i2c_dev) spin_lock(&i2c_dev_list_lock); list_del(&i2c_dev->list); spin_unlock(&i2c_dev_list_lock); + kfree(i2c_dev); } @@ -492,6 +508,15 @@ static int i2cdev_open(struct inode *inode, struct file *file) if (!i2c_dev) return -ENODEV; + /* detach is pending + no more client is allowed */ + if (atomic_read(&i2c_dev->detach)) { + return -ENODEV; + } + + atomic_inc(&i2c_dev->ccount); + + adap = i2c_get_adapter(i2c_dev->adap->nr); if (!adap) return -ENODEV; @@ -505,6 +530,7 @@ static int i2cdev_open(struct inode *inode, struct file *file) */ client = kzalloc(sizeof(*client), GFP_KERNEL); if (!client) { + atomic_dec(&i2c_dev->ccount); i2c_put_adapter(adap); return -ENOMEM; } @@ -513,14 +539,26 @@ static int i2cdev_open(struct inode *inode, struct file *file) client->adapter = adap; file->private_data = client; + return 0; } static int i2cdev_release(struct inode *inode, struct file *file) { struct i2c_client *client = file->private_data; + struct i2c_dev *i2c_dev; i2c_put_adapter(client->adapter); + + i2c_dev = i2c_dev_get_by_minor(client->adapter->nr); + + if (i2c_dev) { + if (atomic_dec_and_test(&i2c_dev->ccount)) { + /* wake up wait queue if necessary */ + wake_up_interruptible(&i2c_dev->crelease); + } + } + kfree(client); file->private_data = NULL; @@ -581,10 +619,17 @@ static int i2cdev_detach_adapter(struct device *dev, void *dummy) return 0; adap = to_i2c_adapter(dev); - i2c_dev = i2c_dev_get_by_minor(adap->nr); + i2c_dev = __i2c_dev_get_by_minor(adap->nr, 1); if (!i2c_dev) /* attach_adapter must have failed */ return 0; + device_remove_file(i2c_dev->dev, &dev_attr_name); + + /* wait that every client registered has released + their reference to the adapter */ + wait_event_interruptible(i2c_dev->crelease, + !atomic_read(&i2c_dev->ccount)); + return_i2c_dev(i2c_dev); device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));