Skip to content

Commit

Permalink
ESP32 task management (#2371)
Browse files Browse the repository at this point in the history
This PR aims to fix some issues relating to tasks and Sming. Using an esp32-s2, here's the task list as it looks presently:

```
#   | Core | Prio | Run Time | % Time | Name
  9 |   0  |   23 |    40591 |    2%  | wifi
  2 |   0  |   22 |    28009 |    1%  | esp_timer
  1 |   0  |   20 |        0 |    0%  | sys_evt
  8 |   0  |   18 |     3003 |    0%  | tiT
  5 |   0  |    1 |        0 |    0%  | Tmr Svc
  6 |   0  |    1 |  1928397 |   96%  | Sming
  4 |   0  |    0 |        0 |    0%  | IDLE
```

*tiT == TCP/IP (LWIP)
*Tmr Svc == FreeRTOS software timers (low resolution, runs at tick rate 1kHz)


And for this PR:


```
#   | Core | Prio | Run Time | % Time | Name
  7 |   0  |   23 |        0 |    0%  | wifi
  1 |   0  |   22 |       46 |    0%  | esp_timer
  5 |   0  |   20 |    21243 |    1%  | Sming
  6 |   0  |   18 |      375 |    0%  | tiT
  4 |   0  |    1 |        0 |    0%  | Tmr Svc
  3 |   0  |    0 |  1978336 |   98%  | IDLE
```

This is for a dual-core ESP32:

```
#   | Core | Prio | Run Time | % Time | Name
  1 |   0  |   24 |        0 |    0%  | ipc0
 14 |   0  |   23 |    25717 |    0%  | wifi
  3 |   0  |   22 |     2537 |    0%  | esp_timer
  8 |   0  |    1 |        0 |    0%  | Tmr Svc
  6 |   0  |    0 |  1971743 |   49%  | IDLE
  2 |   1  |   24 |        0 |    0%  | ipc1
 12 |   1  |   20 |    31857 |    0%  | Sming
 13 |   1  |   18 |     2088 |    0%  | tiT
  7 |   1  |    0 |  1966055 |   49%  | IDLE 
```

*ipcN == InterProcess Communication allows IDF to run code in the context of a specific CPU.

The FreeRTOS scheduler will run the highest-priority thread which is ready to do work.
It reverts to 'round-robin' if two ready threads have the same priority.

Sming is now a high priority task, so is now capable of blocking other tasks.
Sming takes priority over the tcpip thread.

The Sming task implements the standard event loop.
This ensures events are handled in the correct context.
When there are no events to process it will idle correctly and allow lower priority tasks to run.
The task watchdog is configured to fire if an event takes more than about 7 seconds.

Sming code makes good use of stack for performance reasons and needs a reasonably-sized stack (currently 16KBytes).
This applies to any tasks which run Sming code, currently the tcpip and Sming tasks.

We now have one less task to deal with.


**Default event queue**

Once the default event queue is running we could just use the existing code as-is.
However, to get the task watchdog to work correctly we need a custom event handling loop.

That involves replacing all the default event loop functions.

Another benefit of this approach is that where there are no events to process the CPU is allowed to idle.
This is more consistent with how the IDF is supposed to work and may be important for power saving.


**Software timers**

Sming uses its `Timer2` definition for this which now no longer uses the 'legacy' FRC timer implementation.

The IDF provides a queue for software timers managed by the `esp_timer` task.
As this is a higher priority than Sming it behaves like an interrupt, pre-empting Sming when it has work to do.
Instead of handling the timer event directly, we post it to the event queue so it is handled in the main Sming task.


**Hardware timers**

Sming uses its `Timer1` definition for this, which has now been implemented so the HardwareTimer class works as expected.
It uses Timer Group 0, Index 0 for this.


**Other changes**


Wait for UART FIFOs to empty at startup to avoid truncated debug messages.

Found `cache2phys` SDK call which implements `flashmem_get_address`.

SDK config only needs to specify `CONFIG_BOOTLOADER_LOG_LEVEL` setting, others are handled automatically.

Add `Wrap` build function to replace multiple uses of `-Wl,-wrap,XXXX` in makefiles.

WifiEventsImpl attempts to register handlers in static constructor, so provide `wifi_set_event_handler_cb` to defer this (as for ESP8266).

Add `TaskStat` class to allow simple monitoring of task usage. Add example use to Basic_IFS sample.
  • Loading branch information
mikee47 authored and slaff committed Sep 27, 2021
1 parent 36fe3e4 commit cc37231
Show file tree
Hide file tree
Showing 37 changed files with 851 additions and 326 deletions.
104 changes: 87 additions & 17 deletions Sming/Arch/Esp32/Components/driver/hw_timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,103 @@
****/

#include <driver/hw_timer.h>
#include <hal/timer_hal.h>
#include <esp_intr_alloc.h>

static struct {
hw_timer_callback_t func = nullptr;
void* arg = nullptr;
} nmi_callback;
namespace
{
struct TimerConfig {
timer_group_t group;
timer_idx_t index;
timer_hal_context_t hal;
intr_handle_t isr_handle;
hw_timer_callback_t callback;
void* arg;
bool autoload;
};

TimerConfig timer;

static void IRAM_ATTR nmi_handler()
void IRAM_ATTR timerIsr(void* arg)
{
nmi_callback.func(nmi_callback.arg);
auto& timer = *static_cast<TimerConfig*>(arg);

if(timer.callback != nullptr) {
timer.callback(arg);
}

timer_hal_clear_intr_status(&timer.hal);

if(timer.autoload) {
timer_hal_set_alarm_enable(&timer.hal, true);
} else {
timer_hal_set_counter_enable(&timer.hal, false);
}
}

} // namespace

void hw_timer1_attach_interrupt(hw_timer_source_type_t source_type, hw_timer_callback_t callback, void* arg)
{
if(source_type == TIMER_NMI_SOURCE) {
if(arg == NULL) {
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(reinterpret_cast<void (*)()>(callback));
} else {
nmi_callback.func = callback;
nmi_callback.arg = arg;
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(nmi_handler);
}
} else {
// ETS_FRC_TIMER1_INTR_ATTACH(callback, arg);
if(timer.isr_handle != nullptr) {
hw_timer1_detach_interrupt();
}

if(callback == nullptr) {
return;
}

timer.callback = callback;

uint32_t status_reg{0};
uint32_t mask{0};
timer_hal_get_status_reg_mask_bit(&timer.hal, &status_reg, &mask);
esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[timer.group].t0_irq_id + timer.index,
ESP_INTR_FLAG_IRAM, status_reg, mask, timerIsr, &timer, &timer.isr_handle);
timer_hal_clear_intr_status(&timer.hal);
timer_hal_intr_enable(&timer.hal);
}

void hw_timer1_detach_interrupt(void)
{
timer_hal_intr_disable(&timer.hal);
esp_intr_free(timer.isr_handle);
timer.isr_handle = nullptr;
}

void hw_timer1_enable(hw_timer_clkdiv_t div, hw_timer_intr_type_t intr_type, bool auto_load)
{
timer_hal_set_auto_reload(&timer.hal, auto_load);
timer_hal_set_divider(&timer.hal, 1 << div);
timer.autoload = auto_load;
}

void IRAM_ATTR hw_timer1_write(uint32_t ticks)
{
timer_hal_set_counter_value(&timer.hal, ticks);
timer_hal_set_counter_enable(&timer.hal, true);
}

void IRAM_ATTR hw_timer1_disable(void)
{
timer_hal_set_counter_enable(&timer.hal, false);
}

uint32_t hw_timer1_read(void)
{
uint64_t val{0};
timer_hal_get_counter_value(&timer.hal, &val);
return val;
}

void hw_timer_init(void)
{
ets_timer_init();
timer.group = HW_TIMER1_GROUP;
timer.index = HW_TIMER1_INDEX;
timer_hal_init(&timer.hal, timer.group, timer.index);
timer_hal_set_counter_enable(&timer.hal, false);
timer_hal_set_alarm_enable(&timer.hal, true);
timer_hal_set_alarm_value(&timer.hal, 0);
timer_hal_set_level_int_enable(&timer.hal, true);
timer_hal_set_counter_increase(&timer.hal, false);
}
106 changes: 27 additions & 79 deletions Sming/Arch/Esp32/Components/driver/include/driver/hw_timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,12 @@

#pragma once

#if defined(SUBARCH_ESP32)
#define FRC_TIMER_ENABLED
#endif

#include <esp_systemapi.h>
#ifdef FRC_TIMER_ENABLED
#include <soc/frc_timer_reg.h>
#else
#include <esp_timer.h>
#endif
#include <esp_attr.h>
#include <sming_attr.h>
#include <cstdint>

#define HW_TIMER_BASE_CLK APB_CLK_FREQ

#ifdef __cplusplus
extern "C" {
#endif

/**
* @defgroup hw_timer Hardware Timer Driver
* @ingroup drivers
Expand All @@ -35,21 +24,28 @@ extern "C" {

/*************************************
*
* FRC1 timer
*
* This is a 23-bit countdown timer
* Timer1
*
* Used to implement HardwareTimer class.
*
*************************************/

// Timer group/index to use: available on all ESP32 variants
#define HW_TIMER1_GROUP TIMER_GROUP_0
#define HW_TIMER1_INDEX TIMER_0

/**
* @brief Maximum timer interval in ticks
* @note The corresponding time interval depends on the prescaler in use:
*
* /1 - 26.84s
* /16 - 429.50s
* /256 - 6871.95s
*
* /1 - 0.1048s
* /16 - 1.677s
* /256 - 26.84s
* ESP32 supports a wide range of prescalers and uses 54-bit counter value.
* Limiting the range 31 bits avoids issues with overflows and moving to 64-bit calculations.
*/
#define MAX_HW_TIMER1_INTERVAL 0x7fffff
#define MAX_HW_TIMER1_INTERVAL 0x7fffffff

/**
* @brief Minimum hardware interval in microseconds
Expand Down Expand Up @@ -79,7 +75,7 @@ typedef enum {

/**
* @brief Attach an interrupt for the timer
* @param source_type
* @param source_type Ignored, uses APB clock source
* @param callback Callback function invoked via timer interrupt
* @param arg Passed to callback function
*/
Expand All @@ -88,94 +84,50 @@ void IRAM_ATTR hw_timer1_attach_interrupt(hw_timer_source_type_t source_type, hw
/**
* @brief Enable the timer
* @param div
* @param intr_type
* @param intr_type Ignored, always level-triggered
* @param auto_load
*/
inline void IRAM_ATTR hw_timer1_enable(hw_timer_clkdiv_t div, hw_timer_intr_type_t intr_type, bool auto_load)
{
#ifdef FRC_TIMER_ENABLED
uint32_t ctrl = (div & 0x0C) | (intr_type & 0x01) | FRC_TIMER_ENABLE;
if(auto_load) {
ctrl |= FRC_TIMER_AUTOLOAD;
}

REG_WRITE(FRC_TIMER_CTRL_REG(0), ctrl);
// TM1_EDGE_INT_ENABLE();
// ETS_FRC1_INTR_ENABLE();
#endif
}
void IRAM_ATTR hw_timer1_enable(hw_timer_clkdiv_t div, hw_timer_intr_type_t intr_type, bool auto_load);

/**
* @brief Set the timer interval
* @param ticks
*/
__forceinline void IRAM_ATTR hw_timer1_write(uint32_t ticks)
{
#ifdef FRC_TIMER_ENABLED
REG_WRITE(FRC_TIMER_LOAD_REG(0), ticks);
#endif
}
void IRAM_ATTR hw_timer1_write(uint32_t ticks);

/**
* @brief Disable the timer
*/
__forceinline void IRAM_ATTR hw_timer1_disable(void)
{
// TM1_EDGE_INT_DISABLE();
// ETS_FRC1_INTR_DISABLE();
}
void IRAM_ATTR hw_timer1_disable(void);

/**
* @brief Detach interrupt from the timer
*/
__forceinline void IRAM_ATTR hw_timer1_detach_interrupt(void)
{
hw_timer1_disable();
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
}
void IRAM_ATTR hw_timer1_detach_interrupt(void);

/**
* @brief Get timer1 count
* @retval uint32_t Current count value, counts from initial value down to 0
*/
__forceinline uint32_t hw_timer1_read(void)
{
#ifdef FRC_TIMER_ENABLED
return REG_READ(FRC_TIMER_COUNT_REG(0));
#else
return 0;
#endif
}
uint32_t hw_timer1_read(void);

/*************************************
*
* FRC2 timer
*
* This is a 32-bit count-up timer
*
* See idf components/esp32/esp_timer_esp32.c
* Timer2 uses the idf `esp_timer` component for software-based timers (os_timer.cpp).
*
*************************************/

#ifdef FRC_TIMER_ENABLED
constexpr uint32_t HW_TIMER2_CLKDIV = TIMER_CLKDIV_1;
constexpr uint32_t HW_TIMER2_CLK = HW_TIMER_BASE_CLK >> HW_TIMER2_CLKDIV;
#else
constexpr uint32_t HW_TIMER2_CLK = 1000000;
#endif

extern "C" int64_t esp_timer_get_time(void);

/**
* @brief Read current timer2 value
* @retval uint32_t
*/
__forceinline uint32_t hw_timer2_read(void)
{
#ifdef FRC_TIMER_ENABLED
return REG_READ(FRC_TIMER_COUNT_REG(1));
#else
return esp_timer_get_time();
#endif
}

#define NOW() hw_timer2_read()
Expand All @@ -187,7 +139,3 @@ __forceinline uint32_t hw_timer2_read(void)
void hw_timer_init(void);

/** @} */

#ifdef __cplusplus
}
#endif
59 changes: 44 additions & 15 deletions Sming/Arch/Esp32/Components/driver/include/driver/os_timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,51 @@
* os_timer.h
*
* This implementation mimics the behaviour of the ESP8266 Non-OS SDK timers,
* using Timer2 as the reference (which is _not_ in microseconds!)
* using Timer2 as the reference. The ESP32 uses a 1MHz reference so all times
*
* The ESP32 IDF contains more sophisticated timer implementations, but also
* this same API which it refers to as the 'legacy' timer API.
*/

#pragma once

#include <rom/ets_sys.h>
#include <cstdint>

#ifdef __cplusplus
extern "C" {
#endif
// Disarmed
#define OS_TIMER_DEFAULT() \
{ \
}

/**
* @defgroup os_timer OS Timer API
* @ingroup drivers
* @{
*/

typedef ETSTimerFunc os_timer_func_t;
typedef ETSTimer os_timer_t;
using smg_timer_func_t = void (*)(void* arg);

#define os_timer_arm(ptimer, ms, repeat_flag) ets_timer_arm(ptimer, ms, repeat_flag)
#define os_timer_arm_us(ptimer, us, repeat_flag) ets_timer_arm_us(ptimer, us, repeat_flag)
#define os_timer_disarm(ptimer) ets_timer_disarm(ptimer)
#define os_timer_setfn(ptimer, pfunction, parg) ets_timer_setfn(ptimer, pfunction, parg)
struct esp_timer;

struct smg_timer_t {
struct esp_timer* handle;
smg_timer_func_t timer_func;
void* timer_arg;
};

/*
* Re-map the os_timer_* definitions to avoid conflict with the real SDK implementations.
* Those are provided as a 'legacy' API which seems to be used only by WiFi in the SDK.
*/
#define os_timer_func_t smg_timer_func_t
#define os_timer_t smg_timer_t

#define os_timer_arm smg_timer_arm
#define os_timer_arm_us smg_timer_arm_us
#define os_timer_disarm smg_timer_disarm
#define os_timer_setfn smg_timer_setfn
#define os_timer_arm_ticks smg_timer_arm_ticks
#define os_timer_expire smg_timer_expire
#define os_timer_done smg_timer_done

/**
* @brief Set a software timer using the Timer2 tick value
Expand All @@ -44,10 +62,21 @@ typedef ETSTimer os_timer_t;
* This function has been added to Sming for more efficient and flexible use of
* software timers. It can be used alongside the SDK `os_timer_arm_new()` function.
*/
void os_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag);
void smg_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag);

/** @} */
void smg_timer_setfn(os_timer_t* ptimer, os_timer_func_t pfunction, void* parg);
void smg_timer_arm_us(os_timer_t* ptimer, uint32_t time_us, bool repeat_flag);
void smg_timer_arm(os_timer_t* ptimer, uint32_t time_ms, bool repeat_flag);
void smg_timer_disarm(os_timer_t* ptimer);
void smg_timer_done(os_timer_t* ptimer);

#ifdef __cplusplus
static inline uint64_t smg_timer_expire(const os_timer_t* ptimer)
{
if(ptimer == nullptr || ptimer->handle == nullptr) {
return 0;
}
// First field is 'alarm': See esp_timer.c.
return *reinterpret_cast<uint64_t*>(ptimer->handle);
}
#endif

/** @} */
Loading

0 comments on commit cc37231

Please sign in to comment.