Repository navigation
Fix bus and device lifecycle, legacy build, argument validation (review of 2.1.3) - #23
Merged
Merged
Conversation
Legacy build: - add `driver` to component requirements unconditionally: the requirement list is evaluated on the first CMake pass, before sdkconfig exists, so a list derived from CONFIG_I2CDEV_USE_LEGACY_DRIVER never contains it - add i2cdev_get_shared_handle() stub for the legacy driver so the API declared in i2cdev.h links i2cdev.c: - i2cdev_init() no longer keeps a static "initialized" latch: state is derived from the port mutexes, so init/done/init works again - keep the bus and the device handle when a teardown step fails instead of dropping them: i2c_del_master_bus() refusing to run left the port occupied until reboot; i2cdev_done() now retries partially released ports - i2cdev_done() does not delete a port mutex it failed to take - drop per-device handle recreation in the retry loop: it ran without the port mutex, without releasing the device slot and leaked the handle - register devices under the port mutex in i2c_setup_device() and fail the operation if registration is impossible - replace the fixed device array with a per-port list, drop the per-port device limit, release the list in init/done - the bus now lives from the first operation until i2cdev_done(): deleting it on the last i2cdev device broke external users of the shared bus - take the port mutex before checking dev->dev_handle (two tasks could add the same device twice, the IDF driver does not detect duplicates) - reject negative port numbers before they index internal arrays - reject descriptors whose SDA and SCL pins are the same GPIO - apply per-line pullups individually: the bus config has a single flag - return ESP_ERR_INVALID_ARG from read_reg/write_reg for empty buffers - return ESP_ERR_INVALID_STATE from i2c_dev_check_present() when the port is not initialized - keep CONFIG_I2CDEV_NOLOCK limited to per-device mutexes; bus lifecycle works in both modes - replace direct pointer comparisons with NULL by the short form
UncleRus
force-pushed
the
fix/i2cdev-review
branch
from
November 11, 2026 22:47
4d7c973 to
e64f117
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes every problem listed in #22: the legacy build, the bus and device lifecycle of the new driver, argument validation, and the header documentation that did not match the implementation.
Legacy build
driveris added to the component requirements unconditionally. The requirement list is evaluated on the first CMake pass, beforesdkconfigexists, so a list derived fromCONFIG_I2CDEV_USE_LEGACY_DRIVERnever contains it: the legacy build failed withfatal error: driver/i2c.h. The source selector is unchanged and still follows the config.i2cdev_get_shared_handle()gets a stub ini2cdev_legacy.c(ESP_ERR_NOT_SUPPORTED), otherwise the legacy build does not link: the function is declared ini2cdev.hfor both implementations.Bus lifecycle
i2cdev_init()drops thestatic bool initializedlatch. The state is derived from the port mutexes, soinit()/done()/init()works again, and a port with a live bus is not reset.bus_handle,installed,dev->dev_handleand the device list survive an error fromi2c_del_master_bus()/i2c_master_bus_rm_device(), so the call can be repeated.i2cdev_done()now returns the first error and keeps partially released ports for the next attempt.i2cdev_done()never deletes a port mutex it failed to take.i2cdev_done(). It is no longer deleted when the last i2cdev device is removed, because the handle can be shared with external components throughi2cdev_get_shared_handle().calloc()per registration,free()on removal, the whole list is released ininit()anddone(). The 8-device limit is gone;CONFIG_I2CDEV_MAX_DEVICES_PER_PORTis now documented as legacy-only.Device lifecycle
i2c_setup_device()under the port mutex, right afteri2c_master_bus_add_device(). Registration failure fails the operation and removes the device from the bus, so a device can no longer be on the bus without being tracked.dev->dev_handleis checked under the port mutex, which closes the race where two tasks added the same device twice (the IDF driver does not detect duplicates).ESP_ERR_INVALID_ARG/ESP_ERR_INVALID_STATE. That path ran without the port mutex, leaked the device slot, and the trigger condition was wrong.i2c_dev_create_mutex()creates the mutex only (and normalizesaddr_bit_len);i2c_dev_delete_mutex()performs the bus-side cleanup in both locking modes, soCONFIG_I2CDEV_NOLOCKnow disables the per-device mutex only.Validation
sda_pin == scl_pinis rejected withESP_ERR_INVALID_ARG(a zero-initialized descriptor used GPIO0 for both lines).sda_pullup_en/scl_pullup_enare applied per line: the bus config has a single flag, so the mixed case enables the pullup on one GPIO after the bus is created.i2c_dev_check_present()returnsESP_ERR_INVALID_STATEfor an uninitialized port.i2c_dev_read_reg()/i2c_dev_write_reg()reject NULL buffers and zero sizes, matching the legacy implementation.i2c_dev_read()selects the transaction from the whole(out_data, out_size)pair again: a half-filled pair is treated as absent.#include <stdlib.h>added for the allocator functions.Header documentation
check_present()/probe(), descriptor lifetime, macro requirements (I2C_DEV_GIVE_MUTEX()after a successfulI2C_DEV_CHECK(),TAGforI2C_DEV_CHECK_LOGE()), and the cleanup order for a shared bus (external device ->i2c_dev_delete_mutex()->i2cdev_done()).CONFIG_FREERTOS_HZandCONFIG_LOG_MAXIMUM_LEVELremoved, dead fieldclk_flagsmarked as ignored,I2CDEV_MAX_STRETCH_TIMEdocumented as legacy-only, ASCII sections instead of box drawing (broken in generated docs).Behaviour changes
i2c_dev_delete_mutex()/free_desc()can now return a driver error instead ofESP_OK.i2cdev_done()releases it.CONFIG_I2CDEV_NOLOCK=1no longer disables device removal and bus teardown.Testing
v6.1-671-g9bf0c0eb952), target esp32, no warnings (-Werror): new driver,CONFIG_I2CDEV_NOLOCK=1, legacy configuration, and the example.i2cdevbuild against this branch.i2cdev_done()/i2cdev_init()/ repeated use, shared bus handle and external component lifecycle, invalidread_regarguments.