Avoid race conditions in I2C(Wire) callbacks (#995)

Fixes #979

Make sure to read the last byte of I2C data in the case where the IRQ happens
and the STOP signal is also asserted.

Also ensure all branches of the IRQ handler look at the same point in time
value for the IRQ status.
This commit is contained in:
Earle F. Philhower, III 2022-11-23 02:14:44 +01:00 committed by GitHub
parent 913dfad1ad
commit 0133ecc887
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -141,7 +141,22 @@ void TwoWire::begin(uint8_t addr) {
} }
void TwoWire::onIRQ() { void TwoWire::onIRQ() {
if (_i2c->hw->intr_stat & (1 << 12)) { // Make a local copy of the IRQ status up front. If it changes while we're
// running the IRQ callback will fire again after returning. Avoids potential
// race conditions
volatile uint32_t irqstat = _i2c->hw->intr_stat;
// First, pull off any data available
if (irqstat & (1 << 2)) {
// RX_FULL
if (_slaveStartDet && (_buffLen < (int)sizeof(_buff))) {
_buff[_buffLen++] = _i2c->hw->data_cmd & 0xff;
} else {
_i2c->hw->data_cmd;
}
}
// RESTART_DET
if (irqstat & (1 << 12)) {
if (_onReceiveCallback && _buffLen) { if (_onReceiveCallback && _buffLen) {
_onReceiveCallback(_buffLen); _onReceiveCallback(_buffLen);
} }
@ -150,13 +165,15 @@ void TwoWire::onIRQ() {
_slaveStartDet = false; _slaveStartDet = false;
_i2c->hw->clr_restart_det; _i2c->hw->clr_restart_det;
} }
if (_i2c->hw->intr_stat & (1 << 10)) { // START_DET
if (irqstat & (1 << 10)) {
_buffLen = 0; _buffLen = 0;
_buffOff = 0; _buffOff = 0;
_slaveStartDet = true; _slaveStartDet = true;
_i2c->hw->clr_start_det; _i2c->hw->clr_start_det;
} }
if (_i2c->hw->intr_stat & (1 << 9)) { // STOP_DET
if (irqstat & (1 << 9)) {
if (_onReceiveCallback && _buffLen) { if (_onReceiveCallback && _buffLen) {
_onReceiveCallback(_buffLen); _onReceiveCallback(_buffLen);
} }
@ -165,25 +182,17 @@ void TwoWire::onIRQ() {
_slaveStartDet = false; _slaveStartDet = false;
_i2c->hw->clr_stop_det; _i2c->hw->clr_stop_det;
} }
if (_i2c->hw->intr_stat & (1 << 6)) { // TX_ABRT
// TX_ABRT if (irqstat & (1 << 6)) {
_i2c->hw->clr_tx_abrt; _i2c->hw->clr_tx_abrt;
} }
if (_i2c->hw->intr_stat & (1 << 5)) { // RD_REQ
// RD_REQ if (irqstat & (1 << 5)) {
if (_onRequestCallback) { if (_onRequestCallback) {
_onRequestCallback(); _onRequestCallback();
} }
_i2c->hw->clr_rd_req; _i2c->hw->clr_rd_req;
} }
if (_i2c->hw->intr_stat & (1 << 2)) {
// RX_FULL
if (_slaveStartDet && (_buffLen < (int)sizeof(_buff))) {
_buff[_buffLen++] = _i2c->hw->data_cmd & 0xff;
} else {
_i2c->hw->data_cmd;
}
}
} }
void TwoWire::end() { void TwoWire::end() {