Skip to content

Add lock in Wire.cpp to protect concurrent i2c transactions performed by different tasks #8127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 29, 2023

Conversation

mircopz
Copy link
Contributor

@mircopz mircopz commented Apr 26, 2023

Description of Change

This is the problem. In my application, two different tasks access the i2c.
Sometimes I get the following assert:

assert failed: xQueueGenericSend queue.c:832 (pxQueue->pcHead != ((void *)0) || pxQueue->u.xSemaphore.xMutexHolder == ((void *)0) || pxQueue->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle())
...
0x40096eee: xQueueGenericSend at .../esp-idf/components/freertos/queue.c line 821 (discriminator 2)
0x400f05ed: TwoWire::beginTransmission(unsigned short) at .../libraries/Wire/src/Wire.cpp line 418
:  (inlined by) TwoWire::beginTransmission(unsigned short) at .../libraries/Wire/src/Wire.cpp line 408
0x400f076c: TwoWire::beginTransmission(unsigned char) at .../libraries/Wire/src/Wire.cpp line 637

This comes from this piece of code:

    void TwoWire::beginTransmission(uint16_t address)
   ...
    if(nonStop && nonStopTask == xTaskGetCurrentTaskHandle()){
        log_e("Unfinished Repeated Start transaction! Expected requestFrom, not beginTransmission! Clearing...");
        //release lock
        xSemaphoreGive(lock);
    }

The xSemaphoreGive is not always correct, because I can't release a mutex if it is taken by another thread.
The solution is to protect all i2c transaction, by taking the lock at the beginning of each transaction if the current task is different from the one already performing a i2c transaction.

Tests scenarios

I have tested my Pull Request on Arduino-esp32 core v2.0.8 with ESP32 Board with this scenario.

@CLAassistant
Copy link

CLAassistant commented Apr 26, 2023

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ me-no-dev
❌ mircopz
You have signed the CLA already but the status is still pending? Let us recheck it.

@VojtechBartoska VojtechBartoska added Status: Review needed Issue or PR is awaiting review Status: Awaiting triage Issue is waiting for triage labels May 3, 2023
@VojtechBartoska VojtechBartoska added this to the 2.0.9 milestone May 3, 2023
@me-no-dev
Copy link
Member

Can you please provide example code that can replicate the issue you are seeing? The code is meant to release the lock only if called from the same task that last called endTransaction(false). If your code calls that and does not immediately call requestFrom, then you should fix your code. In reality you should never end up in that if

@me-no-dev me-no-dev removed this from the 2.0.9 milestone May 3, 2023
@me-no-dev me-no-dev added Resolution: More info needed More info must be provided in this issue Area: Peripherals API Relates to peripheral's APIs. and removed Status: Awaiting triage Issue is waiting for triage Status: Review needed Issue or PR is awaiting review labels May 3, 2023
@mircopz
Copy link
Contributor Author

mircopz commented May 4, 2023

Sorry but now I don't have any example code... I should work on it to recreate a simple example.
I try to add an example, I don't know if this can help.
This is my application code:

int8_t readBytes(uint8_t regAddr, uint8_t length, uint8_t *data) 
{
  Wire.beginTransmission(devAddr);
  Wire.write(regAddr);
  Wire.endTransmission(false);
  return Wire.requestFrom(devAddr, length, data);
}

The readBytes function is called from two tasks (T1 and T2). Let's assume that T2 calls beginTransmission is done while T1 is marking the transaction as non-stop

uint8_t TwoWire::endTransmission(bool sendStop)
{
 ...
       nonStop = true;
       // HERE --> the other task call its beginTransmission
#if !CONFIG_DISABLE_HAL_LOCKS
       nonStopTask = xTaskGetCurrentTaskHandle();
#endif
 ...
}

In this case, T2 finds nonStop as true, but nonStopTask is not updated yet (it's still T2 and not T1). So T2 tries to release the lock because both conditions are true, but the last task which acquired the lock was T1 (hence the assert)

   if(nonStop && nonStopTask == xTaskGetCurrentTaskHandle()) {
       log_e("Unfinished Repeated Start transaction! Expected requestFrom, not beginTransmission! Clearing...");
       //release lock
       xSemaphoreGive(lock);
   }

With my commit, all i2c transactions are protected, not only the non-stop transactions.

@VojtechBartoska VojtechBartoska added Status: Awaiting triage Issue is waiting for triage and removed Resolution: More info needed More info must be provided in this issue labels Nov 28, 2023
@VojtechBartoska VojtechBartoska added this to the 3.0.0 milestone Nov 28, 2023
@VojtechBartoska
Copy link
Contributor

Adding to 3.0.0 milestone so we will resolve this.

@me-no-dev me-no-dev merged commit db7b349 into espressif:master Nov 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Peripherals API Relates to peripheral's APIs. Status: Awaiting triage Issue is waiting for triage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants