diff --git a/drivers/i2c/busses/i2c-tiny-usb.c b/drivers/i2c/busses/i2c-tiny-usb.c index 57dfe5f1a7d9..30679c2b787b 100644 --- a/drivers/i2c/busses/i2c-tiny-usb.c +++ b/drivers/i2c/busses/i2c-tiny-usb.c @@ -12,6 +12,7 @@ #include #include #include +#include /* include interfaces to usb layer */ #include @@ -172,6 +173,9 @@ struct i2c_tiny_usb { struct usb_device *usb_dev; /* the usb device for this device */ struct usb_interface *interface; /* the interface for this device */ struct i2c_adapter adapter; /* i2c related things */ + bool disconnected; /* set to true on disconnect */ + struct work_struct release_work; /* work struct to release the adapter */ + struct mutex usb_lock; /* lock for usb operations */ }; static int usb_read(struct i2c_adapter *adapter, int cmd, @@ -184,11 +188,20 @@ static int usb_read(struct i2c_adapter *adapter, int cmd, if (!dmadata) return -ENOMEM; + mutex_lock(&dev->usb_lock); + if (dev->disconnected) { + mutex_unlock(&dev->usb_lock); + kfree(dmadata); + return -ENODEV; + } + /* do control transfer */ ret = usb_control_msg(dev->usb_dev, usb_rcvctrlpipe(dev->usb_dev, 0), cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_IN, value, index, dmadata, len, 2000); + mutex_unlock(&dev->usb_lock); + memcpy(data, dmadata, len); kfree(dmadata); return ret; @@ -204,11 +217,20 @@ static int usb_write(struct i2c_adapter *adapter, int cmd, if (!dmadata) return -ENOMEM; + mutex_lock(&dev->usb_lock); + if (dev->disconnected) { + mutex_unlock(&dev->usb_lock); + kfree(dmadata); + return -ENODEV; + } + /* do control transfer */ ret = usb_control_msg(dev->usb_dev, usb_sndctrlpipe(dev->usb_dev, 0), cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE, value, index, dmadata, len, 2000); + mutex_unlock(&dev->usb_lock); + kfree(dmadata); return ret; } @@ -219,6 +241,15 @@ static void i2c_tiny_usb_free(struct i2c_tiny_usb *dev) kfree(dev); } +static void i2c_tiny_usb_release(struct work_struct *work) +{ + struct i2c_tiny_usb *dev = container_of(work, struct i2c_tiny_usb, + release_work); + + i2c_del_adapter(&dev->adapter); + i2c_tiny_usb_free(dev); +} + static int i2c_tiny_usb_probe(struct usb_interface *interface, const struct usb_device_id *id) { @@ -239,6 +270,7 @@ static int i2c_tiny_usb_probe(struct usb_interface *interface, dev->usb_dev = usb_get_dev(interface_to_usbdev(interface)); dev->interface = interface; + mutex_init(&dev->usb_lock); /* save our data pointer in this interface device */ usb_set_intfdata(interface, dev); @@ -268,6 +300,8 @@ static int i2c_tiny_usb_probe(struct usb_interface *interface, dev->adapter.dev.parent = &dev->interface->dev; + INIT_WORK(&dev->release_work, i2c_tiny_usb_release); + /* and finally attach to i2c layer */ i2c_add_adapter(&dev->adapter); @@ -287,9 +321,12 @@ static void i2c_tiny_usb_disconnect(struct usb_interface *interface) { struct i2c_tiny_usb *dev = usb_get_intfdata(interface); - i2c_del_adapter(&dev->adapter); + mutex_lock(&dev->usb_lock); usb_set_intfdata(interface, NULL); - i2c_tiny_usb_free(dev); + dev->disconnected = true; + mutex_unlock(&dev->usb_lock); + + queue_work(system_long_wq, &dev->release_work); dev_dbg(&interface->dev, "disconnected\n"); }