Linux I2C development
 help / color / mirror / Atom feed
* [PATCH 0/3] i2c: dev: fix blocked adapter deregistration
@ 2026-08-27  9:08 Johan Hovold
  2026-08-27  9:08 ` [PATCH 1/3] " Johan Hovold
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Johan Hovold @ 2026-08-27  9:08 UTC (permalink / raw)
  To: Andi Shyti; +Cc: Wolfram Sang, linux-i2c, linux-kernel, Johan Hovold

As part of addressing i2c lifetime issues, this series fixes an issue
which may actually matter to some people, at least if they are playing
around with USB attached i2c controllers, and whose fix does not depend
on any larger rework.

This could possibly go into 7.3, but can also wait for 7.4 as the impact
of the issue (both in terms of number of affected users and symptoms)
should be limited.

Johan


Johan Hovold (3):
  i2c: dev: fix blocked adapter deregistration
  i2c: dev: drop unnecessary sysfs device lookup
  i2c: dev: clean up registration

 drivers/i2c/i2c-dev.c | 155 +++++++++++++++++++++++++-----------------
 1 file changed, 94 insertions(+), 61 deletions(-)

-- 
2.54.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/3] i2c: dev: fix blocked adapter deregistration
  2026-08-27  9:08 [PATCH 0/3] i2c: dev: fix blocked adapter deregistration Johan Hovold
@ 2026-08-27  9:08 ` Johan Hovold
  2026-08-27  9:43   ` Johan Hovold
  2026-08-27  9:08 ` [PATCH 2/3] i2c: dev: drop unnecessary sysfs device lookup Johan Hovold
  2026-08-27  9:08 ` [PATCH 3/3] i2c: dev: clean up registration Johan Hovold
  2 siblings, 1 reply; 5+ messages in thread
From: Johan Hovold @ 2026-08-27  9:08 UTC (permalink / raw)
  To: Andi Shyti; +Cc: Wolfram Sang, linux-i2c, linux-kernel, Johan Hovold, stable

The i2c subsystem allows controllers to be used by non-child devices
that may remain registered after an adapter goes away.

To handle this, adapter deregistration blocks until the last reference
to the adapter is released. Albeit unorthodox, this is mostly fine for
the vast majority of controllers but can cause some trouble when
controllers reside on hotpluggable buses.

Specifically, userspace can prevent an adapter from being deregistered
indefinitely by holding an i2c-dev character device file open. And with
USB attached controllers this prevents further hub events from being
processed by the parent hub until the file is closed.

Fix the i2c-dev implementation by dropping the additional reference
taken at open() and using an rwsem to make sure the adapter is only
accessed while registered.

Note that before commit 611e12ea0f12 ("i2c: core: manage i2c bus device
refcount in i2c_[get|put]_adapter") an adapter going away would instead
have resulted in a use-after-free.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Johan Hovold <johan@kernel.org>
---
 drivers/i2c/i2c-dev.c | 97 +++++++++++++++++++++++++++++++++----------
 1 file changed, 74 insertions(+), 23 deletions(-)

diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c
index ccaac5e29f90..05616249f61b 100644
--- a/drivers/i2c/i2c-dev.c
+++ b/drivers/i2c/i2c-dev.c
@@ -28,6 +28,7 @@
 #include <linux/list.h>
 #include <linux/module.h>
 #include <linux/notifier.h>
+#include <linux/rwsem.h>
 #include <linux/slab.h>
 #include <linux/uaccess.h>
 
@@ -41,11 +42,17 @@
  */
 struct i2c_dev {
 	struct list_head list;
+	struct rw_semaphore rwsem;
 	struct i2c_adapter *adap;
 	struct device dev;
 	struct cdev cdev;
 };
 
+struct i2c_dev_data {
+	struct i2c_dev *i2c_dev;
+	struct i2c_client client;
+};
+
 #define I2C_MINORS	(MINORMASK + 1)
 static LIST_HEAD(i2c_dev_list);
 static DEFINE_SPINLOCK(i2c_dev_list_lock);
@@ -90,8 +97,14 @@ static void put_i2c_dev(struct i2c_dev *i2c_dev, bool del_cdev)
 	spin_lock(&i2c_dev_list_lock);
 	list_del(&i2c_dev->list);
 	spin_unlock(&i2c_dev_list_lock);
-	if (del_cdev)
+	if (del_cdev) {
 		cdev_device_del(&i2c_dev->cdev, &i2c_dev->dev);
+
+		scoped_guard(rwsem_write, &i2c_dev->rwsem) {
+			i2c_dev->adap = NULL;
+		}
+	}
+
 	put_device(&i2c_dev->dev);
 }
 
@@ -134,10 +147,16 @@ ATTRIBUTE_GROUPS(i2c);
 static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
 		loff_t *offset)
 {
+	struct i2c_dev_data *data = file->private_data;
+	struct i2c_client *client = &data->client;
+	struct i2c_dev *i2c_dev = data->i2c_dev;
 	char *tmp;
 	int ret;
 
-	struct i2c_client *client = file->private_data;
+	guard(rwsem_read)(&i2c_dev->rwsem);
+
+	if (!i2c_dev->adap)
+		return -ENODEV;
 
 	/* Adapter must support I2C transfers */
 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
@@ -163,9 +182,16 @@ static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
 static ssize_t i2cdev_write(struct file *file, const char __user *buf,
 		size_t count, loff_t *offset)
 {
+	struct i2c_dev_data *data = file->private_data;
+	struct i2c_client *client = &data->client;
+	struct i2c_dev *i2c_dev = data->i2c_dev;
 	int ret;
 	char *tmp;
-	struct i2c_client *client = file->private_data;
+
+	guard(rwsem_read)(&i2c_dev->rwsem);
+
+	if (!i2c_dev->adap)
+		return -ENODEV;
 
 	/* Adapter must support I2C transfers */
 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
@@ -397,11 +423,15 @@ static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
 	return res;
 }
 
-static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+static long i2cdev_ioctl_locked(struct file *file, unsigned int cmd, unsigned long arg)
 {
-	struct i2c_client *client = file->private_data;
+	struct i2c_dev_data *data = file->private_data;
+	struct i2c_client *client = &data->client;
+	struct i2c_dev *i2c_dev = data->i2c_dev;
 	unsigned long funcs;
 
+	lockdep_assert_held(&i2c_dev->rwsem);
+
 	dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
 		cmd, arg);
 
@@ -507,6 +537,19 @@ static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	return 0;
 }
 
+static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct i2c_dev_data *data = file->private_data;
+	struct i2c_dev *i2c_dev = data->i2c_dev;
+
+	guard(rwsem_read)(&i2c_dev->rwsem);
+
+	if (!i2c_dev->adap)
+		return -ENODEV;
+
+	return i2cdev_ioctl_locked(file, cmd, arg);
+}
+
 #ifdef CONFIG_COMPAT
 
 struct i2c_smbus_ioctl_data32 {
@@ -530,8 +573,16 @@ struct i2c_rdwr_ioctl_data32 {
 
 static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
-	struct i2c_client *client = file->private_data;
+	struct i2c_dev_data *data = file->private_data;
+	struct i2c_client *client = &data->client;
+	struct i2c_dev *i2c_dev = data->i2c_dev;
 	unsigned long funcs;
+
+	guard(rwsem_read)(&i2c_dev->rwsem);
+
+	if (!i2c_dev->adap)
+		return -ENODEV;
+
 	switch (cmd) {
 	case I2C_FUNCS:
 		funcs = i2c_get_functionality(client->adapter);
@@ -588,7 +639,7 @@ static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned lo
 					  compat_ptr(data32.data));
 	}
 	default:
-		return i2cdev_ioctl(file, cmd, arg);
+		return i2cdev_ioctl_locked(file, cmd, arg);
 	}
 }
 #else
@@ -597,13 +648,10 @@ static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned lo
 
 static int i2cdev_open(struct inode *inode, struct file *file)
 {
-	unsigned int minor = iminor(inode);
+	struct i2c_dev *i2c_dev = container_of(inode->i_cdev, struct i2c_dev, cdev);
+	struct i2c_adapter *adap = i2c_dev->adap;
+	struct i2c_dev_data *data;
 	struct i2c_client *client;
-	struct i2c_adapter *adap;
-
-	adap = i2c_get_adapter(minor);
-	if (!adap)
-		return -ENODEV;
 
 	/* This creates an anonymous i2c_client, which may later be
 	 * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
@@ -612,26 +660,27 @@ static int i2cdev_open(struct inode *inode, struct file *file)
 	 * or I2C core code!!  It just holds private copies of addressing
 	 * information and maybe a PEC flag.
 	 */
-	client = kzalloc_obj(*client);
-	if (!client) {
-		i2c_put_adapter(adap);
+	data = kzalloc_obj(*data);
+	if (!data)
 		return -ENOMEM;
-	}
+
+	data->i2c_dev = i2c_dev;
+
+	client = &data->client;
+
 	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
 
 	client->adapter = adap;
-	file->private_data = client;
+	file->private_data = data;
 
 	return 0;
 }
 
 static int i2cdev_release(struct inode *inode, struct file *file)
 {
-	struct i2c_client *client = file->private_data;
+	struct i2c_dev_data *data = file->private_data;
 
-	i2c_put_adapter(client->adapter);
-	kfree(client);
-	file->private_data = NULL;
+	kfree(data);
 
 	return 0;
 }
@@ -675,8 +724,10 @@ static int i2cdev_attach_adapter(struct device *dev)
 	if (IS_ERR(i2c_dev))
 		return NOTIFY_DONE;
 
+	init_rwsem(&i2c_dev->rwsem);
+
 	cdev_init(&i2c_dev->cdev, &i2cdev_fops);
-	i2c_dev->cdev.owner = THIS_MODULE;
+	i2c_dev->cdev.owner = adap->owner;
 
 	device_initialize(&i2c_dev->dev);
 	i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/3] i2c: dev: drop unnecessary sysfs device lookup
  2026-08-27  9:08 [PATCH 0/3] i2c: dev: fix blocked adapter deregistration Johan Hovold
  2026-08-27  9:08 ` [PATCH 1/3] " Johan Hovold
@ 2026-08-27  9:08 ` Johan Hovold
  2026-08-27  9:08 ` [PATCH 3/3] i2c: dev: clean up registration Johan Hovold
  2 siblings, 0 replies; 5+ messages in thread
From: Johan Hovold @ 2026-08-27  9:08 UTC (permalink / raw)
  To: Andi Shyti; +Cc: Wolfram Sang, linux-i2c, linux-kernel, Johan Hovold

The sysfs lifetime rules guarantees that the i2c-dev class device is
still valid while its attribute callbacks are executing so drop the
unnecessary reverse lookup.

Signed-off-by: Johan Hovold <johan@kernel.org>
---
 drivers/i2c/i2c-dev.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c
index 05616249f61b..319bc46cc207 100644
--- a/drivers/i2c/i2c-dev.c
+++ b/drivers/i2c/i2c-dev.c
@@ -47,6 +47,7 @@ struct i2c_dev {
 	struct device dev;
 	struct cdev cdev;
 };
+#define to_i2c_dev(d) container_of((d), struct i2c_dev, dev)
 
 struct i2c_dev_data {
 	struct i2c_dev *i2c_dev;
@@ -111,10 +112,8 @@ static void put_i2c_dev(struct i2c_dev *i2c_dev, bool del_cdev)
 static ssize_t name_show(struct device *dev,
 			 struct device_attribute *attr, char *buf)
 {
-	struct i2c_dev *i2c_dev = i2c_dev_get_by_minor(MINOR(dev->devt));
+	struct i2c_dev *i2c_dev = to_i2c_dev(dev);
 
-	if (!i2c_dev)
-		return -ENODEV;
 	return sysfs_emit(buf, "%s\n", i2c_dev->adap->name);
 }
 static DEVICE_ATTR_RO(name);
@@ -704,9 +703,8 @@ static const struct class i2c_dev_class = {
 
 static void i2cdev_dev_release(struct device *dev)
 {
-	struct i2c_dev *i2c_dev;
+	struct i2c_dev *i2c_dev = to_i2c_dev(dev);
 
-	i2c_dev = container_of(dev, struct i2c_dev, dev);
 	kfree(i2c_dev);
 }
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 3/3] i2c: dev: clean up registration
  2026-08-27  9:08 [PATCH 0/3] i2c: dev: fix blocked adapter deregistration Johan Hovold
  2026-08-27  9:08 ` [PATCH 1/3] " Johan Hovold
  2026-08-27  9:08 ` [PATCH 2/3] i2c: dev: drop unnecessary sysfs device lookup Johan Hovold
@ 2026-08-27  9:08 ` Johan Hovold
  2 siblings, 0 replies; 5+ messages in thread
From: Johan Hovold @ 2026-08-27  9:08 UTC (permalink / raw)
  To: Andi Shyti; +Cc: Wolfram Sang, linux-i2c, linux-kernel, Johan Hovold

Drop the get_free_i2c_dev() and put_i2c_dev() helpers and do all set up
and tear down directly in i2cdev_attach_adapter() and
i2cdev_detach_adapter() for consistency and to make the logic clearer.

Note that the device list is only used at detach so the i2c-dev can be
added after registering the class device.

Signed-off-by: Johan Hovold <johan@kernel.org>
---
 drivers/i2c/i2c-dev.c | 64 ++++++++++++++++---------------------------
 1 file changed, 24 insertions(+), 40 deletions(-)

diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c
index 319bc46cc207..1aa798b87dcd 100644
--- a/drivers/i2c/i2c-dev.c
+++ b/drivers/i2c/i2c-dev.c
@@ -73,42 +73,6 @@ static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
 	return i2c_dev;
 }
 
-static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap)
-{
-	struct i2c_dev *i2c_dev;
-
-	if (adap->nr >= I2C_MINORS) {
-		pr_err("Out of device minors (%d)\n", adap->nr);
-		return ERR_PTR(-ENODEV);
-	}
-
-	i2c_dev = kzalloc_obj(*i2c_dev);
-	if (!i2c_dev)
-		return ERR_PTR(-ENOMEM);
-	i2c_dev->adap = adap;
-
-	spin_lock(&i2c_dev_list_lock);
-	list_add_tail(&i2c_dev->list, &i2c_dev_list);
-	spin_unlock(&i2c_dev_list_lock);
-	return i2c_dev;
-}
-
-static void put_i2c_dev(struct i2c_dev *i2c_dev, bool del_cdev)
-{
-	spin_lock(&i2c_dev_list_lock);
-	list_del(&i2c_dev->list);
-	spin_unlock(&i2c_dev_list_lock);
-	if (del_cdev) {
-		cdev_device_del(&i2c_dev->cdev, &i2c_dev->dev);
-
-		scoped_guard(rwsem_write, &i2c_dev->rwsem) {
-			i2c_dev->adap = NULL;
-		}
-	}
-
-	put_device(&i2c_dev->dev);
-}
-
 static ssize_t name_show(struct device *dev,
 			 struct device_attribute *attr, char *buf)
 {
@@ -718,11 +682,17 @@ static int i2cdev_attach_adapter(struct device *dev)
 		return NOTIFY_DONE;
 	adap = to_i2c_adapter(dev);
 
-	i2c_dev = get_free_i2c_dev(adap);
-	if (IS_ERR(i2c_dev))
+	if (adap->nr >= I2C_MINORS) {
+		pr_err("Out of device minors (%d)\n", adap->nr);
+		return NOTIFY_DONE;
+	}
+
+	i2c_dev = kzalloc_obj(*i2c_dev);
+	if (!i2c_dev)
 		return NOTIFY_DONE;
 
 	init_rwsem(&i2c_dev->rwsem);
+	i2c_dev->adap = adap;
 
 	cdev_init(&i2c_dev->cdev, &i2cdev_fops);
 	i2c_dev->cdev.owner = adap->owner;
@@ -741,11 +711,15 @@ static int i2cdev_attach_adapter(struct device *dev)
 	if (res)
 		goto err_put_i2c_dev;
 
+	spin_lock(&i2c_dev_list_lock);
+	list_add_tail(&i2c_dev->list, &i2c_dev_list);
+	spin_unlock(&i2c_dev_list_lock);
+
 	pr_debug("adapter [%s] registered as minor %d\n", adap->name, adap->nr);
 	return NOTIFY_OK;
 
 err_put_i2c_dev:
-	put_i2c_dev(i2c_dev, false);
+	put_device(&i2c_dev->dev);
 	return NOTIFY_DONE;
 }
 
@@ -762,7 +736,17 @@ static int i2cdev_detach_adapter(struct device *dev)
 	if (!i2c_dev) /* attach_adapter must have failed */
 		return NOTIFY_DONE;
 
-	put_i2c_dev(i2c_dev, true);
+	spin_lock(&i2c_dev_list_lock);
+	list_del(&i2c_dev->list);
+	spin_unlock(&i2c_dev_list_lock);
+
+	cdev_device_del(&i2c_dev->cdev, &i2c_dev->dev);
+
+	scoped_guard(rwsem_write, &i2c_dev->rwsem) {
+		i2c_dev->adap = NULL;
+	}
+
+	put_device(&i2c_dev->dev);
 
 	pr_debug("adapter [%s] unregistered\n", adap->name);
 	return NOTIFY_OK;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/3] i2c: dev: fix blocked adapter deregistration
  2026-08-27  9:08 ` [PATCH 1/3] " Johan Hovold
@ 2026-08-27  9:43   ` Johan Hovold
  0 siblings, 0 replies; 5+ messages in thread
From: Johan Hovold @ 2026-08-27  9:43 UTC (permalink / raw)
  To: Andi Shyti; +Cc: Wolfram Sang, linux-i2c, linux-kernel, stable

On Thu, Aug 27, 2026 at 11:08:02AM +0200, Johan Hovold wrote:
> The i2c subsystem allows controllers to be used by non-child devices
> that may remain registered after an adapter goes away.
> 
> To handle this, adapter deregistration blocks until the last reference
> to the adapter is released. Albeit unorthodox, this is mostly fine for
> the vast majority of controllers but can cause some trouble when
> controllers reside on hotpluggable buses.
> 
> Specifically, userspace can prevent an adapter from being deregistered
> indefinitely by holding an i2c-dev character device file open. And with
> USB attached controllers this prevents further hub events from being
> processed by the parent hub until the file is closed.
> 
> Fix the i2c-dev implementation by dropping the additional reference
> taken at open() and using an rwsem to make sure the adapter is only
> accessed while registered.
> 
> Note that before commit 611e12ea0f12 ("i2c: core: manage i2c bus device
> refcount in i2c_[get|put]_adapter") an adapter going away would instead
> have resulted in a use-after-free.
> 
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Cc: stable@vger.kernel.org
> Signed-off-by: Johan Hovold <johan@kernel.org>
 
>  static int i2cdev_open(struct inode *inode, struct file *file)
>  {
> -	unsigned int minor = iminor(inode);
> +	struct i2c_dev *i2c_dev = container_of(inode->i_cdev, struct i2c_dev, cdev);
> +	struct i2c_adapter *adap = i2c_dev->adap;

Bah, I of course need to take the rwsem here as well as pointed out by
Sashiko.

Let me respin.

Johan 

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-27  9:43 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27  9:08 [PATCH 0/3] i2c: dev: fix blocked adapter deregistration Johan Hovold
2026-08-27  9:08 ` [PATCH 1/3] " Johan Hovold
2026-08-27  9:43   ` Johan Hovold
2026-08-27  9:08 ` [PATCH 2/3] i2c: dev: drop unnecessary sysfs device lookup Johan Hovold
2026-08-27  9:08 ` [PATCH 3/3] i2c: dev: clean up registration Johan Hovold

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox