diff --git a/drivers/at86rf2xx/at86rf2xx.c b/drivers/at86rf2xx/at86rf2xx.c index b8cb1905aebb..d3ed56b7f418 100644 --- a/drivers/at86rf2xx/at86rf2xx.c +++ b/drivers/at86rf2xx/at86rf2xx.c @@ -31,6 +31,7 @@ #include "at86rf2xx_registers.h" #include "at86rf2xx_internal.h" #include "at86rf2xx_netdev.h" +#include "at86rf2xx_csma.h" #define ENABLE_DEBUG (0) #include "debug.h" @@ -47,6 +48,10 @@ void at86rf2xx_setup(at86rf2xx_t *dev, const at86rf2xx_params_t *params) /* radio state is P_ON when first powered-on */ dev->state = AT86RF2XX_STATE_P_ON; dev->pending_tx = 0; + +#ifdef AT86RF2XX_SOFTWARE_CSMA + dev->pending_irq = 0; +#endif } void at86rf2xx_reset(at86rf2xx_t *dev) @@ -112,6 +117,13 @@ void at86rf2xx_reset(at86rf2xx_t *dev) tmp |= (AT86RF2XX_TRX_CTRL_0_CLKM_CTRL__OFF); at86rf2xx_reg_write(dev, AT86RF2XX_REG__TRX_CTRL_0, tmp); +#ifdef AT86RF2XX_SOFTWARE_CSMA + at86rf2xx_set_option(dev, AT86RF2XX_OPT_CSMA, true); + at86rf2xx_set_max_retries(dev, 0); + at86rf2xx_set_csma_max_retries(dev, 0); + at86rf2xx_set_csma_backoff_exp(dev, 0, 0); +#endif + /* enable interrupts */ at86rf2xx_reg_write(dev, AT86RF2XX_REG__IRQ_MASK, AT86RF2XX_IRQ_STATUS_MASK__TRX_END); @@ -131,31 +143,90 @@ size_t at86rf2xx_send(at86rf2xx_t *dev, uint8_t *data, size_t len) DEBUG("[at86rf2xx] Error: data to send exceeds max packet size\n"); return 0; } - at86rf2xx_tx_prepare(dev); +#ifdef AT86RF2XX_SOFTWARE_CSMA + at86rf2xx_csma_load(dev, data, len, 0); + at86rf2xx_csma_send(dev, 0); +#else + bool success = at86rf2xx_tx_prepare(dev); + if (!success) { + return 0; + } at86rf2xx_tx_load(dev, data, len, 0); at86rf2xx_tx_exec(dev); +#endif return len; } -void at86rf2xx_tx_prepare(at86rf2xx_t *dev) +bool at86rf2xx_tx_prepare(at86rf2xx_t *dev) { uint8_t state; dev->pending_tx++; /* make sure ongoing transmissions are finished */ +#ifdef AT86RF2XX_SOFTWARE_CSMA + state = at86rf2xx_get_status(dev); + if (state == AT86RF2XX_STATE_BUSY_RX_AACK || + state == AT86RF2XX_STATE_BUSY_TX_ARET) { + /* We should NOT send right now. This CSMA attempt should fail. */ + dev->pending_tx--; + return false; + } + + int irq_state = irq_disable(); + if (dev->pending_irq != 0) { + irq_restore(irq_state); + dev->pending_tx--; + /* + * If there are pending interrupts, we don't want to send just yet. + * What if the interrupt is for a received packet? + * Instead, we just return false. When we handle the interrupt, we will + * check for a received packet, and then attempt this CSMA probe again. + */ + return false; + } + irq_restore(irq_state); + + at86rf2xx_set_state(dev, AT86RF2XX_STATE_TX_ARET_ON); + if (at86rf2xx_get_status(dev) != AT86RF2XX_STATE_TX_ARET_ON) { + /* + * This means we transitioned to a busy state at the very last + * minute. Fail this CSMA attempt. + */ + dev->pending_tx--; + return false; + } + + /* + * Check for pending interrupts one last time. After this, no receive + * interrupt can happen, because we are in TX_ARET_ON. + */ + irq_state = irq_disable(); + if (dev->pending_irq != 0) { + irq_restore(irq_state); + at86rf2xx_set_state(dev, state); + dev->pending_tx--; + /* + * As above, we return false if there is a pending interrupt. + */ + return false; + } + irq_restore(irq_state); +#else do { state = at86rf2xx_get_status(dev); } while (state == AT86RF2XX_STATE_BUSY_RX_AACK || state == AT86RF2XX_STATE_BUSY_TX_ARET); + at86rf2xx_set_state(dev, AT86RF2XX_STATE_TX_ARET_ON); +#endif + if (state != AT86RF2XX_STATE_TX_ARET_ON) { dev->idle_state = state; } - at86rf2xx_set_state(dev, AT86RF2XX_STATE_TX_ARET_ON); - dev->tx_frame_len = IEEE802154_FCS_LEN; + return true; } size_t at86rf2xx_tx_load(at86rf2xx_t *dev, uint8_t *data, diff --git a/drivers/at86rf2xx/at86rf2xx_csma.c b/drivers/at86rf2xx/at86rf2xx_csma.c new file mode 100644 index 000000000000..572f8d46276a --- /dev/null +++ b/drivers/at86rf2xx/at86rf2xx_csma.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2018 University of California, Berkeley + * + * 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 drivers_at86rf2xx + * @{ + * + * @file + * @brief CSMA Implementation for AT86RF2xx without Deaf Listening + * + * @author Sam Kumar + * + * @} + */ + +#include "net/gnrc.h" +#include "net/gnrc/netdev.h" +#include "net/netdev.h" +#include "random.h" +#include "xtimer.h" + +#include "at86rf2xx.h" +#include "at86rf2xx_csma.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#ifndef AT86RF2XX_SOFTWARE_CSMA_MIN_BACKOFF_EXP +#define AT86RF2XX_SOFTWARE_CSMA_MIN_BACKOFF_EXP 3 +#endif + +#ifndef AT86RF2XX_SOFTWARE_CSMA_MAX_BACKOFF_EXP +#define AT86RF2XX_SOFTWARE_CSMA_MAX_BACKOFF_EXP 5 +#endif + +#ifndef AT86RF2XX_SOFTWARE_CSMA_MAX_TRIES +#define AT86RF2XX_SOFTWARE_CSMA_MAX_TRIES 5 +#endif + +#ifndef AT86RF2XX_SOFTWARE_CSMA_BACKOFF_MICROS +#define AT86RF2XX_SOFTWARE_CSMA_BACKOFF_MICROS 320 +#endif + +#ifndef AT86RF2XX_SOFTWARE_LINK_MAX_TRIES +#define AT86RF2XX_SOFTWARE_LINK_MAX_TRIES 5 +#endif + +#ifndef AT86RF2XX_SOFTWARE_LINK_RETRY_DELAY_MICROS +#define AT86RF2XX_SOFTWARE_LINK_RETRY_DELAY_MICROS 10000 +#endif + +#ifdef AT86RF2XX_SOFTWARE_CSMA + +bool at86rf2xx_csma_probe_and_send_if_pending(at86rf2xx_t* dev) { + if (dev->csma_should_probe_and_send) { + dev->csma_should_probe_and_send = false; + + + bool success = at86rf2xx_tx_prepare(dev); + if (!success) { + dev->csma_should_probe_and_send = true; + return false; + } + at86rf2xx_tx_load(dev, dev->csma_buf, dev->csma_buf_len, 0); + at86rf2xx_tx_exec(dev); + } + + return true; +} + +bool at86rf2xx_csma_clear_pending(at86rf2xx_t* dev) { + bool pending = dev->csma_should_probe_and_send; + dev->csma_should_probe_and_send = false; + return pending; +} + +static void try_send_packet(void* arg) { + at86rf2xx_t* dev = arg; + dev->csma_should_probe_and_send = true; + + int state = irq_disable(); + dev->pending_irq++; + irq_restore(state); + + /* + * arg is actually of the type at86rf2xx_t*, but at86rf2xx_t actually + * "inherits" netdev_t, so this is OK. + */ + netdev_t* ndev = arg; + + if (ndev->event_callback) { + ndev->event_callback(ndev, NETDEV_EVENT_ISR); + } +} + +static void csma_backoff_and_send(at86rf2xx_t* dev, uint32_t additional_delay_micros) { + uint8_t csma_try_index = AT86RF2XX_SOFTWARE_CSMA_MAX_TRIES - dev->csma_num_tries_left; + uint8_t be = AT86RF2XX_SOFTWARE_CSMA_MIN_BACKOFF_EXP + csma_try_index; + if (be > AT86RF2XX_SOFTWARE_CSMA_MAX_BACKOFF_EXP) { + be = AT86RF2XX_SOFTWARE_CSMA_MAX_BACKOFF_EXP; + } + uint32_t max_possible_csma_backoff = ((uint32_t) AT86RF2XX_SOFTWARE_CSMA_BACKOFF_MICROS) << be; + if (max_possible_csma_backoff == 0 && additional_delay_micros == 0) { + try_send_packet(dev); + } else { + uint32_t csma_backoff_micros = random_uint32_range(0, max_possible_csma_backoff); + uint32_t total_delay = csma_backoff_micros + additional_delay_micros; + xtimer_set(&dev->csma_backoff_timer, total_delay); + } +} + +int at86rf2xx_csma_load(at86rf2xx_t* dev, uint8_t* frame, size_t frame_len, size_t offset) { + assert(!dev->csma_in_progress); + + if (offset + frame_len > AT86RF2XX_MAX_PKT_LENGTH) { + return -EOVERFLOW; + } + + memcpy(&dev->csma_buf[offset], frame, frame_len); + dev->csma_buf_len = offset + frame_len; + return 0; +} + +void perform_link_try_with_csma(at86rf2xx_t* dev, uint32_t additional_delay_micros) { + assert(!dev->csma_in_progress); + + dev->csma_num_tries_left = AT86RF2XX_SOFTWARE_CSMA_MAX_TRIES; + dev->csma_in_progress = true; + dev->csma_should_probe_and_send = false; + + csma_backoff_and_send(dev, additional_delay_micros); +} + +void at86rf2xx_csma_send(at86rf2xx_t* dev, uint8_t num_link_tries) { + assert(!dev->link_tx_in_progress); + if (num_link_tries == 0) { + num_link_tries = AT86RF2XX_SOFTWARE_LINK_MAX_TRIES; + } + + dev->csma_backoff_timer.arg = dev; + dev->link_tx_num_tries_left = num_link_tries; + dev->link_tx_in_progress = true; + + perform_link_try_with_csma(dev, 0); +} + +void at86rf2xx_csma_csma_try_succeeded(at86rf2xx_t* dev) { + assert(!dev->csma_should_probe_and_send); + dev->csma_in_progress = false; + dev->csma_num_tries_left = 0; +} + +bool at86rf2xx_csma_csma_try_failed(at86rf2xx_t* dev) { + assert(!dev->csma_should_probe_and_send); + dev->csma_num_tries_left--; + if (dev->csma_num_tries_left == 0) { + dev->csma_in_progress = false; + + // Don't try again; inform upper layer of channel access failure. + return false; + } + + // Perform the next CSMA retry after a backoff. + csma_backoff_and_send(dev, 0); + return true; +} + +void at86rf2xx_csma_link_tx_try_succeeded(at86rf2xx_t* dev) { + assert(!dev->csma_in_progress); + assert(dev->link_tx_in_progress); + dev->link_tx_in_progress = false; + dev->link_tx_num_tries_left = 0; +} + +bool at86rf2xx_csma_link_tx_try_failed(at86rf2xx_t* dev) { + assert(!dev->csma_in_progress); + assert(dev->link_tx_in_progress); + dev->link_tx_num_tries_left--; + if (dev->link_tx_num_tries_left == 0) { + dev->link_tx_in_progress = false; + + // Don't try again; inform upper layer that transmission failed. + return false; + } + + // Perform the next link retransmission. + perform_link_try_with_csma(dev, AT86RF2XX_SOFTWARE_LINK_RETRY_DELAY_MICROS); + return true; +} + +void at86rf2xx_csma_init(at86rf2xx_t* dev) { + memset(&dev->csma_backoff_timer, 0x00, sizeof(dev->csma_backoff_timer)); + dev->csma_backoff_timer.callback = try_send_packet; + dev->csma_in_progress = false; + dev->csma_num_tries_left = 0; + dev->link_tx_in_progress = false; + dev->link_tx_num_tries_left = 0; +} + +#endif diff --git a/drivers/at86rf2xx/at86rf2xx_netdev.c b/drivers/at86rf2xx/at86rf2xx_netdev.c index d6cde3637ec7..9a6655382393 100644 --- a/drivers/at86rf2xx/at86rf2xx_netdev.c +++ b/drivers/at86rf2xx/at86rf2xx_netdev.c @@ -31,6 +31,7 @@ #include "net/netdev/ieee802154.h" #include "at86rf2xx.h" +#include "at86rf2xx_csma.h" #include "at86rf2xx_netdev.h" #include "at86rf2xx_internal.h" #include "at86rf2xx_registers.h" @@ -58,10 +59,18 @@ const netdev_driver_t at86rf2xx_driver = { static void _irq_handler(void *arg) { - netdev_t *dev = (netdev_t *) arg; +#ifdef AT86RF2XX_SOFTWARE_CSMA + at86rf2xx_t* dev = (at86rf2xx_t*) arg; - if (dev->event_callback) { - dev->event_callback(dev, NETDEV_EVENT_ISR); + int state = irq_disable(); + dev->pending_irq++; + irq_restore(state); +#endif + + netdev_t *netdev = (netdev_t *) arg; + + if (netdev->event_callback) { + netdev->event_callback(netdev, NETDEV_EVENT_ISR); } } @@ -91,6 +100,10 @@ static int _init(netdev_t *netdev) memset(&netdev->stats, 0, sizeof(netstats_t)); #endif +#ifdef AT86RF2XX_SOFTWARE_CSMA + at86rf2xx_csma_init(dev); +#endif + return 0; } @@ -100,7 +113,9 @@ static int _send(netdev_t *netdev, const struct iovec *vector, unsigned count) const struct iovec *ptr = vector; size_t len = 0; +#ifndef AT86RF2XX_SOFTWARE_CSMA at86rf2xx_tx_prepare(dev); +#endif /* load packet data into FIFO */ for (unsigned i = 0; i < count; i++, ptr++) { @@ -113,13 +128,22 @@ static int _send(netdev_t *netdev, const struct iovec *vector, unsigned count) #ifdef MODULE_NETSTATS_L2 netdev->stats.tx_bytes += len; #endif +#ifdef AT86RF2XX_SOFTWARE_CSMA + at86rf2xx_csma_load(dev, ptr->iov_base, ptr->iov_len, len); + len += ptr->iov_len; +#else len = at86rf2xx_tx_load(dev, ptr->iov_base, ptr->iov_len, len); +#endif } +#ifdef AT86RF2XX_SOFTWARE_CSMA + at86rf2xx_csma_send(dev, 0); +#else /* send data out directly if pre-loading id disabled */ if (!(dev->netdev.flags & AT86RF2XX_OPT_PRELOADING)) { at86rf2xx_tx_exec(dev); } +#endif /* return the number of bytes that were actually send out */ return (int)len; } @@ -549,12 +573,18 @@ static void _isr(netdev_t *netdev) uint8_t state; uint8_t trac_status; +#ifdef AT86RF2XX_SOFTWARE_CSMA + int irq_state = irq_disable(); + dev->pending_irq--; + irq_restore(irq_state); +#endif + /* If transceiver is sleeping register access is impossible and frames are * lost anyway, so return immediately. */ state = at86rf2xx_get_status(dev); if (state == AT86RF2XX_STATE_SLEEP) { - return; + goto end; } /* read (consume) device status */ @@ -573,7 +603,7 @@ static void _isr(netdev_t *netdev) state == AT86RF2XX_STATE_BUSY_RX_AACK) { DEBUG("[at86rf2xx] EVT - RX_END\n"); if (!(dev->netdev.flags & AT86RF2XX_OPT_TELL_RX_END)) { - return; + goto end; } netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE); } @@ -596,6 +626,14 @@ static void _isr(netdev_t *netdev) DEBUG("[at86rf2xx] EVT - TX_END\n"); if (netdev->event_callback && (dev->netdev.flags & AT86RF2XX_OPT_TELL_TX_END)) { +#ifdef AT86RF2XX_SOFTWARE_CSMA + bool csma_will_retry; + if (trac_status == AT86RF2XX_TRX_STATE__TRAC_SUCCESS + || trac_status == AT86RF2XX_TRX_STATE__TRAC_SUCCESS_DATA_PENDING) { + at86rf2xx_csma_csma_try_succeeded(dev); + at86rf2xx_csma_link_tx_try_succeeded(dev); + } +#endif switch (trac_status) { #ifdef MODULE_OPENTHREAD case AT86RF2XX_TRX_STATE__TRAC_SUCCESS: @@ -614,10 +652,31 @@ static void _isr(netdev_t *netdev) break; #endif case AT86RF2XX_TRX_STATE__TRAC_NO_ACK: +#ifdef AT86RF2XX_SOFTWARE_CSMA + at86rf2xx_csma_csma_try_succeeded(dev); + csma_will_retry = at86rf2xx_csma_link_tx_try_failed(dev); + if (csma_will_retry) { + break; + } +#endif netdev->event_callback(netdev, NETDEV_EVENT_TX_NOACK); DEBUG("[at86rf2xx] TX NO_ACK\n"); break; case AT86RF2XX_TRX_STATE__TRAC_CHANNEL_ACCESS_FAILURE: +#ifdef AT86RF2XX_SOFTWARE_CSMA + csma_will_retry = at86rf2xx_csma_csma_try_failed(dev); + if (csma_will_retry) { + break; + } + + /* + * If all CSMA attempts failed for a try, then just + * give up and cancel all remaining link retries. This + * is consistent with the behavior of the radio when + * hardware CSMA and link retries are used. + */ + at86rf2xx_csma_link_tx_try_succeeded(dev); +#endif netdev->event_callback(netdev, NETDEV_EVENT_TX_MEDIUM_BUSY); DEBUG("[at86rf2xx] TX_CHANNEL_ACCESS_FAILURE\n"); break; @@ -628,4 +687,31 @@ static void _isr(netdev_t *netdev) } } } + +end: +#ifdef AT86RF2XX_SOFTWARE_CSMA + if (!at86rf2xx_csma_probe_and_send_if_pending(dev)) { + bool was_pending = at86rf2xx_csma_clear_pending(dev); + + /* + * at86rf2xx_csma_probe_and_send_if_pending returned false, so + * was_pending MUST be true. + */ + assert(was_pending); + (void) was_pending; + + DEBUG("[at86rf2xx] CSMA timeout while receiving packet; failing attempt.\n"); + bool csma_will_retry = at86rf2xx_csma_csma_try_failed(dev); + if (!csma_will_retry) { + /* + * As described in the comment above, if all CSMA attempts + * fail for a link try, then cancel all remaining link + * retries and give up. + */ + at86rf2xx_csma_link_tx_try_succeeded(dev); + netdev->event_callback(netdev, NETDEV_EVENT_TX_MEDIUM_BUSY); + } + } +#endif + return; } diff --git a/drivers/at86rf2xx/include/at86rf2xx_csma.h b/drivers/at86rf2xx/include/at86rf2xx_csma.h new file mode 100644 index 000000000000..7aa817386a74 --- /dev/null +++ b/drivers/at86rf2xx/include/at86rf2xx_csma.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2018 University of California, Berkeley + * + * 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 drivers_at86rf2xx + * @{ + * + * @file + * @brief CSMA Implementation for AT86RF2xx without Deaf Listening + * + * @author Sam Kumar + */ + +#ifndef AT86RF2XX_CSMA_H +#define AT86RF2XX_CSMA_H + +#include "net/netdev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the Software CSMA module for AT86RF2XX. + * + * @param[in] dev device + */ +void at86rf2xx_csma_init(at86rf2xx_t* dev); + +/** + * @brief Load data into the software CSMA buffer. + * + * @param[in] dev device to use + * @param[in] frame pointer to buffer containing data + * @param[in] frame_len length of the buffer containing data + * @param[in] offset offset at which to write to the buffer + * + * @return returns 0 on success or a negative error code on failure + */ +int at86rf2xx_csma_load(at86rf2xx_t* dev, uint8_t* frame, size_t frame_len, size_t offset); + +/** + * @brief Initiate a send transaction link retries and CSMA for each link + * retry + * + * @param[in] dev device to use + * @param[in] num_link_tries number of link-level tries to perform (0 means use + * default value, AT86RF2XX_SOFTWARE_LINK_MAX_TRIES) + */ +void at86rf2xx_csma_send(at86rf2xx_t* dev, uint8_t num_link_tries); + +/** + * @brief Informs the CSMA module that a CSMA probe was successful: the + * channel was clear and the frame was transmitted. + * + * @param[in] dev device to use + */ +void at86rf2xx_csma_csma_try_succeeded(at86rf2xx_t* dev); + +/** + * @brief Informs the CSMA module that a CSMA probe was unsuccessful: the + * channel was not clear, and therefore the frame was not transmtted. + * + * @param[in] dev device to use + * + * @return returns true if the CSMA module will perform another + * CSMA probe, or false if we have run out of CSMA attempts + */ +bool at86rf2xx_csma_csma_try_failed(at86rf2xx_t* dev); + +/** + * @brief Informs the CSMA module that a link transmission was successful: + * a link-layer acknowledgment was received for the transmitted packet. + * + * @param[in] dev device to use + */ +void at86rf2xx_csma_link_tx_try_succeeded(at86rf2xx_t* dev); + +/** + * @brief Informs the CSMA module that a link transmission was unsuccessful: + * a link-layer acknowledgment was not received for the transmitted + * packet. + * + * @param[in] dev device to use + * + * @return returns true if the CSMA module will perform another + * link-layer attempt, or false if we have run out of + * link-layer attempts. Note that each link-layer attempt + * performs a separate round of CSMA + */ +bool at86rf2xx_csma_link_tx_try_failed(at86rf2xx_t* dev); + +/** + * @brief Requests the CSMA module to send a frame, if a CSMA attempt is + * pending. When the CSMA timer expires, the netdev event callback will + * be triggered, so that is a good place to call this function. This + * may trigger the radio to send a packet, so it should not be called + * in interrupt context. + * + * @param[in] dev device to use + * + * @return returns false if a packet was pending, but could not be + * sent because the radio was busy. + * + */ +bool at86rf2xx_csma_probe_and_send_if_pending(at86rf2xx_t* dev); + +/** + * @brief Cancels a pending CSMA attempt. This is only effective if the CSMA + * timer has expired, but the CSMA attempt has not been made. + * + * @param[in] dev device to use + * + * @return returns true if a CSMA attempt was actually pending, or + * false if there was no CSMA attempt pending (in which + * case, nothing happened). + */ +bool at86rf2xx_csma_clear_pending(at86rf2xx_t* dev); + +#ifdef __cplusplus +} +#endif + +#endif /* AT86RF2XX_CSMA_H */ +/** @} */ diff --git a/drivers/include/at86rf2xx.h b/drivers/include/at86rf2xx.h index 17fb82f064ed..d2943c67baab 100644 --- a/drivers/include/at86rf2xx.h +++ b/drivers/include/at86rf2xx.h @@ -38,6 +38,7 @@ #include "net/netdev.h" #include "net/netdev/ieee802154.h" #include "net/gnrc/nettype.h" +#include "xtimer.h" #ifdef __cplusplus extern "C" { @@ -185,6 +186,21 @@ typedef struct { /* Only radios with the XAH_CTRL_2 register support frame retry reporting */ uint8_t tx_retries; /**< Number of NOACK retransmissions */ #endif + +#ifdef AT86RF2XX_SOFTWARE_CSMA + xtimer_t csma_backoff_timer; + uint8_t csma_num_tries_left; + bool csma_in_progress; + bool csma_should_probe_and_send; + + uint8_t csma_buf[AT86RF2XX_MAX_PKT_LENGTH]; + uint8_t csma_buf_len; + + uint8_t link_tx_num_tries_left; + bool link_tx_in_progress; + + uint8_t pending_irq; +#endif /** @} */ } at86rf2xx_t; @@ -439,8 +455,10 @@ size_t at86rf2xx_send(at86rf2xx_t *dev, uint8_t *data, size_t len); * data is possible after it was called. * * @param[in] dev device to prepare for sending + * + * @return boolean indicating whether the operation succeeded */ -void at86rf2xx_tx_prepare(at86rf2xx_t *dev); +bool at86rf2xx_tx_prepare(at86rf2xx_t *dev); /** * @brief Load chunks of data into the transmit buffer of the given device diff --git a/sys/include/net/gnrc/netdev.h b/sys/include/net/gnrc/netdev.h index b49dff0c70b6..2a620903efdf 100644 --- a/sys/include/net/gnrc/netdev.h +++ b/sys/include/net/gnrc/netdev.h @@ -80,6 +80,13 @@ extern "C" { */ #define GNRC_NETDEV_MAC_INFO_CSMA_ENABLED (0x0100U) +/** + * @brief Determines the size of the netdev send queue. + */ +#ifndef GNRC_NETDEV_SEND_QUEUE_SIZE +#define GNRC_NETDEV_SEND_QUEUE_SIZE 32 +#endif + /** * @brief Structure holding GNRC netdev adapter state * @@ -116,6 +123,19 @@ typedef struct gnrc_netdev { */ kernel_pid_t pid; + /** + * @brief send queue for outgoing frames, used to allow split-phase tx + * operations in the radio driver. + */ + gnrc_pktsnip_t* send_queue_arr[GNRC_NETDEV_SEND_QUEUE_SIZE]; + cib_t send_queue; + + /** + * @brief keeps track of whether a (possibly) split-phase tx operation is + * in progress + */ + bool sending; + #ifdef MODULE_GNRC_MAC /** * @brief general information for the MAC protocol diff --git a/sys/net/gnrc/link_layer/netdev/gnrc_netdev.c b/sys/net/gnrc/link_layer/netdev/gnrc_netdev.c index 74917add8ce2..966c6bcd241b 100644 --- a/sys/net/gnrc/link_layer/netdev/gnrc_netdev.c +++ b/sys/net/gnrc/link_layer/netdev/gnrc_netdev.c @@ -73,14 +73,29 @@ static void _event_cb(netdev_t *dev, netdev_event_t event) break; } -#ifdef MODULE_NETSTATS_L2 case NETDEV_EVENT_TX_MEDIUM_BUSY: - dev->stats.tx_failed++; - break; + /* fallthrough intentional */ case NETDEV_EVENT_TX_COMPLETE: - dev->stats.tx_success++; - break; + /* fallthrough intentional */ + case NETDEV_EVENT_TX_NOACK: +#ifdef MODULE_NETSTATS_L2 + if (event == NETDEV_EVENT_TX_MEDIUM_BUSY) { + dev->stats.tx_failed++; + } else if (event == NETDEV_EVENT_TX_COMPLETE) { + dev->stats.tx_success++; + } #endif + { + int send_queue_idx = cib_get(&gnrc_netdev->send_queue); + if (send_queue_idx == -1) { + gnrc_netdev->sending = false; + break; + } + gnrc_pktsnip_t* pkt = gnrc_netdev->send_queue_arr[send_queue_idx]; + gnrc_netdev->send_queue_arr[send_queue_idx] = NULL; + gnrc_netdev->send(gnrc_netdev, pkt); + } + break; default: DEBUG("gnrc_netdev: warning: unhandled event %u.\n", event); } @@ -130,6 +145,9 @@ static void *_gnrc_netdev_thread(void *args) /* initialize low-level driver */ dev->driver->init(dev); + /* initialize send queue */ + cib_init(&gnrc_netdev->send_queue, GNRC_NETDEV_SEND_QUEUE_SIZE); + /* start the event loop */ while (1) { DEBUG("gnrc_netdev: waiting for incoming messages\n"); @@ -143,7 +161,18 @@ static void *_gnrc_netdev_thread(void *args) case GNRC_NETAPI_MSG_TYPE_SND: DEBUG("gnrc_netdev: GNRC_NETAPI_MSG_TYPE_SND received\n"); gnrc_pktsnip_t *pkt = msg.content.ptr; + if (gnrc_netdev->sending) { + int send_queue_idx = cib_put(&gnrc_netdev->send_queue); + if (send_queue_idx == -1) { + DEBUG("gnrc_netdev: GNRC_NETAPI_MSG_SEND dropping packet\n"); + gnrc_pktbuf_release(pkt); + break; + } + gnrc_netdev->send_queue_arr[send_queue_idx] = pkt; + break; + } gnrc_netdev->send(gnrc_netdev, pkt); + gnrc_netdev->sending = true; break; case GNRC_NETAPI_MSG_TYPE_SET: /* read incoming options */