From ddf80a8bdb48c323941f69bbd10d1e2dfdb82b9a Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Tue, 9 Nov 2021 21:59:27 +0100 Subject: [PATCH 1/3] drivers/dose: introduce watchdog timer --- drivers/dose/Makefile.dep | 4 ++ drivers/dose/Makefile.include | 2 + drivers/dose/dose.c | 121 ++++++++++++++++++++++++++++++---- drivers/include/dose.h | 3 + 4 files changed, 117 insertions(+), 13 deletions(-) diff --git a/drivers/dose/Makefile.dep b/drivers/dose/Makefile.dep index c483c0e946..753019f117 100644 --- a/drivers/dose/Makefile.dep +++ b/drivers/dose/Makefile.dep @@ -3,6 +3,10 @@ FEATURES_REQUIRED += periph_uart FEATURES_OPTIONAL += periph_uart_collision FEATURES_OPTIONAL += periph_uart_rxstart_irq +ifneq (,$(filter dose_watchdog,$(USEMODULE))) + FEATURES_REQUIRED += periph_timer_periodic +endif + USEMODULE += eui_provider USEMODULE += iolist USEMODULE += netdev_eth diff --git a/drivers/dose/Makefile.include b/drivers/dose/Makefile.include index 2c4778ddef..c25fbeaa9b 100644 --- a/drivers/dose/Makefile.include +++ b/drivers/dose/Makefile.include @@ -1,2 +1,4 @@ USEMODULE_INCLUDES_dose := $(LAST_MAKEFILEDIR)/include USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_dose) + +PSEUDOMODULES += dose_watchdog diff --git a/drivers/dose/dose.c b/drivers/dose/dose.c index d499b4ae63..0f08611e42 100644 --- a/drivers/dose/dose.c +++ b/drivers/dose/dose.c @@ -24,6 +24,7 @@ #include "dose.h" #include "random.h" #include "irq.h" +#include "periph/timer.h" #include "net/eui_provider.h" #include "net/netdev/eth.h" @@ -107,21 +108,77 @@ static inline void _disable_sense(dose_t *ctx) #endif } -static dose_signal_t state_transit_blocked(dose_t *ctx, dose_signal_t signal) -{ - uint32_t backoff; - (void) signal; +#ifdef MODULE_DOSE_WATCHDOG +static unsigned _watchdog_users; +static dose_t *_dose_base; +static uint8_t _dose_numof; - if (ctx->state == DOSE_STATE_RECV) { - /* We got here from RECV state. The driver's thread has to look - * if this frame should be processed. By queuing NETDEV_EVENT_ISR, - * the netif thread will call _isr at some time. */ - SETBIT(ctx->flags, DOSE_FLAG_RECV_BUF_DIRTY); - netdev_trigger_event_isr(&ctx->netdev); +static inline void _watchdog_start(void) +{ + if (_watchdog_users) { + return; } - /* Enable interrupt for start bit sensing */ - _enable_sense(ctx); + _watchdog_users++; + timer_start(DOSE_TIMER_DEV); +} + +static inline void _watchdog_stop(void) +{ + if (_watchdog_users == 0 || --_watchdog_users) { + return; + } + + timer_stop(DOSE_TIMER_DEV); +} + +static void _dose_watchdog_cb(void *arg, int chan) +{ + (void) chan; + (void) arg; + + for (unsigned i = 0; i < _dose_numof; ++i) { + dose_t *ctx = &_dose_base[i]; + + switch (ctx->state) { + case DOSE_STATE_RECV: + if (ctx->recv_buf_ptr_last != ctx->recv_buf_ptr) { + ctx->recv_buf_ptr_last = ctx->recv_buf_ptr; + break; + } + + if (ctx->flags & DOSE_FLAG_RECV_BUF_DIRTY) { + break; + } + + /* fall-through */ + case DOSE_STATE_BLOCKED: + state(&_dose_base[i], DOSE_SIGNAL_XTIMER); + break; + default: + break; + } + } +} + +static void _watchdog_init(unsigned timeout_us) +{ + timer_init(DOSE_TIMER_DEV, US_PER_SEC, _dose_watchdog_cb, NULL); + timer_set_periodic(DOSE_TIMER_DEV, 0, timeout_us, TIM_FLAG_RESET_ON_MATCH); + timer_stop(DOSE_TIMER_DEV); +} + +static inline void _set_random_backoff(dose_t *ctx) +{ + (void) ctx; +} + +#else +static inline void _watchdog_start(void) {} +static inline void _watchdog_stop(void) {} +static inline void _set_random_backoff(dose_t *ctx) +{ + uint32_t backoff; /* The timeout will bring us back into IDLE state by a random time. * If we entered this state from RECV state, the random time lays @@ -136,6 +193,27 @@ static dose_signal_t state_transit_blocked(dose_t *ctx, dose_signal_t signal) backoff = random_uint32_range(1 * ctx->timeout_base, 2 * ctx->timeout_base); } xtimer_set(&ctx->timeout, backoff); +} +#endif + +static dose_signal_t state_transit_blocked(dose_t *ctx, dose_signal_t signal) +{ + (void) signal; + + if (ctx->state == DOSE_STATE_RECV) { + /* We got here from RECV state. The driver's thread has to look + * if this frame should be processed. By queuing NETDEV_EVENT_ISR, + * the netif thread will call _isr at some time. */ + SETBIT(ctx->flags, DOSE_FLAG_RECV_BUF_DIRTY); + netdev_trigger_event_isr(&ctx->netdev); + } else if (ctx->state == DOSE_STATE_INIT) { + _watchdog_start(); + } + + /* Enable interrupt for start bit sensing */ + _enable_sense(ctx); + + _set_random_backoff(ctx); return DOSE_SIGNAL_NONE; } @@ -145,6 +223,8 @@ static dose_signal_t state_transit_idle(dose_t *ctx, dose_signal_t signal) (void) ctx; (void) signal; + _watchdog_stop(); + return DOSE_SIGNAL_NONE; } @@ -156,6 +236,7 @@ static dose_signal_t state_transit_recv(dose_t *ctx, dose_signal_t signal) /* We freshly entered this state. Thus, no start bit sensing is required * anymore. Disable RX Start IRQs during the transmission. */ _disable_sense(ctx); + _watchdog_start(); } if (signal == DOSE_SIGNAL_UART) { @@ -182,7 +263,7 @@ static dose_signal_t state_transit_recv(dose_t *ctx, dose_signal_t signal) } } - if (rc == DOSE_SIGNAL_NONE) { + if (rc == DOSE_SIGNAL_NONE && !IS_ACTIVE(MODULE_DOSE_WATCHDOG)) { /* No signal is returned. We stay in the RECV state. */ xtimer_set(&ctx->timeout, ctx->timeout_base); } @@ -197,6 +278,7 @@ static dose_signal_t state_transit_send(dose_t *ctx, dose_signal_t signal) if (ctx->state != DOSE_STATE_SEND) { /* Disable RX Start IRQs during the transmission. */ _disable_sense(ctx); + _watchdog_start(); } /* Don't trace any END octets ... the timeout or the END signal @@ -287,6 +369,9 @@ static void clear_recv_buf(dose_t *ctx) unsigned irq_state = irq_disable(); ctx->recv_buf_ptr = 0; +#ifdef MODULE_DOSE_WATCHDOG + ctx->recv_buf_ptr_last = -1; +#endif CLRBIT(ctx->flags, DOSE_FLAG_RECV_BUF_DIRTY); CLRBIT(ctx->flags, DOSE_FLAG_END_RECEIVED); CLRBIT(ctx->flags, DOSE_FLAG_ESC_RECEIVED); @@ -723,4 +808,14 @@ void dose_setup(dose_t *ctx, const dose_params_t *params, uint8_t index) DEBUG("dose timeout set to %" PRIu32 " µs\n", ctx->timeout_base); ctx->timeout.callback = _isr_xtimer; ctx->timeout.arg = ctx; + +#ifdef MODULE_DOSE_WATCHDOG + if (index >= _dose_numof) { + _dose_numof = index + 1; + } + if (index == 0) { + _dose_base = ctx; + _watchdog_init(ctx->timeout_base * 2); + } +#endif /* MODULE_DOSE_WATCHDOG */ } diff --git a/drivers/include/dose.h b/drivers/include/dose.h index 3ba955ccfe..04c7933712 100644 --- a/drivers/include/dose.h +++ b/drivers/include/dose.h @@ -154,6 +154,9 @@ typedef struct { mutex_t state_mtx; /**< Is unlocked every time a state is (re)entered */ uint8_t recv_buf[DOSE_FRAME_LEN]; /**< Receive buffer for incoming frames */ size_t recv_buf_ptr; /**< Index of the next empty octet of the recveive buffer */ +#if defined(MODULE_DOSE_WATCHDOG) || DOXYGEN + size_t recv_buf_ptr_last; /**< Last value of recv_buf_ptr when the watchdog visited */ +#endif #if !defined(MODULE_PERIPH_UART_RXSTART_IRQ) || DOXYGEN gpio_t sense_pin; /**< GPIO to sense for start bits on the UART's rx line */ #endif From 527d1bf559cb034570aa3275883f34a18592b71f Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 10 Nov 2021 15:18:56 +0100 Subject: [PATCH 2/3] tests/driver_dose: test dose_watchdog on select boards --- tests/driver_dose/Makefile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/driver_dose/Makefile b/tests/driver_dose/Makefile index 456947dcf4..be934a0cab 100644 --- a/tests/driver_dose/Makefile +++ b/tests/driver_dose/Makefile @@ -1,4 +1,16 @@ # the driver to test USEMODULE += dose +# compile-test dose_watchdog on select boards +ifneq (,$(filter samr21-xpro, $(BOARD))) + # xtimer uses timer 1 on this board + CFLAGS += -DDOSE_TIMER_DEV=TIMER_DEV\(0\) + USEMODULE += dose_watchdog +endif + +ifneq (,$(filter same54-xpro, $(BOARD))) + CFLAGS += -DDOSE_TIMER_DEV=TIMER_DEV\(1\) + USEMODULE += dose_watchdog +endif + include ../driver_netdev_common/Makefile.netdev.mk From 3e1076e3b9b0e1bd5601fe26142fcb660aa83bbb Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Thu, 18 Nov 2021 18:55:37 +0100 Subject: [PATCH 3/3] drivers/dose: set backoff timer before send --- drivers/dose/dose.c | 114 +++++++++++++++++++---------------------- drivers/include/dose.h | 1 + 2 files changed, 55 insertions(+), 60 deletions(-) diff --git a/drivers/dose/dose.c b/drivers/dose/dose.c index 0f08611e42..d91c0859a4 100644 --- a/drivers/dose/dose.c +++ b/drivers/dose/dose.c @@ -14,7 +14,7 @@ * @brief Implementation of the Differentially Operated Serial Ethernet driver * * @author Juergen Fitschen - * + * Benjamin Valentin * @} */ @@ -53,6 +53,9 @@ static int _init(netdev_t *dev); static void _poweron(dose_t *dev); static void _poweroff(dose_t *dev, dose_state_t sleep_state); +/* smallest possible xtimer timeout */ +static const xtimer_ticks32_t xtimer_min_timeout = {.ticks32 = XTIMER_BACKOFF}; + static uint16_t crc16_update(uint16_t crc, uint8_t octet) { crc = (uint8_t)(crc >> 8) | (crc << 8); @@ -151,8 +154,7 @@ static void _dose_watchdog_cb(void *arg, int chan) break; } - /* fall-through */ - case DOSE_STATE_BLOCKED: + DEBUG_PUTS("timeout"); state(&_dose_base[i], DOSE_SIGNAL_XTIMER); break; default: @@ -167,53 +169,19 @@ static void _watchdog_init(unsigned timeout_us) timer_set_periodic(DOSE_TIMER_DEV, 0, timeout_us, TIM_FLAG_RESET_ON_MATCH); timer_stop(DOSE_TIMER_DEV); } - -static inline void _set_random_backoff(dose_t *ctx) -{ - (void) ctx; -} - #else static inline void _watchdog_start(void) {} static inline void _watchdog_stop(void) {} -static inline void _set_random_backoff(dose_t *ctx) -{ - uint32_t backoff; - - /* The timeout will bring us back into IDLE state by a random time. - * If we entered this state from RECV state, the random time lays - * in the interval [1 * timeout, 2 * timeout]. If we came from - * SEND state, a time in the interval [2 * timeout, 3 * timeout] - * will be picked. This ensures that responding nodes get preferred - * bus access and sending nodes do not overwhelm listening nodes. */ - if (ctx->state == DOSE_STATE_SEND) { - backoff = random_uint32_range(2 * ctx->timeout_base, 3 * ctx->timeout_base); - } - else { - backoff = random_uint32_range(1 * ctx->timeout_base, 2 * ctx->timeout_base); - } - xtimer_set(&ctx->timeout, backoff); -} #endif static dose_signal_t state_transit_blocked(dose_t *ctx, dose_signal_t signal) { (void) signal; + uint32_t backoff; - if (ctx->state == DOSE_STATE_RECV) { - /* We got here from RECV state. The driver's thread has to look - * if this frame should be processed. By queuing NETDEV_EVENT_ISR, - * the netif thread will call _isr at some time. */ - SETBIT(ctx->flags, DOSE_FLAG_RECV_BUF_DIRTY); - netdev_trigger_event_isr(&ctx->netdev); - } else if (ctx->state == DOSE_STATE_INIT) { - _watchdog_start(); - } - - /* Enable interrupt for start bit sensing */ - _enable_sense(ctx); - - _set_random_backoff(ctx); + backoff = random_uint32_range(xtimer_usec_from_ticks(xtimer_min_timeout), + 2 * ctx->timeout_base); + xtimer_set(&ctx->timeout, backoff); return DOSE_SIGNAL_NONE; } @@ -225,6 +193,22 @@ static dose_signal_t state_transit_idle(dose_t *ctx, dose_signal_t signal) _watchdog_stop(); + if (ctx->state == DOSE_STATE_RECV) { + /* We got here from RECV state. The driver's thread has to look + * if this frame should be processed. By queuing NETDEV_EVENT_ISR, + * the netif thread will call _isr at some time. */ + SETBIT(ctx->flags, DOSE_FLAG_RECV_BUF_DIRTY); + netdev_trigger_event_isr(&ctx->netdev); + } + + /* Enable interrupt for start bit sensing */ + _enable_sense(ctx); + + /* Execute pending send */ + if (ctx->flags & DOSE_FLAG_SEND_PENDING) { + return DOSE_SIGNAL_SEND; + } + return DOSE_SIGNAL_NONE; } @@ -278,7 +262,6 @@ static dose_signal_t state_transit_send(dose_t *ctx, dose_signal_t signal) if (ctx->state != DOSE_STATE_SEND) { /* Disable RX Start IRQs during the transmission. */ _disable_sense(ctx); - _watchdog_start(); } /* Don't trace any END octets ... the timeout or the END signal @@ -287,6 +270,7 @@ static dose_signal_t state_transit_send(dose_t *ctx, dose_signal_t signal) #ifndef MODULE_PERIPH_UART_COLLISION xtimer_set(&ctx->timeout, ctx->timeout_base); #endif + return DOSE_SIGNAL_NONE; } @@ -302,16 +286,16 @@ static void state(dose_t *ctx, dose_signal_t signal) * last 4 bits of a uint8_t, they can be added together and hence * be checked together. */ switch (ctx->state + signal) { - case DOSE_STATE_INIT + DOSE_SIGNAL_INIT: - case DOSE_STATE_RECV + DOSE_SIGNAL_END: - case DOSE_STATE_RECV + DOSE_SIGNAL_XTIMER: - case DOSE_STATE_SEND + DOSE_SIGNAL_END: - case DOSE_STATE_SEND + DOSE_SIGNAL_XTIMER: + case DOSE_STATE_IDLE + DOSE_SIGNAL_SEND: signal = state_transit_blocked(ctx, signal); ctx->state = DOSE_STATE_BLOCKED; break; - case DOSE_STATE_BLOCKED + DOSE_SIGNAL_XTIMER: + case DOSE_STATE_SEND + DOSE_SIGNAL_END: + case DOSE_STATE_SEND + DOSE_SIGNAL_XTIMER: + case DOSE_STATE_INIT + DOSE_SIGNAL_INIT: + case DOSE_STATE_RECV + DOSE_SIGNAL_END: + case DOSE_STATE_RECV + DOSE_SIGNAL_XTIMER: signal = state_transit_idle(ctx, signal); ctx->state = DOSE_STATE_IDLE; break; @@ -325,14 +309,14 @@ static void state(dose_t *ctx, dose_signal_t signal) ctx->state = DOSE_STATE_RECV; break; - case DOSE_STATE_IDLE + DOSE_SIGNAL_SEND: + case DOSE_STATE_BLOCKED + DOSE_SIGNAL_XTIMER: case DOSE_STATE_SEND + DOSE_SIGNAL_UART: signal = state_transit_send(ctx, signal); ctx->state = DOSE_STATE_SEND; break; default: - DEBUG("dose state(): unexpected state transition (STATE=0x%02d SIGNAL=0x%02d)\n", ctx->state, signal); + DEBUG("dose state(): unexpected state transition (STATE=0x%02x SIGNAL=0x%02x)\n", ctx->state, signal); signal = DOSE_SIGNAL_NONE; } } while (signal != DOSE_SIGNAL_NONE); @@ -361,7 +345,17 @@ static void _isr_xtimer(void *arg) { dose_t *dev = arg; - state(dev, DOSE_SIGNAL_XTIMER); + switch (dev->state) { +#ifndef MODULE_DOSE_WATCHDOG + case DOSE_STATE_RECV: +#endif + case DOSE_STATE_BLOCKED: + case DOSE_STATE_SEND: + state(dev, DOSE_SIGNAL_XTIMER); + break; + default: + ; + } } static void clear_recv_buf(dose_t *ctx) @@ -565,11 +559,13 @@ send: crc = 0xffff; pktlen = 0; - /* Switch to state SEND */ - do { - wait_for_state(ctx, DOSE_STATE_IDLE); - state(ctx, DOSE_SIGNAL_SEND); - } while (wait_for_state(ctx, DOSE_STATE_ANY) != DOSE_STATE_SEND); + /* Indicate intention to send */ + SETBIT(ctx->flags, DOSE_FLAG_SEND_PENDING); + state(ctx, DOSE_SIGNAL_SEND); + + /* Wait for transition to SEND state */ + wait_for_state(ctx, DOSE_STATE_SEND); + CLRBIT(ctx->flags, DOSE_FLAG_SEND_PENDING); _send_start(ctx); @@ -773,8 +769,6 @@ static const netdev_driver_t netdev_driver_dose = { void dose_setup(dose_t *ctx, const dose_params_t *params, uint8_t index) { - static const xtimer_ticks32_t min_timeout = {.ticks32 = XTIMER_BACKOFF}; - ctx->netdev.driver = &netdev_driver_dose; mutex_init(&ctx->state_mtx); @@ -802,8 +796,8 @@ void dose_setup(dose_t *ctx, const dose_params_t *params, uint8_t index) * 8 data bits + 1 start bit + 1 stop bit per byte. */ ctx->timeout_base = CONFIG_DOSE_TIMEOUT_BYTES * 10UL * US_PER_SEC / params->baudrate; - if (ctx->timeout_base < xtimer_usec_from_ticks(min_timeout)) { - ctx->timeout_base = xtimer_usec_from_ticks(min_timeout); + if (ctx->timeout_base < xtimer_usec_from_ticks(xtimer_min_timeout)) { + ctx->timeout_base = xtimer_usec_from_ticks(xtimer_min_timeout); } DEBUG("dose timeout set to %" PRIu32 " µs\n", ctx->timeout_base); ctx->timeout.callback = _isr_xtimer; diff --git a/drivers/include/dose.h b/drivers/include/dose.h index 04c7933712..26bb61a5d1 100644 --- a/drivers/include/dose.h +++ b/drivers/include/dose.h @@ -114,6 +114,7 @@ typedef enum { #define DOSE_FLAG_RECV_BUF_DIRTY (BIT0) /**< Receive buffer contains a complete unhandled frame */ #define DOSE_FLAG_END_RECEIVED (BIT1) /**< END octet has been received */ #define DOSE_FLAG_ESC_RECEIVED (BIT2) /**< ESC octet has been received */ +#define DOSE_FLAG_SEND_PENDING (BIT3) /**< A send operation is pending */ /** @} */ /**