Problem
i2c_dev_delete_mutex() never decrements the per-port ref_count and therefore never deletes the master bus. As a result, every call to i2c_dev_delete_mutex() leaks the I2C master bus for that port. The bus can only be cleaned up indirectly via i2cdev_done(), and even then ref_count stays stuck at its last incremented value.
Root cause
In i2cdev.c, i2c_dev_delete_mutex() clears dev->dev_handle before the conditional that is supposed to decrement ref_count:
if (dev->dev_handle)
{
esp_err_t rm_res = i2c_master_bus_rm_device((i2c_master_dev_handle_t)dev->dev_handle);
...
dev->dev_handle = NULL; // <-- handle is now NULL
}
// Later, inside the port mutex:
if (i2c_ports[dev->port].installed
&& i2c_ports[dev->port].ref_count > 0
&& dev->dev_handle != NULL) { // <-- always false
i2c_ports[dev->port].ref_count--;
...
if (i2c_ports[dev->port].ref_count == 0) {
i2c_del_master_bus(...); // <-- never reached for the "real" last device
}
}
The condition checks dev->dev_handle != NULL, but the field has just been cleared. ref_count is therefore never decremented, and i2c_del_master_bus() is never called from this path.
Secondary issue: race with i2cdev_done()
deregister_device(dev) is called from i2c_dev_delete_mutex() without holding i2c_ports[dev->port].lock, while i2cdev_done() walks active_devices[port][i] under the lock. The two paths can run concurrently on different tasks, leading to a use-after-free on the i2c_dev_t.
Repro
- Create two devices on
I2C_NUM_0, i2c_dev_create_mutex(&dev1); i2c_dev_create_mutex(&dev2);
- Trigger first I2C operation on each so they are actually added to the bus.
- Call
i2c_dev_delete_mutex(&dev1); — expected: ref_count decrements to 1.
- Observe
i2c_ports[I2C_NUM_0].ref_count is still 2; bus handle is leaked.
Expected
i2c_dev_delete_mutex() decrements ref_count for each device that was actually added to the bus.
- When
ref_count reaches 0, the bus is deleted via i2c_del_master_bus().
deregister_device() runs under the same port mutex as i2cdev_done().
Problem
i2c_dev_delete_mutex()never decrements the per-portref_countand therefore never deletes the master bus. As a result, every call toi2c_dev_delete_mutex()leaks the I2C master bus for that port. The bus can only be cleaned up indirectly viai2cdev_done(), and even thenref_countstays stuck at its last incremented value.Root cause
In
i2cdev.c,i2c_dev_delete_mutex()clearsdev->dev_handlebefore the conditional that is supposed to decrementref_count:The condition checks
dev->dev_handle != NULL, but the field has just been cleared.ref_countis therefore never decremented, andi2c_del_master_bus()is never called from this path.Secondary issue: race with
i2cdev_done()deregister_device(dev)is called fromi2c_dev_delete_mutex()without holdingi2c_ports[dev->port].lock, whilei2cdev_done()walksactive_devices[port][i]under the lock. The two paths can run concurrently on different tasks, leading to a use-after-free on thei2c_dev_t.Repro
I2C_NUM_0,i2c_dev_create_mutex(&dev1); i2c_dev_create_mutex(&dev2);i2c_dev_delete_mutex(&dev1);— expected:ref_countdecrements to 1.i2c_ports[I2C_NUM_0].ref_countis still 2; bus handle is leaked.Expected
i2c_dev_delete_mutex()decrementsref_countfor each device that was actually added to the bus.ref_countreaches 0, the bus is deleted viai2c_del_master_bus().deregister_device()runs under the same port mutex asi2cdev_done().