mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
464 lines
14 KiB
C
464 lines
14 KiB
C
/*
|
|
* 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 <jose.alamos@haw-hamburg.de>
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#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"
|
|
#include <assert.h>
|
|
|
|
#define CSMA_SENDER_BACKOFF_PERIOD_UNIT_MS (320U)
|
|
#define ACK_TIMEOUT_US (864U)
|
|
|
|
static void _handle_tx_no_ack(ieee802154_submac_t *submac);
|
|
|
|
static void _tx_end(ieee802154_submac_t *submac, int status,
|
|
ieee802154_tx_info_t *info)
|
|
{
|
|
ieee802154_dev_t *dev = submac->dev;
|
|
|
|
ieee802154_radio_request_set_trx_state(dev, submac->state == IEEE802154_STATE_LISTEN ? IEEE802154_TRX_STATE_RX_ON : IEEE802154_TRX_STATE_TRX_OFF);
|
|
|
|
submac->wait_for_ack = false;
|
|
submac->tx = false;
|
|
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
|
|
submac->cb->tx_done(submac, status, info);
|
|
}
|
|
|
|
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 int _perform_csma_ca(ieee802154_submac_t *submac)
|
|
{
|
|
ieee802154_dev_t *dev = submac->dev;
|
|
|
|
if (submac->csma_retries_nb <= submac->csma_retries) {
|
|
ieee802154_radio_request_set_trx_state(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_rx_mode(dev, IEEE802154_RX_AACK_ENABLED);
|
|
_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 */
|
|
ieee802154_radio_request_set_trx_state(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;
|
|
}
|
|
|
|
static bool _has_retrans_left(ieee802154_submac_t *submac)
|
|
{
|
|
return submac->retrans < IEEE802154_SUBMAC_MAX_RETRANSMISSIONS;
|
|
}
|
|
|
|
static void _perform_retrans(ieee802154_submac_t *submac)
|
|
{
|
|
ieee802154_dev_t *dev = submac->dev;
|
|
|
|
if (_has_retrans_left(submac)) {
|
|
submac->retrans++;
|
|
ieee802154_csma_ca_transmit(submac);
|
|
}
|
|
else {
|
|
ieee802154_radio_set_rx_mode(dev, IEEE802154_RX_AACK_ENABLED);
|
|
_tx_end(submac, TX_STATUS_NO_ACK, NULL);
|
|
}
|
|
}
|
|
|
|
void ieee802154_submac_ack_timeout_fired(ieee802154_submac_t *submac)
|
|
{
|
|
/* This is required to avoid race conditions */
|
|
if (submac->wait_for_ack) {
|
|
_handle_tx_no_ack(submac);
|
|
}
|
|
}
|
|
|
|
void ieee802154_submac_crc_error_cb(ieee802154_submac_t *submac)
|
|
{
|
|
ieee802154_dev_t *dev = submac->dev;
|
|
/* switch back to RX_ON state */
|
|
ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_RX_ON);
|
|
while (ieee802154_radio_confirm_set_trx_state(dev) == -EAGAIN) {}
|
|
}
|
|
|
|
/* All callbacks run in the same context */
|
|
void ieee802154_submac_rx_done_cb(ieee802154_submac_t *submac)
|
|
{
|
|
ieee802154_dev_t *dev = submac->dev;
|
|
|
|
if (!_does_handle_ack(dev) && submac->wait_for_ack) {
|
|
uint8_t ack[3];
|
|
|
|
if (ieee802154_radio_read(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_rx_mode(submac->dev,
|
|
IEEE802154_RX_AACK_ENABLED);
|
|
_tx_end(submac, fp ? TX_STATUS_FRAME_PENDING : TX_STATUS_SUCCESS,
|
|
&tx_info);
|
|
}
|
|
}
|
|
else {
|
|
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;
|
|
|
|
/* Some radios will run some house keeping tasks on RX_DONE (e.g
|
|
* sending ACK frames). In such case we need to wait until the radio is
|
|
* not busy
|
|
*/
|
|
while (ieee802154_radio_request_set_trx_state(submac->dev, next_state) == -EBUSY);
|
|
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_rx_mode(dev, IEEE802154_RX_WAIT_FOR_ACK);
|
|
|
|
/* 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)) {
|
|
ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_RX_ON);
|
|
_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)) {
|
|
ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_RX_ON);
|
|
_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);
|
|
break;
|
|
case TX_STATUS_NO_ACK:
|
|
_handle_tx_no_ack(submac);
|
|
break;
|
|
case TX_STATUS_SUCCESS:
|
|
case TX_STATUS_FRAME_PENDING:
|
|
_handle_tx_success(submac, &info);
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int ieee802154_send(ieee802154_submac_t *submac, const iolist_t *iolist)
|
|
{
|
|
ieee802154_dev_t *dev = submac->dev;
|
|
|
|
uint8_t *buf = iolist->iol_base;
|
|
bool cnf = buf[0] & IEEE802154_FCF_ACK_REQ;
|
|
|
|
if (submac->state == IEEE802154_STATE_OFF) {
|
|
return -ENETDOWN;
|
|
}
|
|
|
|
if (submac->tx ||
|
|
ieee802154_radio_request_set_trx_state(dev,
|
|
IEEE802154_TRX_STATE_TX_ON) < 0) {
|
|
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;
|
|
|
|
ieee802154_csma_ca_transmit(submac);
|
|
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->tx = false;
|
|
submac->state = IEEE802154_STATE_LISTEN;
|
|
|
|
ieee802154_radio_request_on(dev);
|
|
|
|
/* 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 = CONFIG_IEEE802154_DEFAULT_CHANNEL,
|
|
.page = CONFIG_IEEE802154_DEFAULT_CHANNEL,
|
|
.pow = CONFIG_IEEE802154_DEFAULT_TXPOWER };
|
|
|
|
ieee802154_radio_config_phy(dev, &conf);
|
|
assert(ieee802154_radio_set_cca_threshold(dev,
|
|
CONFIG_IEEE802154_CCA_THRESH_DEFAULT) >= 0);
|
|
|
|
ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_RX_ON);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 };
|
|
|
|
if (submac->state == IEEE802154_STATE_OFF) {
|
|
return -ENETDOWN;
|
|
}
|
|
|
|
int res;
|
|
if ((res = ieee802154_radio_request_set_trx_state(dev, IEEE802154_TRX_STATE_TRX_OFF)) < 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_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) {}
|
|
|
|
return res;
|
|
}
|
|
|
|
int ieee802154_set_state(ieee802154_submac_t *submac, ieee802154_submac_state_t state)
|
|
{
|
|
int res;
|
|
|
|
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;
|
|
}
|
|
while (ieee802154_radio_confirm_on(dev) == -EAGAIN);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/** @} */
|