1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/net/gnrc/link_layer/gomach/gomach.c

2206 lines
82 KiB
C
Raw Normal View History

/*
* Copyright (C) 2017 INRIA
*
* 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.
*/
/**
* @ingroup net_gnrc_gomach
* @{
*
* @file
* @brief Implementation of GoMacH
*
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
* @}
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "random.h"
#include "timex.h"
#include "periph/rtt.h"
#include "net/gnrc/netif.h"
#include "net/gnrc/netif/internal.h"
#include "net/gnrc/netif/ieee802154.h"
#include "net/netdev/ieee802154.h"
#include "net/gnrc.h"
#include "net/gnrc/nettype.h"
#include "net/netdev.h"
#include "net/gnrc/mac/internal.h"
#include "net/gnrc/gomach/gomach.h"
#include "net/gnrc/gomach/timeout.h"
#include "include/gomach_internal.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
#ifndef LOG_LEVEL
/**
* @brief Default log level define
*/
#define LOG_LEVEL LOG_WARNING
#endif
#include "log.h"
/**
* @brief GoMacH thread's PID
*/
static kernel_pid_t gomach_pid;
static void _gomach_init(gnrc_netif_t *netif);
static int _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt);
static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif);
static void _gomach_msg_handler(gnrc_netif_t *netif, msg_t *msg);
static void _gomach_event_cb(netdev_t *dev, netdev_event_t event);
static const gnrc_netif_ops_t gomach_ops = {
.init = _gomach_init,
.send = _send,
.recv = _recv,
.get = gnrc_netif_get_from_netdev,
.set = gnrc_netif_set_from_netdev,
.msg_handler = _gomach_msg_handler,
};
int gnrc_netif_gomach_create(gnrc_netif_t *netif, char *stack, int stacksize,
char priority, char *name, netdev_t *dev)
{
return gnrc_netif_create(netif, stack, stacksize, priority, name, dev,
&gomach_ops);
}
static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif)
{
netdev_t *dev = netif->dev;
netdev_ieee802154_rx_info_t rx_info;
netdev_ieee802154_t *state = (netdev_ieee802154_t *)netif->dev;
gnrc_pktsnip_t *pkt = NULL;
int bytes_expected = dev->driver->recv(dev, NULL, 0, NULL);
if (bytes_expected > 0) {
int nread;
pkt = gnrc_pktbuf_add(NULL, NULL, bytes_expected, GNRC_NETTYPE_UNDEF);
if (pkt == NULL) {
DEBUG("_recv_ieee802154: cannot allocate pktsnip.\n");
return NULL;
}
nread = dev->driver->recv(dev, pkt->data, bytes_expected, &rx_info);
if (nread <= 0) {
gnrc_pktbuf_release(pkt);
return NULL;
}
if (!(state->flags & NETDEV_IEEE802154_RAW)) {
gnrc_pktsnip_t *ieee802154_hdr;
size_t mhr_len = ieee802154_get_frame_hdr_len(pkt->data);
if (mhr_len == 0) {
DEBUG("_recv_ieee802154: illegally formatted frame received\n");
gnrc_pktbuf_release(pkt);
return NULL;
}
nread -= mhr_len;
/* mark IEEE 802.15.4 header */
ieee802154_hdr = gnrc_pktbuf_mark(pkt, mhr_len, GNRC_NETTYPE_UNDEF);
if (ieee802154_hdr == NULL) {
DEBUG("_recv_ieee802154: no space left in packet buffer\n");
gnrc_pktbuf_release(pkt);
return NULL;
}
netif->mac.prot.gomach.rx_pkt_lqi = rx_info.lqi;
netif->mac.prot.gomach.rx_pkt_rssi = rx_info.rssi;
}
DEBUG("_recv_ieee802154: reallocating.\n");
gnrc_pktbuf_realloc_data(pkt, nread);
}
return pkt;
}
static void gomach_reinit_radio(gnrc_netif_t *netif)
{
/* Initialize low-level driver. */
netif->dev->driver->init(netif->dev);
/* Set MAC address length. */
uint16_t src_len = netif->l2addr_len;
netif->dev->driver->set(netif->dev, NETOPT_SRC_LEN, &src_len, sizeof(src_len));
/* Set the MAC address of the device. */
if (netif->l2addr_len == IEEE802154_LONG_ADDRESS_LEN) {
netif->dev->driver->set(netif->dev,
NETOPT_ADDRESS_LONG,
netif->l2addr,
sizeof(netif->l2addr));
}
else {
netif->dev->driver->set(netif->dev,
NETOPT_ADDR_LEN,
netif->l2addr,
sizeof(netif->l2addr));
}
/* Enable RX-start and TX-started and TX-END interrupts. */
netopt_enable_t enable = NETOPT_ENABLE;
netif->dev->driver->set(netif->dev, NETOPT_RX_START_IRQ, &enable, sizeof(enable));
netif->dev->driver->set(netif->dev, NETOPT_RX_END_IRQ, &enable, sizeof(enable));
netif->dev->driver->set(netif->dev, NETOPT_TX_END_IRQ, &enable, sizeof(enable));
}
static void _gomach_rtt_cb(void *arg)
{
msg_t msg;
(void)arg;
msg.content.value = GNRC_GOMACH_EVENT_RTT_NEW_CYCLE;
msg.type = GNRC_GOMACH_EVENT_RTT_TYPE;
msg_send(&msg, gomach_pid);
if (sched_context_switch_request) {
thread_yield();
}
}
static void _gomach_rtt_handler(uint32_t event, gnrc_netif_t *netif)
{
switch (event & 0xffff) {
case GNRC_GOMACH_EVENT_RTT_NEW_CYCLE: {
/* Start duty-cycle scheme. */
if (!gnrc_gomach_get_duty_cycle_start(netif)) {
gnrc_gomach_set_duty_cycle_start(netif, true);
rtt_clear_alarm();
/* Record the new cycle's starting time. */
netif->mac.prot.gomach.last_wakeup = rtt_get_counter();
}
else {
/* The duty-cycle scheme has already started,
* record the new cycle's starting time. */
netif->mac.prot.gomach.last_wakeup = rtt_get_alarm();
gnrc_gomach_set_enter_new_cycle(netif, true);
}
netif->mac.prot.gomach.last_wakeup_phase_us = xtimer_now_usec64();
/* Set next cycle's starting time. */
uint32_t alarm = netif->mac.prot.gomach.last_wakeup +
RTT_US_TO_TICKS(CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US);
rtt_set_alarm(alarm, _gomach_rtt_cb, NULL);
/* Update neighbors' public channel phases. */
gnrc_gomach_update_neighbor_pubchan(netif);
gnrc_gomach_set_update(netif, true);
} break;
default: {
LOG_ERROR("ERROR: [GOMACH] error RTT message type\n");
break;
}
}
}
static void gomach_bcast_init(gnrc_netif_t *netif)
{
/* Disable auto-ACK when sending broadcast packets, thus not to receive packet. */
gnrc_gomach_set_autoack(netif, NETOPT_DISABLE);
/* Firstly turn the radio to public channel 1. */
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.pub_channel_1);
gnrc_gomach_set_on_pubchan_1(netif, true);
netif->mac.tx.broadcast_seq++;
/* Assemble the broadcast packet. */
gnrc_pktsnip_t *pkt = netif->mac.tx.packet;
gnrc_pktsnip_t *payload = netif->mac.tx.packet->next;
gnrc_gomach_frame_broadcast_t gomach_broadcast_hdr;
gomach_broadcast_hdr.header.type = GNRC_GOMACH_FRAME_BROADCAST;
gomach_broadcast_hdr.seq_nr = netif->mac.tx.broadcast_seq;
pkt->next = gnrc_pktbuf_add(pkt->next, &gomach_broadcast_hdr,
sizeof(gomach_broadcast_hdr),
GNRC_NETTYPE_GOMACH);
if (pkt->next == NULL) {
/* Make append payload after netif header again */
netif->mac.tx.packet->next = payload;
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
LOG_ERROR("ERROR: [GOMACH] bcast: no memory to assemble bcast packet, drop packet.\n");
LOG_ERROR("ERROR: [GOMACH] bcast failed, go to listen mode.\n");
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_END;
gnrc_gomach_set_update(netif, true);
return;
}
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_BCAST_FINISH,
CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US);
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_SEND;
gnrc_gomach_set_update(netif, true);
}
static bool _gomach_send_bcast_busy_handle(gnrc_netif_t *netif)
{
/* Quit sending broadcast packet if we found ongoing transmissions,
* for collision avoidance. */
if ((gnrc_gomach_get_netdev_state(netif) == NETOPT_STATE_RX) ||
(gnrc_netif_get_rx_started(netif) == true)) {
LOG_DEBUG("[GOMACH] bcast: found ongoing transmission, quit broadcast.\n");
/* Queue the broadcast packet back to the queue. */
gnrc_pktsnip_t *payload = netif->mac.tx.packet->next->next;
/* remove gomach header */
netif->mac.tx.packet->next->next = NULL;
gnrc_pktbuf_release(netif->mac.tx.packet->next);
/* make append payload after netif header again */
netif->mac.tx.packet->next = payload;
if (!gnrc_mac_queue_tx_packet(&netif->mac.tx, 0, netif->mac.tx.packet)) {
LOG_DEBUG("[GOMACH] bcast: TX queue full, release packet.\n");
gnrc_pktbuf_release(netif->mac.tx.packet);
}
netif->mac.tx.packet = NULL;
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_END;
gnrc_gomach_set_update(netif, true);
return false;
}
return true;
}
static void gomach_send_bcast_packet(gnrc_netif_t *netif)
{
/* Quit sending broadcast packet if we found ongoing transmissions,
* for collision avoidance. */
if (!_gomach_send_bcast_busy_handle(netif)) {
return;
}
gnrc_pktbuf_hold(netif->mac.tx.packet, 1);
/* Start sending the broadcast packet. */
gnrc_gomach_send(netif, netif->mac.tx.packet, NETOPT_DISABLE);
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_WAIT_TX_FINISH;
gnrc_gomach_set_update(netif, false);
}
static void gomach_wait_bcast_tx_finish(gnrc_netif_t *netif)
{
if (gnrc_gomach_get_tx_finish(netif)) {
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_BCAST_INTERVAL,
CONFIG_GNRC_GOMACH_BCAST_INTERVAL_US);
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_WAIT_NEXT_TX;
gnrc_gomach_set_update(netif, false);
}
/* This is to handle no-TX-complete issue. In case there is no no-TX-complete event,
2019-10-23 21:16:23 +02:00
* we will quit broadcasting, i.e., not getting stuck here. */
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_BCAST_FINISH)) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_BCAST_INTERVAL);
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_END;
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_wait_bcast_wait_next_tx(gnrc_netif_t *netif)
{
/* Quit sending broadcast packet if we found ongoing transmissions,
* for collision avoidance. */
if (!_gomach_send_bcast_busy_handle(netif)) {
return;
}
/* If the whole broadcast duration timeouts, release the packet and go to t2u end. */
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_BCAST_FINISH)) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_BCAST_INTERVAL);
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_END;
gnrc_gomach_set_update(netif, true);
return;
}
/* Toggle the radio channel and go to send the next broadcast packet. */
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_BCAST_INTERVAL)) {
if (gnrc_gomach_get_on_pubchan_1(netif)) {
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.pub_channel_2);
gnrc_gomach_set_on_pubchan_1(netif, false);
}
else {
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.pub_channel_1);
gnrc_gomach_set_on_pubchan_1(netif, true);
}
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_SEND;
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_bcast_end(gnrc_netif_t *netif)
{
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_SLEEP);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_BCAST_INTERVAL);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_BCAST_FINISH);
if (netif->mac.tx.packet) {
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
}
netif->mac.tx.current_neighbor = NULL;
/* Reset the t2u state. */
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_INIT;
/* Switch to the listen mode. */
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_LISTEN;
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP;
gnrc_gomach_set_enter_new_cycle(netif, false);
gnrc_gomach_set_update(netif, true);
}
static void gomach_bcast_update(gnrc_netif_t *netif)
{
/* State machine of GoMacH's broadcast procedure. */
switch (netif->mac.tx.bcast_state) {
case GNRC_GOMACH_BCAST_INIT: {
gomach_bcast_init(netif);
break;
}
case GNRC_GOMACH_BCAST_SEND: {
gomach_send_bcast_packet(netif);
break;
}
case GNRC_GOMACH_BCAST_WAIT_TX_FINISH: {
gomach_wait_bcast_tx_finish(netif);
break;
}
case GNRC_GOMACH_BCAST_WAIT_NEXT_TX: {
gomach_wait_bcast_wait_next_tx(netif);
break;
}
case GNRC_GOMACH_BCAST_END: {
gomach_bcast_end(netif);
break;
}
default: break;
}
}
static void gomach_init_prepare(gnrc_netif_t *netif)
{
rtt_clear_alarm();
/* Random delay for avoiding the same wake-up phase among devices. */
uint32_t random_backoff = random_uint32_range(0, CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US);
xtimer_usleep(random_backoff);
gnrc_gomach_set_quit_cycle(netif, false);
netif->mac.prot.gomach.subchannel_occu_flags = 0;
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
/* Since devices don't broadcast beacons on default, so no need to collect beacons.
* Go to announce its chosen sub-channel sequence. */
netif->mac.prot.gomach.init_state = GNRC_GOMACH_INIT_ANNC_SUBCHAN;
gnrc_gomach_set_update(netif, true);
}
static void gomach_init_announce_subchannel(gnrc_netif_t *netif)
{
/* Choose a sub-channel for the device. */
gnrc_gomach_init_choose_subchannel(netif);
/* Announce the device's chosen sub-channel sequence to its neighbors. */
gnrc_gomach_bcast_subchann_seq(netif, NETOPT_ENABLE);
netif->mac.prot.gomach.init_state = GNRC_GOMACH_INIT_WAIT_FEEDBACK;
gnrc_gomach_set_update(netif, false);
}
static void gomach_init_wait_announce_feedback(gnrc_netif_t *netif)
{
if (gnrc_gomach_get_tx_finish(netif)) {
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
netif->mac.prot.gomach.init_state = GNRC_GOMACH_INIT_END;
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_init_end(gnrc_netif_t *netif)
{
/* Reset initialization state. */
netif->mac.prot.gomach.init_state = GNRC_GOMACH_INIT_PREPARE;
/* Switch to duty-cycle listen mode. */
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_LISTEN;
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_CP_INIT;
/* Start duty-cycle scheme. */
gnrc_gomach_set_duty_cycle_start(netif, false);
_gomach_rtt_handler(GNRC_GOMACH_EVENT_RTT_NEW_CYCLE, netif);
gnrc_gomach_set_update(netif, true);
}
static void gomach_t2k_init(gnrc_netif_t *netif)
{
/* Turn off radio to conserve power */
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_SLEEP);
gnrc_gomach_set_quit_cycle(netif, false);
/* Set waiting timer for the targeted device! */
long int wait_phase_duration = netif->mac.tx.current_neighbor->cp_phase -
gnrc_gomach_phase_now(netif);
if (wait_phase_duration < 0) {
wait_phase_duration += CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US;
}
/* Upon several times of t2k failure, we now doubt that the phase-lock may fail due to drift.
* Here is the phase-lock auto-adjust scheme, trying to catch the neighbot's phase in case of
* phase-lock failure due to timer drift.
* Firstly, put the calculated phase ahead, check whether the neighbor's phase has gone ahead
* of the recorded one */
if (netif->mac.tx.no_ack_counter == (GNRC_GOMACH_REPHASELOCK_THRESHOLD - 2)) {
if ((uint32_t)wait_phase_duration < CONFIG_GNRC_GOMACH_CP_DURATION_US) {
wait_phase_duration = (wait_phase_duration +
CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US) -
CONFIG_GNRC_GOMACH_CP_DURATION_US;
}
else {
wait_phase_duration = wait_phase_duration - CONFIG_GNRC_GOMACH_CP_DURATION_US;
}
}
/* If this is the last t2k trial, the phase-lock auto-adjust scheme delays the estimated phase
* a little bit, to see if the real phase is behind the original calculated one. */
if (netif->mac.tx.no_ack_counter == (GNRC_GOMACH_REPHASELOCK_THRESHOLD - 1)) {
wait_phase_duration = wait_phase_duration + CONFIG_GNRC_GOMACH_CP_DURATION_US;
if ((uint32_t)wait_phase_duration > CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US) {
wait_phase_duration = wait_phase_duration - CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US;
}
}
if ((uint32_t)wait_phase_duration > CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US) {
wait_phase_duration = wait_phase_duration % CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US;
}
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_CP, (uint32_t)wait_phase_duration);
/* Flush the rx-queue. */
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
netif->mac.tx.tx_busy_count = 0;
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_WAIT_CP;
gnrc_gomach_set_update(netif, false);
}
static void gomach_t2k_wait_cp(gnrc_netif_t *netif)
{
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_WAIT_CP)) {
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_IDLE);
/* Turn radio onto the neighbor's public channel, which will not change in this cycle. */
gnrc_gomach_turn_channel(netif, netif->mac.tx.current_neighbor->pub_chanseq);
/* Disable auto-ack, don't try to receive packet! */
gnrc_gomach_set_autoack(netif, NETOPT_DISABLE);
/* Require ACK for the packet waiting to be sent! */
gnrc_gomach_set_ack_req(netif, NETOPT_ENABLE);
/* Enable csma for sending the packet! */
netopt_enable_t csma_enable = NETOPT_ENABLE;
netif->dev->driver->set(netif->dev, NETOPT_CSMA, &csma_enable,
sizeof(netopt_enable_t));
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_TRANS_IN_CP;
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_t2k_trans_in_cp(gnrc_netif_t *netif)
{
/* To-do: should we add a rx-start security check and quit t2k when found
* ongoing transmissions? */
/* If we are retransmitting the packet, use the same sequence number for the
* packet to avoid duplicate packet reception at the receiver side. */
if ((netif->mac.tx.no_ack_counter > 0) || (netif->mac.tx.tx_busy_count > 0)) {
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
device_state->seq = netif->mac.tx.tx_seq;
}
/* Send the data packet here. */
int res = gnrc_gomach_send_data(netif, NETOPT_ENABLE);
if (res < 0) {
LOG_ERROR("ERROR: [GOMACH] t2k transmission fail: %d, drop packet.\n", res);
netif->mac.tx.no_ack_counter = 0;
/* If res is < 0, the data packet will not be released in send().
* so need to release the data here. */
if (netif->mac.tx.packet != NULL) {
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
}
netif->mac.tx.current_neighbor = NULL;
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
gnrc_gomach_set_update(netif, true);
return;
}
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR, CONFIG_GNRC_GOMACH_NO_TX_ISR_US);
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_WAIT_CPTX_FEEDBACK;
gnrc_gomach_set_update(netif, false);
}
static void _cp_tx_success(gnrc_netif_t *netif)
{
/* Since the packet will not be released by the sending function,
* so, here, if TX success, we first release the packet. */
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
/* Here is the phase-lock auto-adjust scheme. Use the new adjusted
* phase upon success. Here the new phase will be put ahead to the
* original phase. */
if (netif->mac.tx.no_ack_counter == (GNRC_GOMACH_REPHASELOCK_THRESHOLD - 2)) {
if (netif->mac.tx.current_neighbor->cp_phase >= CONFIG_GNRC_GOMACH_CP_DURATION_US) {
netif->mac.tx.current_neighbor->cp_phase -= CONFIG_GNRC_GOMACH_CP_DURATION_US;
}
else {
netif->mac.tx.current_neighbor->cp_phase += CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US;
netif->mac.tx.current_neighbor->cp_phase -= CONFIG_GNRC_GOMACH_CP_DURATION_US;
}
}
/* Here is the phase-lock auto-adjust scheme. Use the new adjusted
* phase upon success. Here the new phase will be put behind the original
* phase. */
if (netif->mac.tx.no_ack_counter == (GNRC_GOMACH_REPHASELOCK_THRESHOLD - 1)) {
netif->mac.tx.current_neighbor->cp_phase +=
(CONFIG_GNRC_GOMACH_CP_DURATION_US + 20 * US_PER_MS);
if (netif->mac.tx.current_neighbor->cp_phase >=
CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US) {
netif->mac.tx.current_neighbor->cp_phase -=
CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US;
}
}
netif->mac.tx.no_ack_counter = 0;
netif->mac.tx.t2u_fail_count = 0;
/* If has pending packets, join the vTDMA period, first wait for receiver's beacon. */
if (gnrc_priority_pktqueue_length(&netif->mac.tx.current_neighbor->queue) > 0) {
netif->mac.tx.vtdma_para.slots_num = 0;
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_BEACON,
GNRC_GOMACH_WAIT_BEACON_TIME_US);
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_WAIT_BEACON;
}
else {
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
}
gnrc_gomach_set_update(netif, true);
}
static bool _cp_tx_busy(gnrc_netif_t *netif)
{
/* If the channel busy counter is below threshold, retry CSMA immediately,
* by knowing that the CP will be automatically extended. */
if (netif->mac.tx.tx_busy_count < GNRC_GOMACH_TX_BUSY_THRESHOLD) {
netif->mac.tx.tx_busy_count++;
/* Store the TX sequence number for this packet. Always use the same
* sequence number for sending the same packet, to avoid duplicated
* packet reception at the receiver. */
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
netif->mac.tx.tx_seq = device_state->seq - 1;
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_TRANS_IN_CP;
gnrc_gomach_set_update(netif, true);
return false;
}
return true;
}
static void _cp_tx_default(gnrc_netif_t *netif)
{
netif->mac.tx.no_ack_counter++;
LOG_DEBUG("[GOMACH] t2k %d times No-ACK.\n", netif->mac.tx.no_ack_counter);
/* This packet will be retried. Store the TX sequence number for this packet.
* Always use the same sequence number for sending the same packet. */
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
netif->mac.tx.tx_seq = device_state->seq - 1;
/* If no_ack_counter reaches the threshold, regarded as phase-lock failed. So
* retry to send the packet in t2u, i.e., try to phase-lock with the receiver
* again. */
if (netif->mac.tx.no_ack_counter >= GNRC_GOMACH_REPHASELOCK_THRESHOLD) {
LOG_DEBUG("[GOMACH] t2k failed, go to t2u.\n");
/* Here, we don't queue the packet again, but keep it in tx.packet. */
netif->mac.tx.current_neighbor->mac_type = GNRC_GOMACH_TYPE_UNKNOWN;
netif->mac.tx.t2u_retry_counter = 0;
}
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
gnrc_gomach_set_update(netif, true);
}
static void gomach_t2k_wait_cp_txfeedback(gnrc_netif_t *netif)
{
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR)) {
/* No TX-ISR, go to sleep. */
netif->mac.tx.no_ack_counter++;
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
netif->mac.tx.tx_seq = device_state->seq - 1;
/* Here, we don't queue the packet again, but keep it in tx.packet. */
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
gnrc_gomach_set_update(netif, true);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
return;
}
if (gnrc_gomach_get_tx_finish(netif)) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
switch (gnrc_netif_get_tx_feedback(netif)) {
case TX_FEEDBACK_SUCCESS: {
_cp_tx_success(netif);
break;
}
case TX_FEEDBACK_BUSY:
if (!_cp_tx_busy(netif)) {
return;
}
/* Intentionally falls through */
case TX_FEEDBACK_NOACK:
default: {
_cp_tx_default(netif);
break;
}
}
}
}
static void gomach_t2k_wait_beacon(gnrc_netif_t *netif)
{
/* Process the beacon if we receive it. */
if (gnrc_gomach_get_pkt_received(netif)) {
gnrc_gomach_set_pkt_received(netif, false);
gnrc_gomach_packet_process_in_wait_beacon(netif);
}
/* If we need to quit t2k, don't release the current neighbor pointer. In the
* next cycle, we will try to send to the same receiver. */
if (gnrc_gomach_get_quit_cycle(netif)) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_BEACON);
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
gnrc_gomach_set_update(netif, true);
return;
}
if (netif->mac.tx.vtdma_para.slots_num > 0) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_BEACON);
/* If the sender gets allocated slots, go to attend the receiver's vTDMA for
* burst sending all the pending packets to the receiver. */
if (netif->mac.tx.vtdma_para.slots_num > 0) {
/* Switch the radio to the sub-channel of the receiver. */
gnrc_gomach_turn_channel(netif, netif->mac.tx.vtdma_para.sub_channel_seq);
/* If the allocated slots period is not right behind the beacon, i.e., not the first
* one, turn off the radio and wait for its own slots period. */
if (netif->mac.tx.vtdma_para.slots_position > 0) {
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_SLEEP);
uint32_t wait_slots_duration = netif->mac.tx.vtdma_para.slots_position *
CONFIG_GNRC_GOMACH_VTDMA_SLOT_SIZE_US;
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_SLOTS,
wait_slots_duration);
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_WAIT_SLOTS;
gnrc_gomach_set_update(netif, true);
}
else {
/* If the allocated slots period is the first one in vTDMA,
* start sending packets. */
gnrc_pktsnip_t *pkt =
gnrc_priority_pktqueue_pop(&(netif->mac.tx.current_neighbor->queue));
if (pkt != NULL) {
netif->mac.tx.packet = pkt;
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_VTDMA_TRANS;
}
else {
LOG_ERROR("ERROR: [GOMACH] t2k vTDMA: null packet.\n");
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
}
gnrc_gomach_set_update(netif, true);
}
}
else {
/* No slots get allocated, go to t2k end. */
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
gnrc_gomach_set_update(netif, true);
}
return;
}
/* If no beacon during waiting period, go to t2k end.
* Or, if we have received beacon, but find no allocated slots,
* go to t2k as well. */
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_WAIT_BEACON) ||
!gnrc_gomach_timeout_is_running(netif, GNRC_GOMACH_TIMEOUT_WAIT_BEACON)) {
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
LOG_DEBUG("[GOMACH] t2k: no beacon.\n");
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_t2k_wait_own_slots(gnrc_netif_t *netif)
{
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_WAIT_SLOTS)) {
/* The node is now in its scheduled slots period, start burst sending packets. */
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_IDLE);
gnrc_pktsnip_t *pkt = gnrc_priority_pktqueue_pop(&(netif->mac.tx.current_neighbor->queue));
if (pkt != NULL) {
netif->mac.tx.packet = pkt;
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_VTDMA_TRANS;
}
else {
LOG_ERROR("ERROR: [GOMACH] t2k vTDMA: null packet.\n");
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
}
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_t2k_trans_in_slots(gnrc_netif_t *netif)
{
/* If this packet is being retransmitted, use the same recorded MAC sequence number. */
if (netif->mac.tx.no_ack_counter > 0) {
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
device_state->seq = netif->mac.tx.tx_seq;
}
/* Send data packet in its allocated slots (scheduled slots period). */
int res = gnrc_gomach_send_data(netif, NETOPT_DISABLE);
if (res < 0) {
LOG_ERROR("ERROR: [GOMACH] t2k vTDMA transmission fail: %d, drop packet.\n", res);
/* If res is < 0, the data packet will not be released in send().
* so need to release the data here. */
if (netif->mac.tx.packet != NULL) {
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
}
netif->mac.tx.current_neighbor = NULL;
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
gnrc_gomach_set_update(netif, true);
return;
}
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR, CONFIG_GNRC_GOMACH_NO_TX_ISR_US);
netif->mac.tx.vtdma_para.slots_num--;
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_WAIT_VTDMA_FEEDBACK;
gnrc_gomach_set_update(netif, false);
}
static void _t2k_wait_vtdma_tx_success(gnrc_netif_t *netif)
{
/* First release the packet. */
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
netif->mac.tx.no_ack_counter = 0;
/* If the sender has pending packets and scheduled slots,
* continue vTDMA transmission. */
if ((netif->mac.tx.vtdma_para.slots_num > 0) &&
(gnrc_priority_pktqueue_length(&netif->mac.tx.current_neighbor->queue) > 0)) {
gnrc_pktsnip_t *pkt = gnrc_priority_pktqueue_pop(&netif->mac.tx.current_neighbor->queue);
if (pkt != NULL) {
netif->mac.tx.packet = pkt;
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_VTDMA_TRANS;
}
else {
LOG_ERROR("ERROR: [GOMACH] t2k vTDMA: null packet.\n");
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
}
}
else {
/* If no scheduled slots or pending packets, go to t2k end. */
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
}
gnrc_gomach_set_update(netif, true);
}
static void _t2k_wait_vtdma_tx_default(gnrc_netif_t *netif)
{
/* In case of transmission failure in vTDMA, retransmit the packet in the next
* scheduled slot, or the next cycle's t2k procedure. */
/* Firstly, mark the current TX packet as not ACKed and record the MAC sequence
* number, such that the MAC will use the same MAC sequence to send it.
* Also, by marking no_ack_counter as non-zero, the neighbor and packet pointers
* will then not be released in t2k-end. Then, the packet can be retried right in
* the following cycle. */
netif->mac.tx.no_ack_counter = 1;
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
netif->mac.tx.tx_seq = device_state->seq - 1;
/* Do not release the packet here, continue sending the same packet. ***/
if (netif->mac.tx.vtdma_para.slots_num > 0) {
LOG_DEBUG("[GOMACH] no ACK in vTDMA, retry in next slot.\n");
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_VTDMA_TRANS;
}
else {
/* If no slots for sending, retry in next cycle's t2r, without releasing
* tx.packet pointer. */
LOG_DEBUG("[GOMACH] no ACK in vTDMA, retry in next cycle.\n");
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
}
gnrc_gomach_set_update(netif, true);
}
static void gomach_t2k_wait_vtdma_transfeedback(gnrc_netif_t *netif)
{
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR)) {
/* No TX-ISR, go to sleep. */
netif->mac.tx.no_ack_counter++;
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
netif->mac.tx.tx_seq = device_state->seq - 1;
/* Here, we don't queue the packet again, but keep it in tx.packet. */
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_END;
gnrc_gomach_set_update(netif, true);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
return;
}
if (gnrc_gomach_get_tx_finish(netif)) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
switch (gnrc_netif_get_tx_feedback(netif)) {
case TX_FEEDBACK_SUCCESS: {
_t2k_wait_vtdma_tx_success(netif);
break;
}
case TX_FEEDBACK_BUSY:
case TX_FEEDBACK_NOACK:
default: {
_t2k_wait_vtdma_tx_default(netif);
break;
}
}
}
}
static void gomach_t2k_end(gnrc_netif_t *netif)
{
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_SLEEP);
/* In GoMacH, normally, in case of transmission failure, no packet will be released
* in t2k. Failed packet will only be released in t2u. In case of continuous t2k
* failures, the MAC will goto t2u to retry the packet without releasing it here. */
if ((netif->mac.tx.packet != NULL) && (netif->mac.tx.no_ack_counter == 0)) {
LOG_ERROR("ERROR: [GOMACH] t2k: releasing unexpected packet!\n");
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
}
/* In case no_ack_counter is not 0 here, it means we will retry to send the packet
* in t2k or t2u, then, don't release the neighbor pointer. */
if (netif->mac.tx.no_ack_counter == 0) {
netif->mac.tx.current_neighbor = NULL;
}
/* Clear all timeouts. */
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_CP);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_BEACON);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_SLOTS);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
/* Reset t2k_state to the initial state. */
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_INIT;
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_LISTEN;
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP;
gnrc_gomach_set_enter_new_cycle(netif, false);
gnrc_gomach_set_update(netif, true);
}
static void gomach_t2k_update(gnrc_netif_t *netif)
{
/* State machine of GoMacH's t2k (transmit to phase-known device) procedure. */
switch (netif->mac.tx.t2k_state) {
case GNRC_GOMACH_T2K_INIT: {
gomach_t2k_init(netif);
break;
}
case GNRC_GOMACH_T2K_WAIT_CP: {
gomach_t2k_wait_cp(netif);
break;
}
case GNRC_GOMACH_T2K_TRANS_IN_CP: {
gomach_t2k_trans_in_cp(netif);
break;
}
case GNRC_GOMACH_T2K_WAIT_CPTX_FEEDBACK: {
gomach_t2k_wait_cp_txfeedback(netif);
break;
}
case GNRC_GOMACH_T2K_WAIT_BEACON: {
gomach_t2k_wait_beacon(netif);
break;
}
case GNRC_GOMACH_T2K_WAIT_SLOTS: {
gomach_t2k_wait_own_slots(netif);
break;
}
case GNRC_GOMACH_T2K_VTDMA_TRANS: {
gomach_t2k_trans_in_slots(netif);
break;
}
case GNRC_GOMACH_T2K_WAIT_VTDMA_FEEDBACK: {
gomach_t2k_wait_vtdma_transfeedback(netif);
break;
}
case GNRC_GOMACH_T2K_END: {
gomach_t2k_end(netif);
break;
}
default: break;
}
}
static void gomach_t2u_init(gnrc_netif_t *netif)
{
/* since t2u is right following CP period (wake-up period), the radio is still on,
* so we don't need to turn on it again. */
LOG_DEBUG("[GOMACH] t2u initialization.\n");
gnrc_netif_set_rx_started(netif, false);
gnrc_gomach_set_quit_cycle(netif, false);
gnrc_gomach_set_pkt_received(netif, false);
netif->mac.tx.preamble_sent = 0;
gnrc_gomach_set_got_preamble_ack(netif, false);
gnrc_gomach_set_buffer_full(netif, false);
/* Start sending the preamble firstly on public channel 1. */
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.pub_channel_1);
/* Disable auto-ACK here! Don't try to reply ACK to any node. */
gnrc_gomach_set_autoack(netif, NETOPT_DISABLE);
gnrc_gomach_set_on_pubchan_1(netif, true);
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_PREAMBLE_PREPARE;
gnrc_gomach_set_update(netif, true);
}
static void gomach_t2u_send_preamble_prepare(gnrc_netif_t *netif)
{
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL);
if (netif->mac.tx.preamble_sent != 0) {
/* Toggle the radio channel after each preamble transmission. */
if (gnrc_gomach_get_on_pubchan_1(netif)) {
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.pub_channel_2);
gnrc_gomach_set_on_pubchan_1(netif, false);
}
else {
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.pub_channel_1);
gnrc_gomach_set_on_pubchan_1(netif, true);
}
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL,
CONFIG_GNRC_GOMACH_MAX_PREAM_INTERVAL_US);
}
else {
/* Here, for the first preamble, we set the pream_max_interval timeout to
* 5*MAX_PREAM_INTERVAL due to the fact that the first preamble is
* using csma for sending, and csma costs some time before actually sending
* the packet. */
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL,
(5 * CONFIG_GNRC_GOMACH_MAX_PREAM_INTERVAL_US));
}
gnrc_gomach_set_max_pream_interv(netif, false);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_SEND_PREAMBLE;
gnrc_gomach_set_update(netif, true);
}
static void gomach_t2u_send_preamble(gnrc_netif_t *netif)
{
/* Now, start sending preamble. */
int res;
/* The first preamble is sent with csma for collision avoidance. */
if (netif->mac.tx.preamble_sent == 0) {
res = gnrc_gomach_send_preamble(netif, NETOPT_ENABLE);
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAM_DURATION,
GNRC_GOMACH_PREAMBLE_DURATION_US);
}
else {
res = gnrc_gomach_send_preamble(netif, NETOPT_DISABLE);
}
if (res < 0) {
LOG_ERROR("ERROR: [GOMACH] t2u send preamble failed: %d\n", res);
}
/* In case that packet-buffer is full, quit t2u and release packet. */
if (res == -ENOBUFS) {
LOG_ERROR("ERROR: [GOMACH] t2u: no pkt-buffer for sending preamble.\n");
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAM_DURATION);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
gnrc_gomach_set_update(netif, true);
return;
}
gnrc_netif_set_rx_started(netif, false);
netif->mac.tx.preamble_sent++;
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_WAIT_PREAMBLE_TX;
gnrc_gomach_set_update(netif, false);
}
static void gomach_t2u_wait_preamble_tx(gnrc_netif_t *netif)
{
if (gnrc_gomach_get_tx_finish(netif)) {
/* Set preamble interval timeout. This is a very short timeout (1ms),
* just to catch the rx-start event of receiving possible preamble-ACK. */
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAMBLE,
CONFIG_GNRC_GOMACH_PREAMBLE_INTERVAL_US);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_WAIT_PREAMBLE_ACK;
gnrc_gomach_set_update(netif, false);
return;
}
/* This is mainly to handle no-TX-complete error. Once the max preamble interval
* timeout expired here (i.e., no-TX-complete error), we will quit waiting here
2019-10-23 21:16:23 +02:00
* and go to send the next preamble, thus the MAC will not get stuck here. */
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL)) {
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_PREAMBLE_PREPARE;
gnrc_gomach_set_update(netif, true);
return;
}
}
static bool _handle_in_t2u_send_preamble(gnrc_netif_t *netif)
{
/* If packet buffer is full, release one packet to release memory,
* and reload the next packet.
* In t2u, we need at least some minimum memory to build the preamble packet. */
if (gnrc_gomach_get_buffer_full(netif)) {
gnrc_gomach_set_buffer_full(netif, false);
gnrc_gomach_set_update(netif, true);
/* To-do: should we release all the buffered packets in the queue to
* release memory in such a critical situation? */
LOG_DEBUG("[GOMACH] t2u: pkt-buffer full, release one pkt.\n");
if (netif->mac.tx.packet != NULL) {
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
netif->mac.tx.no_ack_counter = 0;
}
/* Reload the next packet in the neighbor's queue. */
gnrc_pktsnip_t *pkt = gnrc_priority_pktqueue_pop(&netif->mac.tx.current_neighbor->queue);
if (pkt != NULL) {
netif->mac.tx.packet = pkt;
}
else {
LOG_DEBUG("[GOMACH] t2u: null packet, quit t2u.\n");
netif->mac.tx.current_neighbor = NULL;
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
return false;
}
}
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL)) {
gnrc_gomach_set_max_pream_interv(netif, true);
return true;
}
/* if we are receiving packet, wait until RX is completed. */
if ((!gnrc_gomach_timeout_is_running(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END)) &&
gnrc_netif_get_rx_started(netif) &&
(!gnrc_gomach_get_max_pream_interv(netif))) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAMBLE);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL);
/* Set a timeout to wait for the complete of reception. */
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END,
CONFIG_GNRC_GOMACH_WAIT_RX_END_US);
return false;
}
if (gnrc_gomach_get_pkt_received(netif)) {
gnrc_gomach_set_pkt_received(netif, false);
gnrc_gomach_process_pkt_in_wait_preamble_ack(netif);
}
/* Quit t2u if we have to, e.g., the device found ongoing bcast of other devices. */
if (gnrc_gomach_get_quit_cycle(netif)) {
LOG_DEBUG("[GOMACH] quit t2u.\n");
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAMBLE);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAM_DURATION);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
gnrc_gomach_set_update(netif, true);
return false;
}
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END)) {
gnrc_gomach_set_max_pream_interv(netif, true);
}
return true;
}
static void gomach_t2u_wait_preamble_ack(gnrc_netif_t *netif)
{
if (!_handle_in_t2u_send_preamble(netif)) {
return;
}
if (gnrc_gomach_get_got_preamble_ack(netif)) {
/* Require ACK for the packet waiting to be sent! */
gnrc_gomach_set_ack_req(netif, NETOPT_ENABLE);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAMBLE);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAM_DURATION);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_SEND_DATA;
netif->mac.tx.t2u_fail_count = 0;
gnrc_gomach_set_update(netif, true);
return;
}
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_PREAM_DURATION)) {
netif->mac.tx.t2u_retry_counter++;
/* If we reach the maximum t2u retry limit, release the data packet. */
if (netif->mac.tx.t2u_retry_counter >= GNRC_GOMACH_T2U_RETYR_THRESHOLD) {
LOG_DEBUG("[GOMACH] t2u failed: no preamble-ACK.\n");
netif->mac.tx.t2u_retry_counter = 0;
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAMBLE);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL);
netif->mac.tx.t2u_fail_count++;
}
else {
/* If we haven't reach the maximum t2u limit, try again. Set quit_current_cycle
* flag to true such that we will release the current neighbor pointer. */
gnrc_gomach_set_quit_cycle(netif, true);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
}
gnrc_gomach_set_update(netif, true);
return;
}
/* If we didn't catch the RX-start event, go to send the next preamble. */
if ((gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_PREAMBLE)) ||
gnrc_gomach_get_max_pream_interv(netif)) {
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_PREAMBLE_PREPARE;
gnrc_gomach_set_update(netif, true);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAMBLE);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
}
}
static void gomach_t2u_send_data(gnrc_netif_t *netif)
{
/* If we are retrying to send the data, reload its original MAC sequence. */
if (netif->mac.tx.no_ack_counter > 0) {
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
device_state->seq = netif->mac.tx.tx_seq;
}
/* Here, we send the data to the receiver. */
int res = gnrc_gomach_send_data(netif, NETOPT_ENABLE);
if (res < 0) {
LOG_ERROR("ERROR: [GOMACH] t2u data sending error: %d.\n", res);
/* If res is < 0, the data packet will not be released in send().
* so need to release the data here. */
if (netif->mac.tx.packet != NULL) {
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
}
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
gnrc_gomach_set_update(netif, true);
return;
}
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR, CONFIG_GNRC_GOMACH_NO_TX_ISR_US);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_WAIT_DATA_TX;
gnrc_gomach_set_update(netif, false);
}
static void _t2u_data_tx_success(gnrc_netif_t *netif)
{
/* If transmission succeeded, release the data. */
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
netif->mac.tx.no_ack_counter = 0;
netif->mac.tx.t2u_retry_counter = 0;
/* Attend the vTDMA procedure if the sender has pending packets for the receiver. */
if (gnrc_priority_pktqueue_length(&netif->mac.tx.current_neighbor->queue) > 0) {
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_INIT;
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAMBLE);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAM_DURATION);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL);
/* Switch to t2k procedure and wait for the beacon of the receiver. */
netif->mac.tx.vtdma_para.slots_num = 0;
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_BEACON,
GNRC_GOMACH_WAIT_BEACON_TIME_US);
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_WAIT_BEACON;
netif->mac.tx.transmit_state = GNRC_GOMACH_TRANS_TO_KNOWN;
}
else {
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
}
}
static void _t2u_data_tx_fail(gnrc_netif_t *netif)
{
netif->mac.tx.t2u_retry_counter++;
/* If we meet t2u retry limit, release the packet. */
if (netif->mac.tx.t2u_retry_counter >= GNRC_GOMACH_T2U_RETYR_THRESHOLD) {
LOG_DEBUG("[GOMACH] t2u send data failed on channel %d,"
" drop packet.\n", netif->mac.tx.current_neighbor->pub_chanseq);
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
netif->mac.tx.current_neighbor = NULL;
netif->mac.tx.no_ack_counter = 0;
netif->mac.tx.t2u_retry_counter = 0;
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
}
else {
/* Record the MAC sequence of the data, retry t2u in next cycle. */
netif->mac.tx.no_ack_counter = GNRC_GOMACH_REPHASELOCK_THRESHOLD;
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
netif->mac.tx.tx_seq = device_state->seq - 1;
LOG_DEBUG("[GOMACH] t2u send data failed on channel %d.\n",
netif->mac.tx.current_neighbor->pub_chanseq);
/* Set quit_current_cycle to true, thus not to release current_neighbour pointer
* in t2u-end */
gnrc_gomach_set_quit_cycle(netif, true);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
}
}
static void gomach_t2u_wait_tx_feedback(gnrc_netif_t *netif)
{
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR)) {
/* No TX-ISR, go to sleep. */
netif->mac.tx.t2u_retry_counter++;
netif->mac.tx.no_ack_counter = GNRC_GOMACH_REPHASELOCK_THRESHOLD;
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
netif->mac.tx.tx_seq = device_state->seq - 1;
gnrc_gomach_set_quit_cycle(netif, true);
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_END;
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
gnrc_gomach_set_update(netif, true);
return;
}
if (gnrc_gomach_get_tx_finish(netif)) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
if (gnrc_netif_get_tx_feedback(netif) == TX_FEEDBACK_SUCCESS) {
_t2u_data_tx_success(netif);
}
else {
_t2u_data_tx_fail(netif);
}
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_t2u_end(gnrc_netif_t *netif)
{
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_SLEEP);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAMBLE);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_PREAM_DURATION);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_MAX_PREAM_INTERVAL);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
/* In case quit_current_cycle is true, don't release neighbor pointer,
* will retry t2u immediately in next cycle.*/
if (!gnrc_gomach_get_quit_cycle(netif)) {
if (netif->mac.tx.packet != NULL) {
gnrc_pktbuf_release(netif->mac.tx.packet);
netif->mac.tx.packet = NULL;
netif->mac.tx.no_ack_counter = 0;
LOG_WARNING("WARNING: [GOMACH] t2u: drop packet.\n");
}
netif->mac.tx.current_neighbor = NULL;
}
/* Reset t2u state. */
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_INIT;
/* Resume to listen state and go to sleep. */
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_LISTEN;
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP;
gnrc_gomach_set_enter_new_cycle(netif, false);
gnrc_gomach_set_update(netif, true);
}
static void gomach_t2u_update(gnrc_netif_t *netif)
{
/* State machine of GoMacH's t2u (transmit to phase-unknown device) procedure. */
switch (netif->mac.tx.t2u_state) {
case GNRC_GOMACH_T2U_INIT: {
gomach_t2u_init(netif);
break;
}
case GNRC_GOMACH_T2U_PREAMBLE_PREPARE: {
gomach_t2u_send_preamble_prepare(netif);
break;
}
case GNRC_GOMACH_T2U_SEND_PREAMBLE: {
gomach_t2u_send_preamble(netif);
break;
}
case GNRC_GOMACH_T2U_WAIT_PREAMBLE_TX: {
gomach_t2u_wait_preamble_tx(netif);
break;
}
case GNRC_GOMACH_T2U_WAIT_PREAMBLE_ACK: {
gomach_t2u_wait_preamble_ack(netif);
break;
}
case GNRC_GOMACH_T2U_SEND_DATA: {
gomach_t2u_send_data(netif);
break;
}
case GNRC_GOMACH_T2U_WAIT_DATA_TX: {
gomach_t2u_wait_tx_feedback(netif);
break;
}
case GNRC_GOMACH_T2U_END: {
gomach_t2u_end(netif);
break;
}
default: break;
}
}
static void _gomach_phase_backoff(gnrc_netif_t *netif)
{
/* Execute phase backoff for avoiding CP (wake-up period) overlap. */
rtt_clear_alarm();
xtimer_usleep(netif->mac.prot.gomach.backoff_phase_us);
rtt_set_counter(0);
netif->mac.prot.gomach.last_wakeup = rtt_get_counter();
uint32_t alarm = netif->mac.prot.gomach.last_wakeup +
RTT_US_TO_TICKS(CONFIG_GNRC_GOMACH_SUPERFRAME_DURATION_US);
rtt_set_alarm(alarm, _gomach_rtt_cb, NULL);
gnrc_gomach_update_neighbor_phase(netif);
LOG_INFO("INFO: [GOMACH] phase backoffed: %lu us.\n",
(unsigned long)netif->mac.prot.gomach.backoff_phase_us);
}
static void gomach_listen_init(gnrc_netif_t *netif)
{
/* Reset last_seq_info, for avoiding receiving duplicate packets.
* To-do: remove this in the future? */
for (uint8_t i = 0; i < GNRC_GOMACH_DUPCHK_BUFFER_SIZE; i++) {
if (netif->mac.rx.check_dup_pkt.last_nodes[i].node_addr.len != 0) {
netif->mac.rx.check_dup_pkt.last_nodes[i].life_cycle++;
if (netif->mac.rx.check_dup_pkt.last_nodes[i].life_cycle >=
GNRC_GOMACH_RX_DUPCHK_UNIT_LIFE) {
netif->mac.rx.check_dup_pkt.last_nodes[i].node_addr.len = 0;
netif->mac.rx.check_dup_pkt.last_nodes[i].node_addr.addr[0] = 0;
netif->mac.rx.check_dup_pkt.last_nodes[i].node_addr.addr[1] = 0;
netif->mac.rx.check_dup_pkt.last_nodes[i].seq = 0;
netif->mac.rx.check_dup_pkt.last_nodes[i].life_cycle = 0;
}
}
}
if (netif->mac.tx.t2u_fail_count >= GNRC_GOMACH_MAX_T2U_RETYR_THRESHOLD) {
netif->mac.tx.t2u_fail_count = 0;
LOG_DEBUG("[GOMACH]: Re-initialize radio.");
gomach_reinit_radio(netif);
}
gnrc_gomach_set_enter_new_cycle(netif, false);
/* Set listen period timeout. */
uint32_t listen_period = random_uint32_range(0, CONFIG_GNRC_GOMACH_CP_RANDOM_END_US) +
CONFIG_GNRC_GOMACH_CP_DURATION_US;
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_END, listen_period);
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_MAX, GNRC_GOMACH_CP_DURATION_MAX_US);
gnrc_netif_set_rx_started(netif, false);
gnrc_gomach_set_pkt_received(netif, false);
netif->mac.prot.gomach.cp_extend_count = 0;
gnrc_gomach_set_quit_cycle(netif, false);
gnrc_gomach_set_unintd_preamble(netif, false);
gnrc_gomach_set_beacon_fail(netif, false);
gnrc_gomach_set_cp_end(netif, false);
gnrc_gomach_set_got_preamble(netif, false);
/* Flush RX queue and turn on radio. */
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_IDLE);
/* Turn to current public channel. */
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.cur_pub_channel);
/* Enable Auto-ACK for data packet reception. */
gnrc_gomach_set_autoack(netif, NETOPT_ENABLE);
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_CP_LISTEN;
gnrc_gomach_set_update(netif, false);
}
static void _cp_listen_get_pkt(gnrc_netif_t *netif)
{
gnrc_gomach_cp_packet_process(netif);
/* If the device has replied a preamble-ACK, it must waits for the data.
* Here, we extend the CP. */
if (gnrc_gomach_get_got_preamble(netif)) {
gnrc_gomach_set_got_preamble(netif, false);
gnrc_gomach_set_cp_end(netif, false);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_END);
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_END,
CONFIG_GNRC_GOMACH_CP_DURATION_US);
}
else if ((!gnrc_gomach_get_unintd_preamble(netif)) &&
(!gnrc_gomach_get_quit_cycle(netif))) {
gnrc_gomach_set_got_preamble(netif, false);
gnrc_gomach_set_cp_end(netif, false);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_END);
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_END,
CONFIG_GNRC_GOMACH_CP_DURATION_US);
}
}
static void _cp_listen_end(gnrc_netif_t *netif)
{
/* If we found ongoing reception, wait for reception complete. */
if ((gnrc_gomach_get_netdev_state(netif) == NETOPT_STATE_RX) &&
(netif->mac.prot.gomach.cp_extend_count < GNRC_GOMACH_CP_EXTEND_THRESHOLD)) {
netif->mac.prot.gomach.cp_extend_count++;
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END,
CONFIG_GNRC_GOMACH_WAIT_RX_END_US);
}
else {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_END);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_MAX);
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_CP_END;
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_listen_cp_listen(gnrc_netif_t *netif)
{
if (gnrc_gomach_get_pkt_received(netif)) {
gnrc_gomach_set_pkt_received(netif, false);
_cp_listen_get_pkt(netif);
}
/* If we have reached the maximum CP duration, quit CP. */
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_CP_MAX)) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_END);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_MAX);
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_CP_END;
gnrc_gomach_set_update(netif, true);
return;
}
if ((gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_CP_END))) {
gnrc_gomach_set_cp_end(netif, true);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_CP_END);
}
/* If CP duration timeouted or we must quit CP, go to CP end. */
if (gnrc_gomach_get_cp_end(netif) || gnrc_gomach_get_quit_cycle(netif)) {
_cp_listen_end(netif);
}
}
static void gomach_listen_cp_end(gnrc_netif_t *netif)
{
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
gnrc_mac_dispatch(&netif->mac.rx);
/* If we need to quit communications in this cycle, go to sleep. */
if (gnrc_gomach_get_quit_cycle(netif)) {
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP_INIT;
}
else {
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SEND_BEACON;
}
gnrc_gomach_set_update(netif, true);
}
static void gomach_listen_send_beacon(gnrc_netif_t *netif)
{
/* First check if there are slots needed to be allocated. */
uint8_t slot_num = 0;
for (uint8_t i = 0; i < GNRC_GOMACH_SLOSCH_UNIT_COUNT; i++) {
if (netif->mac.rx.slosch_list[i].queue_indicator > 0) {
slot_num += netif->mac.rx.slosch_list[i].queue_indicator;
break;
}
}
if (slot_num > 0) {
/* Disable auto-ACK. Thus not to receive packet (attempt to reply ACK) anymore. */
gnrc_gomach_set_autoack(netif, NETOPT_DISABLE);
/* Assemble and send the beacon. */
int res = gnrc_gomach_send_beacon(netif);
if (res < 0) {
LOG_ERROR("ERROR: [GOMACH] send beacon error: %d.\n", res);
gnrc_gomach_set_beacon_fail(netif, true);
gnrc_gomach_set_update(netif, true);
}
else {
gnrc_gomach_set_update(netif, false);
}
}
else {
/* No need to send beacon, go to next state. */
gnrc_gomach_set_beacon_fail(netif, true);
gnrc_gomach_set_update(netif, true);
}
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_WAIT_BEACON_TX;
}
static void _no_vtdma_after_cp(gnrc_netif_t *netif)
{
/* If the device hasn't allocated transmission slots, check whether it has packets
* to transmit to neighbor. */
if (gnrc_gomach_find_next_tx_neighbor(netif)) {
/* Now, we have packet to send. */
if (netif->mac.tx.current_neighbor == &netif->mac.tx.neighbors[0]) {
/* The packet is for broadcasting. */
/* If we didn't find ongoing preamble stream, go to send broadcast packet. */
if (!gnrc_gomach_get_unintd_preamble(netif)) {
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_TRANSMIT;
netif->mac.tx.transmit_state = GNRC_GOMACH_BROADCAST;
}
else {
/* If we find ongoing preamble stream, go to sleep. */
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP_INIT;
}
}
else {
/* The packet waiting to be sent is for unicast. */
switch (netif->mac.tx.current_neighbor->mac_type) {
case GNRC_GOMACH_TYPE_UNKNOWN: {
/* The neighbor's phase is unknown yet, try to run t2u (transmission
* to unknown device) procedure to phase-lock the neighbor. */
/* If we didn't find ongoing preamble stream, go to t2u procedure. */
if (!gnrc_gomach_get_unintd_preamble(netif)) {
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_TRANSMIT;
netif->mac.tx.transmit_state = GNRC_GOMACH_TRANS_TO_UNKNOWN;
}
else {
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP_INIT;
}
break;
}
case GNRC_GOMACH_TYPE_KNOWN: {
/* If the neighbor's phase is known, go to t2k (transmission
* to known device) procedure. Here, we don't worry that the t2k
* unicast transmission will interrupt with possible ongoing
* preamble transmissions of other devices. */
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_TRANSMIT;
netif->mac.tx.transmit_state = GNRC_GOMACH_TRANS_TO_KNOWN;
break;
}
default: {
LOG_ERROR("ERROR: [GOMACH] vTDMA: unknown MAC type of "
"the neighbor.\n");
break;
}
}
}
}
else {
/* No packet to send, go to sleep. */
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP_INIT;
}
gnrc_gomach_set_update(netif, true);
}
static void gomach_listen_wait_beacon_tx(gnrc_netif_t *netif)
{
if ((gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR))) {
/* No TX-ISR, go to sleep. */
LOG_DEBUG("[GOMACH]: no TX-finish ISR.");
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP_INIT;
gnrc_gomach_set_update(netif, true);
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
return;
}
if (gnrc_gomach_get_tx_finish(netif) ||
gnrc_gomach_get_beacon_fail(netif)) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_NO_TX_ISR);
if ((netif->mac.rx.vtdma_manag.total_slots_num > 0) &&
(!gnrc_gomach_get_beacon_fail(netif))) {
/* If the device has allocated transmission slots to other nodes,
* switch to vTDMA period to receive packets. */
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_VTDMA_INIT;
gnrc_gomach_set_update(netif, true);
}
else {
_no_vtdma_after_cp(netif);
}
}
}
static void gomach_vtdma_init(gnrc_netif_t *netif)
{
/* Switch the radio to the device's sub-channel. */
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.sub_channel_seq);
/* Enable Auto ACK again for data reception */
gnrc_gomach_set_autoack(netif, NETOPT_ENABLE);
/* Set the vTDMA period timeout. */
uint32_t vtdma_duration = netif->mac.rx.vtdma_manag.total_slots_num *
CONFIG_GNRC_GOMACH_VTDMA_SLOT_SIZE_US;
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_VTDMA, vtdma_duration);
gnrc_gomach_set_vTDMA_end(netif, false);
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_VTDMA;
gnrc_gomach_set_update(netif, false);
}
static void gomach_vtdma(gnrc_netif_t *netif)
{
/* Process received packet here. */
if (gnrc_gomach_get_pkt_received(netif)) {
gnrc_gomach_set_pkt_received(netif, false);
gnrc_gomach_packet_process_in_vtdma(netif);
}
if (gnrc_gomach_timeout_is_expired(netif, GNRC_GOMACH_TIMEOUT_VTDMA)) {
gnrc_gomach_set_vTDMA_end(netif, true);
}
/* Go to vTDMA end after vTDMA timeout expires. */
if (gnrc_gomach_get_vTDMA_end(netif)) {
/* Wait for reception complete if found ongoing transmission. */
if (gnrc_gomach_get_netdev_state(netif) == NETOPT_STATE_RX) {
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
gnrc_gomach_set_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END,
CONFIG_GNRC_GOMACH_WAIT_RX_END_US);
return;
}
gnrc_gomach_clear_timeout(netif, GNRC_GOMACH_TIMEOUT_WAIT_RX_END);
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_VTDMA_END;
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_vtdma_end(gnrc_netif_t *netif)
{
gnrc_priority_pktqueue_flush(&netif->mac.rx.queue);
gnrc_mac_dispatch(&netif->mac.rx);
/* Switch the radio to the public-channel. */
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.cur_pub_channel);
/* Check if there is packet to send. */
if (gnrc_gomach_find_next_tx_neighbor(netif)) {
if (netif->mac.tx.current_neighbor == &netif->mac.tx.neighbors[0]) {
/* The packet is for broadcasting. */
if (!gnrc_gomach_get_unintd_preamble(netif)) {
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_TRANSMIT;
netif->mac.tx.transmit_state = GNRC_GOMACH_BROADCAST;
}
else {
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP_INIT;
}
}
else {
switch (netif->mac.tx.current_neighbor->mac_type) {
/* The packet waiting to be sent is for unicast. */
case GNRC_GOMACH_TYPE_UNKNOWN: {
/* The neighbor's phase is unknown yet, try to run t2u (transmission
* to unknown device) procedure to phase-lock the neighbor. */
if (!gnrc_gomach_get_unintd_preamble(netif)) {
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_TRANSMIT;
netif->mac.tx.transmit_state = GNRC_GOMACH_TRANS_TO_UNKNOWN;
}
else {
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP_INIT;
}
} break;
case GNRC_GOMACH_TYPE_KNOWN: {
/* If the neighbor's phase is known, go to t2k (transmission
* to known device) procedure. Here, we don't worry that the t2k
* unicast transmission will interrupt with possible ongoing
* preamble transmissions of other devices. */
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_TRANSMIT;
netif->mac.tx.transmit_state = GNRC_GOMACH_TRANS_TO_KNOWN;
} break;
default: {
LOG_ERROR("ERROR: [GOMACH] vTDMA: unknown MAC type of the neighbor.\n");
break;
}
}
}
}
else {
/* No packet to send, go to sleep. */
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP_INIT;
}
gnrc_gomach_set_update(netif, true);
}
static void gomach_sleep_init(gnrc_netif_t *netif)
{
/* Turn off the radio during sleep period to conserve power. */
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_SLEEP);
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP;
gnrc_gomach_set_update(netif, true);
}
static void gomach_sleep(gnrc_netif_t *netif)
{
/* If we are entering a new cycle, quit sleeping. */
if (gnrc_gomach_get_enter_new_cycle(netif)) {
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_SLEEP_END;
gnrc_gomach_set_update(netif, true);
}
}
static void gomach_sleep_end(gnrc_netif_t *netif)
{
/* Run phase-backoff if needed, select a new wake-up phase. */
if (gnrc_gomach_get_phase_backoff(netif)) {
gnrc_gomach_set_phase_backoff(netif, false);
_gomach_phase_backoff(netif);
}
/* Go to CP (start of the new cycle), start listening on the public-channel. */
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_CP_INIT;
gnrc_gomach_set_update(netif, true);
}
static void gomach_update(gnrc_netif_t *netif)
{
switch (netif->mac.prot.gomach.basic_state) {
case GNRC_GOMACH_INIT: {
/* State machine of GoMacH's initialization procedure. */
switch (netif->mac.prot.gomach.init_state) {
case GNRC_GOMACH_INIT_PREPARE: {
gomach_init_prepare(netif);
break;
}
case GNRC_GOMACH_INIT_ANNC_SUBCHAN: {
gomach_init_announce_subchannel(netif);
break;
}
case GNRC_GOMACH_INIT_WAIT_FEEDBACK: {
gomach_init_wait_announce_feedback(netif);
break;
}
case GNRC_GOMACH_INIT_END: {
gomach_init_end(netif);
break;
}
default: break;
}
break;
}
case GNRC_GOMACH_LISTEN: {
/* State machine of GoMacH's duty-cycled listen procedure. */
switch (netif->mac.rx.listen_state) {
case GNRC_GOMACH_LISTEN_CP_INIT: {
gomach_listen_init(netif);
break;
}
case GNRC_GOMACH_LISTEN_CP_LISTEN: {
gomach_listen_cp_listen(netif);
break;
}
case GNRC_GOMACH_LISTEN_CP_END: {
gomach_listen_cp_end(netif);
break;
}
case GNRC_GOMACH_LISTEN_SEND_BEACON: {
gomach_listen_send_beacon(netif);
break;
}
case GNRC_GOMACH_LISTEN_WAIT_BEACON_TX: {
gomach_listen_wait_beacon_tx(netif);
break;
}
case GNRC_GOMACH_LISTEN_VTDMA_INIT: {
gomach_vtdma_init(netif);
break;
}
case GNRC_GOMACH_LISTEN_VTDMA: {
gomach_vtdma(netif);
break;
}
case GNRC_GOMACH_LISTEN_VTDMA_END: {
gomach_vtdma_end(netif);
break;
}
case GNRC_GOMACH_LISTEN_SLEEP_INIT: {
gomach_sleep_init(netif);
break;
}
case GNRC_GOMACH_LISTEN_SLEEP: {
gomach_sleep(netif);
break;
}
case GNRC_GOMACH_LISTEN_SLEEP_END: {
gomach_sleep_end(netif);
break;
}
default: break;
}
break;
}
case GNRC_GOMACH_TRANSMIT: {
/* State machine of GoMacH's basic transmission scheme. */
switch (netif->mac.tx.transmit_state) {
case GNRC_GOMACH_TRANS_TO_UNKNOWN: {
gomach_t2u_update(netif);
break;
}
case GNRC_GOMACH_TRANS_TO_KNOWN: {
gomach_t2k_update(netif);
break;
}
case GNRC_GOMACH_BROADCAST: {
gomach_bcast_update(netif);
break;
}
default: break;
}
break;
}
default: break;
}
}
static int _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt)
{
if (!gnrc_mac_queue_tx_packet(&netif->mac.tx, 0, pkt)) {
/* TX packet queue full, release the packet. */
DEBUG("[GOMACH] TX queue full, drop packet.\n");
gnrc_pktbuf_release(pkt);
}
gnrc_gomach_set_update(netif, true);
while (gnrc_gomach_get_update(netif)) {
gnrc_gomach_set_update(netif, false);
gomach_update(netif);
}
return 0;
}
static void _gomach_msg_handler(gnrc_netif_t *netif, msg_t *msg)
{
switch (msg->type) {
case GNRC_GOMACH_EVENT_RTT_TYPE: {
_gomach_rtt_handler(msg->content.value, netif);
break;
}
case GNRC_GOMACH_EVENT_TIMEOUT_TYPE: {
/* GoMacH timeout expires. */
gnrc_gomach_timeout_make_expire((gnrc_gomach_timeout_t *) msg->content.ptr);
gnrc_gomach_set_update(netif, true);
break;
}
#if (GNRC_MAC_ENABLE_DUTYCYCLE_RECORD == 1)
case GNRC_MAC_TYPE_GET_DUTYCYCLE: {
/* Output GoMacH's current radio duty-cycle. */
uint64_t duty;
duty = xtimer_now_usec64();
duty = (netif->mac.prot.gomach.awake_duration_sum_ticks) * 100 /
(duty - netif->mac.prot.gomach.system_start_time_ticks);
printf("[GoMacH]: achieved radio duty-cycle: %lu %% \n",
(unsigned long)duty);
break;
}
#endif
default: {
DEBUG("[GoMacH]: Unknown command %" PRIu16 "\n", msg->type);
break;
}
}
while (gnrc_gomach_get_update(netif)) {
gnrc_gomach_set_update(netif, false);
gomach_update(netif);
}
}
/**
* @brief Function called by the device driver on device events
*
* @param[in] event type of event
*/
static void _gomach_event_cb(netdev_t *dev, netdev_event_t event)
{
gnrc_netif_t *netif = (gnrc_netif_t *) dev->context;
if (event == NETDEV_EVENT_ISR) {
msg_t msg;
msg.type = NETDEV_MSG_TYPE_EVENT;
msg.content.ptr = (void *) netif;
if (msg_send(&msg, netif->pid) <= 0) {
DEBUG("[GOMACH] gnrc_netdev: possibly lost interrupt.\n");
}
}
else {
DEBUG("gnrc_netdev: event triggered -> %i\n", event);
switch (event) {
case NETDEV_EVENT_RX_STARTED: {
gnrc_netif_set_rx_started(netif, true);
gnrc_gomach_set_update(netif, true);
break;
}
case NETDEV_EVENT_RX_COMPLETE: {
gnrc_gomach_set_update(netif, true);
gnrc_pktsnip_t *pkt = netif->ops->recv(netif);
if (pkt == NULL) {
gnrc_gomach_set_buffer_full(netif, true);
LOG_DEBUG("[GOMACH] gnrc_netdev: packet is NULL, memory full?\n");
gnrc_gomach_set_pkt_received(netif, false);
gnrc_netif_set_rx_started(netif, false);
break;
}
if (!gnrc_netif_get_rx_started(netif)) {
LOG_DEBUG("[GOMACH] gnrc_netdev: maybe sending kicked in "
"and frame buffer is now corrupted?\n");
gnrc_pktbuf_release(pkt);
gnrc_netif_set_rx_started(netif, false);
break;
}
gnrc_netif_set_rx_started(netif, false);
if (!gnrc_mac_queue_rx_packet(&netif->mac.rx, 0, pkt)) {
LOG_ERROR("ERROR: [GOMACH] gnrc_netdev: can't push RX packet, queue full?\n");
gnrc_pktbuf_release(pkt);
gnrc_gomach_set_pkt_received(netif, false);
break;
}
else {
gnrc_gomach_set_pkt_received(netif, true);
}
break;
}
case NETDEV_EVENT_TX_COMPLETE: {
gnrc_netif_set_tx_feedback(netif, TX_FEEDBACK_SUCCESS);
gnrc_gomach_set_tx_finish(netif, true);
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_IDLE);
gnrc_gomach_set_update(netif, true);
break;
}
case NETDEV_EVENT_TX_NOACK: {
gnrc_netif_set_tx_feedback(netif, TX_FEEDBACK_NOACK);
gnrc_gomach_set_tx_finish(netif, true);
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_IDLE);
gnrc_gomach_set_update(netif, true);
break;
}
case NETDEV_EVENT_TX_MEDIUM_BUSY: {
gnrc_netif_set_tx_feedback(netif, TX_FEEDBACK_BUSY);
gnrc_gomach_set_tx_finish(netif, true);
gnrc_gomach_set_netdev_state(netif, NETOPT_STATE_IDLE);
gnrc_gomach_set_update(netif, true);
break;
}
default: {
DEBUG("WARNING [GoMacH]: unhandled event %u.\n", event);
}
}
}
while (gnrc_gomach_get_update(netif)) {
gnrc_gomach_set_update(netif, false);
gomach_update(netif);
}
}
static void _gomach_init(gnrc_netif_t *netif)
{
netdev_t *dev;
gnrc_netif_default_init(netif);
dev = netif->dev;
dev->event_callback = _gomach_event_cb;
/* Initialize RTT. */
rtt_init();
/* Store pid globally, so that IRQ can use it to send message. */
gomach_pid = netif->pid;
/* Set MAC address length. */
uint16_t src_len = IEEE802154_LONG_ADDRESS_LEN;
dev->driver->set(dev, NETOPT_SRC_LEN, &src_len, sizeof(src_len));
/* Get the MAC address of the device. */
netif->l2addr_len = dev->driver->get(dev,
NETOPT_ADDRESS_LONG,
netif->l2addr,
sizeof(netif->l2addr));
/* Initialize GoMacH's state machines. */
netif->mac.prot.gomach.basic_state = GNRC_GOMACH_INIT;
netif->mac.prot.gomach.init_state = GNRC_GOMACH_INIT_PREPARE;
netif->mac.rx.listen_state = GNRC_GOMACH_LISTEN_CP_INIT;
netif->mac.tx.transmit_state = GNRC_GOMACH_TRANS_TO_UNKNOWN;
netif->mac.tx.bcast_state = GNRC_GOMACH_BCAST_INIT;
netif->mac.tx.t2k_state = GNRC_GOMACH_T2K_INIT;
netif->mac.tx.t2u_state = GNRC_GOMACH_T2U_INIT;
/* Initialize GoMacH's channels. */
netif->mac.prot.gomach.sub_channel_seq = 13;
netif->mac.prot.gomach.pub_channel_1 = 26;
netif->mac.prot.gomach.pub_channel_2 = 11;
netif->mac.prot.gomach.cur_pub_channel = netif->mac.prot.gomach.pub_channel_1;
gnrc_gomach_turn_channel(netif, netif->mac.prot.gomach.cur_pub_channel);
/* Enable RX-start and TX-started and TX-END interrupts. */
netopt_enable_t enable = NETOPT_ENABLE;
dev->driver->set(dev, NETOPT_RX_START_IRQ, &enable, sizeof(enable));
dev->driver->set(dev, NETOPT_TX_END_IRQ, &enable, sizeof(enable));
/* Initialize broadcast sequence number. This at least differs from board
* to board. */
netif->mac.tx.broadcast_seq = netif->l2addr[netif->l2addr_len - 1];
/* Reset all timeouts just to be sure. */
gnrc_gomach_reset_timeouts(netif);
/* Initialize GoMacH's other key parameters. */
netif->mac.tx.no_ack_counter = 0;
gnrc_gomach_set_enter_new_cycle(netif, false);
netif->mac.rx.vtdma_manag.sub_channel_seq = 26;
netif->mac.prot.gomach.subchannel_occu_flags = 0;
gnrc_gomach_set_pkt_received(netif, false);
gnrc_gomach_set_update(netif, false);
gnrc_gomach_set_duty_cycle_start(netif, false);
gnrc_gomach_set_quit_cycle(netif, false);
gnrc_gomach_set_beacon_fail(netif, false);
gnrc_gomach_set_buffer_full(netif, false);
gnrc_gomach_set_phase_backoff(netif, false);
netif->mac.rx.check_dup_pkt.queue_head = 0;
netif->mac.tx.last_tx_neighbor_id = 0;
netdev_ieee802154_t *device_state = (netdev_ieee802154_t *)netif->dev;
device_state->seq = netif->l2addr[netif->l2addr_len - 1];
/* Initialize GoMacH's duplicate-check scheme. */
for (uint8_t i = 0; i < GNRC_GOMACH_DUPCHK_BUFFER_SIZE; i++) {
netif->mac.rx.check_dup_pkt.last_nodes[i].node_addr.len = 0;
}
/* Set the random seed. */
uint32_t seed = 0;
seed = netif->l2addr[netif->l2addr_len - 2];
seed = seed << 8;
seed |= netif->l2addr[netif->l2addr_len - 1];
random_init(seed);
netif->mac.tx.t2u_fail_count = 0;
#if (GNRC_MAC_ENABLE_DUTYCYCLE_RECORD == 1)
/* Start duty cycle recording */
netif->mac.prot.gomach.system_start_time_ticks = xtimer_now_usec64();
netif->mac.prot.gomach.last_radio_on_time_ticks =
netif->mac.prot.gomach.system_start_time_ticks;
netif->mac.prot.gomach.awake_duration_sum_ticks = 0;
netif->mac.prot.gomach.gomach_info |= GNRC_GOMACH_INTERNAL_INFO_RADIO_IS_ON;
#endif
gnrc_gomach_set_update(netif, true);
while (gnrc_gomach_get_update(netif)) {
gnrc_gomach_set_update(netif, false);
gomach_update(netif);
}
}