1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 05:12:57 +01:00

LWMAC: a simple duty cycling 802.15.4 MAC protocol.

This commit is contained in:
zhuoshuguo 2017-01-31 17:25:32 +01:00
parent b70a4885f4
commit a54655890e
20 changed files with 3833 additions and 0 deletions

View File

@ -524,6 +524,12 @@ ifneq (,$(filter netstats_%, $(USEMODULE)))
USEMODULE += netstats
endif
ifneq (,$(filter gnrc_lwmac,$(USEMODULE)))
USEMODULE += gnrc_mac
USEMODULE += gnrc_netdev
FEATURES_REQUIRED += periph_rtt
endif
ifneq (,$(filter pthread,$(USEMODULE)))
USEMODULE += xtimer
USEMODULE += timex

View File

@ -23,6 +23,7 @@
#include "board.h"
#include "net/gnrc/netdev.h"
#include "net/gnrc/netdev/ieee802154.h"
#include "net/gnrc/lwmac/lwmac.h"
#include "net/gnrc.h"
#include "at86rf2xx.h"
@ -58,11 +59,19 @@ void auto_init_at86rf2xx(void)
LOG_ERROR("[auto_init_netif] error initializing at86rf2xx radio #%u\n", i);
}
else {
#ifdef MODULE_GNRC_LWMAC
gnrc_lwmac_init(_at86rf2xx_stacks[i],
AT86RF2XX_MAC_STACKSIZE,
AT86RF2XX_MAC_PRIO,
"at86rf2xx-lwmac",
&gnrc_adpt[i]);
#else
gnrc_netdev_init(_at86rf2xx_stacks[i],
AT86RF2XX_MAC_STACKSIZE,
AT86RF2XX_MAC_PRIO,
"at86rf2xx",
&gnrc_adpt[i]);
#endif
}
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Header definition LWMAC
* @internal
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
*/
#ifndef NET_GNRC_LWMAC_HDR_H
#define NET_GNRC_LWMAC_HDR_H
#include <stdint.h>
#include <stdbool.h>
#include "net/ieee802154.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LWMAC WR (wake-up request packet, i.e., preamble packet) frame type
*/
#define GNRC_LWMAC_FRAMETYPE_WR (0x01U)
/**
* @brief LWMAC WA (wake-up answer packet, i.e., preamble-ACK packet) frame type
*/
#define GNRC_LWMAC_FRAMETYPE_WA (0x02U)
/**
* @brief LWMAC data frame type
*/
#define GNRC_LWMAC_FRAMETYPE_DATA (0x03U)
/**
* @brief LWMAC data frame type with pending data transmission request
*/
#define GNRC_LWMAC_FRAMETYPE_DATA_PENDING (0x04U)
/**
* @brief LWMAC broadcast frame type
*/
#define GNRC_LWMAC_FRAMETYPE_BROADCAST (0x05U)
/**
* @brief LWMAC internal L2 address structure
*/
typedef struct {
uint8_t addr[IEEE802154_LONG_ADDRESS_LEN]; /**< address of node */
uint8_t len; /**< address */
} gnrc_lwmac_l2_addr_t;
/**
* @brief Static initializer for l2_addr_t.
*/
#define GNRC_LWMAC_L2_ADDR_INITIAL { { 0 }, 0 }
/**
* @brief LWMAC header
*/
typedef struct {
uint8_t type; /**< type of frame */
} gnrc_lwmac_hdr_t;
/**
* @brief LWMAC WR (wake-up request packet, i.e., preamble packet) frame
*/
typedef struct __attribute__((packed)) {
gnrc_lwmac_hdr_t header; /**< WR packet header type */
gnrc_lwmac_l2_addr_t dst_addr; /**< WR is broadcast, so destination address needed */
} gnrc_lwmac_frame_wr_t;
/**
* @brief LWMAC WA (wake-up answer packet, i.e., preamble-ACK packet) frame
*/
typedef struct __attribute__((packed)) {
gnrc_lwmac_hdr_t header; /**< WA packet header type */
gnrc_lwmac_l2_addr_t dst_addr; /**< WA is broadcast, so destination address needed */
uint32_t current_phase; /**< Node's current phase value */
} gnrc_lwmac_frame_wa_t;
/**
* @brief LWMAC broadcast data frame
*/
typedef struct __attribute__((packed)) {
gnrc_lwmac_hdr_t header; /**< Broadcast packet header type */
uint8_t seq_nr; /**< Broadcast sequence */
} gnrc_lwmac_frame_broadcast_t;
/**
* @brief LWMAC unicast data frame
*/
typedef struct __attribute__((packed)) {
gnrc_lwmac_hdr_t header; /**< Data packet header type */
} gnrc_lwmac_frame_data_t;
#ifdef __cplusplus
}
#endif
#endif /* NET_GNRC_LWMAC_HDR_H */
/** @} */

View File

@ -0,0 +1,324 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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.
*/
/**
* @defgroup net_gnrc_lwmac Simplest possible MAC layer
* @ingroup net_gnrc
* @brief Lightweight MAC protocol that allows for duty cycling to save
* energy.
*
* ## LWMAC implementation
*
* ## Radio duty cycling
* LWMAC adopts the radio duty-cycle scheme to conserve power. Namely, in each
* cycle period (MAC superframe), a node device wakes up for a short period of
* time (called listen period or wake-up period) for receiving possible incoming
* packets from other devices. Outside the listen period, the node device turns
* off its radio to conserve power.
*
* ## Phase-lock scheme
* LWMAC adopts the phase-lock scheme to further reduce power consumption. Each
* node device in LWMAC will try to record/track its Tx-neighbor's wake-up phase.
* This is called phase-lock. After phase-locking, the sender node will (likely)
* spend less preamble packets (also called WR packet, i.e., wake-up-request, in
* LWMAC) for initiating a hand-shaking procedure for transmitting a data packet,
* compared to the first time it talks to the receiver.
*
* ## Burst transmission
* LWMAC adopts pending-bit technique to enhance its throughput. Namely, in case
* of having multi packets for the receiver, a sender uses the pending-bit flag
* embedded in the MAC header to instruct this situation, and the buffered packets
* will be transmitted in a continuous sequence, back to back, to the receiver in
* one shot.
*
* ## Auto wake-up extension
* LWMAC adopts auto wake-up extension scheme based on timeout (like T-MAC). In short,
* when a packet is successfully received at the receiver side, the receiver will
* reset the wake-up timeout to extend its wake-up period for receiving more potential
* incoming packets. This is to be compatible with the pending-bit technique to allow
* the receiver to absorb more packets when needed, thus boosts the throughput.
*
* ## Simple retransmission scheme
* LWMAC adopts a simple retransmission scheme to enhance link reliability. The data
* packet will only be dropped in case the retransmission counter gets larger than
* @ref GNRC_LWMAC_MAX_DATA_TX_RETRIES.
*
* ## Automatic phase backoff scheme
* LWMAC adopts an automatic phase backoff scheme to reduce WR (preamble) collision
* probability. In multi-hop scenarios, let's say, nodes A <---B <----C (which is
* common in multi-hop data collection networks), in which B has packets for A, and
* C has packets for B. In case A and B's wake-up phases are too close (overlapping).
* Then, especially in high traffic conditions, B and C may initiate transmissions
* at the same time (B sends to A, and C sends to B), a link of either will be
* definitely interfered, leading to collisions and link throughput reduction. To
* this end, by using the automatic phase backoff scheme, if a sender finds its
* receiver's phase is too close to its own phase, it will run a backoff scheme to
* randomly reselect a new wake-up phase for itself.
*
* @{
*
* @file
* @brief Interface definition for the LWMAC protocol
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
*/
#ifndef NET_GNRC_LWMAC_LWMAC_H
#define NET_GNRC_LWMAC_LWMAC_H
#include "kernel_types.h"
#include "net/gnrc/netdev.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Time between consecutive wake-ups.
*
* This macro governs power consumption, latency and throughput!
* In LWMAC, devices adopt duty-cycle scheme to conserve power. That is,
* time is divided into repeated cycles (or, superframes), and in each
* cycle, a node only wakes up for a period of time for receiving potential
* incoming packets for itself. This macro defines the wake-up interval, or,
* in other words, defines the cycle duration used in LWMAC. If the wake-up interval
* is short, nodes will wake up more frequently, which also increases
* the chances for receiving packets from neighbors (i.e., leads to higher
* throughput), but also results in higher power consumption.
* In LWMAC, by default, we regard the wake-up period as the beginning of a cycle.
*/
#ifndef GNRC_LWMAC_WAKEUP_INTERVAL_US
#define GNRC_LWMAC_WAKEUP_INTERVAL_US (100LU * US_PER_MS)
#endif
/**
* @brief The Maximum WR (preamble packet @ref gnrc_lwmac_frame_wr_t) duration time.
*
* Since LWMAC adopts duty-cycle scheme, a node only wakes up for a short
* period in each cycle. Thus, to probe where is the wake-up period of the
* receiver, a sender sends WR (preamble) packets to notice the receiver for
* communication. To ensure that the receiver will catch at least one WR
* packet in one cycle, the sender repeatedly broadcasts a stream of WR packets
* with the broadcast duration (preamble duration) slightly longer period than
* @ref GNRC_LWMAC_WAKEUP_INTERVAL_US.
*/
#ifndef GNRC_LWMAC_PREAMBLE_DURATION_US
#define GNRC_LWMAC_PREAMBLE_DURATION_US ((13LU * GNRC_LWMAC_WAKEUP_INTERVAL_US) / 10)
#endif
/**
* @brief Timeout to send the next WR in case no WA has been received during that
* time.
*
* In LWMAC, when a sender initiates a transmission to a receiver, it starts with
* sending a stream of repeated WR packets with @ref GNRC_LWMAC_TIME_BETWEEN_WR_US interval
* between two consecutive WRs. After sending one WR (preamble) packet, the sender turns
* to the listen mode to receive the potential incoming WA (preamble-ACK) packet with
* a timeout of @ref GNRC_LWMAC_TIME_BETWEEN_WR_US. If no WA is received during
* @ref GNRC_LWMAC_TIME_BETWEEN_WR_US, the sender starts sending the next WR.
* It is referenced to the beginning of both WRs, but due to internal
* overhead, the exact spacing is slightly higher.
* The minimum possible value depends on the time it takes to completely
* send a WR with the given hardware (including processor) and data rate.
*/
#ifndef GNRC_LWMAC_TIME_BETWEEN_WR_US
#define GNRC_LWMAC_TIME_BETWEEN_WR_US (5U * US_PER_MS)
#endif
/**
* @brief How long a node in LWMAC should keep awake and listen on the channel in one cycle.
*
* LWMAC adopts the duty-cycle scheme that a node only wakes up for a short
* period of @ref GNRC_LWMAC_WAKEUP_DURATION_US in each cycle. In the rest of the cycle, the node
* turns off the radio to conserve power. @ref GNRC_LWMAC_WAKEUP_DURATION_US is set to twice the
* duration of @ref GNRC_LWMAC_TIME_BETWEEN_WR_US, to guarantee that the wake-up period is long
* enough that receiver will not miss the WR (preamble) packet.
* Receiver needs to support @ref NETDEV_EVENT_RX_STARTED event in order to use time-between-WR
* as a sensible default here. Otherwise the duration of WRs as well as longest
* possible data broadcasts need to be taken into account.
*/
#ifndef GNRC_LWMAC_WAKEUP_DURATION_US
#define GNRC_LWMAC_WAKEUP_DURATION_US (GNRC_LWMAC_TIME_BETWEEN_WR_US * 2)
#endif
/**
* @brief How long broadcast packets @ref gnrc_lwmac_frame_broadcast_t will be sent to make sure
* every participant has received at least one copy.
*
* Since LWMAC adopts duty-cycle scheme, a node only wakes up for a short period in
* each cycle. Thus, when a node wants to broadcast a packet, it repeatedly broadcasts the
* packet for one @ref GNRC_LWMAC_BROADCAST_DURATION_US duration which is slightly longer
* than @ref GNRC_LWMAC_WAKEUP_INTERVAL_US. This is to ensure that all neighbors will not miss
* the broadcast procedure of the sender and catch at least one copy of the broadcast packet.
*/
#ifndef GNRC_LWMAC_BROADCAST_DURATION_US
#define GNRC_LWMAC_BROADCAST_DURATION_US ((GNRC_LWMAC_WAKEUP_INTERVAL_US * 11) / 10)
#endif
/**
* @brief Time to idle between two successive broadcast packets, referenced to the
* start of the packet.
*
* The same limitation as for @ref GNRC_LWMAC_TIME_BETWEEN_WR_US apply here.
* In LWMAC, when a sender initiates a broadcast, it starts with sending a stream of
* repeated broadcast packets with @ref GNRC_LWMAC_TIME_BETWEEN_BROADCAST_US interval
* between two consecutive broadcast packets. After sending one broadcast packet, the sender
* turns to the listen mode with a timeout of @ref GNRC_LWMAC_TIME_BETWEEN_BROADCAST_US. When this
* timeout expires, the sender sends the next broadcast packet until reaching the maximum
* broadcast duration of @ref GNRC_LWMAC_BROADCAST_DURATION_US.
*/
#ifndef GNRC_LWMAC_TIME_BETWEEN_BROADCAST_US
#define GNRC_LWMAC_TIME_BETWEEN_BROADCAST_US (GNRC_LWMAC_TIME_BETWEEN_WR_US)
#endif
/**
* @brief WR preparation overhead before it can be sent (higher with debugging output).
*
* In LWMAC, when a sender wants to send a data packet to the receiver, it starts
* sending the WR stream a little bit earlier (advance) to the beginning edge
* of destination's wake-up phase over time. The idea is not to miss the wake-up
* period of the receiver, otherwise will lead to a long WR procedure.
*/
#ifndef GNRC_LWMAC_WR_PREPARATION_US
#define GNRC_LWMAC_WR_PREPARATION_US ((3U * US_PER_MS))
#endif
/**
* @brief How long to wait after a WA for data to come in.
*
* When a node in LWMAC gets a WR during its wake-up period, it immediately
* replies a WA packet to the sender for acknowledging the sender's transmission
* request. After sending the WA, the receiver waits for the data packet from the
* sender, with a timeout of @ref GNRC_LWMAC_DATA_DELAY_US duration. In case no data will be
* received in this period, the receiver regards reception failed and go back to
* normal listen mode. However, in case the receiver receives other unintended packets,
* like WR/WA packets from other neighbor communication pairs, the receiver resets
* this timeout and continues to wait for the data packet, with the consideration that
* the sender's data transmission might be delayed due to other ongoing transmissions
* (the data packet is transmitted with CSMA/CA).
* This data timeout is long enough to catch the beginning of the packet if the transceiver
* supports @ref NETDEV_EVENT_RX_STARTED event (this can be important for big packets).
*/
#ifndef GNRC_LWMAC_DATA_DELAY_US
#define GNRC_LWMAC_DATA_DELAY_US (10U * US_PER_MS)
#endif
/**
* @brief CSMA retries for DATA packet after WR->WA was successful.
*
* After receiving the WA packet @ref gnrc_lwmac_frame_wa_t from the receiver, the sender
* starts sending the data packet using CSMA/CA. This macro defines how many CSMA retries
* a sender will be allowed to execute for sending its data, before the data is successfully
* sent (gets data ACK from the receiver).
*/
#ifndef GNRC_LWMAC_DATA_CSMA_RETRIES
#define GNRC_LWMAC_DATA_CSMA_RETRIES (3U)
#endif
/**
* @brief Maximum TX transmission retries for DATA packet in case of no response from the receiver.
*
* When a data packet is scheduled for transmission, i.e., pushed into TX for sending,
* LWMAC defines a maximum of @ref GNRC_LWMAC_MAX_DATA_TX_RETRIES retries for transmission of the
* packet. That is, in case of transmission failure in TX due to no WA from the receiver,
* the sender will not drop the packet, but keeps it and retries to send the data packet
* in the following cycles, until the sender reaches the maximum retries limit defined here.
* Then, the packet will be dropped.
*/
#ifndef GNRC_LWMAC_MAX_DATA_TX_RETRIES
#define GNRC_LWMAC_MAX_DATA_TX_RETRIES (3U)
#endif
/**
* @brief MAX burst transmission packet number in one shot.
*
* LWMAC supports burst transmission based on the pending-bit technique, and this macro
* here defines the largest number of packets allowed to be sent in one consecutive
* sequence. In case a sender has multi packets for one receiver,the burst transmission
* procedure is as follow:
* 1. The sender first uses WR stream to locate the receiver's wake-up period (if the
* sender has already phase-locked the receiver's phase, normally the sender only cost
* one WR to get the first WA from the receiver) and then sends its first data.
* 2. After the transmission of the first data, the sender immediately sends a WR to
* the receiver for starting the second round of transmission of the second data. The
* receiver should also immediately reply WA for continue receiving data packets. In
* case the sender doesn't receive WA during @ref GNRC_LWMAC_TIME_BETWEEN_WR_US, it regards the
* consecutive (burst) transmission failed and quits TX procedure (the data will be queued
* back to the transmission queue for normal transmission attempt in following cycles).
* 3. In case the second transmission succeeds, the sender repeats step (2) to send all the
* following pending packets.
* In short, in burst transmission mode, the sender doesn't tolerate no-WA event. ALl the
* pending data packets should be sent with only one WR cost for leading the transmission.
*/
#ifndef GNRC_LWMAC_MAX_TX_BURST_PKT_NUM
#define GNRC_LWMAC_MAX_TX_BURST_PKT_NUM (GNRC_LWMAC_WAKEUP_INTERVAL_US / GNRC_LWMAC_WAKEUP_DURATION_US)
#endif
/**
* @brief MAX bad Listen period extensions a node can tolerate.
*
* In LWMAC, to allow burst transmissions, when in the wake-up period and by default, a node
* will extend its wake-up period to another @ref GNRC_LWMAC_WAKEUP_DURATION_US after each packet
* reception (except for broadcast packet). However, in some cases, a receiver may
* overhear other unintended packets, e.g., WR or WA packets for other nodes, these are
* called bad extensions for the receiver. If a receiver reaches the maximum bad listen
* extension limit defined here, it goes to sleep mode with the consideration that the
* channel is currently unavailable/busy.
*/
#ifndef GNRC_LWMAC_MAX_RX_EXTENSION_NUM
#define GNRC_LWMAC_MAX_RX_EXTENSION_NUM (3U)
#endif
/**
* @brief CSMA retries for broadcast packet.
*
* Currently, each broadcast packet is sent with CSMA/CA for collision avoidance.
* Too many CSMA retries may lead to running out of destinations wake-up period.
*/
#ifndef GNRC_LWMAC_BROADCAST_CSMA_RETRIES
#define GNRC_LWMAC_BROADCAST_CSMA_RETRIES (3U)
#endif
/**
* @brief Default message queue size to use for the LWMAC thread.
*
* The value of this macro should be enough for supporting the manipulation of
* LWMAC.
*
*/
#ifndef GNRC_LWMAC_IPC_MSG_QUEUE_SIZE
#define GNRC_LWMAC_IPC_MSG_QUEUE_SIZE (8U)
#endif
/**
* @brief Initialize an instance of the LWMAC layer
*
* The initialization starts a new thread that connects to the given netdev
* device and starts a link layer event loop.
*
* @param[in] stack stack for the control thread
* @param[in] stacksize size of *stack*
* @param[in] priority priority for the thread housing the LWMAC instance
* @param[in] name name of the thread housing the LWMAC instance
* @param[in] dev netdev device, needs to be already initialized
*
* @return PID of LWMAC thread on success
* @return -EINVAL if creation of thread fails
* @return -ENODEV if *dev* is invalid
*/
kernel_pid_t gnrc_lwmac_init(char *stack, int stacksize, char priority,
const char *name, gnrc_netdev_t *dev);
#ifdef __cplusplus
}
#endif
#endif /* NET_GNRC_LWMAC_LWMAC_H */
/** @} */

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Timeout handling of LWMAC
*
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
*/
#ifndef NET_GNRC_LWMAC_TIMEOUT_H
#define NET_GNRC_LWMAC_TIMEOUT_H
#include <stdint.h>
#include <stdbool.h>
#include "net/gnrc/netdev.h"
#include "net/gnrc/lwmac/types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Static initializer for @ref gnrc_lwmac_timeout_t.
*/
#define GNRC_LWMAC_TIMEOUT_INITIAL { {}, {}, false, TIMEOUT_DISABLED }
/**
* @brief Set LWMAC timeout of type @p type of offset @p offset.
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
* @param[in] type LWMAC timeout type
* @param[in] offset timeout offset
*/
void gnrc_lwmac_set_timeout(gnrc_netdev_t *gnrc_netdev,
gnrc_lwmac_timeout_type_t type,
uint32_t offset);
/**
* @brief Clear LWMAC timeout of type @p type.
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
* @param[in] type LWMAC timeout type
*/
void gnrc_lwmac_clear_timeout(gnrc_netdev_t *gnrc_netdev, gnrc_lwmac_timeout_type_t type);
/**
* @brief Check whether LWMAC timeout of type @p type is running.
*
* @param[in] gnrc_netdev gnrc_netdev structure
* @param[in] type LWMAC timeout type
*
* @return true, if timeout of type @p type is running.
* @return false, if timeout of type @p type is not running.
*/
bool gnrc_lwmac_timeout_is_running(gnrc_netdev_t *gnrc_netdev,
gnrc_lwmac_timeout_type_t type);
/**
* @brief Check whether LWMAC timeout of type @p type is expired. It will clear
* the timeout once it is found expired.
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
* @param[in] type LWMAC timeout type
*
* @return true, if timeout of type @p type is expired.
* @return false, if timeout of type @p type is not expired, or not exist.
*/
bool gnrc_lwmac_timeout_is_expired(gnrc_netdev_t *gnrc_netdev, gnrc_lwmac_timeout_type_t type);
/**
* @brief Reset all LWMAC timeouts.
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
*/
void gnrc_lwmac_reset_timeouts(gnrc_netdev_t *gnrc_netdev);
/**
* @brief Make a specific LWMAC timeout expired.
*
* @param[in,out] timeout LWMAC tiemout
*/
void gnrc_lwmac_timeout_make_expire(gnrc_lwmac_timeout_t *timeout);
#ifdef __cplusplus
}
#endif
#endif /* NET_GNRC_LWMAC_TIMEOUT_H */
/** @} */

View File

@ -0,0 +1,223 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Definition of internal types used by LWMAC
*
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
*/
#ifndef NET_GNRC_LWMAC_TYPES_H
#define NET_GNRC_LWMAC_TYPES_H
#include "msg.h"
#include "xtimer.h"
#include "net/gnrc/lwmac/hdr.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LWMAC RTT event type.
*/
#define GNRC_LWMAC_EVENT_RTT_TYPE (0x4300)
/**
* @brief LWMAC RTT start event type.
*/
#define GNRC_LWMAC_EVENT_RTT_START (0x4301)
/**
* @brief LWMAC RTT stop event type.
*/
#define GNRC_LWMAC_EVENT_RTT_STOP (0x4302)
/**
* @brief LWMAC RTT pause event type.
*/
#define GNRC_LWMAC_EVENT_RTT_PAUSE (0x4303)
/**
* @brief LWMAC RTT resume event type.
*/
#define GNRC_LWMAC_EVENT_RTT_RESUME (0x4304)
/**
* @brief LWMAC RTT wakeup pending event type.
*/
#define GNRC_LWMAC_EVENT_RTT_WAKEUP_PENDING (0x4305)
/**
* @brief LWMAC RTT sleep pending event type.
*/
#define GNRC_LWMAC_EVENT_RTT_SLEEP_PENDING (0x4306)
/**
* @brief LWMAC timeout event type.
*/
#define GNRC_LWMAC_EVENT_TIMEOUT_TYPE (0x4400)
/**
* @brief LWMAC duty-cycle active flag.
*
* Keep track of duty cycling to avoid late RTT events after stopping.
*/
#define GNRC_LWMAC_DUTYCYCLE_ACTIVE (0x01)
/**
* @brief LWMAC needs reschedule flag.
*
* Used internally for rescheduling state machine update, e.g. after state
* transition caused in update.
*/
#define GNRC_LWMAC_NEEDS_RESCHEDULE (0x02)
/**
* @brief LWMAC check radio's on/off state flag.
*/
#define GNRC_LWMAC_RADIO_IS_ON (0x04)
/**
* @brief Enable/disable duty-cycle record and print out.
* Set "1" to enable, set "0" to disable.
*/
#ifndef GNRC_LWMAC_ENABLE_DUTYCYLE_RECORD
#define GNRC_LWMAC_ENABLE_DUTYCYLE_RECORD (0U)
#endif
/**
* @brief The default largest number of parallel timeouts in LWMAC
*/
#ifndef GNRC_LWMAC_TIMEOUT_COUNT
#define GNRC_LWMAC_TIMEOUT_COUNT (3U)
#endif
/**
* @brief Internal states of LWMAC
*/
typedef enum {
GNRC_LWMAC_UNDEF = -1, /**< Undefined state of LWMAC */
GNRC_LWMAC_STOPPED, /**< LWMAC's main state machine has been stopped */
GNRC_LWMAC_START, /**< Start LWMAC's main state machine */
GNRC_LWMAC_STOP, /**< Stop LWMAC's main state machine */
GNRC_LWMAC_RESET, /**< Reset LWMAC's main state machine */
GNRC_LWMAC_LISTENING, /**< Listen the channel for receiving packets */
GNRC_LWMAC_RECEIVING, /**< RX is handled in own state machine */
GNRC_LWMAC_TRANSMITTING, /**< TX is handled in own state machine */
GNRC_LWMAC_SLEEPING, /**< Turn off radio to conserve power */
GNRC_LWMAC_STATE_COUNT /**< Count of LWMAC's states */
} gnrc_lwmac_state_t;
/**
* @brief TX states of LWMAC
*/
typedef enum {
GNRC_LWMAC_TX_STATE_STOPPED, /**< Tx schedule stopped, stop sending packet */
GNRC_LWMAC_TX_STATE_INIT, /**< Initiate transmission */
GNRC_LWMAC_TX_STATE_SEND_BROADCAST, /**< directly goes to SUCCESSFUL or FAILED when finished */
GNRC_LWMAC_TX_STATE_SEND_WR, /**< Send a wakeup request */
GNRC_LWMAC_TX_STATE_WAIT_WR_SENT, /**< Wait until WR sent to set timeout */
GNRC_LWMAC_TX_STATE_WAIT_FOR_WA, /**< Wait for dest node's wakeup ackknowledge */
GNRC_LWMAC_TX_STATE_SEND_DATA, /**< Send the actual payload data */
GNRC_LWMAC_TX_STATE_WAIT_FEEDBACK, /**< Wait if packet was ACKed */
GNRC_LWMAC_TX_STATE_SUCCESSFUL, /**< Transmission has finished successfully */
GNRC_LWMAC_TX_STATE_FAILED /**< Payload data couldn't be delivered to dest */
} gnrc_lwmac_tx_state_t;
/**
* @brief Static initializer for gnrc_lwmac_tx_state_t.
*/
#define GNRC_LWMAC_TX_STATE_INITIAL GNRC_LWMAC_TX_STATE_STOPPED
/**
* @brief RX states of LWMAC
*/
typedef enum {
GNRC_LWMAC_RX_STATE_STOPPED, /**< Rx schedule stopped */
GNRC_LWMAC_RX_STATE_INIT, /**< Initiate reception */
GNRC_LWMAC_RX_STATE_WAIT_FOR_WR, /**< Wait for a wakeup request */
GNRC_LWMAC_RX_STATE_SEND_WA, /**< Send wakeup ackknowledge to requesting node */
GNRC_LWMAC_RX_STATE_WAIT_WA_SENT, /**< Wait until WA sent to set timeout */
GNRC_LWMAC_RX_STATE_WAIT_FOR_DATA, /**< Wait for actual payload data */
GNRC_LWMAC_RX_STATE_SUCCESSFUL, /**< Recption has finished successfully */
GNRC_LWMAC_RX_STATE_FAILED /**< Reception over, but nothing received */
} gnrc_lwmac_rx_state_t;
/**
* @brief Static initializer for gnrc_lwmac_rx_state_t.
*/
#define GNRC_LWMAC_RX_STATE_INITIAL GNRC_LWMAC_RX_STATE_STOPPED
/**
* @brief LWMAC uninitialized phase value
*/
#define GNRC_LWMAC_PHASE_UNINITIALIZED (0)
/**
* @brief LWMAC max phase value
*/
#define GNRC_LWMAC_PHASE_MAX (-1)
/**
* @brief LWMAC timeout types
*/
typedef enum {
GNRC_LWMAC_TIMEOUT_DISABLED, /**< Timeout is diabled */
GNRC_LWMAC_TIMEOUT_WR, /**< WR timeout, waiting WA */
GNRC_LWMAC_TIMEOUT_NO_RESPONSE, /**< Maximum WR duration timeout awaiting WA */
GNRC_LWMAC_TIMEOUT_DATA, /**< Timeout awaiting data packet from receiver */
GNRC_LWMAC_TIMEOUT_WAIT_DEST_WAKEUP, /**< Timeout for waiting receiver's wake-up phase */
GNRC_LWMAC_TIMEOUT_WAKEUP_PERIOD, /**< Wake up period timeout for going to sleep */
GNRC_LWMAC_TIMEOUT_NEXT_BROADCAST, /**< Timeout for waiting to send the next broadcast packet */
GNRC_LWMAC_TIMEOUT_BROADCAST_END, /**< Timeout awaiting the end of the whole broadcast period */
} gnrc_lwmac_timeout_type_t;
/**
* @brief LWMAC timeout structure
*/
typedef struct {
xtimer_t timer; /**< xtimer entity */
msg_t msg; /**< msg entity */
bool expired; /**< If type != DISABLED, this indicates if timeout has expired */
gnrc_lwmac_timeout_type_t type; /**< timeout type */
} gnrc_lwmac_timeout_t;
/**
* @brief LWMAC specific structure for storing internal states.
*/
typedef struct lwmac {
gnrc_lwmac_state_t state; /**< Internal state of MAC layer */
uint32_t last_wakeup; /**< Used to calculate wakeup times */
uint8_t lwmac_info; /**< LWMAC's internal informations (flags) */
gnrc_lwmac_timeout_t timeouts[GNRC_LWMAC_TIMEOUT_COUNT]; /**< Store timeouts used for protocol */
#if (GNRC_LWMAC_ENABLE_DUTYCYLE_RECORD == 1)
/* Parameters for recording duty-cycle */
uint32_t last_radio_on_time_ticks; /**< The last time in ticks when radio is on */
uint32_t radio_off_time_ticks; /**< The time in ticks when radio is off */
uint32_t system_start_time_ticks; /**< The time in ticks when chip is started */
uint32_t awake_duration_sum_ticks; /**< The sum of time in ticks when radio is on */
uint32_t pkt_start_sending_time_ticks; /**< The time in ticks when the packet is started
to be sent */
#endif
} gnrc_lwmac_t;
#ifdef __cplusplus
}
#endif
#endif /* NET_GNRC_LWMAC_TYPES_H */
/** @} */

View File

@ -29,6 +29,7 @@
#include "net/gnrc/priority_pktqueue.h"
#include "net/ieee802154.h"
#include "net/gnrc/mac/mac.h"
#include "net/gnrc/lwmac/types.h"
#ifdef __cplusplus
extern "C" {
@ -66,6 +67,12 @@ typedef struct {
#if (GNRC_MAC_DISPATCH_BUFFER_SIZE != 0) || defined(DOXYGEN)
gnrc_pktsnip_t *dispatch_buffer[GNRC_MAC_DISPATCH_BUFFER_SIZE]; /**< dispatch packet buffer */
#endif /* (GNRC_MAC_DISPATCH_BUFFER_SIZE != 0) || defined(DOXYGEN) */
#ifdef MODULE_GNRC_LWMAC
gnrc_lwmac_l2_addr_t l2_addr; /**< Records the sender's address */
gnrc_lwmac_rx_state_t state; /**< LWMAC specific internal reception state */
uint8_t rx_bad_exten_count; /**< Count how many unnecessary RX extensions have been executed */
#endif
} gnrc_mac_rx_t;
/**
@ -157,6 +164,15 @@ typedef struct {
gnrc_priority_pktqueue_node_t _queue_nodes[GNRC_MAC_TX_QUEUE_SIZE]; /**< Shared buffer for TX queue nodes */
gnrc_pktsnip_t *packet; /**< currently scheduled packet for sending */
#endif /* (GNRC_MAC_TX_QUEUE_SIZE != 0) || defined(DOXYGEN) */
#ifdef MODULE_GNRC_LWMAC
gnrc_lwmac_tx_state_t state; /**< LWMAC specific internal transmission state */
uint32_t wr_sent; /**< Count how many WRs were sent until WA received */
uint32_t timestamp; /**< Records the receiver's current phase */
uint8_t bcast_seqnr; /**< Sequence number for broadcast data to filter at receiver */
uint8_t tx_burst_count; /**< Count how many consecutive packets have been transmitted */
uint8_t tx_retry_count; /**< Count how many Tx-retrials have been executed before packet drop */
#endif
} gnrc_mac_tx_t;
/**

View File

@ -152,6 +152,14 @@ typedef struct gnrc_netdev {
*/
gnrc_mac_tx_t tx;
#endif /* ((GNRC_MAC_TX_QUEUE_SIZE != 0) || (GNRC_MAC_NEIGHBOR_COUNT == 0)) || defined(DOXYGEN) */
#ifdef MODULE_GNRC_LWMAC
/**
* @brief LWMAC specific structure object for storing LWMAC internal states.
*/
gnrc_lwmac_t lwmac;
#endif
#endif /* MODULE_GNRC_MAC */
} gnrc_netdev_t;

View File

@ -56,6 +56,17 @@ typedef enum {
GNRC_NETTYPE_SIXLOWPAN, /**< Protocol is 6LoWPAN */
#endif
/**
* @{
* @name Link layer
*/
#ifdef MODULE_GNRC_LWMAC
GNRC_NETTYPE_LWMAC, /**< Protocol is lwMAC */
#endif
/**
* @}
*/
/**
* @{
* @name Network layer

View File

@ -67,6 +67,9 @@ endif
ifneq (,$(filter gnrc_pkt,$(USEMODULE)))
DIRS += pkt
endif
ifneq (,$(filter gnrc_lwmac,$(USEMODULE)))
DIRS += link_layer/lwmac
endif
ifneq (,$(filter gnrc_pktbuf_static,$(USEMODULE)))
DIRS += pktbuf_static
endif

View File

@ -241,6 +241,17 @@ void gnrc_mac_dispatch(gnrc_mac_rx_t *rx)
for (unsigned i = 0; i < GNRC_MAC_DISPATCH_BUFFER_SIZE; i++) {
if (rx->dispatch_buffer[i]) {
#ifdef MODULE_GNRC_LWMAC
/* save pointer to netif header */
gnrc_pktsnip_t *netif = rx->dispatch_buffer[i]->next->next;
/* remove lwmac header */
rx->dispatch_buffer[i]->next->next = NULL;
gnrc_pktbuf_release(rx->dispatch_buffer[i]->next);
/* make append netif header after payload again */
rx->dispatch_buffer[i]->next = netif;
#endif
if (!gnrc_netapi_dispatch_receive(rx->dispatch_buffer[i]->type,
GNRC_NETREG_DEMUX_CTX_ALL,
rx->dispatch_buffer[i])) {

View File

@ -0,0 +1,3 @@
MODULE = gnrc_lwmac
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,370 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Interface definition for internal functions of LWMAC protocol
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
*/
#ifndef LWMAC_INTERNAL_H
#define LWMAC_INTERNAL_H
#include <stdint.h>
#include "periph/rtt.h"
#include "net/gnrc/netdev.h"
#include "net/gnrc/mac/types.h"
#include "net/gnrc/lwmac/types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Flag to track if the sender can continue to transmit packet to
* the receiver in its TX procedure.
*
* LWMAC supports burst transmission based on the pending-bit technique.
* Namely, if the sender has multi packets for the same receiver, it can
* successively transmit its packets back to back with this flag set up,
* with the awareness that the receiver will also keep awake for receptions.
*/
#define GNRC_NETDEV_LWMAC_TX_CONTINUE (0x0008U)
/**
* @brief Flag to track if the sender should quit Tx in current cycle.
*
* This flag is mainly for collision avoidance. In case a node overhears
* ongoing broadcast packets stream or other ongoing transmissions of
* other communication pairs during its wake-up period, it sets up this
* flag, which quits all its potential transmission attempts in this current
* cycle (started by the wake-up period), thus not to collide with other
* (neighbor) nodes' transmissions.
*/
#define GNRC_NETDEV_LWMAC_QUIT_TX (0x0010U)
/**
* @brief Flag to track if the device need to reselect a new wake-up phase.
*
* This flag is mainly for potential collision avoidance. In multi-hop scenario,
* it could be dangerous that a sender's wake-up phase is close to its receiver's,
* which may lead to collisions when the sender is sending to the receiver while
* the sender's son nodes are also sending to the sender. To avoid this, in case a
* sender finds its phase close to its receiver's, it sets up this flag and then
* randomly reselects a new wake-up phase.
*/
#define GNRC_NETDEV_LWMAC_PHASE_BACKOFF (0x0020U)
/**
* @brief Flag to track if the device needs to quit the wake-up (listening) procedure.
*
* LWMAC adopts an auto wake-up extension scheme. That is, normally, after each data
* reception in the wake-up period, it extends the wake-up period to another basic
* duration, thus to receive more potential incoming packets, which is also correlated to
* the pending-bit transmission scheme to support burst transmissions to boost throughput.
* However, in some situations, like receiving broadcast (stream) packet, the receiver
* should immediately goto sleep (by setting up this flag) after one reception, thus not
* to receive duplicate broadcast packets.
*/
#define GNRC_NETDEV_LWMAC_QUIT_RX (0x0040U)
/**
* @brief Type to pass information about parsing.
*/
typedef struct {
gnrc_lwmac_hdr_t *header; /**< LWMAC header of packet */
gnrc_lwmac_l2_addr_t src_addr; /**< copied source address of packet */
gnrc_lwmac_l2_addr_t dst_addr; /**< copied destination address of packet */
} gnrc_lwmac_packet_info_t;
/**
* @brief Next RTT event must be at least this far in the future.
*
* When setting an RTT alarm to short in the future it could be possible that
* the counter already passed the calculated alarm before it could be set.
*/
#define GNRC_LWMAC_RTT_EVENT_MARGIN_TICKS (RTT_MS_TO_TICKS(2))
/**
* @brief set the TX-continue flag of the device
*
* @param[in] dev ptr to netdev device
* @param[in] tx_continue value for LWMAC tx-continue flag
*
*/
static inline void gnrc_netdev_lwmac_set_tx_continue(gnrc_netdev_t *dev, bool tx_continue)
{
if (tx_continue) {
dev->mac_info |= GNRC_NETDEV_LWMAC_TX_CONTINUE;
}
else {
dev->mac_info &= ~GNRC_NETDEV_LWMAC_TX_CONTINUE;
}
}
/**
* @brief get the TX-continue flag of the device
*
* @param[in] dev ptr to netdev device
*
* @return true if tx continue
* @return false if tx will continue
*/
static inline bool gnrc_netdev_lwmac_get_tx_continue(gnrc_netdev_t *dev)
{
return (dev->mac_info & GNRC_NETDEV_LWMAC_TX_CONTINUE);
}
/**
* @brief set the quit-TX flag of the device
*
* @param[in] dev ptr to netdev device
* @param[in] quit_tx value for LWMAC quit-TX flag
*
*/
static inline void gnrc_netdev_lwmac_set_quit_tx(gnrc_netdev_t *dev, bool quit_tx)
{
if (quit_tx) {
dev->mac_info |= GNRC_NETDEV_LWMAC_QUIT_TX;
}
else {
dev->mac_info &= ~GNRC_NETDEV_LWMAC_QUIT_TX;
}
}
/**
* @brief get the quit-TX flag of the device
*
* @param[in] dev ptr to netdev device
*
* @return true if quit tx
* @return false if will not quit tx
*/
static inline bool gnrc_netdev_lwmac_get_quit_tx(gnrc_netdev_t *dev)
{
return (dev->mac_info & GNRC_NETDEV_LWMAC_QUIT_TX);
}
/**
* @brief set the phase-backoff flag of the device
*
* @param[in] dev ptr to netdev device
* @param[in] backoff value for LWMAC phase-backoff flag
*
*/
static inline void gnrc_netdev_lwmac_set_phase_backoff(gnrc_netdev_t *dev, bool backoff)
{
if (backoff) {
dev->mac_info |= GNRC_NETDEV_LWMAC_PHASE_BACKOFF;
}
else {
dev->mac_info &= ~GNRC_NETDEV_LWMAC_PHASE_BACKOFF;
}
}
/**
* @brief get the phase-backoff of the device
*
* @param[in] dev ptr to netdev device
*
* @return true if will run phase-backoff
* @return false if will not run phase-backoff
*/
static inline bool gnrc_netdev_lwmac_get_phase_backoff(gnrc_netdev_t *dev)
{
return (dev->mac_info & GNRC_NETDEV_LWMAC_PHASE_BACKOFF);
}
/**
* @brief set the quit-RX flag of the device
*
* @param[in] dev ptr to netdev device
* @param[in] quit_rx value for LWMAC quit-Rx flag
*
*/
static inline void gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev_t *dev, bool quit_rx)
{
if (quit_rx) {
dev->mac_info |= GNRC_NETDEV_LWMAC_QUIT_RX;
}
else {
dev->mac_info &= ~GNRC_NETDEV_LWMAC_QUIT_RX;
}
}
/**
* @brief get the quit-RX flag of the device
*
* @param[in] dev ptr to netdev device
*
* @return true if will quit rx
* @return false if will not quit rx
*/
static inline bool gnrc_netdev_lwmac_get_quit_rx(gnrc_netdev_t *dev)
{
return (dev->mac_info & GNRC_NETDEV_LWMAC_QUIT_RX);
}
/**
* @brief set the duty-cycle-active flag of LWMAC
*
* @param[in] dev ptr to netdev device
* @param[in] active value for LWMAC duty-cycle-active flag
*
*/
static inline void gnrc_netdev_lwmac_set_dutycycle_active(gnrc_netdev_t *dev, bool active)
{
if (active) {
dev->lwmac.lwmac_info |= GNRC_LWMAC_DUTYCYCLE_ACTIVE;
}
else {
dev->lwmac.lwmac_info &= ~GNRC_LWMAC_DUTYCYCLE_ACTIVE;
}
}
/**
* @brief get the duty-cycle-active flag of LWMAC
*
* @param[in] dev ptr to netdev device
*
* @return true if active
* @return false if not active
*/
static inline bool gnrc_netdev_lwmac_get_dutycycle_active(gnrc_netdev_t *dev)
{
return (dev->lwmac.lwmac_info & GNRC_LWMAC_DUTYCYCLE_ACTIVE);
}
/**
* @brief set the needs-rescheduling flag of LWMAC
*
* @param[in] dev ptr to netdev device
* @param[in] reschedule value for LWMAC needs-rescheduling flag
*
*/
static inline void gnrc_netdev_lwmac_set_reschedule(gnrc_netdev_t *dev, bool reschedule)
{
if (reschedule) {
dev->lwmac.lwmac_info |= GNRC_LWMAC_NEEDS_RESCHEDULE;
}
else {
dev->lwmac.lwmac_info &= ~GNRC_LWMAC_NEEDS_RESCHEDULE;
}
}
/**
* @brief get the needs-rescheduling flag of LWMAC
*
* @param[in] dev ptr to netdev device
*
* @return true if needs rescheduling
* @return false if no need for rescheduling
*/
static inline bool gnrc_netdev_lwmac_get_reschedule(gnrc_netdev_t *dev)
{
return (dev->lwmac.lwmac_info & GNRC_LWMAC_NEEDS_RESCHEDULE);
}
/**
* @brief Parse an incoming packet and extract important information.
*
* Copies addresses into @p info, but header points inside @p pkt.
*
* @param[in] pkt packet that will be parsed
* @param[out] info structure that will hold parsed information
*
* @return 0 if correctly parsed
* @return <0 on error
*/
int _gnrc_lwmac_parse_packet(gnrc_pktsnip_t *pkt, gnrc_lwmac_packet_info_t *info);
/**
* @brief Shortcut to get the state of netdev.
*
* @param[in] gnrc_netdev gnrc_netdev structure
*
* @return state of netdev
*/
netopt_state_t _gnrc_lwmac_get_netdev_state(gnrc_netdev_t *gnrc_netdev);
/**
* @brief Shortcut to set the state of netdev
*
* @param[in] gnrc_netdev gnrc_netdev structure
* @param[in] devstate new state for netdev
*/
void _gnrc_lwmac_set_netdev_state(gnrc_netdev_t *gnrc_netdev, netopt_state_t devstate);
/**
* @brief Convert RTT ticks to device phase
*
* @param[in] ticks RTT ticks
*
* @return device phase
*/
static inline uint32_t _gnrc_lwmac_ticks_to_phase(uint32_t ticks)
{
assert(GNRC_LWMAC_WAKEUP_INTERVAL_US != 0);
return (ticks % RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US));
}
/**
* @brief Get device's current phase
*
* @return device phase
*/
static inline uint32_t _gnrc_lwmac_phase_now(void)
{
return _gnrc_lwmac_ticks_to_phase(rtt_get_counter());
}
/**
* @brief Calculate how many ticks remaining to the targeted phase in the future
*
* @param[in] phase device phase
*
* @return RTT ticks
*/
static inline uint32_t _gnrc_lwmac_ticks_until_phase(uint32_t phase)
{
long int tmp = phase - _gnrc_lwmac_phase_now();
if (tmp < 0) {
/* Phase in next interval */
tmp += RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US);
}
return (uint32_t)tmp;
}
/**
* @brief Store the received packet to the dispatch buffer and remove possible
* duplicate packets.
*
* @param[in,out] buffer RX dispatch packet buffer
* @param[in] pkt received packet
*
* @return 0 if correctly stored
* @return <0 on error
*/
int _gnrc_lwmac_dispatch_defer(gnrc_pktsnip_t * buffer[], gnrc_pktsnip_t * pkt);
#ifdef __cplusplus
}
#endif
#endif /* LWMAC_INTERNAL_H */
/** @} */

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Implementation of RX state machine
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
* @}
*/
#ifndef RX_STATE_MACHINE_H
#define RX_STATE_MACHINE_H
#include "net/gnrc/pkt.h"
#include "net/gnrc/netdev.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Start LWMAC RX procedure to receive packet
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
*
*/
void gnrc_lwmac_rx_start(gnrc_netdev_t *gnrc_netdev);
/**
* @brief Stop LWMAC RX procedure
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
*
*/
void gnrc_lwmac_rx_stop(gnrc_netdev_t *gnrc_netdev);
/**
* @brief Update LWMAC RX procedure for packet reception
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
*
*/
void gnrc_lwmac_rx_update(gnrc_netdev_t *gnrc_netdev);
#ifdef __cplusplus
}
#endif
#endif /* RX_STATE_MACHINE_H */

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Implementation of TX state machine
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
* @}
*/
#ifndef TX_STATE_MACHINE_H
#define TX_STATE_MACHINE_H
#include "net/gnrc/pkt.h"
#include "net/gnrc/netdev.h"
#include "net/gnrc/mac/types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Start LWMAC TX procedure to transmit packet @p pkt to @p neighbor
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
* @param[in] pkt packet to transmit
* @param[in] neighbor Tx neighbor
*
*/
void gnrc_lwmac_tx_start(gnrc_netdev_t *gnrc_netdev,
gnrc_pktsnip_t *pkt,
gnrc_mac_tx_neighbor_t *neighbor);
/**
* @brief Stop LWMAC TX procedure
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
*
*/
void gnrc_lwmac_tx_stop(gnrc_netdev_t *gnrc_netdev);
/**
* @brief Update LWMAC TX procedure for transmission
*
* @param[in,out] gnrc_netdev gnrc_netdev structure
*
*/
void gnrc_lwmac_tx_update(gnrc_netdev_t *gnrc_netdev);
#ifdef __cplusplus
}
#endif
#endif /* TX_STATE_MACHINE_H */

View File

@ -0,0 +1,920 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Implementation of the LWMAC protocol
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
* @}
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "kernel_types.h"
#include "msg.h"
#include "thread.h"
#include "timex.h"
#include "random.h"
#include "periph/rtt.h"
#include "net/gnrc.h"
#include "net/netdev.h"
#include "net/gnrc/netdev.h"
#include "net/gnrc/lwmac/types.h"
#include "net/gnrc/lwmac/lwmac.h"
#include "net/gnrc/mac/internal.h"
#include "net/gnrc/lwmac/timeout.h"
#include "include/tx_state_machine.h"
#include "include/rx_state_machine.h"
#include "include/lwmac_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 LWMAC thread's PID
*/
kernel_pid_t lwmac_pid;
static void rtt_cb(void *arg);
static void lwmac_set_state(gnrc_netdev_t *gnrc_netdev, gnrc_lwmac_state_t newstate);
static void lwmac_schedule_update(gnrc_netdev_t *gnrc_netdev);
static void rtt_handler(uint32_t event, gnrc_netdev_t *gnrc_netdev);
static gnrc_mac_tx_neighbor_t *_next_tx_neighbor(gnrc_netdev_t *gnrc_netdev)
{
int next = -1;
uint32_t phase_nearest = GNRC_LWMAC_PHASE_MAX;
for (int i = 0; i < GNRC_MAC_NEIGHBOR_COUNT; i++) {
if (gnrc_priority_pktqueue_length(&gnrc_netdev->tx.neighbors[i].queue) > 0) {
/* Unknown destinations are initialized with their phase at the end
* of the local interval, so known destinations that still wakeup
* in this interval will be preferred. */
uint32_t phase_check = _gnrc_lwmac_ticks_until_phase(gnrc_netdev->tx.neighbors[i].phase);
if (phase_check <= phase_nearest) {
next = i;
phase_nearest = phase_check;
DEBUG("[LWMAC-int] Advancing queue #%d\n", i);
}
}
}
return (next < 0) ? NULL : &(gnrc_netdev->tx.neighbors[next]);
}
static uint32_t _next_inphase_event(uint32_t last, uint32_t interval)
{
/* Counter did overflow since last wakeup */
if (rtt_get_counter() < last) {
/* TODO: Not sure if this was tested :) */
uint32_t tmp = -last;
tmp /= interval;
tmp++;
last += tmp * interval;
}
/* Add margin to next wakeup so that it will be at least 2ms in the future */
while (last < (rtt_get_counter() + GNRC_LWMAC_RTT_EVENT_MARGIN_TICKS)) {
last += interval;
}
return last;
}
inline void lwmac_schedule_update(gnrc_netdev_t *gnrc_netdev)
{
gnrc_netdev_lwmac_set_reschedule(gnrc_netdev, true);
}
void lwmac_set_state(gnrc_netdev_t *gnrc_netdev, gnrc_lwmac_state_t newstate)
{
gnrc_lwmac_state_t oldstate = gnrc_netdev->lwmac.state;
if (newstate == oldstate) {
return;
}
if (newstate >= GNRC_LWMAC_STATE_COUNT) {
LOG_ERROR("ERROR: [LWMAC] Trying to set invalid state %u\n", newstate);
return;
}
/* Already change state, but might be reverted to oldstate when needed */
gnrc_netdev->lwmac.state = newstate;
/* Actions when leaving old state */
switch (oldstate) {
case GNRC_LWMAC_RECEIVING:
case GNRC_LWMAC_TRANSMITTING: {
/* Enable duty cycling again */
rtt_handler(GNRC_LWMAC_EVENT_RTT_RESUME, gnrc_netdev);
#if (GNRC_LWMAC_ENABLE_DUTYCYLE_RECORD == 1)
/* Output duty-cycle ratio */
uint64_t duty;
duty = (uint64_t) rtt_get_counter();
duty = ((uint64_t) gnrc_netdev->lwmac.awake_duration_sum_ticks) * 100 /
(duty - (uint64_t)gnrc_netdev->lwmac.system_start_time_ticks);
printf("[LWMAC]: achieved duty-cycle: %lu %% \n", (uint32_t)duty);
#endif
break;
}
case GNRC_LWMAC_SLEEPING: {
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAKEUP_PERIOD);
break;
}
default:
break;
}
/* Actions when entering new state */
switch (newstate) {
/*********************** Operation states *********************************/
case GNRC_LWMAC_LISTENING: {
_gnrc_lwmac_set_netdev_state(gnrc_netdev, NETOPT_STATE_IDLE);
break;
}
case GNRC_LWMAC_SLEEPING: {
/* Put transceiver to sleep */
_gnrc_lwmac_set_netdev_state(gnrc_netdev, NETOPT_STATE_SLEEP);
/* We may have come here through RTT handler, so timeout may still be active */
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAKEUP_PERIOD);
if (gnrc_netdev_lwmac_get_phase_backoff(gnrc_netdev)) {
gnrc_netdev_lwmac_set_phase_backoff(gnrc_netdev, false);
uint32_t alarm;
rtt_clear_alarm();
alarm = random_uint32_range(RTT_US_TO_TICKS((3 * GNRC_LWMAC_WAKEUP_DURATION_US / 2)),
RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US -
(3 * GNRC_LWMAC_WAKEUP_DURATION_US / 2)));
LOG_WARNING("WARNING: [LWMAC] phase backoffed: %lu us\n", RTT_TICKS_TO_US(alarm));
gnrc_netdev->lwmac.last_wakeup = gnrc_netdev->lwmac.last_wakeup + alarm;
alarm = _next_inphase_event(gnrc_netdev->lwmac.last_wakeup,
RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US));
rtt_set_alarm(alarm, rtt_cb, (void *) GNRC_LWMAC_EVENT_RTT_WAKEUP_PENDING);
}
/* Return immediately, so no rescheduling */
return;
}
/* Trying to send data */
case GNRC_LWMAC_TRANSMITTING: {
rtt_handler(GNRC_LWMAC_EVENT_RTT_PAUSE, gnrc_netdev); /**< No duty cycling while RXing */
_gnrc_lwmac_set_netdev_state(gnrc_netdev, NETOPT_STATE_IDLE); /**< Power up netdev */
break;
}
/* Receiving incoming data */
case GNRC_LWMAC_RECEIVING: {
rtt_handler(GNRC_LWMAC_EVENT_RTT_PAUSE, gnrc_netdev); /**< No duty cycling while TXing */
_gnrc_lwmac_set_netdev_state(gnrc_netdev, NETOPT_STATE_IDLE); /**< Power up netdev */
break;
}
case GNRC_LWMAC_STOPPED: {
_gnrc_lwmac_set_netdev_state(gnrc_netdev, NETOPT_STATE_OFF);
break;
}
/*********************** Control states ***********************************/
case GNRC_LWMAC_START: {
rtt_handler(GNRC_LWMAC_EVENT_RTT_START, gnrc_netdev);
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_LISTENING);
break;
}
case GNRC_LWMAC_STOP: {
rtt_handler(GNRC_LWMAC_EVENT_RTT_STOP, gnrc_netdev);
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_STOPPED);
break;
}
case GNRC_LWMAC_RESET: {
LOG_WARNING("WARNING: [LWMAC] Reset not yet implemented\n");
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_STOP);
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_START);
break;
}
/**************************************************************************/
default: {
LOG_DEBUG("[LWMAC] No actions for entering state %u\n", newstate);
return;
}
}
lwmac_schedule_update(gnrc_netdev);
}
static void _sleep_management(gnrc_netdev_t *gnrc_netdev)
{
/* If a packet is scheduled, no other (possible earlier) packet can be
* sent before the first one is handled, even no broadcast
*/
if (!gnrc_lwmac_timeout_is_running(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAIT_DEST_WAKEUP)) {
gnrc_mac_tx_neighbor_t *neighbour;
/* Check if there is packet remaining for retransmission */
if (gnrc_netdev->tx.current_neighbor != NULL) {
neighbour = gnrc_netdev->tx.current_neighbor;
}
else {
/* Check if there are broadcasts to send and transmit immediately */
if (gnrc_priority_pktqueue_length(&(gnrc_netdev->tx.neighbors[0].queue)) > 0) {
gnrc_netdev->tx.current_neighbor = &(gnrc_netdev->tx.neighbors[0]);
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_TRANSMITTING);
return;
}
neighbour = _next_tx_neighbor(gnrc_netdev);
}
if (neighbour != NULL) {
/* if phase is unknown, send immediately. */
if (neighbour->phase > RTT_TICKS_TO_US(GNRC_LWMAC_WAKEUP_INTERVAL_US)) {
gnrc_netdev->tx.current_neighbor = neighbour;
gnrc_netdev_lwmac_set_tx_continue(gnrc_netdev, false);
gnrc_netdev->tx.tx_burst_count = 0;
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_TRANSMITTING);
return;
}
/* Offset in microseconds when the earliest (phase) destination
* node wakes up that we have packets for. */
int time_until_tx = RTT_TICKS_TO_US(_gnrc_lwmac_ticks_until_phase(neighbour->phase));
/* If there's not enough time to prepare a WR to catch the phase
* postpone to next interval */
if (time_until_tx < GNRC_LWMAC_WR_PREPARATION_US) {
time_until_tx += GNRC_LWMAC_WAKEUP_INTERVAL_US;
}
time_until_tx -= GNRC_LWMAC_WR_PREPARATION_US;
/* add a random time before goto TX, for avoiding one node for
* always holding the medium (if the receiver's phase is recorded earlier in this
* particular node) */
uint32_t random_backoff;
random_backoff = random_uint32_range(0, GNRC_LWMAC_TIME_BETWEEN_WR_US);
time_until_tx = time_until_tx + random_backoff;
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAIT_DEST_WAKEUP, time_until_tx);
/* Register neighbour to be the next */
gnrc_netdev->tx.current_neighbor = neighbour;
/* Stop dutycycling, we're preparing to send. This prevents the
* timeout arriving late, so that the destination phase would
* be missed. */
/* TODO: bad for power savings */
rtt_handler(GNRC_LWMAC_EVENT_RTT_PAUSE, gnrc_netdev);
}
}
else if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAIT_DEST_WAKEUP)) {
LOG_DEBUG("[LWMAC] Got timeout for dest wakeup, ticks: %" PRIu32 "\n", rtt_get_counter());
gnrc_netdev_lwmac_set_tx_continue(gnrc_netdev, false);
gnrc_netdev->tx.tx_burst_count = 0;
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_TRANSMITTING);
}
}
static void _rx_management_failed(gnrc_netdev_t *gnrc_netdev)
{
/* This may happen frequently because we'll receive WA from
* every node in range. */
LOG_DEBUG("[LWMAC] Reception was NOT successful\n");
gnrc_lwmac_rx_stop(gnrc_netdev);
if (gnrc_netdev->rx.rx_bad_exten_count >= GNRC_LWMAC_MAX_RX_EXTENSION_NUM) {
gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev, true);
}
/* Here we check if we are close to the end of the cycle. If yes,
* go to sleep. Firstly, get the relative phase. */
uint32_t phase = rtt_get_counter();
if (phase < gnrc_netdev->lwmac.last_wakeup) {
phase = (RTT_US_TO_TICKS(GNRC_LWMAC_PHASE_MAX) - gnrc_netdev->lwmac.last_wakeup) +
phase;
}
else {
phase = phase - gnrc_netdev->lwmac.last_wakeup;
}
/* If the relative phase is beyond 4/5 cycle time, go to sleep. */
if (phase > (4*RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US)/5)) {
gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev, true);
}
if (gnrc_netdev_lwmac_get_quit_rx(gnrc_netdev)) {
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_SLEEPING);
}
else {
/* Go back to LISTENING for keep hearing on the channel */
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_LISTENING);
}
}
static void _rx_management_success(gnrc_netdev_t *gnrc_netdev)
{
LOG_DEBUG("[LWMAC] Reception was successful\n");
gnrc_lwmac_rx_stop(gnrc_netdev);
/* Dispatch received packets, timing is not critical anymore */
gnrc_mac_dispatch(&gnrc_netdev->rx);
/* Here we check if we are close to the end of the cycle. If yes,
* go to sleep. Firstly, get the relative phase. */
uint32_t phase = rtt_get_counter();
if (phase < gnrc_netdev->lwmac.last_wakeup) {
phase = (RTT_US_TO_TICKS(GNRC_LWMAC_PHASE_MAX) - gnrc_netdev->lwmac.last_wakeup) +
phase;
}
else {
phase = phase - gnrc_netdev->lwmac.last_wakeup;
}
/* If the relative phase is beyond 4/5 cycle time, go to sleep. */
if (phase > (4*RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US)/5)) {
gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev, true);
}
if (gnrc_netdev_lwmac_get_quit_rx(gnrc_netdev)) {
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_SLEEPING);
}
else {
/* Go back to LISTENING after successful reception */
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_LISTENING);
}
}
static void _rx_management(gnrc_netdev_t *gnrc_netdev)
{
gnrc_lwmac_rx_state_t state_rx = gnrc_netdev->rx.state;
switch (state_rx) {
case GNRC_LWMAC_RX_STATE_STOPPED: {
gnrc_lwmac_rx_start(gnrc_netdev);
gnrc_lwmac_rx_update(gnrc_netdev);
break;
}
case GNRC_LWMAC_RX_STATE_FAILED: {
_rx_management_failed(gnrc_netdev);
break;
}
case GNRC_LWMAC_RX_STATE_SUCCESSFUL: {
_rx_management_success(gnrc_netdev);
break;
}
default:
gnrc_lwmac_rx_update(gnrc_netdev);
}
/* If state has changed, reschedule main state machine */
if (state_rx != gnrc_netdev->rx.state) {
lwmac_schedule_update(gnrc_netdev);
}
}
static void _tx_management_stopped(gnrc_netdev_t *gnrc_netdev)
{
gnrc_pktsnip_t *pkt;
/* If there is packet remaining for retransmission,
* retransmit it (i.e., the retransmission scheme of LWMAC). */
if (gnrc_netdev->tx.packet != NULL) {
LOG_WARNING("WARNING: [LWMAC] TX %d times retry\n",
gnrc_netdev->tx.tx_retry_count);
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_INIT;
gnrc_netdev->tx.wr_sent = 0;
gnrc_lwmac_tx_update(gnrc_netdev);
}
else {
if ((pkt = gnrc_priority_pktqueue_pop(
&gnrc_netdev->tx.current_neighbor->queue))) {
gnrc_netdev->tx.tx_retry_count = 0;
gnrc_lwmac_tx_start(gnrc_netdev, pkt, gnrc_netdev->tx.current_neighbor);
gnrc_lwmac_tx_update(gnrc_netdev);
}
else {
/* Shouldn't happen, but never observed this case */
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_SLEEPING);
}
}
}
static void _tx_management_success(gnrc_netdev_t *gnrc_netdev)
{
if (gnrc_netdev->tx.current_neighbor == &(gnrc_netdev->tx.neighbors[0])) {
LOG_INFO("[LWMAC] Broadcast transmission done\n");
}
gnrc_lwmac_tx_stop(gnrc_netdev);
/* In case have pending packets for the same receiver, continue to
* send immediately, before the maximum transmit-limit */
if ((gnrc_netdev_lwmac_get_tx_continue(gnrc_netdev)) &&
(gnrc_netdev->tx.tx_burst_count < GNRC_LWMAC_MAX_TX_BURST_PKT_NUM)) {
lwmac_schedule_update(gnrc_netdev);
}
else {
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_SLEEPING);
}
}
static void _tx_management(gnrc_netdev_t *gnrc_netdev)
{
gnrc_lwmac_tx_state_t state_tx = gnrc_netdev->tx.state;
switch (state_tx) {
case GNRC_LWMAC_TX_STATE_STOPPED: {
_tx_management_stopped(gnrc_netdev);
break;
}
case GNRC_LWMAC_TX_STATE_FAILED: {
/* If transmission failure, do not try burst transmissions and quit other
* transmission attempts in this cycle for collision avoidance */
gnrc_netdev_lwmac_set_tx_continue(gnrc_netdev, false);
gnrc_netdev_lwmac_set_quit_tx(gnrc_netdev, true);
/* falls through */
/* TX packet will therefore be dropped. No automatic resending here,
* we did our best.
*/
}
case GNRC_LWMAC_TX_STATE_SUCCESSFUL: {
_tx_management_success(gnrc_netdev);
break;
}
default:
gnrc_lwmac_tx_update(gnrc_netdev);
}
/* If state has changed, reschedule main state machine */
if (state_tx != gnrc_netdev->tx.state) {
lwmac_schedule_update(gnrc_netdev);
}
}
static void _lwmac_update_listening(gnrc_netdev_t *gnrc_netdev)
{
/* In case has pending packet to send, clear rtt alarm thus to goto
* transmission initialization (in SLEEPING management) right after the
* listening period */
if ((_next_tx_neighbor(gnrc_netdev) != NULL) ||
(gnrc_netdev->tx.current_neighbor != NULL)) {
rtt_handler(GNRC_LWMAC_EVENT_RTT_PAUSE, gnrc_netdev);
}
/* Set timeout for if there's no successful rx transaction that will
* change state to SLEEPING. */
if (!gnrc_lwmac_timeout_is_running(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAKEUP_PERIOD)) {
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAKEUP_PERIOD, GNRC_LWMAC_WAKEUP_DURATION_US);
}
else if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAKEUP_PERIOD)) {
/* Dispatch first as there still may be broadcast packets. */
gnrc_mac_dispatch(&gnrc_netdev->rx);
gnrc_netdev->lwmac.state = GNRC_LWMAC_SLEEPING;
/* Enable duty cycling again */
rtt_handler(GNRC_LWMAC_EVENT_RTT_RESUME, gnrc_netdev);
_gnrc_lwmac_set_netdev_state(gnrc_netdev, NETOPT_STATE_SLEEP);
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAKEUP_PERIOD);
/* if there is a packet for transmission, schedule update to start
* transmission initialization immediately. */
gnrc_mac_tx_neighbor_t *neighbour = _next_tx_neighbor(gnrc_netdev);
if ((neighbour != NULL) || (gnrc_netdev->tx.current_neighbor != NULL)) {
/* This triggers packet sending procedure in sleeping immediately. */
lwmac_schedule_update(gnrc_netdev);
return;
}
}
if (gnrc_priority_pktqueue_length(&gnrc_netdev->rx.queue) > 0) {
/* Do wake-up extension in each packet reception. */
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WAKEUP_PERIOD);
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_RECEIVING);
}
}
/* Main state machine. Call whenever something happens */
static bool lwmac_update(gnrc_netdev_t *gnrc_netdev)
{
gnrc_netdev_lwmac_set_reschedule(gnrc_netdev, false);
switch (gnrc_netdev->lwmac.state) {
case GNRC_LWMAC_SLEEPING: {
/* Quit scheduling transmission if 'quit-tx' flag is found set, thus
* to avoid potential collisions with ongoing transmissions of other
* neighbor nodes */
if (gnrc_netdev_lwmac_get_quit_tx(gnrc_netdev)) {
return false;
}
_sleep_management(gnrc_netdev);
break;
}
case GNRC_LWMAC_LISTENING: {
_lwmac_update_listening(gnrc_netdev);
break;
}
case GNRC_LWMAC_RECEIVING: {
_rx_management(gnrc_netdev);
break;
}
case GNRC_LWMAC_TRANSMITTING: {
_tx_management(gnrc_netdev);
break;
}
default:
LOG_DEBUG("[LWMAC] No actions in state %u\n", gnrc_netdev->lwmac.state);
}
return gnrc_netdev_lwmac_get_reschedule(gnrc_netdev);
}
static void rtt_cb(void *arg)
{
msg_t msg;
msg.content.value = ((uint32_t) arg) & 0xffff;
msg.type = GNRC_LWMAC_EVENT_RTT_TYPE;
msg_send(&msg, lwmac_pid);
if (sched_context_switch_request) {
thread_yield();
}
}
void rtt_handler(uint32_t event, gnrc_netdev_t *gnrc_netdev)
{
uint32_t alarm;
switch (event & 0xffff) {
case GNRC_LWMAC_EVENT_RTT_WAKEUP_PENDING: {
/* A new cycle starts, set sleep timing and initialize related MAC-info flags. */
gnrc_netdev->lwmac.last_wakeup = rtt_get_alarm();
alarm = _next_inphase_event(gnrc_netdev->lwmac.last_wakeup,
RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_DURATION_US));
rtt_set_alarm(alarm, rtt_cb, (void *) GNRC_LWMAC_EVENT_RTT_SLEEP_PENDING);
gnrc_netdev_lwmac_set_quit_tx(gnrc_netdev, false);
gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev, false);
gnrc_netdev_lwmac_set_phase_backoff(gnrc_netdev, false);
gnrc_netdev->rx.rx_bad_exten_count = 0;
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_LISTENING);
break;
}
case GNRC_LWMAC_EVENT_RTT_SLEEP_PENDING: {
/* Set next wake-up timing. */
alarm = _next_inphase_event(gnrc_netdev->lwmac.last_wakeup,
RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US));
rtt_set_alarm(alarm, rtt_cb, (void *) GNRC_LWMAC_EVENT_RTT_WAKEUP_PENDING);
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_SLEEPING);
break;
}
/* Set initial wake-up alarm that starts the cycle */
case GNRC_LWMAC_EVENT_RTT_START: {
LOG_DEBUG("[LWMAC] RTT: Initialize duty cycling\n");
alarm = rtt_get_counter() + RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_DURATION_US);
rtt_set_alarm(alarm, rtt_cb, (void *) GNRC_LWMAC_EVENT_RTT_SLEEP_PENDING);
gnrc_netdev_lwmac_set_dutycycle_active(gnrc_netdev, true);
break;
}
case GNRC_LWMAC_EVENT_RTT_STOP:
case GNRC_LWMAC_EVENT_RTT_PAUSE: {
rtt_clear_alarm();
LOG_DEBUG("[LWMAC] RTT: Stop duty cycling, now in state %u\n",
gnrc_netdev->lwmac.state);
gnrc_netdev_lwmac_set_dutycycle_active(gnrc_netdev, false);
break;
}
case GNRC_LWMAC_EVENT_RTT_RESUME: {
LOG_DEBUG("[LWMAC] RTT: Resume duty cycling\n");
rtt_clear_alarm();
alarm = _next_inphase_event(gnrc_netdev->lwmac.last_wakeup,
RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US));
rtt_set_alarm(alarm, rtt_cb, (void *) GNRC_LWMAC_EVENT_RTT_WAKEUP_PENDING);
gnrc_netdev_lwmac_set_dutycycle_active(gnrc_netdev, true);
break;
}
default:
break;
}
}
/**
* @brief Function called by the device driver on device events
*
* @param[in] event type of event
* @param[in] data optional parameter
*/
static void _event_cb(netdev_t *dev, netdev_event_t event)
{
gnrc_netdev_t *gnrc_netdev = (gnrc_netdev_t *) dev->context;
if (event == NETDEV_EVENT_ISR) {
msg_t msg;
msg.type = NETDEV_MSG_TYPE_EVENT;
msg.content.ptr = (void *) gnrc_netdev;
if (msg_send(&msg, gnrc_netdev->pid) <= 0) {
LOG_WARNING("WARNING: [LWMAC] gnrc_netdev: possibly lost interrupt.\n");
}
}
else {
DEBUG("gnrc_netdev: event triggered -> %i\n", event);
switch (event) {
case NETDEV_EVENT_RX_STARTED: {
LOG_DEBUG("[LWMAC] NETDEV_EVENT_RX_STARTED\n");
gnrc_netdev_set_rx_started(gnrc_netdev, true);
break;
}
case NETDEV_EVENT_RX_COMPLETE: {
LOG_DEBUG("[LWMAC] NETDEV_EVENT_RX_COMPLETE\n");
gnrc_pktsnip_t *pkt = gnrc_netdev->recv(gnrc_netdev);
/* Prevent packet corruption when a packet is sent before the previous
* received packet has been downloaded. This happens e.g. when a timeout
* expires that causes the tx state machine to send a packet. When a
* packet arrives after the timeout, the notification is queued but the
* tx state machine continues to send and then destroys the received
* packet in the frame buffer. After completion, the queued notification
* will be handled a corrupted packet will be downloaded. Therefore
* keep track that RX_STARTED is followed by RX_COMPLETE.
*
* TODO: transceivers might have 2 frame buffers, so make this optional
*/
if (pkt == NULL) {
gnrc_netdev_set_rx_started(gnrc_netdev, false);
break;
}
gnrc_netdev_set_rx_started(gnrc_netdev, false);
if (!gnrc_mac_queue_rx_packet(&gnrc_netdev->rx, 0, pkt)) {
LOG_ERROR("ERROR: [LWMAC] Can't push RX packet @ %p, memory full?\n", pkt);
gnrc_pktbuf_release(pkt);
break;
}
lwmac_schedule_update(gnrc_netdev);
break;
}
case NETDEV_EVENT_TX_STARTED: {
gnrc_netdev_set_tx_feedback(gnrc_netdev, TX_FEEDBACK_UNDEF);
gnrc_netdev_set_rx_started(gnrc_netdev, false);
break;
}
case NETDEV_EVENT_TX_COMPLETE: {
gnrc_netdev_set_tx_feedback(gnrc_netdev, TX_FEEDBACK_SUCCESS);
gnrc_netdev_set_rx_started(gnrc_netdev, false);
lwmac_schedule_update(gnrc_netdev);
break;
}
case NETDEV_EVENT_TX_NOACK: {
gnrc_netdev_set_tx_feedback(gnrc_netdev, TX_FEEDBACK_NOACK);
gnrc_netdev_set_rx_started(gnrc_netdev, false);
lwmac_schedule_update(gnrc_netdev);
break;
}
case NETDEV_EVENT_TX_MEDIUM_BUSY: {
gnrc_netdev_set_tx_feedback(gnrc_netdev, TX_FEEDBACK_BUSY);
gnrc_netdev_set_rx_started(gnrc_netdev, false);
lwmac_schedule_update(gnrc_netdev);
break;
}
default:
LOG_WARNING("WARNING: [LWMAC] Unhandled netdev event: %u\n", event);
}
}
}
/**
* @brief Startup code and event loop of the LWMAC layer
*
* @param[in] args expects a pointer to the underlying netdev device
*
* @return never returns
*/
static void *_lwmac_thread(void *args)
{
gnrc_netdev_t *gnrc_netdev = (gnrc_netdev_t *)args;
netdev_t *dev = gnrc_netdev->dev;
gnrc_netdev->pid = thread_getpid();
gnrc_netapi_opt_t *opt;
int res;
msg_t msg, reply, msg_queue[GNRC_LWMAC_IPC_MSG_QUEUE_SIZE];
LOG_INFO("[LWMAC] Starting LWMAC\n");
/* RTT is used for scheduling wakeup */
rtt_init();
/* Store pid globally, so that IRQ can use it to send msg */
lwmac_pid = thread_getpid();
/* setup the MAC layers message queue */
msg_init_queue(msg_queue, GNRC_LWMAC_IPC_MSG_QUEUE_SIZE);
/* register the event callback with the device driver */
dev->event_callback = _event_cb;
dev->context = (void *) gnrc_netdev;
/* register the device to the network stack*/
gnrc_netif_add(thread_getpid());
/* initialize low-level driver */
dev->driver->init(dev);
/* Enable RX- and TX-started interrupts */
netopt_enable_t enable = NETOPT_ENABLE;
dev->driver->set(dev, NETOPT_RX_START_IRQ, &enable, sizeof(enable));
dev->driver->set(dev, NETOPT_TX_START_IRQ, &enable, sizeof(enable));
dev->driver->set(dev, NETOPT_TX_END_IRQ, &enable, sizeof(enable));
uint16_t src_len = 8;
dev->driver->set(dev, NETOPT_SRC_LEN, &src_len, sizeof(src_len));
/* Get own address from netdev */
gnrc_netdev->l2_addr_len = dev->driver->get(dev, NETOPT_ADDRESS_LONG,
&gnrc_netdev->l2_addr,
IEEE802154_LONG_ADDRESS_LEN);
assert(gnrc_netdev->l2_addr_len > 0);
/* Initialize broadcast sequence number. This at least differs from board
* to board */
gnrc_netdev->tx.bcast_seqnr = gnrc_netdev->l2_addr[0];
/* Reset all timeouts just to be sure */
gnrc_lwmac_reset_timeouts(gnrc_netdev);
/* Start duty cycling */
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_START);
#if (GNRC_LWMAC_ENABLE_DUTYCYLE_RECORD == 1)
/* Start duty cycle recording */
gnrc_netdev->lwmac.system_start_time_ticks = rtt_get_counter();
gnrc_netdev->lwmac.last_radio_on_time_ticks = gnrc_netdev->lwmac.system_start_time_ticks;
gnrc_netdev->lwmac.awake_duration_sum_ticks = 0;
gnrc_netdev->lwmac.lwmac_info |= GNRC_LWMAC_RADIO_IS_ON;
#endif
/* start the event loop */
while (1) {
msg_receive(&msg);
/* Handle NETDEV, NETAPI, RTT and TIMEOUT messages */
switch (msg.type) {
/* RTT raised an interrupt */
case GNRC_LWMAC_EVENT_RTT_TYPE: {
if (gnrc_netdev_lwmac_get_dutycycle_active(gnrc_netdev)) {
rtt_handler(msg.content.value, gnrc_netdev);
lwmac_schedule_update(gnrc_netdev);
}
else {
LOG_DEBUG("[LWMAC] Ignoring late RTT event while dutycycling is off\n");
}
break;
}
/* An LWMAC timeout occured */
case GNRC_LWMAC_EVENT_TIMEOUT_TYPE: {
gnrc_lwmac_timeout_make_expire((gnrc_lwmac_timeout_t *) msg.content.ptr);
lwmac_schedule_update(gnrc_netdev);
break;
}
/* Transceiver raised an interrupt */
case NETDEV_MSG_TYPE_EVENT: {
LOG_DEBUG("[LWMAC] GNRC_NETDEV_MSG_TYPE_EVENT received\n");
/* Forward event back to driver */
dev->driver->isr(dev);
break;
}
/* TX: Queue for sending */
case GNRC_NETAPI_MSG_TYPE_SND: {
/* TODO: how to announce failure to upper layers? */
LOG_DEBUG("[LWMAC] GNRC_NETAPI_MSG_TYPE_SND received\n");
gnrc_pktsnip_t *pkt = (gnrc_pktsnip_t *) msg.content.ptr;
if (!gnrc_mac_queue_tx_packet(&gnrc_netdev->tx, 0, pkt)) {
gnrc_pktbuf_release(pkt);
LOG_WARNING("WARNING: [LWMAC] TX queue full, drop packet\n");
}
lwmac_schedule_update(gnrc_netdev);
break;
}
/* NETAPI set/get. Can't this be refactored away from here? */
case GNRC_NETAPI_MSG_TYPE_SET: {
LOG_DEBUG("[LWMAC] GNRC_NETAPI_MSG_TYPE_SET received\n");
opt = (gnrc_netapi_opt_t *)msg.content.ptr;
/* Depending on option forward to NETDEV or handle here */
switch (opt->opt) {
/* Handle state change requests */
case NETOPT_STATE: {
netopt_state_t *state = (netopt_state_t *) opt->data;
res = opt->data_len;
switch (*state) {
case NETOPT_STATE_OFF: {
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_STOP);
break;
}
case NETOPT_STATE_IDLE: {
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_START);
break;
}
case NETOPT_STATE_RESET: {
lwmac_set_state(gnrc_netdev, GNRC_LWMAC_RESET);
break;
}
default:
res = -EINVAL;
LOG_ERROR("ERROR: [LWMAC] NETAPI tries to set unsupported"
" state %u\n",*state);
}
lwmac_schedule_update(gnrc_netdev);
break;
}
/* Forward to netdev by default*/
default:
/* set option for device driver */
res = dev->driver->set(dev, opt->opt, opt->data, opt->data_len);
LOG_DEBUG("[LWMAC] Response of netdev->set: %i\n", res);
}
/* send reply to calling thread */
reply.type = GNRC_NETAPI_MSG_TYPE_ACK;
reply.content.value = (uint32_t)res;
msg_reply(&msg, &reply);
break;
}
case GNRC_NETAPI_MSG_TYPE_GET: {
/* TODO: filter out MAC layer options -> for now forward
everything to the device driver */
LOG_DEBUG("[LWMAC] GNRC_NETAPI_MSG_TYPE_GET received\n");
/* read incoming options */
opt = (gnrc_netapi_opt_t *)msg.content.ptr;
/* get option from device driver */
res = dev->driver->get(dev, opt->opt, opt->data, opt->data_len);
LOG_DEBUG("[LWMAC] Response of netdev->get: %i\n", res);
/* send reply to calling thread */
reply.type = GNRC_NETAPI_MSG_TYPE_ACK;
reply.content.value = (uint32_t)res;
msg_reply(&msg, &reply);
break;
}
default:
LOG_ERROR("ERROR: [LWMAC] Unknown command %" PRIu16 "\n", msg.type);
break;
}
/* Execute main state machine because something just happend*/
while (gnrc_netdev_lwmac_get_reschedule(gnrc_netdev)) {
lwmac_update(gnrc_netdev);
}
}
LOG_ERROR("ERROR: [LWMAC] terminated\n");
/* never reached */
return NULL;
}
kernel_pid_t gnrc_lwmac_init(char *stack, int stacksize, char priority,
const char *name, gnrc_netdev_t *dev)
{
kernel_pid_t res;
/* check if given netdev device is defined and the driver is set */
if (dev == NULL || dev->dev == NULL) {
LOG_ERROR("ERROR: [LWMAC] No netdev supplied or driver not set\n");
return -ENODEV;
}
/* create new LWMAC thread */
res = thread_create(stack, stacksize, priority, THREAD_CREATE_STACKTEST,
_lwmac_thread, (void *)dev, name);
if (res <= 0) {
LOG_ERROR("ERROR: [LWMAC] Couldn't create thread\n");
return -EINVAL;
}
return res;
}

View File

@ -0,0 +1,192 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Implementation of internal functions of LWMAC
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
* @}
*/
#include <stdbool.h>
#include "periph/rtt.h"
#include "net/gnrc.h"
#include "net/gnrc/mac/mac.h"
#include "net/gnrc/netdev.h"
#include "net/gnrc/lwmac/lwmac.h"
#include "include/lwmac_internal.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
int _gnrc_lwmac_parse_packet(gnrc_pktsnip_t *pkt, gnrc_lwmac_packet_info_t *info)
{
gnrc_netif_hdr_t *netif_hdr;
gnrc_pktsnip_t *lwmac_snip;
gnrc_lwmac_hdr_t *lwmac_hdr;
assert(info != NULL);
assert(pkt != NULL);
netif_hdr = (gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_NETIF))->data;
if (netif_hdr == NULL) {
return -1;
}
/* Dissect LWMAC header, Every frame has header as first member */
lwmac_hdr = (gnrc_lwmac_hdr_t *) pkt->data;
switch (lwmac_hdr->type) {
case GNRC_LWMAC_FRAMETYPE_WR: {
lwmac_snip = gnrc_pktbuf_mark(pkt, sizeof(gnrc_lwmac_frame_wr_t),
GNRC_NETTYPE_LWMAC);
break;
}
case GNRC_LWMAC_FRAMETYPE_WA: {
lwmac_snip = gnrc_pktbuf_mark(pkt, sizeof(gnrc_lwmac_frame_wa_t),
GNRC_NETTYPE_LWMAC);
break;
}
case GNRC_LWMAC_FRAMETYPE_DATA_PENDING:
case GNRC_LWMAC_FRAMETYPE_DATA: {
lwmac_snip = gnrc_pktbuf_mark(pkt, sizeof(gnrc_lwmac_frame_data_t),
GNRC_NETTYPE_LWMAC);
break;
}
case GNRC_LWMAC_FRAMETYPE_BROADCAST: {
lwmac_snip = gnrc_pktbuf_mark(pkt, sizeof(gnrc_lwmac_frame_broadcast_t),
GNRC_NETTYPE_LWMAC);
break;
}
default: {
return -2;
}
}
/* Memory location may have changed while marking */
lwmac_hdr = lwmac_snip->data;
if (lwmac_hdr->type == GNRC_LWMAC_FRAMETYPE_WA) {
/* WA is broadcast, so get dst address out of header instead of netif */
info->dst_addr = ((gnrc_lwmac_frame_wa_t *)lwmac_hdr)->dst_addr;
}
else if (lwmac_hdr->type == GNRC_LWMAC_FRAMETYPE_WR) {
/* WR is broadcast, so get dst address out of header instead of netif */
info->dst_addr = ((gnrc_lwmac_frame_wr_t *)lwmac_hdr)->dst_addr;
}
else if (netif_hdr->dst_l2addr_len) {
info->dst_addr.len = netif_hdr->dst_l2addr_len;
memcpy(info->dst_addr.addr,
gnrc_netif_hdr_get_dst_addr(netif_hdr),
netif_hdr->dst_l2addr_len);
}
if (netif_hdr->src_l2addr_len) {
info->src_addr.len = netif_hdr->src_l2addr_len;
memcpy(info->src_addr.addr,
gnrc_netif_hdr_get_src_addr(netif_hdr),
netif_hdr->src_l2addr_len);
}
info->header = lwmac_hdr;
return 0;
}
void _gnrc_lwmac_set_netdev_state(gnrc_netdev_t *gnrc_netdev, netopt_state_t devstate)
{
gnrc_netdev->dev->driver->set(gnrc_netdev->dev,
NETOPT_STATE,
&devstate,
sizeof(devstate));
#if (GNRC_LWMAC_ENABLE_DUTYCYLE_RECORD == 1)
if (devstate == NETOPT_STATE_IDLE) {
if (!(gnrc_netdev->lwmac.lwmac_info & GNRC_LWMAC_RADIO_IS_ON)) {
gnrc_netdev->lwmac.last_radio_on_time_ticks = rtt_get_counter();
gnrc_netdev->lwmac.lwmac_info |= GNRC_LWMAC_RADIO_IS_ON;
}
return;
}
else if ((devstate == NETOPT_STATE_SLEEP) &&
(gnrc_netdev->lwmac.lwmac_info & GNRC_LWMAC_RADIO_IS_ON)) {
gnrc_netdev->lwmac.radio_off_time_ticks = rtt_get_counter();
gnrc_netdev->lwmac.awake_duration_sum_ticks +=
(gnrc_netdev->lwmac.radio_off_time_ticks -
gnrc_netdev->lwmac.last_radio_on_time_ticks);
gnrc_netdev->lwmac.lwmac_info &= ~GNRC_LWMAC_RADIO_IS_ON;
}
#endif
}
netopt_state_t _gnrc_lwmac_get_netdev_state(gnrc_netdev_t *gnrc_netdev)
{
netopt_state_t state;
if (0 < gnrc_netdev->dev->driver->get(gnrc_netdev->dev,
NETOPT_STATE,
&state,
sizeof(state))) {
return state;
}
return -1;
}
int _gnrc_lwmac_dispatch_defer(gnrc_pktsnip_t *buffer[], gnrc_pktsnip_t *pkt)
{
assert(buffer != NULL);
assert(pkt != NULL);
/* We care about speed here, so assume packet structure */
assert(pkt->next->type == GNRC_NETTYPE_LWMAC);
assert(pkt->next->next->type == GNRC_NETTYPE_NETIF);
gnrc_lwmac_frame_broadcast_t *bcast = NULL;
if (((gnrc_lwmac_hdr_t *)pkt->next->data)->type == GNRC_LWMAC_FRAMETYPE_BROADCAST) {
bcast = pkt->next->data;
}
for (unsigned i = 0; i < GNRC_MAC_DISPATCH_BUFFER_SIZE; i++) {
/* Buffer will be filled bottom-up and emptied completely so no holes */
if (buffer[i] == NULL) {
buffer[i] = pkt;
return 0;
}
else if (bcast &&
(((gnrc_lwmac_hdr_t *)buffer[i]->next->data)->type == GNRC_LWMAC_FRAMETYPE_BROADCAST) &&
(bcast->seq_nr == ((gnrc_lwmac_frame_broadcast_t *)buffer[i]->next->data)->seq_nr)) {
/* Filter same broadcasts, compare sequence number */
gnrc_netif_hdr_t *hdr_queued, *hdr_new;
hdr_new = pkt->next->next->data;
hdr_queued = buffer[i]->next->next->data;
/* Sequence numbers match, compare source addresses */
if ((hdr_new->src_l2addr_len == hdr_queued->src_l2addr_len) &&
(memcmp(gnrc_netif_hdr_get_src_addr(hdr_new),
gnrc_netif_hdr_get_src_addr(hdr_queued),
hdr_new->src_l2addr_len) == 0)) {
/* Source addresses match, same packet */
DEBUG("[LWMAC] Found duplicate broadcast packet, dropping\n");
gnrc_pktbuf_release(pkt);
return -2;
}
}
}
DEBUG("[LWMAC] Dispatch buffer full, dropping packet\n");
gnrc_pktbuf_release(pkt);
return -1;
}

View File

@ -0,0 +1,437 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Implementation of RX state machine of LWMAC protocol
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
* @}
*/
#include "net/gnrc.h"
#include "net/gnrc/lwmac/lwmac.h"
#include "net/gnrc/mac/internal.h"
#include "net/gnrc/lwmac/timeout.h"
#include "include/rx_state_machine.h"
#include "include/lwmac_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 Flag to track if the receiver has got a broadcast packet
*/
#define GNRC_LWMAC_RX_FOUND_BROADCAST (0x01U)
/**
* @brief Flag to track if the receiver has got a WR packet
*/
#define GNRC_LWMAC_RX_FOUND_WR (0x02U)
/**
* @brief Flag to track if the receiver has got a data packet
*/
#define GNRC_LWMAC_RX_FOUND_DATA (0x04U)
static uint8_t _packet_process_in_wait_for_wr(gnrc_netdev_t *gnrc_netdev)
{
uint8_t rx_info = 0;
gnrc_pktsnip_t *pkt;
assert(gnrc_netdev != NULL);
while ((pkt = gnrc_priority_pktqueue_pop(&gnrc_netdev->rx.queue)) != NULL) {
LOG_DEBUG("[LWMAC-rx] Inspecting pkt @ %p\n", pkt);
/* Parse packet */
gnrc_lwmac_packet_info_t info;
if (_gnrc_lwmac_parse_packet(pkt, &info) != 0) {
LOG_DEBUG("[LWMAC-rx] Packet could not be parsed\n");
gnrc_pktbuf_release(pkt);
continue;
}
if (info.header->type == GNRC_LWMAC_FRAMETYPE_BROADCAST) {
_gnrc_lwmac_dispatch_defer(gnrc_netdev->rx.dispatch_buffer, pkt);
gnrc_mac_dispatch(&gnrc_netdev->rx);
rx_info |= GNRC_LWMAC_RX_FOUND_BROADCAST;
/* quit listening period to avoid receiving duplicate broadcast packets */
gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev, true);
/* quit TX in this cycle to avoid collisions with broadcast packets */
gnrc_netdev_lwmac_set_quit_tx(gnrc_netdev, true);
break;
}
if (info.header->type != GNRC_LWMAC_FRAMETYPE_WR) {
LOG_DEBUG("[LWMAC-rx] Packet is not WR: 0x%02x\n", info.header->type);
gnrc_pktbuf_release(pkt);
continue;
}
/* No need to keep pkt anymore */
gnrc_pktbuf_release(pkt);
if (!(memcmp(&info.dst_addr.addr, &gnrc_netdev->l2_addr,
gnrc_netdev->l2_addr_len) == 0)) {
LOG_DEBUG("[LWMAC-rx] Packet is WR but not for us\n");
/* quit TX in this cycle to avoid collisions with other senders, since
* found ongoing WR (preamble) stream */
gnrc_netdev_lwmac_set_quit_tx(gnrc_netdev, true);
continue;
}
/* If reach here, the node gets a WR for itself. */
/* Save source address for later addressing */
gnrc_netdev->rx.l2_addr = info.src_addr;
rx_info |= GNRC_LWMAC_RX_FOUND_WR;
break;
}
return rx_info;
}
/* return false if send wa failed, otherwise return true */
static bool _send_wa(gnrc_netdev_t *gnrc_netdev)
{
gnrc_pktsnip_t *pkt;
gnrc_pktsnip_t *pkt_lwmac;
gnrc_netif_hdr_t *nethdr_wa;
assert(gnrc_netdev != NULL);
assert(gnrc_netdev->rx.l2_addr.len != 0);
/* if found ongoing transmission,
* quit sending WA for collision avoidance. */
if (_gnrc_lwmac_get_netdev_state(gnrc_netdev) == NETOPT_STATE_RX) {
gnrc_netdev->rx.rx_bad_exten_count++;
return false;
}
/* Assemble WA packet */
gnrc_lwmac_frame_wa_t lwmac_hdr;
lwmac_hdr.header.type = GNRC_LWMAC_FRAMETYPE_WA;
lwmac_hdr.dst_addr = gnrc_netdev->rx.l2_addr;
uint32_t phase_now = _gnrc_lwmac_phase_now();
/* Embed the current 'relative phase timing' (counted from the start of this cycle)
* of the receiver into its WA packet, thus to allow the sender to infer the
* receiver's exact wake-up timing */
if (phase_now > _gnrc_lwmac_ticks_to_phase(gnrc_netdev->lwmac.last_wakeup)) {
lwmac_hdr.current_phase = (phase_now -
_gnrc_lwmac_ticks_to_phase(gnrc_netdev->lwmac.last_wakeup));
}
else {
lwmac_hdr.current_phase = (phase_now + RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US)) -
_gnrc_lwmac_ticks_to_phase(gnrc_netdev->lwmac.last_wakeup);
}
pkt = gnrc_pktbuf_add(NULL, &lwmac_hdr, sizeof(lwmac_hdr), GNRC_NETTYPE_LWMAC);
if (pkt == NULL) {
LOG_ERROR("ERROR: [LWMAC-rx] Cannot allocate pktbuf of type GNRC_NETTYPE_LWMAC\n");
gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev, true);
return false;
}
pkt_lwmac = pkt;
pkt = gnrc_pktbuf_add(pkt, NULL,
sizeof(gnrc_netif_hdr_t) + gnrc_netdev->rx.l2_addr.len,
GNRC_NETTYPE_NETIF);
if (pkt == NULL) {
LOG_ERROR("ERROR: [LWMAC-rx] Cannot allocate pktbuf of type GNRC_NETTYPE_NETIF\n");
gnrc_pktbuf_release(pkt_lwmac);
gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev, true);
return false;
}
/* We wouldn't get here if add the NETIF header had failed, so no
sanity checks needed */
nethdr_wa = (gnrc_netif_hdr_t *)(gnrc_pktsnip_search_type(pkt,
GNRC_NETTYPE_NETIF)->data);
/* Construct NETIF header and insert address for WA packet */
gnrc_netif_hdr_init(nethdr_wa, 0, gnrc_netdev->rx.l2_addr.len);
/* Send WA as broadcast*/
nethdr_wa->flags |= GNRC_NETIF_HDR_FLAGS_BROADCAST;
/* Disable Auto ACK */
netopt_enable_t autoack = NETOPT_DISABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_AUTOACK, &autoack,
sizeof(autoack));
/* Send WA */
if (gnrc_netdev->send(gnrc_netdev, pkt) < 0) {
LOG_ERROR("ERROR: [LWMAC-rx] Send WA failed.");
if (pkt != NULL) {
gnrc_pktbuf_release(pkt);
}
gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev, true);
return false;
}
/* Enable Auto ACK again for data reception */
autoack = NETOPT_ENABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_AUTOACK, &autoack,
sizeof(autoack));
return true;
}
static uint8_t _packet_process_in_wait_for_data(gnrc_netdev_t *gnrc_netdev)
{
uint8_t rx_info = 0;
gnrc_pktsnip_t *pkt;
assert(gnrc_netdev != NULL);
pkt = NULL;
while ((pkt = gnrc_priority_pktqueue_pop(&gnrc_netdev->rx.queue)) != NULL) {
LOG_DEBUG("[LWMAC-rx] Inspecting pkt @ %p\n", pkt);
/* Parse packet */
gnrc_lwmac_packet_info_t info;
if (_gnrc_lwmac_parse_packet(pkt, &info) != 0) {
LOG_DEBUG("[LWMAC-rx] Packet could not be parsed\n");
gnrc_pktbuf_release(pkt);
continue;
}
if (info.header->type == GNRC_LWMAC_FRAMETYPE_BROADCAST) {
_gnrc_lwmac_dispatch_defer(gnrc_netdev->rx.dispatch_buffer, pkt);
gnrc_mac_dispatch(&gnrc_netdev->rx);
/* quit listening period to avoid receiving duplicate broadcast packets */
gnrc_netdev_lwmac_set_quit_rx(gnrc_netdev, true);
continue;
}
if (!(memcmp(&info.src_addr.addr, &gnrc_netdev->rx.l2_addr.addr,
gnrc_netdev->rx.l2_addr.len) == 0)) {
LOG_DEBUG("[LWMAC-rx] Packet is not from destination\n");
gnrc_pktbuf_release(pkt);
/* Reset timeout to wait for the data packet */
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA);
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA, GNRC_LWMAC_DATA_DELAY_US);
continue;
}
if (!(memcmp(&info.dst_addr.addr, &gnrc_netdev->l2_addr,
gnrc_netdev->l2_addr_len) == 0)) {
LOG_DEBUG("[LWMAC-rx] Packet is not for us\n");
gnrc_pktbuf_release(pkt);
/* Reset timeout to wait for the data packet */
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA);
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA, GNRC_LWMAC_DATA_DELAY_US);
continue;
}
/* Sender maybe didn't get the WA */
if (info.header->type == GNRC_LWMAC_FRAMETYPE_WR) {
LOG_DEBUG("[LWMAC-rx] Found a WR while waiting for DATA\n");
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA);
rx_info |= GNRC_LWMAC_RX_FOUND_WR;
/* Push WR back to rx queue */
gnrc_mac_queue_rx_packet(&gnrc_netdev->rx, 0, pkt);
break;
}
switch (info.header->type) {
case GNRC_LWMAC_FRAMETYPE_DATA:
case GNRC_LWMAC_FRAMETYPE_DATA_PENDING: {
/* Receiver gets the data packet */
_gnrc_lwmac_dispatch_defer(gnrc_netdev->rx.dispatch_buffer, pkt);
gnrc_mac_dispatch(&gnrc_netdev->rx);
LOG_DEBUG("[LWMAC-rx] Found DATA!\n");
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA);
rx_info |= GNRC_LWMAC_RX_FOUND_DATA;
return rx_info;
}
default: {
gnrc_pktbuf_release(pkt);
}
}
}
return rx_info;
}
void gnrc_lwmac_rx_start(gnrc_netdev_t *gnrc_netdev)
{
if (gnrc_netdev == NULL) {
return;
}
/* RX address should have been reset, probably not stopped then */
assert(gnrc_netdev->rx.l2_addr.len == 0);
/* Don't attempt to send a WA if channel is busy to get timings right */
gnrc_netdev->mac_info &= ~GNRC_NETDEV_MAC_INFO_CSMA_ENABLED;
netopt_enable_t csma_disable = NETOPT_DISABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_CSMA, &csma_disable,
sizeof(csma_disable));
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_INIT;
}
void gnrc_lwmac_rx_stop(gnrc_netdev_t *gnrc_netdev)
{
if (!gnrc_netdev) {
return;
}
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA);
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_STOPPED;
gnrc_netdev->rx.l2_addr.len = 0;
}
/* Returns whether rescheduling is needed or not */
static bool _lwmac_rx_update(gnrc_netdev_t *gnrc_netdev)
{
bool reschedule = false;
if (!gnrc_netdev) {
return reschedule;
}
switch (gnrc_netdev->rx.state) {
case GNRC_LWMAC_RX_STATE_INIT: {
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA);
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_WAIT_FOR_WR;
reschedule = true;
break;
}
case GNRC_LWMAC_RX_STATE_WAIT_FOR_WR: {
LOG_DEBUG("[LWMAC-rx] GNRC_LWMAC_RX_STATE_WAIT_FOR_WR\n");
uint8_t rx_info = _packet_process_in_wait_for_wr(gnrc_netdev);
/* if found broadcast packet, goto rx successful */
if (rx_info & GNRC_LWMAC_RX_FOUND_BROADCAST) {
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_SUCCESSFUL;
reschedule = true;
break;
}
if (!(rx_info & GNRC_LWMAC_RX_FOUND_WR)) {
LOG_DEBUG("[LWMAC-rx] No WR found, stop RX\n");
gnrc_netdev->rx.rx_bad_exten_count++;
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_FAILED;
reschedule = true;
break;
}
gnrc_priority_pktqueue_flush(&gnrc_netdev->rx.queue);
/* Found WR packet (preamble), goto next state to send WA (preamble-ACK) */
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_SEND_WA;
reschedule = true;
break;
}
case GNRC_LWMAC_RX_STATE_SEND_WA: {
LOG_DEBUG("[LWMAC-rx] GNRC_LWMAC_RX_STATE_SEND_WA\n");
if (!_send_wa(gnrc_netdev)) {
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_FAILED;
reschedule = true;
break;
}
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_WAIT_WA_SENT;
reschedule = false;
break;
}
case GNRC_LWMAC_RX_STATE_WAIT_WA_SENT: {
LOG_DEBUG("[LWMAC-rx] GNRC_LWMAC_RX_STATE_WAIT_WA_SENT\n");
if (gnrc_netdev_get_tx_feedback(gnrc_netdev) == TX_FEEDBACK_UNDEF) {
LOG_DEBUG("[LWMAC-rx] WA not yet completely sent\n");
break;
}
/* When reach here, WA has been sent, set timeout for expected data arrival */
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA, GNRC_LWMAC_DATA_DELAY_US);
_gnrc_lwmac_set_netdev_state(gnrc_netdev, NETOPT_STATE_IDLE);
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_WAIT_FOR_DATA;
reschedule = false;
break;
}
case GNRC_LWMAC_RX_STATE_WAIT_FOR_DATA: {
LOG_DEBUG("[LWMAC-rx] GNRC_LWMAC_RX_STATE_WAIT_FOR_DATA\n");
uint8_t rx_info = _packet_process_in_wait_for_data(gnrc_netdev);
/* If WA got lost we wait for data but we will be hammered with WR
* packets. So a WR indicates a lost WA => reset RX state machine. */
if (rx_info & GNRC_LWMAC_RX_FOUND_WR) {
LOG_INFO("[LWMAC-rx] WA probably got lost, reset RX state machine\n");
/* Start over again */
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_INIT;
reschedule = true;
break;
}
/* Only timeout if no packet (presumably the expected data) is being
* received. This won't be blocked by WRs as they restart the state
* machine (see above).
*/
if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA)) {
if (!gnrc_netdev_get_rx_started(gnrc_netdev)) {
LOG_INFO("[LWMAC-rx] DATA timed out\n");
gnrc_netdev->rx.rx_bad_exten_count++;
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_FAILED;
reschedule = true;
}
else {
/* If radio is receiving packet, reset wait data timeout */
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_DATA, GNRC_LWMAC_DATA_DELAY_US);
}
break;
}
if (!(rx_info & GNRC_LWMAC_RX_FOUND_DATA)) {
LOG_DEBUG("[LWMAC-rx] No DATA yet\n");
break;
}
gnrc_netdev->rx.state = GNRC_LWMAC_RX_STATE_SUCCESSFUL;
reschedule = true;
break;
}
case GNRC_LWMAC_RX_STATE_SUCCESSFUL:
case GNRC_LWMAC_RX_STATE_FAILED: {
break;
}
case GNRC_LWMAC_RX_STATE_STOPPED: {
LOG_DEBUG("[LWMAC-rx] Reception state machine is stopped\n");
}
}
return reschedule;
}
void gnrc_lwmac_rx_update(gnrc_netdev_t *gnrc_netdev)
{
/* Update until no rescheduling needed */
while (_lwmac_rx_update(gnrc_netdev)) {}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Timeout handling of LWMAC protocol
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
* @}
*/
#include <errno.h>
#include "net/gnrc/lwmac/timeout.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
#if ENABLE_DEBUG
char *lwmac_timeout_names[] = {
[GNRC_LWMAC_TIMEOUT_DISABLED] = "DISABLED",
[GNRC_LWMAC_TIMEOUT_WR] = "WR",
[GNRC_LWMAC_TIMEOUT_NO_RESPONSE] = "NO_RESPONSE",
[GNRC_LWMAC_TIMEOUT_DATA] = "DATA",
[GNRC_LWMAC_TIMEOUT_WAIT_DEST_WAKEUP] = "WAIT_FOR_DEST_WAKEUP",
[GNRC_LWMAC_TIMEOUT_WAKEUP_PERIOD] = "WAKEUP_PERIOD",
};
#endif
static inline void _lwmac_clear_timeout(gnrc_lwmac_timeout_t *timeout)
{
assert(timeout);
xtimer_remove(&(timeout->timer));
timeout->type = GNRC_LWMAC_TIMEOUT_DISABLED;
}
/* Return index >= 0 if found, -ENONENT if not found */
static int _lwmac_find_timeout(gnrc_lwmac_t *lwmac, gnrc_lwmac_timeout_type_t type)
{
assert(lwmac);
for (unsigned i = 0; i < GNRC_LWMAC_TIMEOUT_COUNT; i++) {
if (lwmac->timeouts[i].type == type) {
return i;
}
}
return -ENOENT;
}
inline bool gnrc_lwmac_timeout_is_running(gnrc_netdev_t *gnrc_netdev,
gnrc_lwmac_timeout_type_t type)
{
assert(gnrc_netdev);
return (_lwmac_find_timeout(&gnrc_netdev->lwmac, type) >= 0);
}
bool gnrc_lwmac_timeout_is_expired(gnrc_netdev_t *gnrc_netdev, gnrc_lwmac_timeout_type_t type)
{
assert(gnrc_netdev);
int index = _lwmac_find_timeout(&gnrc_netdev->lwmac, type);
if (index >= 0) {
if (gnrc_netdev->lwmac.timeouts[index].expired) {
_lwmac_clear_timeout(&gnrc_netdev->lwmac.timeouts[index]);
}
return gnrc_netdev->lwmac.timeouts[index].expired;
}
return false;
}
gnrc_lwmac_timeout_t *_lwmac_acquire_timeout(gnrc_netdev_t *gnrc_netdev,
gnrc_lwmac_timeout_type_t type)
{
assert(gnrc_netdev);
if (gnrc_lwmac_timeout_is_running(gnrc_netdev, type)) {
return NULL;
}
for (unsigned i = 0; i < GNRC_LWMAC_TIMEOUT_COUNT; i++) {
if (gnrc_netdev->lwmac.timeouts[i].type == GNRC_LWMAC_TIMEOUT_DISABLED) {
gnrc_netdev->lwmac.timeouts[i].type = type;
return &gnrc_netdev->lwmac.timeouts[i];
}
}
return NULL;
}
void gnrc_lwmac_timeout_make_expire(gnrc_lwmac_timeout_t *timeout)
{
assert(timeout);
timeout->expired = true;
}
void gnrc_lwmac_clear_timeout(gnrc_netdev_t *gnrc_netdev, gnrc_lwmac_timeout_type_t type)
{
assert(gnrc_netdev);
int index = _lwmac_find_timeout(&gnrc_netdev->lwmac, type);
if (index >= 0) {
_lwmac_clear_timeout(&gnrc_netdev->lwmac.timeouts[index]);
}
}
void gnrc_lwmac_set_timeout(gnrc_netdev_t *gnrc_netdev,
gnrc_lwmac_timeout_type_t type,
uint32_t offset)
{
assert(gnrc_netdev);
gnrc_lwmac_timeout_t *timeout;
if ((timeout = _lwmac_acquire_timeout(gnrc_netdev, type))) {
DEBUG("[LWMAC] Set timeout %s in %" PRIu32 " us\n",
lwmac_timeout_names[type], offset);
timeout->expired = false;
timeout->msg.type = GNRC_LWMAC_EVENT_TIMEOUT_TYPE;
timeout->msg.content.ptr = (void *) timeout;
xtimer_set_msg(&(timeout->timer), offset,
&(timeout->msg), gnrc_netdev->pid);
}
else {
DEBUG("[LWMAC] Cannot set timeout %s, too many concurrent timeouts\n",
lwmac_timeout_names[type]);
}
}
void gnrc_lwmac_reset_timeouts(gnrc_netdev_t *gnrc_netdev)
{
assert(gnrc_netdev);
for (unsigned i = 0; i < GNRC_LWMAC_TIMEOUT_COUNT; i++) {
if (gnrc_netdev->lwmac.timeouts[i].type != GNRC_LWMAC_TIMEOUT_DISABLED) {
_lwmac_clear_timeout(&gnrc_netdev->lwmac.timeouts[i]);
}
}
}

View File

@ -0,0 +1,810 @@
/*
* Copyright (C) 2015 Daniel Krebs
* 2016 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_lwmac
* @{
*
* @file
* @brief Implementation of TX state machine of LWMAC protocol
*
* @author Daniel Krebs <github@daniel-krebs.net>
* @author Shuguo Zhuo <shuguo.zhuo@inria.fr>
* @}
*/
#include "periph/rtt.h"
#include "net/gnrc.h"
#include "net/gnrc/lwmac/lwmac.h"
#include "random.h"
#include "net/gnrc/mac/internal.h"
#include "net/gnrc/lwmac/timeout.h"
#include "include/tx_state_machine.h"
#include "include/lwmac_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 Flag to track if send packet success
*/
#define GNRC_LWMAC_TX_SUCCESS (0x01U)
/**
* @brief Flag to track if send packet fail
*/
#define GNRC_LWMAC_TX_FAIL (0x02U)
static uint8_t _send_bcast(gnrc_netdev_t *gnrc_netdev)
{
assert(gnrc_netdev != NULL);
uint8_t tx_info = 0;
gnrc_pktsnip_t *pkt = gnrc_netdev->tx.packet;
bool first = false;
if (gnrc_lwmac_timeout_is_running(gnrc_netdev, GNRC_LWMAC_TIMEOUT_BROADCAST_END)) {
if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_BROADCAST_END)) {
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NEXT_BROADCAST);
gnrc_pktbuf_release(pkt);
gnrc_netdev->tx.packet = NULL;
tx_info |= GNRC_LWMAC_TX_SUCCESS;
return tx_info;
}
}
else {
LOG_INFO("[LWMAC-tx] Initialize broadcasting\n");
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_BROADCAST_END,
GNRC_LWMAC_BROADCAST_DURATION_US);
gnrc_pktsnip_t *pkt_payload;
/* Prepare packet with LWMAC header*/
gnrc_lwmac_frame_broadcast_t hdr = {};
hdr.header.type = GNRC_LWMAC_FRAMETYPE_BROADCAST;
hdr.seq_nr = gnrc_netdev->tx.bcast_seqnr++;
pkt_payload = pkt->next;
pkt->next = gnrc_pktbuf_add(pkt->next, &hdr, sizeof(hdr), GNRC_NETTYPE_LWMAC);
if (pkt->next == NULL) {
LOG_ERROR("ERROR: [LWMAC-tx] Cannot allocate pktbuf of type FRAMETYPE_BROADCAST\n");
gnrc_netdev->tx.packet->next = pkt_payload;
/* Drop the broadcast packet */
LOG_ERROR("ERROR: [LWMAC-tx] Memory maybe full, drop the broadcast packet\n");
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
/* clear packet point to avoid TX retry */
gnrc_netdev->tx.packet = NULL;
tx_info |= GNRC_LWMAC_TX_FAIL;
return tx_info;
}
/* No Auto-ACK for broadcast packets */
netopt_enable_t autoack = NETOPT_DISABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_AUTOACK, &autoack,
sizeof(autoack));
first = true;
}
if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NEXT_BROADCAST) ||
first) {
/* if found ongoing transmission, quit this cycle for collision avoidance.
* Broadcast packet will be re-queued and try to send in the next cycle. */
if (_gnrc_lwmac_get_netdev_state(gnrc_netdev) == NETOPT_STATE_RX) {
/* save pointer to netif header */
gnrc_pktsnip_t *netif = pkt->next->next;
/* remove LWMAC header */
pkt->next->next = NULL;
gnrc_pktbuf_release(pkt->next);
/* make append netif header after payload again */
pkt->next = netif;
if (!gnrc_mac_queue_tx_packet(&gnrc_netdev->tx, 0, gnrc_netdev->tx.packet)) {
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
LOG_WARNING("WARNING: [LWMAC-tx] TX queue full, drop packet\n");
}
/* drop pointer so it wont be free'd */
gnrc_netdev->tx.packet = NULL;
tx_info |= GNRC_LWMAC_TX_FAIL;
return tx_info;
}
/* Don't let the packet be released yet, we want to send it again */
gnrc_pktbuf_hold(pkt, 1);
int res = gnrc_netdev->send(gnrc_netdev, pkt);
if (res < 0) {
LOG_ERROR("ERROR: [LWMAC-tx] Send broadcast pkt failed.");
tx_info |= GNRC_LWMAC_TX_FAIL;
return tx_info;
}
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NEXT_BROADCAST,
GNRC_LWMAC_TIME_BETWEEN_BROADCAST_US);
LOG_INFO("[LWMAC-tx] Broadcast sent\n");
}
return tx_info;
}
static uint8_t _send_wr(gnrc_netdev_t *gnrc_netdev)
{
assert(gnrc_netdev != NULL);
uint8_t tx_info = 0;
gnrc_pktsnip_t *pkt;
gnrc_pktsnip_t *pkt_lwmac;
gnrc_netif_hdr_t *nethdr;
/* if found ongoing transmission, quit this cycle for collision avoidance.
* Data packet will be re-queued and try to send in the next cycle. */
if (_gnrc_lwmac_get_netdev_state(gnrc_netdev) == NETOPT_STATE_RX) {
if (!gnrc_mac_queue_tx_packet(&gnrc_netdev->tx, 0, gnrc_netdev->tx.packet)) {
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
LOG_WARNING("WARNING: [LWMAC-tx] TX queue full, drop packet\n");
}
/* drop pointer so it wont be free'd */
gnrc_netdev->tx.packet = NULL;
tx_info |= GNRC_LWMAC_TX_FAIL;
return tx_info;
}
/* Assemble WR */
gnrc_lwmac_frame_wr_t wr_hdr = {};
wr_hdr.header.type = GNRC_LWMAC_FRAMETYPE_WR;
memcpy(&(wr_hdr.dst_addr.addr), gnrc_netdev->tx.current_neighbor->l2_addr,
gnrc_netdev->tx.current_neighbor->l2_addr_len);
wr_hdr.dst_addr.len = gnrc_netdev->tx.current_neighbor->l2_addr_len;
pkt = gnrc_pktbuf_add(NULL, &wr_hdr, sizeof(wr_hdr), GNRC_NETTYPE_LWMAC);
if (pkt == NULL) {
LOG_ERROR("ERROR: [LWMAC-tx] Cannot allocate pktbuf of type GNRC_NETTYPE_LWMAC\n");
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
LOG_ERROR("ERROR: [LWMAC-tx] Memory maybe full, drop the data packet\n");
/* clear packet point to avoid TX retry */
gnrc_netdev->tx.packet = NULL;
tx_info |= GNRC_LWMAC_TX_FAIL;
return tx_info;
}
/* track the location of this lwmac_frame_wr_t header */
pkt_lwmac = pkt;
pkt = gnrc_pktbuf_add(pkt, NULL, sizeof(gnrc_netif_hdr_t), GNRC_NETTYPE_NETIF);
if (pkt == NULL) {
LOG_ERROR("ERROR: [LWMAC-tx] Cannot allocate pktbuf of type GNRC_NETTYPE_NETIF\n");
gnrc_pktbuf_release(pkt_lwmac);
LOG_ERROR("ERROR: [LWMAC-tx] Memory maybe full, drop the data packet\n");
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
/* clear packet point to avoid TX retry */
gnrc_netdev->tx.packet = NULL;
tx_info |= GNRC_LWMAC_TX_FAIL;
return tx_info;
}
/* We wouldn't get here if adding the NETIF header had failed, so no
* sanity checks needed */
nethdr = (gnrc_netif_hdr_t *) (gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_NETIF))->data;
/* Construct NETIF header and insert address for WR packet */
gnrc_netif_hdr_init(nethdr, 0, 0);
/* Send WR as broadcast*/
nethdr->flags |= GNRC_NETIF_HDR_FLAGS_BROADCAST;
/* Disable Auto ACK */
netopt_enable_t autoack = NETOPT_DISABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_AUTOACK, &autoack,
sizeof(autoack));
/* Prepare WR, this will discard any frame in the transceiver that has
* possibly arrived in the meantime but we don't care at this point. */
int res = gnrc_netdev->send(gnrc_netdev, pkt);
if (res < 0) {
LOG_ERROR("ERROR: [LWMAC-tx] Send WR failed.");
if (pkt != NULL) {
gnrc_pktbuf_release(pkt);
}
tx_info |= GNRC_LWMAC_TX_FAIL;
return tx_info;
}
gnrc_priority_pktqueue_flush(&gnrc_netdev->rx.queue);
return tx_info;
}
static uint8_t _packet_process_in_wait_for_wa(gnrc_netdev_t *gnrc_netdev)
{
assert(gnrc_netdev != NULL);
uint8_t tx_info = 0;
gnrc_pktsnip_t *pkt;
bool found_wa = false;
bool postponed = false;
bool from_expected_destination = false;
while ((pkt = gnrc_priority_pktqueue_pop(&gnrc_netdev->rx.queue)) != NULL) {
LOG_DEBUG("[LWMAC-tx] Inspecting pkt @ %p\n", pkt);
/* Parse packet */
gnrc_lwmac_packet_info_t info;
int ret = _gnrc_lwmac_parse_packet(pkt, &info);
if (ret != 0) {
LOG_DEBUG("[LWMAC-tx] Packet could not be parsed: %i\n", ret);
gnrc_pktbuf_release(pkt);
continue;
}
if (memcmp(&info.src_addr.addr, &gnrc_netdev->tx.current_neighbor->l2_addr,
gnrc_netdev->tx.current_neighbor->l2_addr_len) == 0) {
from_expected_destination = true;
}
if (info.header->type == GNRC_LWMAC_FRAMETYPE_BROADCAST) {
_gnrc_lwmac_dispatch_defer(gnrc_netdev->rx.dispatch_buffer, pkt);
gnrc_mac_dispatch(&gnrc_netdev->rx);
/* Drop pointer to it can't get released */
pkt = NULL;
continue;
}
/* Check if destination is talking to another node. It will sleep
* after a finished transaction so there's no point in trying any
* further now. */
if (!(memcmp(&info.dst_addr.addr, &gnrc_netdev->l2_addr,
gnrc_netdev->l2_addr_len) == 0) && from_expected_destination) {
if (!gnrc_mac_queue_tx_packet(&gnrc_netdev->tx, 0, gnrc_netdev->tx.packet)) {
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
LOG_WARNING("WARNING: [LWMAC-tx] TX queue full, drop packet\n");
}
/* drop pointer so it wont be free'd */
gnrc_netdev->tx.packet = NULL;
postponed = true;
gnrc_pktbuf_release(pkt);
break;
}
/* if found anther node is also trying to send data,
* quit this cycle for collision avoidance. */
if (info.header->type == GNRC_LWMAC_FRAMETYPE_WR) {
if (!gnrc_mac_queue_tx_packet(&gnrc_netdev->tx, 0, gnrc_netdev->tx.packet)) {
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
LOG_WARNING("WARNING: [LWMAC-tx] TX queue full, drop packet\n");
}
/* drop pointer so it wont be free'd */
gnrc_netdev->tx.packet = NULL;
postponed = true;
gnrc_pktbuf_release(pkt);
break;
}
if (info.header->type != GNRC_LWMAC_FRAMETYPE_WA) {
LOG_DEBUG("[LWMAC-tx] Packet is not WA: 0x%02x\n", info.header->type);
gnrc_pktbuf_release(pkt);
continue;
}
if (from_expected_destination) {
/* calculate the phase of the receiver based on WA */
gnrc_netdev->tx.timestamp = _gnrc_lwmac_phase_now();
gnrc_lwmac_frame_wa_t *wa_hdr;
wa_hdr = (gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_LWMAC))->data;
if (gnrc_netdev->tx.timestamp >= wa_hdr->current_phase) {
gnrc_netdev->tx.timestamp = gnrc_netdev->tx.timestamp -
wa_hdr->current_phase;
}
else {
gnrc_netdev->tx.timestamp += RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US);
gnrc_netdev->tx.timestamp -= wa_hdr->current_phase;
}
uint32_t own_phase;
own_phase = _gnrc_lwmac_ticks_to_phase(gnrc_netdev->lwmac.last_wakeup);
if (own_phase >= gnrc_netdev->tx.timestamp) {
own_phase = own_phase - gnrc_netdev->tx.timestamp;
}
else {
own_phase = gnrc_netdev->tx.timestamp - own_phase;
}
if ((own_phase < RTT_US_TO_TICKS((3 * GNRC_LWMAC_WAKEUP_DURATION_US / 2))) ||
(own_phase > RTT_US_TO_TICKS(GNRC_LWMAC_WAKEUP_INTERVAL_US -
(3 * GNRC_LWMAC_WAKEUP_DURATION_US / 2)))) {
gnrc_netdev_lwmac_set_phase_backoff(gnrc_netdev, true);
LOG_WARNING("WARNING: [LWMAC-tx] phase close\n");
}
}
/* No need to keep pkt anymore */
gnrc_pktbuf_release(pkt);
if (!from_expected_destination) {
LOG_DEBUG("[LWMAC-tx] Packet is not from expected destination\n");
break;
}
/* All checks passed so this must be a valid WA */
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WR);
found_wa = true;
break;
}
if (postponed) {
LOG_INFO("[LWMAC-tx] Destination is talking to another node, postpone\n");
tx_info |= GNRC_LWMAC_TX_FAIL;
return tx_info;
}
if (!found_wa) {
LOG_DEBUG("[LWMAC-tx] No WA yet\n");
return tx_info;
}
/* Save newly calculated phase for destination */
gnrc_netdev->tx.current_neighbor->phase = gnrc_netdev->tx.timestamp;
LOG_INFO("[LWMAC-tx] New phase: %" PRIu32 "\n", gnrc_netdev->tx.timestamp);
/* We've got our WA, so discard the rest, TODO: no flushing */
gnrc_priority_pktqueue_flush(&gnrc_netdev->rx.queue);
tx_info |= GNRC_LWMAC_TX_SUCCESS;
return tx_info;
}
/* return false if send data failed, otherwise return true */
static bool _send_data(gnrc_netdev_t *gnrc_netdev)
{
assert(gnrc_netdev != NULL);
gnrc_pktsnip_t *pkt = gnrc_netdev->tx.packet;
gnrc_pktsnip_t *pkt_payload;
/* Enable Auto ACK again */
netopt_enable_t autoack = NETOPT_ENABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_AUTOACK,
&autoack, sizeof(autoack));
/* It's okay to retry sending DATA. Timing doesn't matter anymore and
* destination is waiting for a certain amount of time. */
uint8_t csma_retries = GNRC_LWMAC_DATA_CSMA_RETRIES;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_CSMA_RETRIES,
&csma_retries, sizeof(csma_retries));
gnrc_netdev->mac_info |= GNRC_NETDEV_MAC_INFO_CSMA_ENABLED;
netopt_enable_t csma_enable = NETOPT_ENABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_CSMA,
&csma_enable, sizeof(csma_enable));
pkt_payload = pkt->next;
/* Insert LWMAC header above NETIF header. The burst (consecutive) transmission
* scheme works here (sender side). If the sender finds it has pending packets
* for the receiver (and under burst limit), it sets the packet type to
* FRAMETYPE_DATA_PENDING, to notice the receiver for next incoming packet.
* In case the sender has no more packet for the receiver, it simply sets the
* data type to FRAMETYPE_DATA. */
gnrc_lwmac_hdr_t hdr;
if ((gnrc_priority_pktqueue_length(&gnrc_netdev->tx.current_neighbor->queue) > 0) &&
(gnrc_netdev->tx.tx_burst_count < GNRC_LWMAC_MAX_TX_BURST_PKT_NUM)) {
hdr.type = GNRC_LWMAC_FRAMETYPE_DATA_PENDING;
gnrc_netdev_lwmac_set_tx_continue(gnrc_netdev, true);
gnrc_netdev->tx.tx_burst_count++;
}
else {
hdr.type = GNRC_LWMAC_FRAMETYPE_DATA;
gnrc_netdev_lwmac_set_tx_continue(gnrc_netdev, false);
}
pkt->next = gnrc_pktbuf_add(pkt->next, &hdr, sizeof(hdr), GNRC_NETTYPE_LWMAC);
if (pkt->next == NULL) {
LOG_ERROR("ERROR: [LWMAC-tx] Cannot allocate pktbuf of type GNRC_NETTYPE_LWMAC\n");
LOG_ERROR("ERROR: [LWMAC-tx] Memory maybe full, drop the data packet\n");
gnrc_netdev->tx.packet->next = pkt_payload;
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
/* clear packet point to avoid TX retry */
gnrc_netdev->tx.packet = NULL;
return false;
}
/* if found ongoing transmission, quit this cycle for collision avoidance.
* Data packet will be re-queued and try to send in the next cycle. */
if (_gnrc_lwmac_get_netdev_state(gnrc_netdev) == NETOPT_STATE_RX) {
/* save pointer to netif header */
gnrc_pktsnip_t *netif = pkt->next->next;
/* remove LWMAC header */
pkt->next->next = NULL;
gnrc_pktbuf_release(pkt->next);
/* make append netif header after payload again */
pkt->next = netif;
if (!gnrc_mac_queue_tx_packet(&gnrc_netdev->tx, 0, gnrc_netdev->tx.packet)) {
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
LOG_WARNING("WARNING: [LWMAC-tx] TX queue full, drop packet\n");
}
/* drop pointer so it wont be free'd */
gnrc_netdev->tx.packet = NULL;
return false;
}
/* Send data */
int res = gnrc_netdev->send(gnrc_netdev, pkt);
if (res < 0) {
LOG_ERROR("ERROR: [LWMAC-tx] Send data failed.");
if (pkt != NULL) {
gnrc_pktbuf_release(pkt);
}
/* clear packet point to avoid TX retry */
gnrc_netdev->tx.packet = NULL;
return false;
}
/* Packet has been released by netdev, so drop pointer */
gnrc_netdev->tx.packet = NULL;
DEBUG("[LWMAC-tx]: spent %lu WR in TX\n", gnrc_netdev->tx.wr_sent);
#if (LWMAC_ENABLE_DUTYCYLE_RECORD == 1)
gnrc_netdev->lwmac.pkt_start_sending_time_ticks =
rtt_get_counter() - gnrc_netdev->lwmac.pkt_start_sending_time_ticks;
DEBUG("[LWMAC-tx]: pkt sending delay in TX: %lu us\n",
RTT_TICKS_TO_US(gnrc_netdev->lwmac.pkt_start_sending_time_ticks));
#endif
return true;
}
void gnrc_lwmac_tx_start(gnrc_netdev_t *gnrc_netdev,
gnrc_pktsnip_t *pkt,
gnrc_mac_tx_neighbor_t *neighbor)
{
assert(gnrc_netdev != NULL);
assert(pkt != NULL);
assert(neighbor != NULL);
if (gnrc_netdev->tx.packet) {
LOG_WARNING("WARNING: [LWMAC-tx] Starting but tx.packet is still set\n");
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
}
gnrc_netdev->tx.packet = pkt;
gnrc_netdev->tx.current_neighbor = neighbor;
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_INIT;
gnrc_netdev->tx.wr_sent = 0;
#if (LWMAC_ENABLE_DUTYCYLE_RECORD == 1)
gnrc_netdev->lwmac.pkt_start_sending_time_ticks = rtt_get_counter();
#endif
}
void gnrc_lwmac_tx_stop(gnrc_netdev_t *gnrc_netdev)
{
assert(gnrc_netdev != NULL);
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WR);
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NO_RESPONSE);
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NEXT_BROADCAST);
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_BROADCAST_END);
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_STOPPED;
/* Release packet in case of failure */
if (gnrc_netdev->tx.packet) {
if (gnrc_netdev->tx.tx_retry_count >= GNRC_LWMAC_MAX_DATA_TX_RETRIES) {
gnrc_netdev->tx.tx_retry_count = 0;
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
gnrc_netdev->tx.packet = NULL;
LOG_WARNING("WARNING: [LWMAC-tx] Drop TX packet\n");
}
else {
gnrc_netdev->tx.tx_retry_count++;
return;
}
}
if (!gnrc_netdev_lwmac_get_tx_continue(gnrc_netdev)) {
gnrc_netdev->tx.current_neighbor = NULL;
}
}
/* Returns whether rescheduling is needed or not */
static bool _lwmac_tx_update(gnrc_netdev_t *gnrc_netdev)
{
assert(gnrc_netdev != NULL);
bool reschedule = false;
switch (gnrc_netdev->tx.state) {
case GNRC_LWMAC_TX_STATE_INIT: {
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WR);
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NO_RESPONSE);
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NEXT_BROADCAST);
gnrc_lwmac_clear_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_BROADCAST_END);
/* if found ongoing transmission,
* quit this cycle for collision avoidance. */
if (_gnrc_lwmac_get_netdev_state(gnrc_netdev) == NETOPT_STATE_RX) {
if (!gnrc_mac_queue_tx_packet(&gnrc_netdev->tx, 0, gnrc_netdev->tx.packet)) {
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
LOG_WARNING("WARNING: [LWMAC-tx] TX queue full, drop packet\n");
}
/* drop pointer so it wont be free'd */
gnrc_netdev->tx.packet = NULL;
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
/* check if the packet is for broadcast */
if (gnrc_netif_hdr_get_flag(gnrc_netdev->tx.packet) &
(GNRC_NETIF_HDR_FLAGS_BROADCAST | GNRC_NETIF_HDR_FLAGS_MULTICAST)) {
/* Set CSMA retries as configured and enable */
uint8_t csma_retries = GNRC_LWMAC_BROADCAST_CSMA_RETRIES;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_CSMA_RETRIES,
&csma_retries, sizeof(csma_retries));
gnrc_netdev->mac_info |= GNRC_NETDEV_MAC_INFO_CSMA_ENABLED;
netopt_enable_t csma_enable = NETOPT_ENABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_CSMA,
&csma_enable, sizeof(csma_enable));
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_SEND_BROADCAST;
reschedule = true;
break;
}
else {
/* Use CSMA for the first WR */
gnrc_netdev->mac_info |= GNRC_NETDEV_MAC_INFO_CSMA_ENABLED;
netopt_enable_t csma_disable = NETOPT_ENABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_CSMA,
&csma_disable, sizeof(csma_disable));
/* Set a timeout for the maximum transmission procedure */
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NO_RESPONSE, GNRC_LWMAC_PREAMBLE_DURATION_US);
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_SEND_WR;
reschedule = true;
break;
}
}
case GNRC_LWMAC_TX_STATE_SEND_BROADCAST: {
uint8_t tx_info = _send_bcast(gnrc_netdev);
if (tx_info & GNRC_LWMAC_TX_SUCCESS) {
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_SUCCESSFUL;
reschedule = true;
break;
}
if (tx_info & GNRC_LWMAC_TX_FAIL) {
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
break;
}
case GNRC_LWMAC_TX_STATE_SEND_WR: {
/* In case of no Tx-isr error (e.g., no Tx-isr), goto TX failure. */
if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NO_RESPONSE)) {
LOG_WARNING("WARNING: [LWMAC-tx] No response from destination, "
"probably no TX-ISR\n");
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
LOG_DEBUG("[LWMAC-tx] GNRC_LWMAC_TX_STATE_SEND_WR\n");
uint8_t tx_info = _send_wr(gnrc_netdev);
if (tx_info & GNRC_LWMAC_TX_FAIL) {
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_WAIT_WR_SENT;
reschedule = false;
break;
}
case GNRC_LWMAC_TX_STATE_WAIT_WR_SENT: {
LOG_DEBUG("[LWMAC-tx] GNRC_LWMAC_TX_STATE_WAIT_WR_SENT\n");
/* In case of no Tx-isr error (e.g., no Tx-isr), goto TX failure. */
if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NO_RESPONSE)) {
LOG_WARNING("WARNING: [LWMAC-tx] No response from destination\n");
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
if (gnrc_netdev_get_tx_feedback(gnrc_netdev) == TX_FEEDBACK_UNDEF) {
LOG_DEBUG("[LWMAC-tx] WR not yet completely sent\n");
break;
}
/* If found ongoing transmission, goto TX failure, i.e., postpone transmission to
* next cycle. This is mainly for collision avoidance. */
if (gnrc_netdev_get_tx_feedback(gnrc_netdev) == TX_FEEDBACK_BUSY) {
if (!gnrc_mac_queue_tx_packet(&gnrc_netdev->tx, 0, gnrc_netdev->tx.packet)) {
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
LOG_WARNING("WARNING: [LWMAC-tx] TX queue full, drop packet\n");
}
/* clear packet point to avoid TX retry */
gnrc_netdev->tx.packet = NULL;
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
if (gnrc_netdev->tx.wr_sent == 0) {
/* Only the first WR use CSMA */
gnrc_netdev->mac_info &= ~GNRC_NETDEV_MAC_INFO_CSMA_ENABLED;
netopt_enable_t csma_disable = NETOPT_DISABLE;
gnrc_netdev->dev->driver->set(gnrc_netdev->dev, NETOPT_CSMA,
&csma_disable, sizeof(csma_disable));
}
gnrc_netdev->tx.wr_sent++;
/* Set timeout for next WR in case no WA will be received */
gnrc_lwmac_set_timeout(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WR, GNRC_LWMAC_TIME_BETWEEN_WR_US);
/* Debug WR timing */
LOG_DEBUG("[LWMAC-tx] Destination phase was: %" PRIu32 "\n",
gnrc_netdev->tx.current_neighbor->phase);
LOG_DEBUG("[LWMAC-tx] Phase when sent was: %" PRIu32 "\n",
_gnrc_lwmac_ticks_to_phase(gnrc_netdev->tx.timestamp));
LOG_DEBUG("[LWMAC-tx] Ticks when sent was: %" PRIu32 "\n",
gnrc_netdev->tx.timestamp);
_gnrc_lwmac_set_netdev_state(gnrc_netdev, NETOPT_STATE_IDLE);
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_WAIT_FOR_WA;
reschedule = false;
break;
}
case GNRC_LWMAC_TX_STATE_WAIT_FOR_WA: {
LOG_DEBUG("[LWMAC-tx] GNRC_LWMAC_TX_STATE_WAIT_FOR_WA\n");
if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NO_RESPONSE)) {
LOG_WARNING("WARNING: [LWMAC-tx] No response from destination\n");
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_WR)) {
/* In case the sender is in consecutive (burst) transmission to the receiver,
* meaning that the sender has already successfully sent at least one data to
* the receiver, then the sender will only spend one WR for triggering the next
* transmission procedure. And, if this WR doesn't work (no WA replied), the
* sender regards consecutive transmission failed.
*/
if (gnrc_netdev_lwmac_get_tx_continue(gnrc_netdev)) {
LOG_DEBUG("[LWMAC-tx] Tx burst fail\n");
if (!gnrc_mac_queue_tx_packet(&gnrc_netdev->tx, 0, gnrc_netdev->tx.packet)) {
gnrc_pktbuf_release(gnrc_netdev->tx.packet);
LOG_WARNING("WARNING: [LWMAC-tx] TX queue full, drop packet\n");
}
/* drop pointer so it wont be free'd */
gnrc_netdev->tx.packet = NULL;
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
else {
/* If this is the first transmission to the receiver for locating the
* latter's wake-up period, the sender just keep sending WRs until it
* finds the WA.
*/
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_SEND_WR;
reschedule = true;
break;
}
}
if (_gnrc_lwmac_get_netdev_state(gnrc_netdev) == NETOPT_STATE_RX) {
/* Wait for completion of frame reception */
break;
}
uint8_t tx_info = _packet_process_in_wait_for_wa(gnrc_netdev);
if (tx_info & GNRC_LWMAC_TX_FAIL) {
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
if (tx_info & GNRC_LWMAC_TX_SUCCESS) {
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_SEND_DATA;
reschedule = true;
break;
}
else {
/* No WA yet */
break;
}
}
case GNRC_LWMAC_TX_STATE_SEND_DATA: {
LOG_DEBUG("[LWMAC-tx] GNRC_LWMAC_TX_STATE_SEND_DATA\n");
if (!_send_data(gnrc_netdev)) {
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_WAIT_FEEDBACK;
reschedule = false;
break;
}
case GNRC_LWMAC_TX_STATE_WAIT_FEEDBACK: {
/* In case of no Tx-isr error, goto TX failure. */
if (gnrc_lwmac_timeout_is_expired(gnrc_netdev, GNRC_LWMAC_TIMEOUT_NO_RESPONSE)) {
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
LOG_DEBUG("[LWMAC-tx] GNRC_LWMAC_TX_STATE_WAIT_FEEDBACK\n");
if (gnrc_netdev_get_tx_feedback(gnrc_netdev) == TX_FEEDBACK_UNDEF) {
break;
}
else if (gnrc_netdev_get_tx_feedback(gnrc_netdev) == TX_FEEDBACK_SUCCESS) {
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_SUCCESSFUL;
reschedule = true;
break;
}
else if (gnrc_netdev_get_tx_feedback(gnrc_netdev) == TX_FEEDBACK_NOACK) {
LOG_ERROR("ERROR: [LWMAC-tx] Not ACKED\n");
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
else if (gnrc_netdev_get_tx_feedback(gnrc_netdev) == TX_FEEDBACK_BUSY) {
LOG_ERROR("ERROR: [LWMAC-tx] Channel busy \n");
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
LOG_ERROR("ERROR: [LWMAC-tx] Tx feedback unhandled: %i\n",
gnrc_netdev_get_tx_feedback(gnrc_netdev));
gnrc_netdev->tx.state = GNRC_LWMAC_TX_STATE_FAILED;
reschedule = true;
break;
}
case GNRC_LWMAC_TX_STATE_SUCCESSFUL:
case GNRC_LWMAC_TX_STATE_FAILED: {
break;
}
case GNRC_LWMAC_TX_STATE_STOPPED: {
LOG_DEBUG("[LWMAC-tx] Transmission state machine is stopped\n");
}
}
return reschedule;
}
void gnrc_lwmac_tx_update(gnrc_netdev_t *gnrc_netdev)
{
/* Update until no rescheduling needed */
while (_lwmac_tx_update(gnrc_netdev)) {}
}