1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 04:52:59 +01:00

Merge pull request #16746 from jia200x/pr/hal/fb_lock

ieee802154/submac: reimplement using FSM
This commit is contained in:
benpicco 2021-09-03 18:13:58 +02:00 committed by GitHub
commit c98c488559
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 776 additions and 407 deletions

View File

@ -69,8 +69,7 @@ static int _confirm_transmit(ieee802154_dev_t *dev, ieee802154_tx_info_t *info)
{
(void) dev;
if (RFCORE->XREG_FSMSTAT1bits.TX_ACTIVE != 0
|| !(RFCORE_XREG_CSPCTRL & CC2538_CSP_MCU_CTRL_MASK)) {
if (cc2538_tx_busy) {
return -EAGAIN;
}
@ -176,50 +175,60 @@ static int _read(ieee802154_dev_t *dev, void *buf, size_t size, ieee802154_rx_in
{
(void) dev;
int res;
size_t pkt_len = rfcore_read_byte();
size_t pkt_len;
pkt_len -= IEEE802154_FCS_LEN;
if (pkt_len > size) {
return -ENOBUFS;
}
if (buf != NULL) {
rfcore_read_fifo(buf, pkt_len);
res = pkt_len;
if (info != NULL) {
uint8_t corr_val;
int8_t rssi_val;
rssi_val = rfcore_read_byte();
/* The number of dB above maximum sensitivity detected for the
* received packet */
/* Make sure there is no overflow even if no signal with such
low sensitivity should be detected */
const int hw_rssi_min = IEEE802154_RADIO_RSSI_OFFSET -
CC2538_RSSI_OFFSET;
int8_t hw_rssi = rssi_val > hw_rssi_min ?
(CC2538_RSSI_OFFSET + rssi_val) : IEEE802154_RADIO_RSSI_OFFSET;
info->rssi = hw_rssi - IEEE802154_RADIO_RSSI_OFFSET;
corr_val = rfcore_read_byte() & CC2538_CORR_VAL_MASK;
if (corr_val < CC2538_CORR_VAL_MIN) {
corr_val = CC2538_CORR_VAL_MIN;
}
else if (corr_val > CC2538_CORR_VAL_MAX) {
corr_val = CC2538_CORR_VAL_MAX;
}
/* Interpolate the correlation value between 0 - 255
* to provide an LQI value */
info->lqi = 255 * (corr_val - CC2538_CORR_VAL_MIN) /
(CC2538_CORR_VAL_MAX - CC2538_CORR_VAL_MIN);
}
}
else {
if (!buf) {
res = 0;
goto end;
}
/* The upper layer shouldn't call this function if the RX_DONE event was
* not triggered */
if (!(RFCORE_XREG_RXFIFOCNT > 0)) {
assert(false);
}
pkt_len = rfcore_read_byte() - IEEE802154_FCS_LEN;
if (pkt_len > size) {
res = -ENOBUFS;
goto end;
}
rfcore_read_fifo(buf, pkt_len);
res = pkt_len;
if (info != NULL) {
uint8_t corr_val;
int8_t rssi_val;
rssi_val = rfcore_read_byte();
/* The number of dB above maximum sensitivity detected for the
* received packet */
/* Make sure there is no overflow even if no signal with such
low sensitivity should be detected */
const int hw_rssi_min = IEEE802154_RADIO_RSSI_OFFSET -
CC2538_RSSI_OFFSET;
int8_t hw_rssi = rssi_val > hw_rssi_min ?
(CC2538_RSSI_OFFSET + rssi_val) : IEEE802154_RADIO_RSSI_OFFSET;
info->rssi = hw_rssi - IEEE802154_RADIO_RSSI_OFFSET;
corr_val = rfcore_read_byte() & CC2538_CORR_VAL_MASK;
if (corr_val < CC2538_CORR_VAL_MIN) {
corr_val = CC2538_CORR_VAL_MIN;
}
else if (corr_val > CC2538_CORR_VAL_MAX) {
corr_val = CC2538_CORR_VAL_MAX;
}
/* Interpolate the correlation value between 0 - 255
* to provide an LQI value */
info->lqi = 255 * (corr_val - CC2538_CORR_VAL_MIN) /
(CC2538_CORR_VAL_MAX - CC2538_CORR_VAL_MIN);
}
end:
/* Enable RX Chain */
RFCORE_XREG_FRMCTRL0 &= ~CC2538_FRMCTRL0_RX_MODE_DIS;
RFCORE_SFR_RFST = ISFLUSHRX;
return res;
}
@ -296,7 +305,9 @@ static int _request_set_trx_state(ieee802154_dev_t *dev, ieee802154_trx_state_t
{
(void) dev;
int irq = irq_disable();
if (cc2538_tx_busy || cc2538_rx_busy) {
irq_restore(irq);
return -EBUSY;
}
@ -306,16 +317,20 @@ static int _request_set_trx_state(ieee802154_dev_t *dev, ieee802154_trx_state_t
if (RFCORE->XREG_FSMSTAT0bits.FSM_FFCTRL_STATE != FSM_STATE_IDLE) {
RFCORE_SFR_RFST = ISRFOFF;
}
cc2538_rx_busy = false;
break;
case IEEE802154_TRX_STATE_RX_ON:
RFCORE_XREG_RFIRQM0 |= RXPKTDONE;
RFCORE_SFR_RFST = ISFLUSHRX;
/* Enable RX Chain */
RFCORE_XREG_FRMCTRL0 &= ~CC2538_FRMCTRL0_RX_MODE_DIS;
RFCORE_SFR_RFST = ISRXON;
break;
}
RFCORE_SFR_RFIRQF0 = 0;
RFCORE_SFR_RFIRQF1 = 0;
irq_restore(irq);
return 0;
}
@ -352,7 +367,7 @@ void cc2538_irq_handler(void)
uint8_t pkt_len = rfcore_peek_rx_fifo(0);
if (rfcore_peek_rx_fifo(pkt_len) & CC2538_CRC_BIT_MASK) {
/* Disable RX while the frame has not been processed */
RFCORE_XREG_RXMASKCLR = 0xFF;
RFCORE_XREG_FRMCTRL0 |= CC2538_FRMCTRL0_RX_MODE_DIS;
/* If AUTOACK is disabled or the ACK request bit is not set */
if (IS_ACTIVE(CONFIG_IEEE802154_AUTO_ACK_DISABLE) ||
(!(rfcore_peek_rx_fifo(1) & IEEE802154_FCF_ACK_REQ))) {
@ -365,7 +380,6 @@ void cc2538_irq_handler(void)
}
else {
/* Disable RX while the frame has not been processed */
RFCORE_XREG_RXMASKCLR = 0xFF;
/* CRC failed; discard packet. The RX chain is not busy anymore */
cc2538_rx_busy = false;
cc2538_rf_hal->cb(cc2538_rf_hal, IEEE802154_RADIO_INDICATION_CRC_ERROR);
@ -384,7 +398,6 @@ void cc2538_irq_handler(void)
RFCORE_XREG_CSPCTRL |= CC2538_CSP_MCU_CTRL_MASK;
if (!cc2538_cca) {
if (RFCORE_XREG_CSPZ > 0) {
RFCORE_XREG_RXMASKCLR = CC2538_RXENABLE_RXON_MASK;
RFCORE_SFR_RFST = ISTXON;
}
else {
@ -551,7 +564,8 @@ static const ieee802154_radio_ops_t cc2538_rf_ops = {
| IEEE802154_CAP_IRQ_CCA_DONE
| IEEE802154_CAP_IRQ_RX_START
| IEEE802154_CAP_IRQ_TX_START
| IEEE802154_CAP_PHY_OQPSK,
| IEEE802154_CAP_PHY_OQPSK
| IEEE802154_CAP_RX_CONTINUOUS,
.write = _write,
.read = _read,

View File

@ -41,6 +41,7 @@ extern "C" {
#define NETDEV_SUBMAC_FLAGS_TX_DONE (1 << 1) /**< Flag for TX Done event */
#define NETDEV_SUBMAC_FLAGS_RX_DONE (1 << 2) /**< Flag for RX Done event */
#define NETDEV_SUBMAC_FLAGS_CRC_ERROR (1 << 3) /**< Flag for CRC ERROR event */
#define NETDEV_SUBMAC_FLAGS_BH_REQUEST (1 << 4) /**< Flag for Bottom Half request event */
/**
* @brief IEEE 802.15.4 SubMAC netdev descriptor
@ -51,6 +52,8 @@ typedef struct {
xtimer_t ack_timer; /**< xtimer descriptor for the ACK timeout timer */
int isr_flags; /**< netdev submac @ref NETDEV_EVENT_ISR flags */
int8_t retrans; /**< number of frame retransmissions of the last TX */
bool dispatch; /**< whether an event should be dispatched or not */
netdev_event_t ev; /**< event to be dispatched */
} netdev_ieee802154_submac_t;
/**

View File

@ -22,7 +22,6 @@ static const netdev_driver_t netdev_submac_driver;
static void _ack_timeout(void *arg)
{
(void)arg;
netdev_ieee802154_submac_t *netdev_submac = arg;
netdev_t *netdev = arg;
@ -33,23 +32,12 @@ static void _ack_timeout(void *arg)
static netopt_state_t _get_submac_state(ieee802154_submac_t *submac)
{
ieee802154_submac_state_t state = ieee802154_get_state(submac);
netopt_state_t netopt_state;
switch (state) {
case IEEE802154_STATE_OFF:
netopt_state = NETOPT_STATE_SLEEP;
break;
case IEEE802154_STATE_IDLE:
netopt_state = NETOPT_STATE_STANDBY;
break;
case IEEE802154_STATE_LISTEN:
default:
netopt_state = NETOPT_STATE_IDLE;
break;
if (ieee802154_submac_state_is_idle(submac)) {
return NETOPT_STATE_SLEEP;
}
else {
return NETOPT_STATE_IDLE;
}
return netopt_state;
}
static int _get(netdev_t *netdev, netopt_t opt, void *value, size_t max_len)
@ -82,12 +70,12 @@ static int _get(netdev_t *netdev, netopt_t opt, void *value, size_t max_len)
static int _set_submac_state(ieee802154_submac_t *submac, netopt_state_t state)
{
switch (state) {
case NETOPT_STATE_STANDBY:
return ieee802154_set_state(submac, IEEE802154_STATE_IDLE);
case NETOPT_STATE_SLEEP:
return ieee802154_set_state(submac, IEEE802154_STATE_OFF);
return ieee802154_set_idle(submac);
break;
case NETOPT_STATE_IDLE:
return ieee802154_set_state(submac, IEEE802154_STATE_LISTEN);
return ieee802154_set_rx(submac);
break;
default:
return -ENOTSUP;
}
@ -135,6 +123,18 @@ static int _set(netdev_t *netdev, netopt_t opt, const void *value,
opt, value, value_len);
}
void ieee802154_submac_bh_request(ieee802154_submac_t *submac)
{
netdev_ieee802154_submac_t *netdev_submac = container_of(submac,
netdev_ieee802154_submac_t,
submac);
netdev_t *netdev = &netdev_submac->dev.netdev;
netdev_submac->isr_flags |= NETDEV_SUBMAC_FLAGS_BH_REQUEST;
netdev->event_callback(netdev, NETDEV_EVENT_ISR);
}
void ieee802154_submac_ack_timer_set(ieee802154_submac_t *submac, uint16_t us)
{
netdev_ieee802154_submac_t *netdev_submac = container_of(submac,
@ -161,7 +161,13 @@ static int _send(netdev_t *netdev, const iolist_t *pkt)
dev);
ieee802154_submac_t *submac = &netdev_submac->submac;
return ieee802154_send(submac, pkt);
int res = ieee802154_send(submac, pkt);
if (res >= 0) {
/* HACK: Used to mark a transmission when called
* inside the TX Done callback */
netdev_submac->ev = NETDEV_EVENT_TX_STARTED;
}
return res;
}
static void _isr(netdev_t *netdev)
@ -172,12 +178,17 @@ static void _isr(netdev_t *netdev)
dev);
ieee802154_submac_t *submac = &netdev_submac->submac;
bool can_dispatch = true;
do {
irq_disable();
int flags = netdev_submac->isr_flags;
netdev_submac->isr_flags = 0;
irq_enable();
if (flags & NETDEV_SUBMAC_FLAGS_BH_REQUEST) {
ieee802154_submac_bh_process(submac);
}
if (flags & NETDEV_SUBMAC_FLAGS_ACK_TIMEOUT) {
ieee802154_submac_ack_timeout_fired(&netdev_submac->submac);
}
@ -193,7 +204,33 @@ static void _isr(netdev_t *netdev)
if (flags & NETDEV_SUBMAC_FLAGS_CRC_ERROR) {
ieee802154_submac_crc_error_cb(submac);
}
if (flags) {
can_dispatch = false;
}
} while (netdev_submac->isr_flags != 0);
if (netdev_submac->dispatch) {
/* The SubMAC will not generate further events after calling TX Done
* or RX Done, but there might be pending ISR events that might not be
* caught by the previous loop.
* This should be safe to make sure that all events are cached */
if (!can_dispatch) {
netdev->event_callback(netdev, NETDEV_EVENT_ISR);
return;
}
netdev_submac->dispatch = false;
/* TODO: Prevent race condition when state goes to PREPARE */
netdev->event_callback(netdev, netdev_submac->ev);
/* HACK: the TX_STARTED event is used to indicate a frame was
* sent during the event callback.
* If no frame was sent go back to RX */
if (netdev_submac->ev != NETDEV_EVENT_TX_STARTED) {
ieee802154_set_rx(submac);
}
}
}
static int _recv(netdev_t *netdev, void *buf, size_t len, void *info)
@ -231,24 +268,24 @@ static void submac_tx_done(ieee802154_submac_t *submac, int status,
netdev_ieee802154_submac_t *netdev_submac = container_of(submac,
netdev_ieee802154_submac_t,
submac);
netdev_t *netdev = &netdev_submac->dev.netdev;
if (info) {
netdev_submac->retrans = info->retrans;
}
netdev_submac->dispatch = true;
switch (status) {
case TX_STATUS_SUCCESS:
netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE);
netdev_submac->ev = NETDEV_EVENT_TX_COMPLETE;
break;
case TX_STATUS_FRAME_PENDING:
netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE_DATA_PENDING);
netdev_submac->ev = NETDEV_EVENT_TX_COMPLETE_DATA_PENDING;
break;
case TX_STATUS_MEDIUM_BUSY:
netdev->event_callback(netdev, NETDEV_EVENT_TX_MEDIUM_BUSY);
netdev_submac->ev = NETDEV_EVENT_TX_MEDIUM_BUSY;
break;
case TX_STATUS_NO_ACK:
netdev->event_callback(netdev, NETDEV_EVENT_TX_NOACK);
netdev_submac->ev = NETDEV_EVENT_TX_NOACK;
break;
default:
break;
@ -260,9 +297,8 @@ static void submac_rx_done(ieee802154_submac_t *submac)
netdev_ieee802154_submac_t *netdev_submac = container_of(submac,
netdev_ieee802154_submac_t,
submac);
netdev_t *netdev = &netdev_submac->dev.netdev;
netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE);
netdev_submac->dispatch = true;
netdev_submac->ev = NETDEV_EVENT_RX_COMPLETE;
}
static const ieee802154_submac_cb_t _cb = {

View File

@ -157,6 +157,18 @@ typedef enum {
* set if the source address matches one from the table.
*/
IEEE802154_CAP_SRC_ADDR_MATCH = BIT18,
/**
* @brief the device stays in RX_ON on @ref
* IEEE802154_RADIO_INDICATION_RX_DONE or @ref
* IEEE802154_RADIO_INDICATION_CRC_ERROR
*
* Radios that provide this feature don't need to call @ref
* ieee802154_radio_request_set_trx_state on after receiving a frame, in
* case more frames are expected. This does not affect Framebuffer
* protection (e.g a radio might still be listening but its framebuffer is
* locked because the upper layer didn't call @ref ieee802154_radio_read)
*/
IEEE802154_CAP_RX_CONTINUOUS = BIT19,
} ieee802154_rf_caps_t;
/**
@ -272,11 +284,10 @@ typedef enum {
* The transceiver or driver MUST handle the ACK reply if the Ack Request
* bit is set in the received frame and promiscuous mode is disabled.
*
* The transceiver will be in a "FB Lock" state where no more frames are
* The transceiver might be in a "FB Lock" state where no more frames are
* received. This is done in order to avoid overwriting the Frame Buffer
* with new frame arrivals. In order to leave this state, the upper layer
* must set the transceiver state (@ref
* ieee802154_radio_ops::request_set_trx_state).
* must call @ref ieee802154_radio_ops::read.
*/
IEEE802154_RADIO_INDICATION_RX_DONE,
@ -561,11 +572,13 @@ struct ieee802154_radio_ops {
* This function reads the received frame from the internal framebuffer.
* It should try to copy the received PSDU frame into @p buf. The FCS
* field will **not** be copied and its size **not** be taken into account
* for the return value.
* for the return value. If the radio provides any kind of framebuffer protection,
* this function should release it.
*
* @post It's not safe to call this function again before setting the
* transceiver state to @ref IEEE802154_TRX_STATE_RX_ON (thus flushing
* the RX FIFO).
* @post Don't call this function if there was no reception event
* (either @ref IEEE802154_RADIO_INDICATION_RX_DONE or @ref
* IEEE802154_RADIO_INDICATION_CRC_ERROR). Otherwise there's risk of RX
* underflow.
*
* @param[in] dev IEEE802.15.4 device descriptor
* @param[out] buf buffer to write the received PSDU frame into.
@ -1410,6 +1423,22 @@ static inline uint32_t ieee802154_radio_get_phy_modes(ieee802154_dev_t *dev)
return (dev->driver->caps & IEEE802154_RF_CAPS_PHY_MASK);
}
/**
* @brief Check whether the radio stays in RX_ON after @ref
* IEEE802154_RADIO_INDICATION_RX_DONE or @ref
* IEEE802154_RADIO_INDICATION_CRC_ERROR events (see @ref
* IEEE802154_CAP_RX_CONTINUOUS)
*
* @param[in] dev IEEE802.15.4 device descriptor
*
* @return true if the device stays in RX_ON state
* @return false otherwise
*/
static inline bool ieee802154_radio_has_rx_continuous(ieee802154_dev_t *dev)
{
return (dev->driver->caps & IEEE802154_CAP_RX_CONTINUOUS);
}
/**
* @brief Convert a @ref ieee802154_phy_mode_t to a @ref ieee802154_rf_caps_t
* value.

View File

@ -20,6 +20,85 @@
* - Maintaining part of the MAC Information Base, e.g IEEE 802.15.4 addresses,
* channel settings, CSMA-CA params, etc.
*
* The SubMAC defines the following state machine:
*
* +--------+ +--------+ +--------+
* | |------->| | | |
* | RX | |PREPARE |<--->| TX |
* | | +--->| | | |
* +--------+ | +--------+ +--------+
* ^ | ^ |
* | | | |
* | | | |
* | | +--------+ |
* | | | | v
* | | |WAIT FOR|<--------+
* | | | ACK | |
* | | +--------+ |
* | | | |
* | | | |
* | | v |
* | | +--------+ |
* | +-----| | |
* | | IDLE | |
* +------------->| |<-------+
* +--------+
*
* - IDLE: The transceiver is off and therefore cannot receive frames. Sending
* frames might be triggered using @ref ieee802154_send. The next SubMAC
* state would be PREPARE.
* - RX: The device is ready to receive frames. In case the SubMAC receives a
* frame it will call @ref ieee802154_submac_cb_t::rx_done and immediately go
* to IDLE. Same as the IDLE state, it's possible
* to trigger frames using @ref ieee802154_send.
* - PREPARE: The frame is already in the framebuffer and waiting to be
* transmitted. This state might handle CSMA-CA backoff timer in case the
* device doesn't support it. The SubMAC will then request the transmission
* and go immediately to the TX state.
* - TX: The frame was already sent and it's waiting for the TX DONE event from
* the radio. The SubMAC might call @ref ieee802154_submac_cb_t::tx_done if
* any of the following criteria are meet:
* - The transmitted frame didn't request ACK
* - The radio already handles retransmissions
* - WAIT FOR ACK: The SubMAC is waiting for an ACK frame.
* In case a valid ACK frame is received, the SubMAC will
* either to IDLE.
* In case the ACK frame is invalid or there's an ACK timeout event
* (either triggered by the radio or a timer), the SubMAC goes to either
* IDLE if there are no more retransmissions left or no more CSMA-CA
* retries or PREPARE otherwise.
*
* The events that trigger state machine changes are defined in
* @ref ieee802154_fsm_state_t
*
* The following events are valid for each state:
*
* +---------------+----+-------+---------+----+--------------+
* | Event/State | RX | IDLE | PREPARE | TX | WAIT FOR ACK |
* +---------------+----+-------+---------+----+--------------+
* | TX_DONE | - | - | - | X | - |
* | RX_DONE | X | X* | X* | X* | X |
* | CRC_ERROR | X | X* | X* | X* | X |
* | ACK_TIMEOUT | - | - | - | - | X |
* | BH | - | - | X | - | - |
* | REQ_TX | X | X | - | - | - |
* | REQ_SET_RX_ON | - | X | - | - | - |
* | REQ_SET_IDLE | X | - | - | - | - |
* +---------------+----+-------+---------+----+--------------+
* *: RX_DONE and CRC_ERROR during these events might be a race condition
* between the ACK Timer and the radios RX_DONE event. If this happens, the
* SubMAC will react accordingly
*
* Unexpected events will be reported and asserted.
*
* The upper layer needs to implement the following callbacks:
*
* - @ref ieee802154_submac_cb_t::rx_done.
* - @ref ieee802154_submac_cb_t::tx_done.
* - @ref ieee802154_submac_ack_timer_set
* - @ref ieee802154_submac_ack_timer_cancel
* - @ref ieee802154_submac_bh_request
*
* @{
*
* @author José I. Alamos <jose.alamos@haw-hamburg.de>
@ -31,7 +110,9 @@
extern "C" {
#endif
#include <stdio.h>
#include <string.h>
#include "assert.h"
#include "net/ieee802154.h"
#include "net/ieee802154/radio.h"
@ -43,29 +124,6 @@ extern "C" {
*/
typedef struct ieee802154_submac ieee802154_submac_t;
/**
* @brief SubMAC states
*/
typedef enum {
/**
* @brief SubMAC and network devices are off.
*
* The corresponding network device is put in a state with the
* lowest energy consumption.
*/
IEEE802154_STATE_OFF,
/**
* @brief SubMAC is ready to be used.
*/
IEEE802154_STATE_IDLE,
/**
* @brief SubMAC is ready to be used and listening to incoming frames.
*/
IEEE802154_STATE_LISTEN,
} ieee802154_submac_state_t;
/**
* @brief IEEE 802.15.4 SubMAC callbacks.
*/
@ -74,10 +132,11 @@ typedef struct {
* @brief RX done event
*
* This function is called from the SubMAC to indicate a IEEE 802.15.4
* frame is ready to be fetched from the device.
* frame is ready to be fetched from the device. Use @ref
* ieee802154_read_frame and/or @ref ieee802154_get_frame_length for this
* purpose.
*
* @post If @ref ieee802154_submac_t::state is @ref IEEE802154_STATE_LISTEN, the
* SubMAC is ready to receive frames
* The SubMAC will automatically go to IDLE.
*
* @note ACK frames are automatically handled and discarded by the SubMAC.
* @param[in] submac pointer to the SubMAC descriptor
@ -89,8 +148,7 @@ typedef struct {
* This function is called from the SubMAC to indicate that the TX
* procedure finished.
*
* @pre If @ref ieee802154_submac_t::state is @ref IEEE802154_STATE_LISTEN, the
* SubMAC is ready to receive frames.
* The SubMAC will automatically go to IDLE.
*
* @param[in] submac pointer to the SubMAC descriptor
* @param[out] info TX information associated to the transmission (status,
@ -100,6 +158,34 @@ typedef struct {
ieee802154_tx_info_t *info);
} ieee802154_submac_cb_t;
/**
* @brief Internal SubMAC FSM state machine states
*/
typedef enum {
IEEE802154_FSM_STATE_INVALID, /**< Invalid state */
IEEE802154_FSM_STATE_RX, /**< SubMAC is ready to receive frames */
IEEE802154_FSM_STATE_IDLE, /**< The transceiver is off */
IEEE802154_FSM_STATE_PREPARE, /**< The SubMAC is preparing the next transmission */
IEEE802154_FSM_STATE_TX, /**< The SubMAC is currently transmitting a frame */
IEEE802154_FSM_STATE_WAIT_FOR_ACK, /**< The SubMAC is waiting for an ACK frame */
IEEE802154_FSM_STATE_NUMOF, /**< Number of SubMAC FSM states */
} ieee802154_fsm_state_t;
/**
* @brief Internal SubMAC FSM state machine events
*/
typedef enum {
IEEE802154_FSM_EV_TX_DONE, /**< Radio reports frame was sent */
IEEE802154_FSM_EV_RX_DONE, /**< Radio reports frame was received */
IEEE802154_FSM_EV_CRC_ERROR, /**< Radio reports frame was received but CRC failed */
IEEE802154_FSM_EV_ACK_TIMEOUT, /**< ACK timer fired */
IEEE802154_FSM_EV_BH, /**< The Bottom Half should process an event */
IEEE802154_FSM_EV_REQUEST_TX, /**< The upper layer requested to transmit a frame */
IEEE802154_FSM_EV_REQUEST_SET_RX_ON, /**< The upper layer requested to go to RX */
IEEE802154_FSM_EV_REQUEST_SET_IDLE, /**< The upper layer requested to go to IDLE */
IEEE802154_FSM_EV_NUMOF, /**< Number of SubMAC FSM events */
} ieee802154_fsm_ev_t;
/**
* @brief IEEE 802.15.4 SubMAC descriptor
*/
@ -110,7 +196,6 @@ struct ieee802154_submac {
const ieee802154_submac_cb_t *cb; /**< pointer to the SubMAC callbacks */
ieee802154_csma_be_t be; /**< CSMA-CA backoff exponent params */
bool wait_for_ack; /**< SubMAC is waiting for an ACK frame */
bool tx; /**< SubMAC is currently transmitting a frame */
uint16_t panid; /**< IEEE 802.15.4 PAN ID */
uint16_t channel_num; /**< IEEE 802.15.4 channel number */
uint8_t channel_page; /**< IEEE 802.15.4 channel page */
@ -119,33 +204,11 @@ struct ieee802154_submac {
uint8_t backoff_mask; /**< internal value used for random backoff calculation */
uint8_t csma_retries; /**< maximum number of CSMA-CA retries */
int8_t tx_pow; /**< Transmission power (in dBm) */
ieee802154_submac_state_t state; /**< State of the SubMAC */
ieee802154_fsm_state_t fsm_state; /**< State of the SubMAC */
ieee802154_phy_mode_t phy_mode; /**< IEEE 802.15.4 PHY mode */
const iolist_t *psdu; /**< stores the current PSDU */
};
/**
* @brief Get the internal state of the SubMAC
*
* @param[in] submac pointer to the SubMAC descriptor
*
* @return the SubMAC state
*/
static inline ieee802154_submac_state_t ieee802154_get_state(ieee802154_submac_t *submac)
{
return submac->state;
}
/**
* @brief Set the internal state of the SubMAC
*
* @param[in] submac pointer to the SubMAC descriptor
* @param[in] state the desired state
*
* @return 0 on success
* @return negative errno on error.
*/
int ieee802154_set_state(ieee802154_submac_t *submac, ieee802154_submac_state_t state);
/**
* @brief Transmit an IEEE 802.15.4 PSDU
*
@ -157,7 +220,9 @@ int ieee802154_set_state(ieee802154_submac_t *submac, ieee802154_submac_state_t
* @param[in] iolist pointer to the PSDU frame (without FCS)
*
* @return 0 on success
* @return negative errno on error
* @return -EBUSY if the SubMAC is not in RX or IDLE state or if called inside
* @ref ieee802154_submac_cb_t::rx_done or
* @ref ieee802154_submac_cb_t::tx_done
*/
int ieee802154_send(ieee802154_submac_t *submac, const iolist_t *iolist);
@ -252,6 +317,9 @@ static inline ieee802154_phy_mode_t ieee802154_get_phy_mode(
*
* @return 0 on success
* @return -ENOTSUP if the PHY settings are not supported
* @return -EBUSY if the SubMAC is not in RX or IDLE state or if called inside
* @ref ieee802154_submac_cb_t::rx_done or
* @ref ieee802154_submac_cb_t::tx_done
* @return negative errno on error
*/
int ieee802154_set_phy_conf(ieee802154_submac_t *submac, uint16_t channel_num,
@ -267,6 +335,9 @@ int ieee802154_set_phy_conf(ieee802154_submac_t *submac, uint16_t channel_num,
*
* @return 0 on success
* @return -ENOTSUP if the channel number is not supported
* @return -EBUSY if the SubMAC is not in RX or IDLE state or if called inside
* @ref ieee802154_submac_cb_t::rx_done or
* @ref ieee802154_submac_cb_t::tx_done
* @return negative errno on error
*/
static inline int ieee802154_set_channel_number(ieee802154_submac_t *submac,
@ -286,6 +357,9 @@ static inline int ieee802154_set_channel_number(ieee802154_submac_t *submac,
*
* @return 0 on success
* @return -ENOTSUP if the channel page is not supported
* @return -EBUSY if the SubMAC is not in RX or IDLE state or if called inside
* @ref ieee802154_submac_cb_t::rx_done or
* @ref ieee802154_submac_cb_t::tx_done
* @return negative errno on error
*/
static inline int ieee802154_set_channel_page(ieee802154_submac_t *submac,
@ -305,6 +379,9 @@ static inline int ieee802154_set_channel_page(ieee802154_submac_t *submac,
*
* @return 0 on success
* @return -ENOTSUP if the transmission power is not supported
* @return -EBUSY if the SubMAC is not in RX or IDLE state or if called inside
* @ref ieee802154_submac_cb_t::rx_done or
* @ref ieee802154_submac_cb_t::tx_done
* @return negative errno on error
*/
static inline int ieee802154_set_tx_power(ieee802154_submac_t *submac,
@ -317,6 +394,9 @@ static inline int ieee802154_set_tx_power(ieee802154_submac_t *submac,
/**
* @brief Get the received frame length
*
* @pre this function MUST be called either inside @ref ieee802154_submac_cb_t::rx_done
* or in SLEEP state.
*
* @param[in] submac pointer to the SubMAC
*
* @return length of the PSDU (excluding FCS length)
@ -331,6 +411,9 @@ static inline int ieee802154_get_frame_length(ieee802154_submac_t *submac)
*
* This functions reads the received PSDU from the device (excluding FCS)
*
* @pre this function MUST be called either inside @ref ieee802154_submac_cb_t::rx_done
* or in SLEEP state.
*
* @param[in] submac pointer to the SubMAC descriptor
* @param[out] buf buffer to write into. If NULL, the packet is discarded
* @param[in] len length of the buffer
@ -345,9 +428,64 @@ static inline int ieee802154_read_frame(ieee802154_submac_t *submac, void *buf,
return ieee802154_radio_read(&submac->dev, buf, len, info);
}
/**
* @brief Set the SubMAC to IDLE state.
*
* Frames won't be received in this state. However, it's still possible to send
* frames.
*
* @param[in] submac pointer to the SubMAC descriptor
*
* @return success or error code.
* @retval 0 on success
* @retval -EBUSY if the SubMAC is currently busy
*/
int ieee802154_set_idle(ieee802154_submac_t *submac);
/**
* @brief Set the SubMAC to RX state
*
* During this state the SubMAC accepts incoming frames.
*
* @param[in] submac pointer to the SubMAC descriptor
*
* @return success or error code.
* @retval 0 on success
* @retval -EBUSY if the SubMAC is currently busy
*/
int ieee802154_set_rx(ieee802154_submac_t *submac);
/**
* @brief Check whether the SubMAC is in RX state
*
* @param[in] submac pointer to the SubMAC descriptor
*
* @retval true if the SubMAC is in RX state
* @retval false otherwise
*/
static inline bool ieee802154_submac_state_is_rx(ieee802154_submac_t *submac)
{
return submac->fsm_state == IEEE802154_FSM_STATE_RX;
}
/**
* @brief Check whether the SubMAC is in IDLE state
*
* @param[in] submac pointer to the SubMAC descriptor
*
* @retval true if the SubMAC is in IDLE state
* @retval false otherwise
*/
static inline bool ieee802154_submac_state_is_idle(ieee802154_submac_t *submac)
{
return submac->fsm_state == IEEE802154_FSM_STATE_IDLE;
}
/**
* @brief Init the IEEE 802.15.4 SubMAC
*
* The SubMAC state machine starts in RX state.
*
* @param[in] submac pointer to the SubMAC descriptor
* @param[in] short_addr pointer to the IEEE 802.15.4 short address
* @param[in] ext_addr pointer to the IEEE 802.15.4 extended address
@ -378,6 +516,28 @@ extern void ieee802154_submac_ack_timer_set(ieee802154_submac_t *submac,
*/
extern void ieee802154_submac_ack_timer_cancel(ieee802154_submac_t *submac);
/**
* @brief @ref ieee802154_submac_bh_process should be called as soon as possible.
*
* @note This function should be implemented by the user of the SubMAC.
*
* @param[in] submac pointer to the SubMAC descriptor
*/
extern void ieee802154_submac_bh_request(ieee802154_submac_t *submac);
/**
* @brief Process an FSM event
*
* @internal
*
* @param[in] submac pointer to the SubMAC descriptor
* @param[in] ev the event to be processed
*
* @return Next FSM event
*/
ieee802154_fsm_state_t ieee802154_submac_process_ev(ieee802154_submac_t *submac,
ieee802154_fsm_ev_t ev);
/**
* @brief Indicate the SubMAC that the ACK timeout fired.
*
@ -387,28 +547,50 @@ extern void ieee802154_submac_ack_timer_cancel(ieee802154_submac_t *submac);
*
* @param[in] submac pointer to the SubMAC descriptor
*/
void ieee802154_submac_ack_timeout_fired(ieee802154_submac_t *submac);
static inline void ieee802154_submac_ack_timeout_fired(ieee802154_submac_t *submac)
{
ieee802154_submac_process_ev(submac, IEEE802154_FSM_EV_ACK_TIMEOUT);
}
/**
* @brief Indicate the SubMAC that the BH should process an internal event
*
* @param[in] submac pointer to the SubMAC descriptor
*/
static inline void ieee802154_submac_bh_process(ieee802154_submac_t *submac)
{
ieee802154_submac_process_ev(submac, IEEE802154_FSM_EV_BH);
}
/**
* @brief Indicate the SubMAC that the device received a frame.
*
* @param[in] submac pointer to the SubMAC descriptor
*/
void ieee802154_submac_rx_done_cb(ieee802154_submac_t *submac);
static inline void ieee802154_submac_rx_done_cb(ieee802154_submac_t *submac)
{
ieee802154_submac_process_ev(submac, IEEE802154_FSM_EV_RX_DONE);
}
/**
* @brief Indicate the SubMAC that a frame with invalid CRC was received.
*
* @param[in] submac pointer to the SubMAC descriptor
*/
void ieee802154_submac_crc_error_cb(ieee802154_submac_t *submac);
static inline void ieee802154_submac_crc_error_cb(ieee802154_submac_t *submac)
{
ieee802154_submac_process_ev(submac, IEEE802154_FSM_EV_CRC_ERROR);
}
/**
* @brief Indicate the SubMAC that the device finished the transmission procedure.
*
* @param[in] submac pointer to the SubMAC descriptor
*/
void ieee802154_submac_tx_done_cb(ieee802154_submac_t *submac);
static inline void ieee802154_submac_tx_done_cb(ieee802154_submac_t *submac)
{
ieee802154_submac_process_ev(submac, IEEE802154_FSM_EV_TX_DONE);
}
#ifdef __cplusplus
}

View File

@ -13,6 +13,7 @@
* @author José I. Alamos <jose.alamos@haw-hamburg.de>
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "net/ieee802154/submac.h"
@ -22,12 +23,32 @@
#include "luid.h"
#include "kernel_defines.h"
#include "errno.h"
#include <assert.h>
#define CSMA_SENDER_BACKOFF_PERIOD_UNIT_MS (320U)
#define ENABLE_DEBUG 0
#include "debug.h"
#define CSMA_SENDER_BACKOFF_PERIOD_UNIT_US (320U)
#define ACK_TIMEOUT_US (864U)
static void _handle_tx_no_ack(ieee802154_submac_t *submac);
static char *str_states[IEEE802154_FSM_STATE_NUMOF] = {
"INVALID",
"RX",
"IDLE",
"PREPARE",
"TX",
"WAIT_FOR_ACK",
};
static char *str_ev[IEEE802154_FSM_EV_NUMOF] = {
"TX_DONE",
"RX_DONE",
"CRC_ERROR",
"ACK_TIMEOUT",
"BH",
"REQUEST_TX",
"REQUEST_SET_RX_ON",
"REQUEST_SET_IDLE",
};
static inline void _req_set_trx_state_wait_busy(ieee802154_dev_t *dev,
ieee802154_trx_state_t state)
@ -40,25 +61,10 @@ static inline void _req_set_trx_state_wait_busy(ieee802154_dev_t *dev,
*/
do {
res = ieee802154_radio_request_set_trx_state(dev, state);
}
while (res == -EBUSY);
assert(res >= 0);
}
} while (res == -EBUSY);
static void _tx_end(ieee802154_submac_t *submac, int status,
ieee802154_tx_info_t *info)
{
ieee802154_dev_t *dev = &submac->dev;
ieee802154_trx_state_t next_state = submac->state == IEEE802154_STATE_LISTEN
? IEEE802154_TRX_STATE_RX_ON
: IEEE802154_TRX_STATE_TRX_OFF;
_req_set_trx_state_wait_busy(dev, next_state);
submac->wait_for_ack = false;
submac->tx = false;
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
submac->cb->tx_done(submac, status, info);
assert(res >= 0);
}
static inline bool _does_handle_ack(ieee802154_dev_t *dev)
@ -67,77 +73,10 @@ static inline bool _does_handle_ack(ieee802154_dev_t *dev)
ieee802154_radio_has_irq_ack_timeout(dev);
}
static int _perform_csma_ca(ieee802154_submac_t *submac)
static inline bool _does_handle_csma(ieee802154_dev_t *dev)
{
ieee802154_dev_t *dev = &submac->dev;
if (submac->csma_retries_nb <= submac->csma_retries) {
_req_set_trx_state_wait_busy(dev, IEEE802154_TRX_STATE_TX_ON);
/* delay for an adequate random backoff period */
uint32_t bp = (random_uint32() & submac->backoff_mask) *
CSMA_SENDER_BACKOFF_PERIOD_UNIT_MS;
xtimer_usleep(bp);
/* try to send after a CCA */
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
while (ieee802154_radio_request_transmit(dev) == -EBUSY) {}
/* Prepare for next iteration */
if (submac->backoff_mask + 1 < submac->be.max) {
submac->backoff_mask = (submac->backoff_mask << 1) | 1;
}
else {
submac->backoff_mask = (1 << submac->be.max) - 1;
}
submac->csma_retries_nb++;
}
else {
ieee802154_radio_set_frame_filter_mode(dev, IEEE802154_FILTER_ACCEPT);
_tx_end(submac, TX_STATUS_MEDIUM_BUSY, NULL);
}
return 0;
}
/**
* @brief Perform CSMA-CA transmission (possibly with retransmission)
*
* If radio supports @ref IEEE802154_CAP_FRAME_RETRANS, the device will automatically retransmit.
* If radio supports @ref IEEE802154_CAP_AUTO_CSMA, this function will use the
* internal CSMA-CA acceleration to perform the transmission.
*
* @param submac pointer to the SubMAC
*
* @return 0 on success
* @return negative errno on error
*/
int ieee802154_csma_ca_transmit(ieee802154_submac_t *submac)
{
ieee802154_dev_t *dev = &submac->dev;
/* If radio has Auto CSMA-CA or Frame Retransmissions, simply send and wait for the transmit confirmation. */
if (ieee802154_radio_has_auto_csma(dev) ||
ieee802154_radio_has_frame_retrans(dev)) {
/* Make sure we are in TX_ON */
_req_set_trx_state_wait_busy(dev, IEEE802154_TRX_STATE_TX_ON);
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
int res;
while ((res = ieee802154_radio_request_transmit(dev)) == -EBUSY) {}
return res;
}
else {
submac->csma_retries_nb = 0;
submac->backoff_mask = (1 << submac->be.min) - 1;
_perform_csma_ca(submac);
}
return 0;
return ieee802154_radio_has_frame_retrans(dev) ||
ieee802154_radio_has_auto_csma(dev);
}
static bool _has_retrans_left(ieee802154_submac_t *submac)
@ -145,180 +84,332 @@ static bool _has_retrans_left(ieee802154_submac_t *submac)
return submac->retrans < IEEE802154_SUBMAC_MAX_RETRANSMISSIONS;
}
static void _perform_retrans(ieee802154_submac_t *submac)
static ieee802154_fsm_state_t _tx_end(ieee802154_submac_t *submac, int status,
ieee802154_tx_info_t *info)
{
ieee802154_dev_t *dev = &submac->dev;
submac->wait_for_ack = false;
_req_set_trx_state_wait_busy(&submac->dev, IEEE802154_TRX_STATE_TRX_OFF);
submac->cb->tx_done(submac, status, info);
return IEEE802154_FSM_STATE_IDLE;
}
static void _print_debug(ieee802154_fsm_state_t old, ieee802154_fsm_state_t new,
ieee802154_fsm_ev_t ev)
{
DEBUG("%s--(%s)->%s\n", str_states[old], str_ev[ev], str_states[new]);
}
static ieee802154_fsm_state_t _handle_tx_no_ack(ieee802154_submac_t *submac)
{
/* In case of ACK Timeout, either trigger retransmissions or end
* the TX procedure */
if (_has_retrans_left(submac)) {
submac->retrans++;
int res = ieee802154_csma_ca_transmit(submac);
(void) res;
assert(res >= 0);
_req_set_trx_state_wait_busy(&submac->dev, IEEE802154_TRX_STATE_TX_ON);
ieee802154_submac_bh_request(submac);
return IEEE802154_FSM_STATE_PREPARE;
}
else {
ieee802154_radio_set_frame_filter_mode(dev, IEEE802154_FILTER_ACCEPT);
_tx_end(submac, TX_STATUS_NO_ACK, NULL);
ieee802154_radio_set_frame_filter_mode(&submac->dev, IEEE802154_FILTER_ACCEPT);
return _tx_end(submac, TX_STATUS_NO_ACK, NULL);
}
}
void ieee802154_submac_ack_timeout_fired(ieee802154_submac_t *submac)
static int _handle_fsm_ev_request_tx(ieee802154_submac_t *submac)
{
/* This is required to avoid race conditions */
if (submac->wait_for_ack) {
_handle_tx_no_ack(submac);
ieee802154_dev_t *dev = &submac->dev;
/* Set state to TX_ON */
int res = ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_TX_ON);
if (res < 0) {
return res;
}
else {
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
/* write frame to radio */
ieee802154_radio_write(dev, submac->psdu);
ieee802154_submac_bh_request(submac);
return 0;
}
}
void ieee802154_submac_crc_error_cb(ieee802154_submac_t *submac)
static ieee802154_fsm_state_t _fsm_state_rx(ieee802154_submac_t *submac, ieee802154_fsm_ev_t ev)
{
ieee802154_dev_t *dev = &submac->dev;
/* switch back to RX_ON state */
_req_set_trx_state_wait_busy(dev, IEEE802154_TRX_STATE_RX_ON);
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
switch (ev) {
case IEEE802154_FSM_EV_REQUEST_TX:
if (_handle_fsm_ev_request_tx(submac) < 0) {
return IEEE802154_FSM_STATE_RX;
}
return IEEE802154_FSM_STATE_PREPARE;
case IEEE802154_FSM_EV_RX_DONE:
/* Make sure it's not an ACK frame */
if (ieee802154_radio_len(&submac->dev) > (int)IEEE802154_MIN_FRAME_LEN) {
_req_set_trx_state_wait_busy(&submac->dev, IEEE802154_TRX_STATE_TRX_OFF);
submac->cb->rx_done(submac);
return IEEE802154_FSM_STATE_IDLE;
}
else {
ieee802154_radio_read(&submac->dev, NULL, 0, NULL);
/* Keep on current state */
return IEEE802154_FSM_STATE_RX;
}
case IEEE802154_FSM_EV_CRC_ERROR:
ieee802154_radio_read(&submac->dev, NULL, 0, NULL);
/* Keep on current state */
return IEEE802154_FSM_STATE_RX;
case IEEE802154_FSM_EV_REQUEST_SET_IDLE:
/* Try to turn off the transceiver */
if ((ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_TRX_OFF)) < 0) {
/* Keep on current state */
return IEEE802154_FSM_STATE_RX;
}
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
return IEEE802154_FSM_STATE_IDLE;
default:
break;
}
return IEEE802154_FSM_STATE_INVALID;
}
/* All callbacks run in the same context */
void ieee802154_submac_rx_done_cb(ieee802154_submac_t *submac)
static ieee802154_fsm_state_t _fsm_state_idle(ieee802154_submac_t *submac, ieee802154_fsm_ev_t ev)
{
ieee802154_dev_t *dev = &submac->dev;
if (!_does_handle_ack(dev) && submac->wait_for_ack) {
uint8_t ack[3];
switch (ev) {
case IEEE802154_FSM_EV_REQUEST_TX:
if (_handle_fsm_ev_request_tx(submac) < 0) {
return IEEE802154_FSM_STATE_IDLE;
}
return IEEE802154_FSM_STATE_PREPARE;
case IEEE802154_FSM_EV_REQUEST_SET_RX_ON:
/* Try to go turn on the transceiver */
if ((ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_RX_ON)) < 0) {
/* Keep on current state */
return IEEE802154_FSM_STATE_IDLE;
}
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
return IEEE802154_FSM_STATE_RX;
case IEEE802154_FSM_EV_RX_DONE:
case IEEE802154_FSM_EV_CRC_ERROR:
/* This might happen in case there's a race condition between ACK_TIMEOUT
* and TX_DONE. We simply discard the frame and keep the state as
* it is
*/
ieee802154_radio_read(&submac->dev, NULL, 0, NULL);
return IEEE802154_FSM_STATE_IDLE;
default:
break;
}
return IEEE802154_FSM_STATE_INVALID;
}
if (ieee802154_radio_read(dev, ack, 3, NULL) &&
static ieee802154_fsm_state_t _fsm_state_prepare(ieee802154_submac_t *submac,
ieee802154_fsm_ev_t ev)
{
ieee802154_dev_t *dev = &submac->dev;
switch (ev) {
case IEEE802154_FSM_EV_BH:
if (!_does_handle_csma(dev)) {
/* delay for an adequate random backoff period */
uint32_t bp = (random_uint32() & submac->backoff_mask) *
CSMA_SENDER_BACKOFF_PERIOD_UNIT_US;
xtimer_usleep(bp);
/* Prepare for next iteration */
uint8_t curr_be = (submac->backoff_mask + 1) >> 1;
if (curr_be < submac->be.max) {
submac->backoff_mask = (submac->backoff_mask << 1) | 1;
}
}
while (ieee802154_radio_request_transmit(dev) == -EBUSY) {}
return IEEE802154_FSM_STATE_TX;
case IEEE802154_FSM_EV_RX_DONE:
case IEEE802154_FSM_EV_CRC_ERROR:
/* This might happen in case there's a race condition between ACK_TIMEOUT
* and TX_DONE. We simply discard the frame and keep the state as
* it is
*/
ieee802154_radio_read(&submac->dev, NULL, 0, NULL);
return IEEE802154_FSM_STATE_PREPARE;
default:
break;
}
return IEEE802154_FSM_STATE_INVALID;
}
static ieee802154_fsm_state_t _fsm_state_tx_process_tx_done(ieee802154_submac_t *submac,
ieee802154_tx_info_t *info)
{
ieee802154_dev_t *dev = &submac->dev;
switch (info->status) {
case TX_STATUS_FRAME_PENDING:
assert(_does_handle_ack(&submac->dev));
/* FALL-THRU */
case TX_STATUS_SUCCESS:
submac->csma_retries_nb = 0;
/* If the radio handles ACK, the TX_DONE event marks completion of
* the transmission procedure. Report TX done to the upper layer */
if (_does_handle_ack(&submac->dev) || !submac->wait_for_ack) {
return _tx_end(submac, info->status, info);
}
/* If the radio doesn't handle ACK, set the transceiver state to RX_ON
* and enable the ACK filter */
else {
ieee802154_radio_set_frame_filter_mode(dev, IEEE802154_FILTER_ACK_ONLY);
_req_set_trx_state_wait_busy(dev, IEEE802154_TRX_STATE_RX_ON);
/* Handle ACK reception */
ieee802154_submac_ack_timer_set(submac, ACK_TIMEOUT_US);
return IEEE802154_FSM_STATE_WAIT_FOR_ACK;
}
break;
case TX_STATUS_NO_ACK:
assert(_does_handle_ack(&submac->dev));
submac->csma_retries_nb = 0;
return _handle_tx_no_ack(submac);
case TX_STATUS_MEDIUM_BUSY:
/* If radio has retransmissions or CSMA-CA, this means the CSMA-CA
* procedure failed. We finish the SubMAC operation and report
* medium busy
*/
if (_does_handle_csma(&submac->dev)
|| submac->csma_retries_nb++ >= submac->csma_retries) {
return _tx_end(submac, info->status, info);
}
/* Otherwise, this is a failed CCA attempt. Proceed with CSMA-CA */
else {
/* The HAL should guarantee that's still possible to transmit
* in the current state, since the radio is still in TX_ON.
* Therefore, this is valid */
ieee802154_submac_bh_request(submac);
return IEEE802154_FSM_STATE_PREPARE;
}
}
return IEEE802154_FSM_STATE_INVALID;
}
static ieee802154_fsm_state_t _fsm_state_tx(ieee802154_submac_t *submac, ieee802154_fsm_ev_t ev)
{
int res;
ieee802154_tx_info_t info;
switch (ev) {
case IEEE802154_FSM_EV_TX_DONE:
res = ieee802154_radio_confirm_transmit(&submac->dev, &info);
(void)res;
assert(res >= 0);
return _fsm_state_tx_process_tx_done(submac, &info);
case IEEE802154_FSM_EV_RX_DONE:
case IEEE802154_FSM_EV_CRC_ERROR:
/* This might happen in case there's a race condition between ACK_TIMEOUT
* and TX_DONE. We simply discard the frame and keep the state as
* it is
*/
ieee802154_radio_read(&submac->dev, NULL, 0, NULL);
return IEEE802154_FSM_STATE_TX;
default:
break;
}
return IEEE802154_FSM_STATE_INVALID;
}
static ieee802154_fsm_state_t _fsm_state_wait_for_ack(ieee802154_submac_t *submac,
ieee802154_fsm_ev_t ev)
{
uint8_t ack[3];
switch (ev) {
case IEEE802154_FSM_EV_RX_DONE:
assert(!ieee802154_radio_has_irq_ack_timeout(&submac->dev));
if (ieee802154_radio_read(&submac->dev, ack, 3, NULL) &&
ack[0] & IEEE802154_FCF_TYPE_ACK) {
ieee802154_submac_ack_timer_cancel(submac);
ieee802154_tx_info_t tx_info;
tx_info.retrans = submac->retrans;
bool fp = (ack[0] & IEEE802154_FCF_FRAME_PEND);
ieee802154_radio_set_frame_filter_mode(&submac->dev, IEEE802154_FILTER_ACCEPT);
_tx_end(submac, fp ? TX_STATUS_FRAME_PENDING : TX_STATUS_SUCCESS,
&tx_info);
return _tx_end(submac, fp ? TX_STATUS_FRAME_PENDING : TX_STATUS_SUCCESS,
&tx_info);
}
}
else {
assert(!submac->tx);
submac->cb->rx_done(submac);
/* Only set the radio to the SubMAC default state only if the upper
* layer didn't try to send more data. Otherwise there's risk of not
* being compliant with the Radio HAL API (e.g the radio might try
* to set a different state in the middle of a transmission).
*/
if (submac->tx) {
return;
}
/* The Radio HAL will be in "FB Lock" state. We need to do a state
* transition here in order to release it */
ieee802154_trx_state_t next_state = submac->state == IEEE802154_STATE_LISTEN ? IEEE802154_TRX_STATE_RX_ON : IEEE802154_TRX_STATE_TRX_OFF;
_req_set_trx_state_wait_busy(&submac->dev, next_state);
while (ieee802154_radio_confirm_set_trx_state(&submac->dev) == -EAGAIN) {}
}
}
static void _handle_tx_success(ieee802154_submac_t *submac,
ieee802154_tx_info_t *info)
{
ieee802154_dev_t *dev = &submac->dev;
ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_RX_ON);
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
if (ieee802154_radio_has_frame_retrans(dev) ||
ieee802154_radio_has_irq_ack_timeout(dev) || !submac->wait_for_ack) {
if (!ieee802154_radio_has_frame_retrans_info(dev)) {
info->retrans = -1;
}
_tx_end(submac, info->status, info);
}
else {
ieee802154_radio_set_frame_filter_mode(dev, IEEE802154_FILTER_ACK_ONLY);
/* Handle ACK reception */
ieee802154_submac_ack_timer_set(submac, ACK_TIMEOUT_US);
}
}
static void _handle_tx_medium_busy(ieee802154_submac_t *submac)
{
ieee802154_dev_t *dev = &submac->dev;
if (ieee802154_radio_has_frame_retrans(dev) ||
ieee802154_radio_has_auto_csma(dev)) {
_tx_end(submac, TX_STATUS_MEDIUM_BUSY, NULL);
}
else {
/* CCA failed. Continue with the CSMA-CA algorithm */
_perform_csma_ca(submac);
}
}
static void _handle_tx_no_ack(ieee802154_submac_t *submac)
{
ieee802154_dev_t *dev = &submac->dev;
if (ieee802154_radio_has_frame_retrans(dev)) {
_tx_end(submac, TX_STATUS_NO_ACK, NULL);
}
else {
/* Perform retransmissions */
_perform_retrans(submac);
}
}
void ieee802154_submac_tx_done_cb(ieee802154_submac_t *submac)
{
ieee802154_dev_t *dev = &submac->dev;
ieee802154_tx_info_t info;
ieee802154_radio_confirm_transmit(dev, &info);
switch (info.status) {
case TX_STATUS_MEDIUM_BUSY:
_handle_tx_medium_busy(submac);
return IEEE802154_FSM_STATE_WAIT_FOR_ACK;
case IEEE802154_FSM_EV_CRC_ERROR:
/* Received invalid ACK. Drop frame */
ieee802154_radio_read(&submac->dev, NULL, 0, NULL);
return IEEE802154_FSM_STATE_WAIT_FOR_ACK;
case IEEE802154_FSM_EV_ACK_TIMEOUT:
return _handle_tx_no_ack(submac);
default:
break;
case TX_STATUS_NO_ACK:
_handle_tx_no_ack(submac);
}
return IEEE802154_FSM_STATE_INVALID;
}
ieee802154_fsm_state_t ieee802154_submac_process_ev(ieee802154_submac_t *submac,
ieee802154_fsm_ev_t ev)
{
ieee802154_fsm_state_t new_state;
switch (submac->fsm_state) {
case IEEE802154_FSM_STATE_RX:
new_state = _fsm_state_rx(submac, ev);
break;
case TX_STATUS_SUCCESS:
case TX_STATUS_FRAME_PENDING:
_handle_tx_success(submac, &info);
case IEEE802154_FSM_STATE_IDLE:
new_state = _fsm_state_idle(submac, ev);
break;
case IEEE802154_FSM_STATE_PREPARE:
new_state = _fsm_state_prepare(submac, ev);
break;
case IEEE802154_FSM_STATE_TX:
new_state = _fsm_state_tx(submac, ev);
break;
case IEEE802154_FSM_STATE_WAIT_FOR_ACK:
new_state = _fsm_state_wait_for_ack(submac, ev);
break;
default:
assert(false);
break;
new_state = IEEE802154_FSM_STATE_INVALID;
}
if (new_state == IEEE802154_FSM_STATE_INVALID) {
_print_debug(submac->fsm_state, new_state, ev);
assert(false);
}
submac->fsm_state = new_state;
return submac->fsm_state;
}
int ieee802154_send(ieee802154_submac_t *submac, const iolist_t *iolist)
{
ieee802154_dev_t *dev = &submac->dev;
ieee802154_fsm_state_t current_state = submac->fsm_state;
if (current_state != IEEE802154_FSM_STATE_RX && current_state != IEEE802154_FSM_STATE_IDLE) {
return -EBUSY;
}
uint8_t *buf = iolist->iol_base;
bool cnf = buf[0] & IEEE802154_FCF_ACK_REQ;
if (submac->state == IEEE802154_STATE_OFF) {
return -ENETDOWN;
}
submac->wait_for_ack = cnf;
submac->psdu = iolist;
submac->retrans = 0;
submac->csma_retries_nb = 0;
submac->backoff_mask = (1 << submac->be.min) - 1;
if (submac->tx ||
ieee802154_radio_request_set_trx_state(dev,
IEEE802154_TRX_STATE_TX_ON) < 0) {
if (ieee802154_submac_process_ev(submac, IEEE802154_FSM_EV_REQUEST_TX)
!= IEEE802154_FSM_STATE_PREPARE) {
return -EBUSY;
}
submac->tx = true;
ieee802154_radio_write(dev, iolist);
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
submac->wait_for_ack = cnf;
submac->retrans = 0;
return ieee802154_csma_ca_transmit(submac);
return 0;
}
int ieee802154_submac_init(ieee802154_submac_t *submac, const network_uint16_t *short_addr,
@ -326,10 +417,10 @@ int ieee802154_submac_init(ieee802154_submac_t *submac, const network_uint16_t *
{
ieee802154_dev_t *dev = &submac->dev;
submac->tx = false;
submac->state = IEEE802154_STATE_LISTEN;
submac->fsm_state = IEEE802154_FSM_STATE_RX;
int res;
if ((res = ieee802154_radio_request_on(dev)) < 0) {
return res;
}
@ -358,6 +449,7 @@ int ieee802154_submac_init(ieee802154_submac_t *submac, const network_uint16_t *
/* Get supported PHY modes */
int supported_phy_modes = ieee802154_radio_get_phy_modes(dev);
assert(supported_phy_modes != 0);
uint32_t default_phy_cap = ieee802154_phy_mode_to_cap(CONFIG_IEEE802154_DEFAULT_PHY_MODE);
@ -400,7 +492,6 @@ int ieee802154_submac_init(ieee802154_submac_t *submac, const network_uint16_t *
assert(res >= 0);
_req_set_trx_state_wait_busy(dev, IEEE802154_TRX_STATE_RX_ON);
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {};
return res;
}
@ -414,14 +505,19 @@ int ieee802154_set_phy_conf(ieee802154_submac_t *submac, uint16_t channel_num,
.channel = channel_num,
.page = channel_page,
.pow = tx_pow };
int res;
ieee802154_fsm_state_t current_state = submac->fsm_state;
if (submac->state == IEEE802154_STATE_OFF) {
return -ENETDOWN;
/* Changing state can be only performed on IDLE or RX state */
if (current_state != IEEE802154_FSM_STATE_RX && current_state != IEEE802154_FSM_STATE_IDLE) {
return -EBUSY;
}
int res;
if ((res = ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_TRX_OFF)) < 0) {
return res;
/* If the radio is listening, turn it off first */
if (current_state == IEEE802154_FSM_STATE_RX) {
if ((res = ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_TRX_OFF)) < 0) {
return res;
}
}
res = ieee802154_radio_config_phy(dev, &conf);
@ -433,52 +529,61 @@ int ieee802154_set_phy_conf(ieee802154_submac_t *submac, uint16_t channel_num,
}
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
ieee802154_radio_request_set_trx_state(dev, submac->state == IEEE802154_STATE_LISTEN ? IEEE802154_TRX_STATE_RX_ON : IEEE802154_TRX_STATE_TRX_OFF);
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
/* Go back to RX if needed */
if (current_state == IEEE802154_FSM_STATE_RX) {
_req_set_trx_state_wait_busy(dev, IEEE802154_TRX_STATE_RX_ON);
}
return res;
}
int ieee802154_set_state(ieee802154_submac_t *submac, ieee802154_submac_state_t state)
int ieee802154_set_rx(ieee802154_submac_t *submac)
{
int res;
ieee802154_fsm_state_t current_state = submac->fsm_state;
ieee802154_fsm_state_t next_state;
int res = -EBUSY;
ieee802154_dev_t *dev = &submac->dev;
if (submac->tx) {
return -EBUSY;
}
if (state == submac->state) {
return -EALREADY;
}
/* Wake up the radio if it was off */
if (submac->state == IEEE802154_STATE_OFF) {
if ((res = ieee802154_radio_request_on(dev)) < 0) {
return res;
switch (current_state) {
case IEEE802154_FSM_STATE_RX:
res = -EALREADY;
break;
case IEEE802154_FSM_STATE_IDLE:
next_state = ieee802154_submac_process_ev(submac,
IEEE802154_FSM_EV_REQUEST_SET_RX_ON);
if (next_state == IEEE802154_FSM_STATE_RX) {
res = 0;
}
while (ieee802154_radio_confirm_on(dev) == -EAGAIN);
break;
default:
break;
}
if (state == IEEE802154_STATE_OFF) {
res = ieee802154_radio_off(dev);
}
else {
ieee802154_trx_state_t new_state =
state == IEEE802154_STATE_IDLE
? IEEE802154_TRX_STATE_TRX_OFF
: IEEE802154_TRX_STATE_RX_ON;
if ((res = ieee802154_radio_request_set_trx_state(dev, new_state)) < 0) {
return res;
}
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN);
}
submac->state = state;
return res;
}
int ieee802154_set_idle(ieee802154_submac_t *submac)
{
ieee802154_fsm_state_t current_state = submac->fsm_state;
ieee802154_fsm_state_t next_state;
int res = -EBUSY;
switch (current_state) {
case IEEE802154_FSM_STATE_IDLE:
res = -EALREADY;
break;
case IEEE802154_FSM_STATE_RX:
next_state = ieee802154_submac_process_ev(submac,
IEEE802154_FSM_EV_REQUEST_SET_IDLE);
if (next_state == IEEE802154_FSM_STATE_IDLE) {
res = 0;
}
break;
default:
break;
}
return res;
}
/** @} */