From d69faddb6cc720863142cd4216a02c9d3517c341 Mon Sep 17 00:00:00 2001 From: Sam Kumar Date: Fri, 5 Jan 2018 14:41:53 -0800 Subject: [PATCH 1/2] netdev: add send queue to support split-phase send operation Before this commit, the gnrc_netdev->send function was called immediately when a SEND message was received by netdev. This requires the radio driver to "block" the thread until the radio is free to send a packet. This commit changes netdev so support split-phase sending in the radio driver. The netdev thread now waits until the current send operation finishes before issuing a new one. This requires it to queue messages to send until the radio driver is no longer busy. This allows the event loop to handle other events, such as servicing interrupts or receiving packets, while the radio is busy sending a packet. Therefore, it has better concurrency characteristics than the previous model, which requires the gnrc_netdev->send function to block the event loop. --- sys/include/net/gnrc/netdev.h | 20 ++++++++++ sys/net/gnrc/link_layer/netdev/gnrc_netdev.c | 39 +++++++++++++++++--- 2 files changed, 54 insertions(+), 5 deletions(-) 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 */ From 7ae8c4d1eb99d45a95a409792dc66ee8cb2becaf Mon Sep 17 00:00:00 2001 From: Sam Kumar Date: Sat, 6 Jan 2018 21:56:41 -0800 Subject: [PATCH 2/2] at86rf2xx: implement software CSMA/link retries; fix concurrency bugs Although software CSMA and link retries are provided by the AT86RF233 radio's "Extended Operating Mode", the radio is unable to receive any frames in between CSMA probes and link retries. This interacts very poorly with protocols that require bidirectional communication. If two nodes try to send frames to each other at the same time, the CSMA backoff happens as it should, but when one of the nodes finally attempts to send a frame after a CSMA backoff, the other node is still performing CSMA backoff and therefore is not listening. The packet is therefore lost. Therefore, we implement CSMA and link retries in software, in such a way that packets may be received between CSMA probes or link retries. This functionality is enabled by defining the AT86Rf2XX_SOFTWARE_CSMA flag at compile-time. As this operation is split-phase, it requires the send queue at the netdev layer, introduced in the previous commit. We also fix concurrency issues in the at86rf2xx driver that were exacerbated when we introduced software CSMA and link retries. Concurrency bugs in at86rf2xx driver may cause a sent packet to overwrite the frame buffer while a packet is being received, causing outgoing packets to be "received" by the driver, and incoming packets to be dropped AFTER the radio sends a link-layer acknowledgment. Our fix is to detect, in a race-free way, if a frame is being received when a frame is sent, and to mark it as a CSMA failure before overwriting the radio's frame buffer. Note that the fix only works when software CSMA is enabled for the AT86RF2XX radio (the AT86RF2XX_SOFTWARE_CSMA flag). --- drivers/at86rf2xx/at86rf2xx.c | 79 +++++++- drivers/at86rf2xx/at86rf2xx_csma.c | 205 +++++++++++++++++++++ drivers/at86rf2xx/at86rf2xx_netdev.c | 96 +++++++++- drivers/at86rf2xx/include/at86rf2xx_csma.h | 130 +++++++++++++ drivers/include/at86rf2xx.h | 20 +- 5 files changed, 520 insertions(+), 10 deletions(-) create mode 100644 drivers/at86rf2xx/at86rf2xx_csma.c create mode 100644 drivers/at86rf2xx/include/at86rf2xx_csma.h 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