Skip to content

Commit

Permalink
at86rf2xx: fix receive before send detection
Browse files Browse the repository at this point in the history
The at86rf2xx radio handled a transfer complete condition with the radio
in the BUSY_TX_ARET state as a finished transmission. This condition and
state also occurs when a reception occurs just before switching to
transmitting. This would cause a condition where first a TX_COMPLETE was
signalled and second a RX_COMPLETE was signalled. The network stack
would then read the transmitted frame as a received frame.

The patch fixes the errornous RX callback by only submitting the
TX_COMPLETE condition when there are at least 2 frames pending
(at86rf2xx::pending_tx).
  • Loading branch information
bergzand committed Nov 16, 2019
1 parent 92191ef commit ecd9334
Showing 1 changed file with 64 additions and 45 deletions.
109 changes: 64 additions & 45 deletions drivers/at86rf2xx/at86rf2xx_netdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,51 @@ static int _set(netdev_t *netdev, netopt_t opt, const void *val, size_t len)
return res;
}

static void _isr_send_complete(at86rf2xx_t *dev, uint8_t trac_status)
{
netdev_t *netdev = &dev->netdev.netdev;
/* Only radios with the XAH_CTRL_2 register support frame retry reporting */
#if AT86RF2XX_HAVE_RETRIES
dev->tx_retries = (at86rf2xx_reg_read(dev, AT86RF2XX_REG__XAH_CTRL_2)
& AT86RF2XX_XAH_CTRL_2__ARET_FRAME_RETRIES_MASK) >>
AT86RF2XX_XAH_CTRL_2__ARET_FRAME_RETRIES_OFFSET;
#endif

DEBUG("[at86rf2xx] EVT - TX_END\n");

if (netdev->event_callback && (dev->flags & AT86RF2XX_OPT_TELL_TX_END)) {
switch (trac_status) {
#ifdef MODULE_OPENTHREAD
case AT86RF2XX_TRX_STATE__TRAC_SUCCESS:
netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE);
DEBUG("[at86rf2xx] TX SUCCESS\n");
break;
case AT86RF2XX_TRX_STATE__TRAC_SUCCESS_DATA_PENDING:
netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE_DATA_PENDING);
DEBUG("[at86rf2xx] TX SUCCESS DATA PENDING\n");
break;
#else
case AT86RF2XX_TRX_STATE__TRAC_SUCCESS:
case AT86RF2XX_TRX_STATE__TRAC_SUCCESS_DATA_PENDING:
netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE);
DEBUG("[at86rf2xx] TX SUCCESS\n");
break;
#endif
case AT86RF2XX_TRX_STATE__TRAC_NO_ACK:
netdev->event_callback(netdev, NETDEV_EVENT_TX_NOACK);
DEBUG("[at86rf2xx] TX NO_ACK\n");
break;
case AT86RF2XX_TRX_STATE__TRAC_CHANNEL_ACCESS_FAILURE:
netdev->event_callback(netdev, NETDEV_EVENT_TX_MEDIUM_BUSY);
DEBUG("[at86rf2xx] TX_CHANNEL_ACCESS_FAILURE\n");
break;
default:
DEBUG("[at86rf2xx] Unhandled TRAC_STATUS: %d\n",
trac_status >> 5);
}
}
}

static void _isr(netdev_t *netdev)
{
at86rf2xx_t *dev = (at86rf2xx_t *) netdev;
Expand Down Expand Up @@ -667,54 +712,28 @@ static void _isr(netdev_t *netdev)
}
netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE);
}
else if ((state == AT86RF2XX_STATE_TX_ARET_ON)
|| (state == AT86RF2XX_STATE_BUSY_TX_ARET)) {
else if ((state == AT86RF2XX_STATE_TX_ARET_ON)) {
/* check for more pending TX calls and return to idle state if
* there are none */
assert(dev->pending_tx != 0);
if ((--dev->pending_tx) == 0) {
at86rf2xx_set_state(dev, dev->idle_state);
DEBUG("[at86rf2xx] return to idle state 0x%x\n", dev->idle_state);
}
/* Only radios with the XAH_CTRL_2 register support frame retry reporting */
#if AT86RF2XX_HAVE_RETRIES
dev->tx_retries = (at86rf2xx_reg_read(dev, AT86RF2XX_REG__XAH_CTRL_2)
& AT86RF2XX_XAH_CTRL_2__ARET_FRAME_RETRIES_MASK) >>
AT86RF2XX_XAH_CTRL_2__ARET_FRAME_RETRIES_OFFSET;
#endif

DEBUG("[at86rf2xx] EVT - TX_END\n");

if (netdev->event_callback && (dev->flags & AT86RF2XX_OPT_TELL_TX_END)) {
switch (trac_status) {
#ifdef MODULE_OPENTHREAD
case AT86RF2XX_TRX_STATE__TRAC_SUCCESS:
netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE);
DEBUG("[at86rf2xx] TX SUCCESS\n");
break;
case AT86RF2XX_TRX_STATE__TRAC_SUCCESS_DATA_PENDING:
netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE_DATA_PENDING);
DEBUG("[at86rf2xx] TX SUCCESS DATA PENDING\n");
break;
#else
case AT86RF2XX_TRX_STATE__TRAC_SUCCESS:
case AT86RF2XX_TRX_STATE__TRAC_SUCCESS_DATA_PENDING:
netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE);
DEBUG("[at86rf2xx] TX SUCCESS\n");
break;
#endif
case AT86RF2XX_TRX_STATE__TRAC_NO_ACK:
netdev->event_callback(netdev, NETDEV_EVENT_TX_NOACK);
DEBUG("[at86rf2xx] TX NO_ACK\n");
break;
case AT86RF2XX_TRX_STATE__TRAC_CHANNEL_ACCESS_FAILURE:
netdev->event_callback(netdev, NETDEV_EVENT_TX_MEDIUM_BUSY);
DEBUG("[at86rf2xx] TX_CHANNEL_ACCESS_FAILURE\n");
break;
default:
DEBUG("[at86rf2xx] Unhandled TRAC_STATUS: %d\n",
trac_status >> 5);
}
/* Radio is idle, any TX transaction is done */
dev->pending_tx = 0;
at86rf2xx_set_state(dev, dev->idle_state);
DEBUG("[at86rf2xx] return to idle state 0x%x\n", dev->idle_state);
_isr_send_complete(dev, trac_status);
}
/* Only the case when an interrupt was received and the radio is busy
* with a next PDU transmission when _isr is called.
* dev->pending == 1 means a receive and immediately a send happened.
* The receive is discarded as the send already overwrote the internal
* buffer.
* dev->pending == 2 means two transmits occured and this is the isr for
* the first.
*/
else if ((state == AT86RF2XX_STATE_BUSY_TX_ARET)) {
if (dev->pending_tx > 1) {
dev->pending_tx--;
_isr_send_complete(dev, trac_status);
}
}
}
Expand Down

0 comments on commit ecd9334

Please sign in to comment.