From 56a8fece01ef9d3e280b2cf33d2363134ca2f81d Mon Sep 17 00:00:00 2001 From: Jose Alamos Date: Mon, 16 Aug 2021 10:26:23 +0200 Subject: [PATCH 1/5] ieee802154/radio: add IEEE802154_CAP_RX_CONTINUOUS --- sys/include/net/ieee802154/radio.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sys/include/net/ieee802154/radio.h b/sys/include/net/ieee802154/radio.h index ba2569e0b5..361e258ae8 100644 --- a/sys/include/net/ieee802154/radio.h +++ b/sys/include/net/ieee802154/radio.h @@ -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; /** @@ -1410,6 +1422,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. From 3c1909b4432a0b94b100aa34ed1967e6e5418c83 Mon Sep 17 00:00:00 2001 From: Jose Alamos Date: Mon, 16 Aug 2021 10:29:34 +0200 Subject: [PATCH 2/5] ieee802154/radio: update FB Lock handle --- sys/include/net/ieee802154/radio.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sys/include/net/ieee802154/radio.h b/sys/include/net/ieee802154/radio.h index 361e258ae8..5bb05cf7cc 100644 --- a/sys/include/net/ieee802154/radio.h +++ b/sys/include/net/ieee802154/radio.h @@ -284,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, @@ -573,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. From 0b5b896e70b7189a3f8eed6424b98493db21c935 Mon Sep 17 00:00:00 2001 From: Jose Alamos Date: Mon, 16 Aug 2021 14:31:34 +0200 Subject: [PATCH 3/5] cc2538_rf: fix race condition when changing state --- cpu/cc2538/radio/cc2538_rf_radio_ops.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cpu/cc2538/radio/cc2538_rf_radio_ops.c b/cpu/cc2538/radio/cc2538_rf_radio_ops.c index 9b1edf2d3a..4793a78d39 100644 --- a/cpu/cc2538/radio/cc2538_rf_radio_ops.c +++ b/cpu/cc2538/radio/cc2538_rf_radio_ops.c @@ -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; } @@ -296,7 +295,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,6 +307,7 @@ 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; @@ -316,6 +318,10 @@ static int _request_set_trx_state(ieee802154_dev_t *dev, ieee802154_trx_state_t break; } + RFCORE_SFR_RFIRQF0 = 0; + RFCORE_SFR_RFIRQF1 = 0; + + irq_restore(irq); return 0; } From 0fb55b21c5fc44e31c40f95394f037e08584ecfc Mon Sep 17 00:00:00 2001 From: Jose Alamos Date: Mon, 16 Aug 2021 14:32:18 +0200 Subject: [PATCH 4/5] cc2538_rf: adapt radio HAL changes This PR adapts the cc2538 to the new Radio HAL changes: - Move RX FLUSH to the `read` function - Add RX_CONTINUOS cap --- cpu/cc2538/radio/cc2538_rf_radio_ops.c | 98 ++++++++++++++------------ 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/cpu/cc2538/radio/cc2538_rf_radio_ops.c b/cpu/cc2538/radio/cc2538_rf_radio_ops.c index 4793a78d39..b62b21ef08 100644 --- a/cpu/cc2538/radio/cc2538_rf_radio_ops.c +++ b/cpu/cc2538/radio/cc2538_rf_radio_ops.c @@ -175,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; } @@ -311,7 +321,6 @@ static int _request_set_trx_state(ieee802154_dev_t *dev, ieee802154_trx_state_t 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; @@ -358,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))) { @@ -371,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); @@ -390,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 { @@ -557,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, From 8f97f734536a4b669373aa67828f420f299cdb57 Mon Sep 17 00:00:00 2001 From: Jose Alamos Date: Mon, 16 Aug 2021 14:25:47 +0200 Subject: [PATCH 5/5] ieee802154/submac: reimplement using FSM --- .../include/net/netdev/ieee802154_submac.h | 3 + .../netdev_ieee802154_submac.c | 98 ++- sys/include/net/ieee802154/submac.h | 298 +++++++-- sys/net/link_layer/ieee802154/submac.c | 633 ++++++++++-------- 4 files changed, 679 insertions(+), 353 deletions(-) diff --git a/drivers/include/net/netdev/ieee802154_submac.h b/drivers/include/net/netdev/ieee802154_submac.h index d4d9453433..ce71d2b32f 100644 --- a/drivers/include/net/netdev/ieee802154_submac.h +++ b/drivers/include/net/netdev/ieee802154_submac.h @@ -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; /** diff --git a/drivers/netdev_ieee802154_submac/netdev_ieee802154_submac.c b/drivers/netdev_ieee802154_submac/netdev_ieee802154_submac.c index 0726e1ee15..baa1efd509 100644 --- a/drivers/netdev_ieee802154_submac/netdev_ieee802154_submac.c +++ b/drivers/netdev_ieee802154_submac/netdev_ieee802154_submac.c @@ -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 = { diff --git a/sys/include/net/ieee802154/submac.h b/sys/include/net/ieee802154/submac.h index e6a0b2c5e1..044903e4e3 100644 --- a/sys/include/net/ieee802154/submac.h +++ b/sys/include/net/ieee802154/submac.h @@ -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 @@ -31,7 +110,9 @@ extern "C" { #endif +#include #include +#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 } diff --git a/sys/net/link_layer/ieee802154/submac.c b/sys/net/link_layer/ieee802154/submac.c index a545629231..6ad22c3a5f 100644 --- a/sys/net/link_layer/ieee802154/submac.c +++ b/sys/net/link_layer/ieee802154/submac.c @@ -13,6 +13,7 @@ * @author José I. Alamos */ +#include #include #include #include "net/ieee802154/submac.h" @@ -22,12 +23,32 @@ #include "luid.h" #include "kernel_defines.h" #include "errno.h" -#include -#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; + +} + /** @} */