diff --git a/Makefile.dep b/Makefile.dep index 949924ec7281fd9acc0baf8e628b458eb2588d96..2a4fb44e1dc55c1b4cda2fa5e17d227af6277a93 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -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 diff --git a/sys/auto_init/netif/auto_init_at86rf2xx.c b/sys/auto_init/netif/auto_init_at86rf2xx.c index 88ba50284ece6f2b634a9ee82251d46f667e99d2..1766b4f7c250084167ae62f22b7eecbcea06759d 100644 --- a/sys/auto_init/netif/auto_init_at86rf2xx.c +++ b/sys/auto_init/netif/auto_init_at86rf2xx.c @@ -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 } } } diff --git a/sys/include/net/gnrc/lwmac/hdr.h b/sys/include/net/gnrc/lwmac/hdr.h new file mode 100644 index 0000000000000000000000000000000000000000..08fd018a3f23f5009e0f894545d488ecdcdef138 --- /dev/null +++ b/sys/include/net/gnrc/lwmac/hdr.h @@ -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 */ +/** @} */ diff --git a/sys/include/net/gnrc/lwmac/lwmac.h b/sys/include/net/gnrc/lwmac/lwmac.h new file mode 100644 index 0000000000000000000000000000000000000000..b682da4c6e9092eb3fc7deca0bf49784e674f5f3 --- /dev/null +++ b/sys/include/net/gnrc/lwmac/lwmac.h @@ -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 */ +/** @} */ diff --git a/sys/include/net/gnrc/lwmac/timeout.h b/sys/include/net/gnrc/lwmac/timeout.h new file mode 100644 index 0000000000000000000000000000000000000000..0a664b9deadcfc4a8273453b60fd9e35f90c52f1 --- /dev/null +++ b/sys/include/net/gnrc/lwmac/timeout.h @@ -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 */ +/** @} */ diff --git a/sys/include/net/gnrc/lwmac/types.h b/sys/include/net/gnrc/lwmac/types.h new file mode 100644 index 0000000000000000000000000000000000000000..4d2e03815ed8220414d4c84fa9cb1e9804bb5ba8 --- /dev/null +++ b/sys/include/net/gnrc/lwmac/types.h @@ -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 */ +/** @} */ diff --git a/sys/include/net/gnrc/mac/types.h b/sys/include/net/gnrc/mac/types.h index 63ac0e25dccf7283494797692ab1ad3a2dab8e9f..ac844a9ce3ed4958adc24a289cf956fdc361a5f0 100644 --- a/sys/include/net/gnrc/mac/types.h +++ b/sys/include/net/gnrc/mac/types.h @@ -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; /** diff --git a/sys/include/net/gnrc/netdev.h b/sys/include/net/gnrc/netdev.h index c6882b53d32f6cc734b684a9e9b2c55c697a510d..b49dff0c70b62920b26332f18fc73854364a69a9 100644 --- a/sys/include/net/gnrc/netdev.h +++ b/sys/include/net/gnrc/netdev.h @@ -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; diff --git a/sys/include/net/gnrc/nettype.h b/sys/include/net/gnrc/nettype.h index a455d24026cf3a182968243010fdc9099b2909a0..40d84fa9a536378ac92e74b5618862dceadfad89 100644 --- a/sys/include/net/gnrc/nettype.h +++ b/sys/include/net/gnrc/nettype.h @@ -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 diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile index e03f6f26d661acd1e07dfeec6d223c68ca55c8c9..de9e238a2ae01bcf86fca60f7902f0b473315e31 100644 --- a/sys/net/gnrc/Makefile +++ b/sys/net/gnrc/Makefile @@ -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 diff --git a/sys/net/gnrc/link_layer/gnrc_mac/internal.c b/sys/net/gnrc/link_layer/gnrc_mac/internal.c index 1279438e254fb4e7309ce7041fcbdc388ae16a0e..8e4fb09c293cfff10f12133ace25b7f81491e6ef 100644 --- a/sys/net/gnrc/link_layer/gnrc_mac/internal.c +++ b/sys/net/gnrc/link_layer/gnrc_mac/internal.c @@ -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])) { diff --git a/sys/net/gnrc/link_layer/lwmac/Makefile b/sys/net/gnrc/link_layer/lwmac/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4c7540701604ca78a00ec691b0a4362fa772dc75 --- /dev/null +++ b/sys/net/gnrc/link_layer/lwmac/Makefile @@ -0,0 +1,3 @@ +MODULE = gnrc_lwmac + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/link_layer/lwmac/include/lwmac_internal.h b/sys/net/gnrc/link_layer/lwmac/include/lwmac_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..88a5a1e397be1e5ad09f457c1020393c20d4c10c --- /dev/null +++ b/sys/net/gnrc/link_layer/lwmac/include/lwmac_internal.h @@ -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 */ +/** @} */ diff --git a/sys/net/gnrc/link_layer/lwmac/include/rx_state_machine.h b/sys/net/gnrc/link_layer/lwmac/include/rx_state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..4d585e0dade0d7cee9adcf3ebfac0ddcaee0ba1d --- /dev/null +++ b/sys/net/gnrc/link_layer/lwmac/include/rx_state_machine.h @@ -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 */ diff --git a/sys/net/gnrc/link_layer/lwmac/include/tx_state_machine.h b/sys/net/gnrc/link_layer/lwmac/include/tx_state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..4787c23444664d1742c9beed7d4300fb90628ee7 --- /dev/null +++ b/sys/net/gnrc/link_layer/lwmac/include/tx_state_machine.h @@ -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 */ diff --git a/sys/net/gnrc/link_layer/lwmac/lwmac.c b/sys/net/gnrc/link_layer/lwmac/lwmac.c new file mode 100644 index 0000000000000000000000000000000000000000..db855afcc1a8d200d7f001cc21c93b4e3b518acf --- /dev/null +++ b/sys/net/gnrc/link_layer/lwmac/lwmac.c @@ -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; +} diff --git a/sys/net/gnrc/link_layer/lwmac/lwmac_internal.c b/sys/net/gnrc/link_layer/lwmac/lwmac_internal.c new file mode 100644 index 0000000000000000000000000000000000000000..5ec668601333aaec4cb0522f16c61f02825a6bbf --- /dev/null +++ b/sys/net/gnrc/link_layer/lwmac/lwmac_internal.c @@ -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; +} diff --git a/sys/net/gnrc/link_layer/lwmac/rx_state_machine.c b/sys/net/gnrc/link_layer/lwmac/rx_state_machine.c new file mode 100644 index 0000000000000000000000000000000000000000..3f35ad7e688798d7153757e23a616b1357b1c3df --- /dev/null +++ b/sys/net/gnrc/link_layer/lwmac/rx_state_machine.c @@ -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)) {} +} diff --git a/sys/net/gnrc/link_layer/lwmac/timeout.c b/sys/net/gnrc/link_layer/lwmac/timeout.c new file mode 100644 index 0000000000000000000000000000000000000000..6291f2ee930b05f83df38a97f871c80479f02db4 --- /dev/null +++ b/sys/net/gnrc/link_layer/lwmac/timeout.c @@ -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]); + } + } +} diff --git a/sys/net/gnrc/link_layer/lwmac/tx_state_machine.c b/sys/net/gnrc/link_layer/lwmac/tx_state_machine.c new file mode 100644 index 0000000000000000000000000000000000000000..44df3bd520eb095cfc9d150b6106dffae9035c7f --- /dev/null +++ b/sys/net/gnrc/link_layer/lwmac/tx_state_machine.c @@ -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)) {} +}