diff --git a/extras/mactimer/component.mk b/extras/mactimer/component.mk new file mode 100644 index 0000000..9575109 --- /dev/null +++ b/extras/mactimer/component.mk @@ -0,0 +1,10 @@ +# Component makefile for extras/mactimer + +# Expected anyone using mactimer includes it as 'mactimer/mactimer.h' +INC_DIRS += $(mactimer_ROOT).. + +# args for passing into compile rule generation +mactimer_INC_DIR = +mactimer_SRC_DIR = $(mactimer_ROOT) + +$(eval $(call component_compile_rules,mactimer)) diff --git a/extras/mactimer/mactimer.c b/extras/mactimer/mactimer.c new file mode 100644 index 0000000..febebf9 --- /dev/null +++ b/extras/mactimer/mactimer.c @@ -0,0 +1,167 @@ +/* + * MAC NMI interrupt based timer support. + * + * Copyright (C) 2018 to 2019 OurAirQuality.org + * + * Licensed under the Apache License, Version 2.0, January 2004 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/ + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS WITH THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include "esp/dport_regs.h" +#include "mactimer/mactimer.h" + +/* The MAC timer handler offers a higher priority timer interrupt, but comes + * with some significant practical limitations. + */ + +static mactimer_t* timer_list = 0; + +void mactimer_setfn(mactimer_t *timer, mactimer_func_t *func, void *parg) +{ + timer->callback = func; + timer->timer_arg = parg; + timer->trigger_usec = 0; + timer->next = NULL; +} + +/* Return the Mac timer count, a 64 bit value. + * This can be called without the NMI interrupt disabled. */ +uint64_t IRAM mactime_get_count(void) { + uint32_t high1 = *(uint32_t volatile *)0x3FF21008; + uint32_t low = *(uint32_t volatile *)0x3FF21004; + uint32_t high2 = *(uint32_t volatile *)0x3FF21008; + + if (high1 != high2) { + /* The high word just clocked over, so resample the low value to + * match. It will not change for some time now so the new low word + * matches high2. */ + low = *(uint32_t volatile *)0x3FF21004; + } + + return ((uint64_t)high2 << 32UL) | (uint32_t)low; +} + +/* Set the Mac timer to trigger at the given absolute count. This is expected + * to be called with the NMI disabled, such as from a handler. */ +static void IRAM mactime_set_trigger(uint64_t count) { + *(uint32_t volatile *)0x3FF2109C = (uint32_t)count; + *(uint32_t volatile *)0x3FF210A0 = (uint32_t)(count >> 32UL); + *(uint32_t volatile *)0x3FF21098 |= 0x80000000; +} + +/* Insert the timer into the queue to trigger at the given absolute + * count. This does not actually set the timer trigger, and the caller is + * expected to do so. This is typically called from a handler to set the next + * trigger time, and the MAC timer handler sets the next trigger count if + * necessary before returning. */ +void IRAM mactime_add_pending(mactimer_t *timer, uint64_t count) +{ + mactimer_t *prev = NULL; + mactimer_t *curr = timer_list; + while (curr) { + if (((int64_t)count - (int64_t)curr->trigger_usec) < 1) { + break; + } + prev = curr; + curr = curr->next; + } + + timer->next = curr; + timer->trigger_usec = count; + + if (prev != NULL) { + prev->next = timer; + } else { + timer_list = timer; + } +} + +/* This is called outside the NMI context, with the NMI enabled, and it + * disables the NMI to synchronize access to the data structures. If a MAC + * timer handler wishes to set another timeout, such as for a periodic timer, + * then it need only call mactime_add_pending() before returning. + */ +void mactimer_arm(mactimer_t *timer, uint64_t count) +{ + /* Guard against being called withing the NMI handler. */ + if (sdk_NMIIrqIsOn == 0) { + /* Disable the maskable interrupts. */ + vPortEnterCritical(); + /* Disable the NMI. */ + do { + DPORT.DPORT0 &= 0xFFFFFFE0; + } while (DPORT.DPORT0 & 1); + } + + mactime_add_pending(timer, mactime_get_count() + count); + mactime_set_trigger(timer_list->trigger_usec); + + if (sdk_NMIIrqIsOn == 0) { + /* Reenable the NMI. */ + DPORT.DPORT0 = (DPORT.DPORT0 & 0xFFFFFFE0) | 1; + /* Enable the maskable interrupts. */ + vPortExitCritical(); + } +} + +/* + * NMI handler. The callbacks are called in this NMI context. If there are + * pending timers remaining when done then a new timeout is set. + * + * This is a fragile context that can be called even when processor interrupts + * are masked, so it can not touch data synchronized by disabling maskable + * interrupts. So don't expect to be able call into the FreeRTOS functions or + * the C library etc. + * + * It can be called with a flash operation in progress, so that the flash is + * not readable, so handlers can not depend on code or data stored in + * flash. Keep handlers in IRAM, and watch our for constant data that might be + * linked into flash. + * + * It might delay handling of MAC interrupts which could compromise the Wifi + * handling, so keep any handlers as quick as possible. + */ +static IRAM void mactimer_handler() +{ + while (timer_list) { + if (((int64_t)timer_list->trigger_usec - (int64_t)mactime_get_count()) > 10) { + /* Nothing remaining to handle now. */ + break; + } + + mactimer_t *timer = timer_list; + timer_list = timer->next; + timer->next = NULL; + timer->callback(timer->timer_arg); + } + + if (timer_list) { + /* Reset the trigger. */ + mactime_set_trigger(timer_list->trigger_usec); + } +} + +extern void IRAM sdk_wDev_MacTimSetFunc(void * arg0); + +void mactimer_init() +{ + timer_list = NULL; + + sdk_wDev_MacTimSetFunc(mactimer_handler); +} diff --git a/extras/mactimer/mactimer.h b/extras/mactimer/mactimer.h new file mode 100644 index 0000000..1475ec3 --- /dev/null +++ b/extras/mactimer/mactimer.h @@ -0,0 +1,35 @@ +/* + * MAC NMI interrupt based timer support. + * + * Copyright (C) 2018 to 2019 OurAirQuality.org + * + * Licensed under the Apache License, Version 2.0, January 2004 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/ + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS WITH THE SOFTWARE. + * + */ + +typedef void mactimer_func_t(void *); + +typedef struct mactimer_st { + struct mactimer_st *next; + mactimer_func_t *callback; + uint64_t trigger_usec; + void *timer_arg; +} mactimer_t; + +void mactimer_setfn(mactimer_t *timer, mactimer_func_t *func, void *parg); +uint64_t mactime_get_count(void); +void mactime_add_pending(mactimer_t *timer, uint64_t count); +void mactimer_arm(mactimer_t *timer, uint64_t count); +void mactimer_init(void);