Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor linked list to lock-free ring buffer #6139

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e2263e2
Refactor for consistency - use PolledTimeout throughout or don't use …
dok-net Aug 1, 2019
70c904e
Fix compiler warning
dok-net Aug 1, 2019
b998e45
Refactor PolledTimeout into library - portability (even NONOS to Free…
dok-net Aug 1, 2019
9af6ac0
Branch had become mangled with lots of unrelated changes - reduced to…
dok-net Jun 25, 2019
900aeb0
Core can't reference to libraries, need to move FunctionalInterrupt t…
dok-net Jun 25, 2019
20556d1
Fix crazy dependencies.
dok-net Jun 25, 2019
ec5337e
Updated examples
dok-net Jun 26, 2019
e904025
Remove redundant C-style casts
dok-net Jun 28, 2019
c14d86c
Updated credits
dok-net Jul 4, 2019
52130ce
Corrected library author and maintainer
dok-net Jul 5, 2019
7ce3faa
Update Ticker library
dok-net Jul 19, 2019
ebddd3d
More efficient loop extension
dok-net Jul 24, 2019
676ff32
yield() plugin code more straightforward, same efficiency
dok-net Jul 24, 2019
9f60051
Two-level hook system for esp_yield to accommodate host test environment
dok-net Jul 26, 2019
e571f20
Fix igrr's email address
dok-net Jul 26, 2019
c824058
Rename FunctionalInterrupt to ScheduledInterrupts. This was a review …
dok-net Jul 26, 2019
01ab60c
Add FastScheduler library, based on pluggable scheduler work for Sche…
dok-net Jul 26, 2019
59df727
Pull in Ticker.cpp into host build, FastScheduler needs it.
dok-net Jul 26, 2019
f4fbf2f
circular_queue Windows portability fix - included for version consist…
dok-net Jul 27, 2019
725b6c9
Enhanced circular_queue from EspSoftwareSerial.
dok-net Jul 30, 2019
db64356
Futher enhancements to circular_queue from EspSoftwareSerial.
dok-net Jul 31, 2019
3aceb03
ESP32 portability fix
dok-net Aug 1, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions cores/esp8266/HardwareSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <PolledTimeout.h>
#include "Arduino.h"
#include "HardwareSerial.h"
#include "Esp.h"
Expand Down Expand Up @@ -139,9 +138,9 @@ size_t HardwareSerial::readBytes(char* buffer, size_t size)

while (got < size)
{
esp8266::polledTimeout::oneShotFastMs timeOut(_timeout);
unsigned long startMillis = millis();
size_t avail;
while ((avail = available()) == 0 && !timeOut);
while ((avail = available()) == 0 && (millis() - startMillis) < _timeout);
if (avail == 0)
break;
got += read(buffer + got, std::min(size - got, avail));
Expand Down
30 changes: 10 additions & 20 deletions cores/esp8266/core_esp8266_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
//This may be used to change user task stack size:
//#define CONT_STACKSIZE 4096
#include <Arduino.h>
#include "Schedule.h"
extern "C" {
#include "ets_sys.h"
#include "os_type.h"
Expand All @@ -40,7 +39,6 @@ extern "C" {
#define OPTIMISTIC_YIELD_TIME_US 16000

extern "C" void call_user_start();
extern void loop();
extern void setup();
extern void (*__init_array_start)(void);
extern void (*__init_array_end)(void);
Expand Down Expand Up @@ -83,19 +81,15 @@ void preloop_update_frequency() {
#endif
}


static inline void esp_yield_within_cont() __attribute__((always_inline));
static void esp_yield_within_cont() {
cont_yield(g_pcont);
run_scheduled_recurrent_functions();
}

extern "C" void esp_yield() {
extern "C" void __esp_yield() __attribute__((weak));
extern "C" void __esp_yield() {
if (cont_can_yield(g_pcont)) {
esp_yield_within_cont();
cont_yield(g_pcont);
}
}

extern "C" void esp_yield(void) __attribute__ ((weak, alias("__esp_yield")));

extern "C" void esp_schedule() {
// always on CONT stack here
ets_post(LOOP_TASK_PRIORITY, 0, 0);
Expand All @@ -104,7 +98,7 @@ extern "C" void esp_schedule() {
extern "C" void __yield() {
if (cont_can_yield(g_pcont)) {
esp_schedule();
esp_yield_within_cont();
esp_yield();
}
else {
panic();
Expand All @@ -121,23 +115,19 @@ extern "C" void optimistic_yield(uint32_t interval_us) {
}
}

extern "C" void __loop_end (void)
{
run_scheduled_functions();
run_scheduled_recurrent_functions();
extern "C" void esp_loop(void) __attribute__((weak));
extern "C" void esp_loop(void) {
loop();
}

extern "C" void loop_end (void) __attribute__ ((weak, alias("__loop_end")));

static void loop_wrapper() {
static bool setup_done = false;
preloop_update_frequency();
if(!setup_done) {
setup();
setup_done = true;
}
loop();
loop_end();
esp_loop();
esp_schedule();
}

Expand Down
19 changes: 15 additions & 4 deletions cores/esp8266/core_esp8266_wiring_digital.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,21 @@ typedef struct {
bool functional;
} interrupt_handler_t;

//duplicate from functionalInterrupt.h keep in sync
//duplicates from functionalInterrupt.h keep in sync
typedef struct InterruptInfo {
uint8_t pin;
uint8_t value;
uint32_t micro;
} InterruptInfo;

typedef struct {
typedef struct FunctionInfo {
std::function<void(void)> reqFunction;
std::function<void(InterruptInfo)> reqScheduledFunction;
} FunctionInfo;

typedef struct ArgStructure {
InterruptInfo* interruptInfo;
void* functionInfo;
FunctionInfo* functionInfo;
} ArgStructure;

static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
Expand Down Expand Up @@ -172,7 +177,13 @@ void ICACHE_RAM_ATTR interrupt_handler(void*)
ETS_GPIO_INTR_ENABLE();
}

extern void cleanupFunctional(void* arg);
static void cleanupFunctional(void* arg)
{
ArgStructure* localArg = (ArgStructure*)arg;
delete localArg->interruptInfo;
delete localArg->functionInfo;
delete localArg;
}

static void set_interrupt_handlers(uint8_t pin, voidFuncPtr userFunc, void* arg, uint8_t mode, bool functional)
{
Expand Down
2 changes: 1 addition & 1 deletion libraries/ESP8266mDNS/src/LEAmDNS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
*
*/

#include <Schedule.h>
#include <FastScheduler.h>

#include "LEAmDNS_Priv.h"

Expand Down
10 changes: 10 additions & 0 deletions libraries/FastScheduler/library.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name=FastScheduler
version=1.0
author=Dirk O. Kaar <[email protected]>
maintainer=Dirk O. Kaar <[email protected]>
sentence=
paragraph=
category=Other
url=https://github.com/esp8266/Arduino
architectures=esp8266
dot_a_linkage=true
198 changes: 198 additions & 0 deletions libraries/FastScheduler/src/FastScheduler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#include "FastScheduler.h"
#include <PolledTimeout.h>
#include <Ticker.h>
#ifdef ESP8266
#include <interrupts.h>
#include <coredecls.h>
#else
#include <mutex>
#endif
#include "circular_queue/circular_queue_mp.h"

extern "C"
{
void esp_loop()
{
loop();
run_scheduled_functions(SCHEDULE_FUNCTION_FROM_LOOP);
}

#ifdef ESP8266
void __esp_yield();

extern "C" void esp_yield()
{
__esp_yield();
run_scheduled_functions(SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS);
}
#endif
}

typedef std::function<bool(void)> mFuncT;

struct scheduled_fn_t
{
mFuncT mFunc = nullptr;
esp8266::polledTimeout::periodicFastUs callNow;
schedule_e policy = SCHEDULE_FUNCTION_FROM_LOOP;
scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { }
};

// anonymous namespace provides compilation-unit internal linkage
namespace {
static circular_queue_mp<scheduled_fn_t> schedule_queue(SCHEDULED_FN_MAX_COUNT);
static esp8266::polledTimeout::periodicFastMs yieldSchedulerNow(100); // yield every 100ms
static schedule_e activePolicy;

class FastSchedulerTicker;
// A local heap for Ticker instances to prevent global heap exhaustion
class TickerHeap : public circular_queue_mp<FastSchedulerTicker*> {
public:
TickerHeap(const size_t capacity) : circular_queue_mp<FastSchedulerTicker*>(capacity)
{
heap = new char[capacity * sizeof(Ticker)];
for (size_t i = 0; i < capacity; ++i) push(reinterpret_cast<FastSchedulerTicker*>(heap + i * sizeof(Ticker)));
}
~TickerHeap()
{
delete heap;
}
protected:
char* heap;
};
static TickerHeap tickerHeap(SCHEDULED_FN_MAX_COUNT);

class FastSchedulerTicker : public Ticker
{
public:
static void operator delete(void* ptr) {
tickerHeap.push(static_cast<FastSchedulerTicker*>(ptr));
}
};
static_assert(sizeof(FastSchedulerTicker) == sizeof(Ticker), "sizeof(FastSchedulerTicker) != sizeof(Ticker)");

void ticker_scheduled(FastSchedulerTicker* ticker, const std::function<bool(void)>& fn, uint32_t repeat_us, schedule_e policy)
{
#ifdef ESP8266
auto repeat_ms = (repeat_us + 500) / 1000;
ticker->once_ms(repeat_ms, [ticker, fn, repeat_us, policy]()
#else
ticker->once_us(repeat_us, [ticker, fn, repeat_us, policy]()
#endif
{
if (!schedule_function([ticker, fn, repeat_us, policy]()
{
if (fn()) ticker_scheduled(ticker, fn, repeat_us, policy);
else delete ticker;
return false;
}, policy))
{
ticker_scheduled(ticker, fn, repeat_us, policy);
}
});
}

#ifdef ESP8266
constexpr uint32_t TICKER_MIN_US = 5000;
#else
constexpr uint32_t TICKER_MIN_US = 5000;
#endif
};

bool IRAM_ATTR schedule_recurrent_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us, schedule_e policy)
{
if (repeat_us >= TICKER_MIN_US)
{
// failure to aquire a Ticker must be returned to caller now. Specifically, allocating
// Tickers from inside the scheduled function doesn't work, any exhaustion of the scheduler
// can dead-lock it forever.
auto tickerPlace = tickerHeap.pop();
if (!tickerPlace) return false;
auto ticker = new(tickerPlace) FastSchedulerTicker;
if (!schedule_function([ticker, fn, repeat_us, policy]()
{
ticker_scheduled(ticker, std::move(fn), repeat_us, policy);
return false;
}, SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS))
{
delete ticker;
return false;
}
return true;
}
else
{
scheduled_fn_t item;
item.mFunc = std::move(fn);
if (repeat_us) item.callNow.reset(repeat_us);
item.policy = policy;
return schedule_queue.push(std::move(item));
}
}

bool IRAM_ATTR schedule_recurrent_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us, schedule_e policy)
{
return schedule_recurrent_function_us(std::function<bool(void)>(fn), repeat_us, policy);
}

bool IRAM_ATTR schedule_function(std::function<void(void)>&& fn, schedule_e policy)
{
return schedule_recurrent_function_us([fn]() { fn(); return false; }, 0, policy);
}

bool IRAM_ATTR schedule_function(const std::function<void(void)>& fn, schedule_e policy)
{
return schedule_function(std::function<void(void)>(fn), policy);
}

bool run_function(scheduled_fn_t& func)
{
if (yieldSchedulerNow) {
#if defined(ESP8266)
esp_schedule();
cont_yield(g_pcont);
#elif defined(ESP32)
vPortYield();
#else
yield();
#endif
}
return
(func.policy != SCHEDULE_FUNCTION_WITHOUT_YIELDELAYCALLS && activePolicy != SCHEDULE_FUNCTION_FROM_LOOP)
|| !func.callNow
|| func.mFunc();
}

void run_scheduled_functions(schedule_e policy)
{
// Note to the reader:
// There is no exposed API to remove a scheduled function:
// Scheduled functions are removed only from this function, and
// its purpose is that it is never called from an interrupt
// (always on cont stack).

static std::atomic<bool> fence(false);
#ifdef ESP8266
{
esp8266::InterruptLock lockAllInterruptsInThisScope;
if (fence.load()) {
// prevent any recursive calls from yield()
return;
}
fence.store(true);
}
#else
if (fence.exchange(true)) return;
#endif

yieldSchedulerNow.reset(100);
activePolicy = policy;

// run scheduled function:
// - when its schedule policy allows it anytime
// - or if we are called at loop() time
// and
// - its time policy allows it
schedule_queue.for_each_rev_requeue(run_function);
fence.store(false);
}
Loading