Merge pull request #17180 from benpicco/drivers/dose-watchdog

drivers/dose: introduce watchdog timer
This commit is contained in:
Francisco 2022-01-18 23:43:29 +01:00 committed by GitHub
commit bc6624e67b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 152 additions and 41 deletions

View File

@ -3,6 +3,10 @@ FEATURES_REQUIRED += periph_uart
FEATURES_OPTIONAL += periph_uart_collision FEATURES_OPTIONAL += periph_uart_collision
FEATURES_OPTIONAL += periph_uart_rxstart_irq FEATURES_OPTIONAL += periph_uart_rxstart_irq
ifneq (,$(filter dose_watchdog,$(USEMODULE)))
FEATURES_REQUIRED += periph_timer_periodic
endif
USEMODULE += eui_provider USEMODULE += eui_provider
USEMODULE += iolist USEMODULE += iolist
USEMODULE += netdev_eth USEMODULE += netdev_eth

View File

@ -1,2 +1,4 @@
USEMODULE_INCLUDES_dose := $(LAST_MAKEFILEDIR)/include USEMODULE_INCLUDES_dose := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_dose) USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_dose)
PSEUDOMODULES += dose_watchdog

View File

@ -14,7 +14,7 @@
* @brief Implementation of the Differentially Operated Serial Ethernet driver * @brief Implementation of the Differentially Operated Serial Ethernet driver
* *
* @author Juergen Fitschen <me@jue.yt> * @author Juergen Fitschen <me@jue.yt>
* * Benjamin Valentin <benjamin.valentin@ml-pa.com>
* @} * @}
*/ */
@ -24,6 +24,7 @@
#include "dose.h" #include "dose.h"
#include "random.h" #include "random.h"
#include "irq.h" #include "irq.h"
#include "periph/timer.h"
#include "net/eui_provider.h" #include "net/eui_provider.h"
#include "net/netdev/eth.h" #include "net/netdev/eth.h"
@ -52,6 +53,9 @@ static int _init(netdev_t *dev);
static void _poweron(dose_t *dev); static void _poweron(dose_t *dev);
static void _poweroff(dose_t *dev, dose_state_t sleep_state); 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) static uint16_t crc16_update(uint16_t crc, uint8_t octet)
{ {
crc = (uint8_t)(crc >> 8) | (crc << 8); crc = (uint8_t)(crc >> 8) | (crc << 8);
@ -107,10 +111,87 @@ static inline void _disable_sense(dose_t *ctx)
#endif #endif
} }
#ifdef MODULE_DOSE_WATCHDOG
static unsigned _watchdog_users;
static dose_t *_dose_base;
static uint8_t _dose_numof;
static inline void _watchdog_start(void)
{
if (_watchdog_users) {
return;
}
_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;
}
DEBUG_PUTS("timeout");
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);
}
#else
static inline void _watchdog_start(void) {}
static inline void _watchdog_stop(void) {}
#endif
static dose_signal_t state_transit_blocked(dose_t *ctx, dose_signal_t signal) static dose_signal_t state_transit_blocked(dose_t *ctx, dose_signal_t signal)
{ {
uint32_t backoff;
(void) signal; (void) signal;
uint32_t backoff;
backoff = random_uint32_range(xtimer_usec_from_ticks(xtimer_min_timeout),
2 * ctx->timeout_base);
xtimer_set(&ctx->timeout, backoff);
return DOSE_SIGNAL_NONE;
}
static dose_signal_t state_transit_idle(dose_t *ctx, dose_signal_t signal)
{
(void) ctx;
(void) signal;
_watchdog_stop();
if (ctx->state == DOSE_STATE_RECV) { if (ctx->state == DOSE_STATE_RECV) {
/* We got here from RECV state. The driver's thread has to look /* We got here from RECV state. The driver's thread has to look
@ -123,27 +204,10 @@ static dose_signal_t state_transit_blocked(dose_t *ctx, dose_signal_t signal)
/* Enable interrupt for start bit sensing */ /* Enable interrupt for start bit sensing */
_enable_sense(ctx); _enable_sense(ctx);
/* The timeout will bring us back into IDLE state by a random time. /* Execute pending send */
* If we entered this state from RECV state, the random time lays if (ctx->flags & DOSE_FLAG_SEND_PENDING) {
* in the interval [1 * timeout, 2 * timeout]. If we came from return DOSE_SIGNAL_SEND;
* 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);
return DOSE_SIGNAL_NONE;
}
static dose_signal_t state_transit_idle(dose_t *ctx, dose_signal_t signal)
{
(void) ctx;
(void) signal;
return DOSE_SIGNAL_NONE; return DOSE_SIGNAL_NONE;
} }
@ -156,6 +220,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 /* We freshly entered this state. Thus, no start bit sensing is required
* anymore. Disable RX Start IRQs during the transmission. */ * anymore. Disable RX Start IRQs during the transmission. */
_disable_sense(ctx); _disable_sense(ctx);
_watchdog_start();
} }
if (signal == DOSE_SIGNAL_UART) { if (signal == DOSE_SIGNAL_UART) {
@ -182,7 +247,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. */ /* No signal is returned. We stay in the RECV state. */
xtimer_set(&ctx->timeout, ctx->timeout_base); xtimer_set(&ctx->timeout, ctx->timeout_base);
} }
@ -205,6 +270,7 @@ static dose_signal_t state_transit_send(dose_t *ctx, dose_signal_t signal)
#ifndef MODULE_PERIPH_UART_COLLISION #ifndef MODULE_PERIPH_UART_COLLISION
xtimer_set(&ctx->timeout, ctx->timeout_base); xtimer_set(&ctx->timeout, ctx->timeout_base);
#endif #endif
return DOSE_SIGNAL_NONE; return DOSE_SIGNAL_NONE;
} }
@ -220,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 * last 4 bits of a uint8_t, they can be added together and hence
* be checked together. */ * be checked together. */
switch (ctx->state + signal) { switch (ctx->state + signal) {
case DOSE_STATE_INIT + DOSE_SIGNAL_INIT: case DOSE_STATE_IDLE + DOSE_SIGNAL_SEND:
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:
signal = state_transit_blocked(ctx, signal); signal = state_transit_blocked(ctx, signal);
ctx->state = DOSE_STATE_BLOCKED; ctx->state = DOSE_STATE_BLOCKED;
break; 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); signal = state_transit_idle(ctx, signal);
ctx->state = DOSE_STATE_IDLE; ctx->state = DOSE_STATE_IDLE;
break; break;
@ -243,14 +309,14 @@ static void state(dose_t *ctx, dose_signal_t signal)
ctx->state = DOSE_STATE_RECV; ctx->state = DOSE_STATE_RECV;
break; break;
case DOSE_STATE_IDLE + DOSE_SIGNAL_SEND: case DOSE_STATE_BLOCKED + DOSE_SIGNAL_XTIMER:
case DOSE_STATE_SEND + DOSE_SIGNAL_UART: case DOSE_STATE_SEND + DOSE_SIGNAL_UART:
signal = state_transit_send(ctx, signal); signal = state_transit_send(ctx, signal);
ctx->state = DOSE_STATE_SEND; ctx->state = DOSE_STATE_SEND;
break; break;
default: 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; signal = DOSE_SIGNAL_NONE;
} }
} while (signal != DOSE_SIGNAL_NONE); } while (signal != DOSE_SIGNAL_NONE);
@ -279,7 +345,17 @@ static void _isr_xtimer(void *arg)
{ {
dose_t *dev = 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) static void clear_recv_buf(dose_t *ctx)
@ -287,6 +363,9 @@ static void clear_recv_buf(dose_t *ctx)
unsigned irq_state = irq_disable(); unsigned irq_state = irq_disable();
ctx->recv_buf_ptr = 0; 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_RECV_BUF_DIRTY);
CLRBIT(ctx->flags, DOSE_FLAG_END_RECEIVED); CLRBIT(ctx->flags, DOSE_FLAG_END_RECEIVED);
CLRBIT(ctx->flags, DOSE_FLAG_ESC_RECEIVED); CLRBIT(ctx->flags, DOSE_FLAG_ESC_RECEIVED);
@ -480,11 +559,13 @@ send:
crc = 0xffff; crc = 0xffff;
pktlen = 0; pktlen = 0;
/* Switch to state SEND */ /* Indicate intention to send */
do { SETBIT(ctx->flags, DOSE_FLAG_SEND_PENDING);
wait_for_state(ctx, DOSE_STATE_IDLE); state(ctx, DOSE_SIGNAL_SEND);
state(ctx, DOSE_SIGNAL_SEND);
} while (wait_for_state(ctx, DOSE_STATE_ANY) != DOSE_STATE_SEND); /* Wait for transition to SEND state */
wait_for_state(ctx, DOSE_STATE_SEND);
CLRBIT(ctx->flags, DOSE_FLAG_SEND_PENDING);
_send_start(ctx); _send_start(ctx);
@ -688,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) 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; ctx->netdev.driver = &netdev_driver_dose;
mutex_init(&ctx->state_mtx); mutex_init(&ctx->state_mtx);
@ -717,10 +796,20 @@ 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. * 8 data bits + 1 start bit + 1 stop bit per byte.
*/ */
ctx->timeout_base = CONFIG_DOSE_TIMEOUT_BYTES * 10UL * US_PER_SEC / params->baudrate; ctx->timeout_base = CONFIG_DOSE_TIMEOUT_BYTES * 10UL * US_PER_SEC / params->baudrate;
if (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(min_timeout); ctx->timeout_base = xtimer_usec_from_ticks(xtimer_min_timeout);
} }
DEBUG("dose timeout set to %" PRIu32 " µs\n", ctx->timeout_base); DEBUG("dose timeout set to %" PRIu32 " µs\n", ctx->timeout_base);
ctx->timeout.callback = _isr_xtimer; ctx->timeout.callback = _isr_xtimer;
ctx->timeout.arg = ctx; 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 */
} }

View File

@ -114,6 +114,7 @@ typedef enum {
#define DOSE_FLAG_RECV_BUF_DIRTY (BIT0) /**< Receive buffer contains a complete unhandled frame */ #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_END_RECEIVED (BIT1) /**< END octet has been received */
#define DOSE_FLAG_ESC_RECEIVED (BIT2) /**< ESC 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 */
/** @} */ /** @} */
/** /**
@ -154,6 +155,9 @@ typedef struct {
mutex_t state_mtx; /**< Is unlocked every time a state is (re)entered */ 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 */ 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 */ 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 #if !defined(MODULE_PERIPH_UART_RXSTART_IRQ) || DOXYGEN
gpio_t sense_pin; /**< GPIO to sense for start bits on the UART's rx line */ gpio_t sense_pin; /**< GPIO to sense for start bits on the UART's rx line */
#endif #endif

View File

@ -1,4 +1,16 @@
# the driver to test # the driver to test
USEMODULE += dose 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 include ../driver_netdev_common/Makefile.netdev.mk