/* * Copyright (C) 2020 HAW Hamburg * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @{ * * @file * @author José I. Alamos */ #include #include #include #include "net/ieee802154/submac.h" #include "net/ieee802154.h" #include "xtimer.h" #include "random.h" #include "luid.h" #include "kernel_defines.h" #include "errno.h" #define ENABLE_DEBUG 0 #include "debug.h" #define CSMA_SENDER_BACKOFF_PERIOD_UNIT_US (320U) #define ACK_TIMEOUT_US (864U) 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 bool _does_handle_ack(ieee802154_dev_t *dev) { return ieee802154_radio_has_frame_retrans(dev) || ieee802154_radio_has_irq_ack_timeout(dev); } static inline bool _does_handle_csma(ieee802154_dev_t *dev) { return ieee802154_radio_has_frame_retrans(dev) || ieee802154_radio_has_auto_csma(dev); } static bool _has_retrans_left(ieee802154_submac_t *submac) { return submac->retrans < CONFIG_IEEE802154_DEFAULT_MAX_FRAME_RETRANS; } static ieee802154_fsm_state_t _tx_end(ieee802154_submac_t *submac, int status, ieee802154_tx_info_t *info) { int res; /* This is required to prevent unused variable warnings */ (void) res; submac->wait_for_ack = false; res = ieee802154_radio_set_idle(&submac->dev, true); assert(res >= 0); 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) { int res; /* This is required to prevent unused variable warnings */ (void) res; /* In case of ACK Timeout, either trigger retransmissions or end * the TX procedure */ if (_has_retrans_left(submac)) { submac->retrans++; res = ieee802154_radio_set_idle(&submac->dev, true); assert(res >= 0); ieee802154_submac_bh_request(submac); return IEEE802154_FSM_STATE_PREPARE; } else { ieee802154_radio_set_frame_filter_mode(&submac->dev, IEEE802154_FILTER_ACCEPT); return _tx_end(submac, TX_STATUS_NO_ACK, NULL); } } static int _handle_fsm_ev_request_tx(ieee802154_submac_t *submac) { ieee802154_dev_t *dev = &submac->dev; /* Set state to TX_ON */ int res = ieee802154_radio_set_idle(dev, false); if (res < 0) { return res; } else { /* write frame to radio */ ieee802154_radio_write(dev, submac->psdu); ieee802154_submac_bh_request(submac); return 0; } } static ieee802154_fsm_state_t _fsm_state_rx(ieee802154_submac_t *submac, ieee802154_fsm_ev_t ev) { ieee802154_dev_t *dev = &submac->dev; int res; /* This is required to prevent unused variable warnings */ (void) res; 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 */ while (ieee802154_radio_set_idle(&submac->dev, false) < 0) {} if (ieee802154_radio_len(&submac->dev) > (int)IEEE802154_MIN_FRAME_LEN) { submac->cb->rx_done(submac); return IEEE802154_FSM_STATE_IDLE; } else { ieee802154_radio_read(&submac->dev, NULL, 0, NULL); /* If the radio doesn't support RX Continuous, go to RX */ res = ieee802154_radio_set_rx(&submac->dev); assert(res >= 0); /* Keep on current state */ return IEEE802154_FSM_STATE_RX; } case IEEE802154_FSM_EV_CRC_ERROR: while (ieee802154_radio_set_idle(&submac->dev, false) < 0) {} ieee802154_radio_read(&submac->dev, NULL, 0, NULL); /* If the radio doesn't support RX Continuous, go to RX */ res = ieee802154_radio_set_rx(&submac->dev); assert(res >= 0); /* 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_idle(dev, false)) < 0) { /* Keep on current state */ return IEEE802154_FSM_STATE_RX; } while (ieee802154_radio_confirm_set_idle(dev) == -EAGAIN) {} return IEEE802154_FSM_STATE_IDLE; default: break; } return IEEE802154_FSM_STATE_INVALID; } static ieee802154_fsm_state_t _fsm_state_idle(ieee802154_submac_t *submac, ieee802154_fsm_ev_t ev) { ieee802154_dev_t *dev = &submac->dev; 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_set_rx(dev) < 0)) { /* Keep on current state */ return IEEE802154_FSM_STATE_IDLE; } 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; } 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; int res; /* This is required to prevent unused variable warnings */ (void) res; 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); res = ieee802154_radio_set_rx(dev); assert (res >= 0); /* 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) { ieee802154_tx_info_t info; int res; /* This is required to prevent unused variable warnings */ (void) res; switch (ev) { case IEEE802154_FSM_EV_TX_DONE: res = ieee802154_radio_confirm_transmit(&submac->dev, &info); 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); return _tx_end(submac, fp ? TX_STATUS_FRAME_PENDING : TX_STATUS_SUCCESS, &tx_info); } 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; } 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 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: 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_fsm_state_t current_state = submac->fsm_state; if (current_state != IEEE802154_FSM_STATE_RX && current_state != IEEE802154_FSM_STATE_IDLE) { return -EBUSY; } if (iolist == NULL) { return 0; } uint8_t *buf = iolist->iol_base; bool cnf = buf[0] & IEEE802154_FCF_ACK_REQ; 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 (ieee802154_submac_process_ev(submac, IEEE802154_FSM_EV_REQUEST_TX) != IEEE802154_FSM_STATE_PREPARE) { return -EBUSY; } return 0; } int ieee802154_submac_init(ieee802154_submac_t *submac, const network_uint16_t *short_addr, const eui64_t *ext_addr) { ieee802154_dev_t *dev = &submac->dev; submac->fsm_state = IEEE802154_FSM_STATE_RX; int res; if ((res = ieee802154_radio_request_on(dev)) < 0) { return res; } /* generate EUI-64 and short address */ memcpy(&submac->ext_addr, ext_addr, sizeof(eui64_t)); memcpy(&submac->short_addr, short_addr, sizeof(network_uint16_t)); submac->panid = CONFIG_IEEE802154_DEFAULT_PANID; submac->be.min = CONFIG_IEEE802154_DEFAULT_CSMA_CA_MIN_BE; submac->csma_retries = CONFIG_IEEE802154_DEFAULT_CSMA_CA_RETRIES; submac->be.max = CONFIG_IEEE802154_DEFAULT_CSMA_CA_MAX_BE; submac->tx_pow = CONFIG_IEEE802154_DEFAULT_TXPOWER; if (ieee802154_radio_has_24_ghz(dev)) { submac->channel_num = CONFIG_IEEE802154_DEFAULT_CHANNEL; /* 2.4 GHz only use page 0 */ submac->channel_page = 0; } else { submac->channel_num = CONFIG_IEEE802154_DEFAULT_SUBGHZ_CHANNEL; submac->channel_page = CONFIG_IEEE802154_DEFAULT_SUBGHZ_PAGE; } /* 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); /* Check if configuration provides valid PHY */ if (CONFIG_IEEE802154_DEFAULT_PHY_MODE != IEEE802154_PHY_DISABLED && (supported_phy_modes & default_phy_cap)) { /* Check if default PHY is supported */ submac->phy_mode = CONFIG_IEEE802154_DEFAULT_PHY_MODE; } else { /* Get first set bit, and use it as the default, * * by this order, the priority is defined on the ieee802154_rf_caps_t * definition, first IEEE 802.15.4-2006 PHY modes, then * IEEE 802.15.4g-2012 PHY modes. */ unsigned bit = bitarithm_lsb(supported_phy_modes); submac->phy_mode = ieee802154_cap_to_phy_mode(1 << bit); } /* If the radio is still not in TRX_OFF state, spin */ while (ieee802154_radio_confirm_on(dev) == -EAGAIN) {} /* Configure address filter */ ieee802154_radio_config_addr_filter(dev, IEEE802154_AF_SHORT_ADDR, &submac->short_addr); ieee802154_radio_config_addr_filter(dev, IEEE802154_AF_EXT_ADDR, &submac->ext_addr); ieee802154_radio_config_addr_filter(dev, IEEE802154_AF_PANID, &submac->panid); /* Configure PHY settings (mode, channel, TX power) */ ieee802154_phy_conf_t conf = { .phy_mode = submac->phy_mode, .channel = submac->channel_num, .page = submac->channel_page, .pow = submac->tx_pow }; ieee802154_radio_config_phy(dev, &conf); ieee802154_radio_set_cca_threshold(dev, CONFIG_IEEE802154_CCA_THRESH_DEFAULT); assert(res >= 0); while (ieee802154_radio_set_rx(dev) < 0) {} return res; } int ieee802154_set_phy_conf(ieee802154_submac_t *submac, uint16_t channel_num, uint8_t channel_page, int8_t tx_pow) { ieee802154_dev_t *dev = &submac->dev; const ieee802154_phy_conf_t conf = { .phy_mode = submac->phy_mode, .channel = channel_num, .page = channel_page, .pow = tx_pow }; int res; ieee802154_fsm_state_t current_state = submac->fsm_state; /* 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; } /* If the radio is listening, turn it off first */ if (current_state == IEEE802154_FSM_STATE_RX) { if ((res = ieee802154_radio_request_set_idle(dev, false)) < 0) { return res; } } res = ieee802154_radio_config_phy(dev, &conf); if (res >= 0) { submac->channel_num = channel_num; submac->channel_page = channel_page; submac->tx_pow = tx_pow; } while (ieee802154_radio_confirm_set_idle(dev) == -EAGAIN) {} /* Go back to RX if needed */ if (current_state == IEEE802154_FSM_STATE_RX) { res = ieee802154_radio_set_rx(dev); assert (res >= 0); } return res; } int ieee802154_set_rx(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_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; } break; default: break; } 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; } /** @} */