1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-19 23:22:16 +03:00
esp8266/cores/esp8266/Schedule.cpp
Dirk O. Kaar c312a2eaf1
Implement esp_yield() as a replacement for delay(0)
esp_yield() now also calls esp_schedule(), original esp_yield() function renamed to esp_suspend().

Don't use delay(0) in the Core internals, libraries and examples. Use yield() when the code is
supposed to be called from CONT, use esp_yield() when the code can be called from either CONT or SYS.
Clean-up esp_yield() and esp_schedule() declarations across the code and use coredecls.h instead.

Implement helper functions for libraries that were previously using esp_yield(), esp_schedule() and
esp_delay() directly to wait for certain SYS context tasks to complete. Correctly use esp_delay()
for timeouts, make sure scheduled functions have a chance to run (e.g. LwIP_Ethernet uses recurrent)

Related issues:
- #6107 - discussion about the esp_yield() and esp_delay() usage in ClientContext
- #6212 - discussion about replacing delay() with a blocking loop
- #6680 - pull request introducing LwIP-based Ethernet
- #7146 - discussion that originated UART code changes
- #7969 - proposal to remove delay(0) from the example code
- #8291 - discussion related to the run_scheduled_recurrent_functions() usage in LwIP Ethernet
- #8317 - yieldUntil() implementation, similar to the esp_delay() overload with a timeout and a 0 interval
2021-10-17 00:19:01 +03:00

248 lines
6.4 KiB
C++

/*
Schedule.cpp - Scheduled functions.
Copyright (c) 2020 esp8266/Arduino
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <assert.h>
#include "Schedule.h"
#include "PolledTimeout.h"
#include "interrupts.h"
#include "coredecls.h"
typedef std::function<void(void)> mSchedFuncT;
struct scheduled_fn_t
{
scheduled_fn_t* mNext = nullptr;
mSchedFuncT mFunc;
};
static scheduled_fn_t* sFirst = nullptr;
static scheduled_fn_t* sLast = nullptr;
static scheduled_fn_t* sUnused = nullptr;
static int sCount = 0;
typedef std::function<bool(void)> mRecFuncT;
struct recurrent_fn_t
{
recurrent_fn_t* mNext = nullptr;
mRecFuncT mFunc;
esp8266::polledTimeout::periodicFastUs callNow;
std::function<bool(void)> alarm = nullptr;
recurrent_fn_t(esp8266::polledTimeout::periodicFastUs interval) : callNow(interval) { }
};
static recurrent_fn_t* rFirst = nullptr;
static recurrent_fn_t* rLast = nullptr;
// Returns a pointer to an unused sched_fn_t,
// or if none are available allocates a new one,
// or nullptr if limit is reached
IRAM_ATTR // called from ISR
static scheduled_fn_t* get_fn_unsafe()
{
scheduled_fn_t* result = nullptr;
// try to get an item from unused items list
if (sUnused)
{
result = sUnused;
sUnused = sUnused->mNext;
}
// if no unused items, and count not too high, allocate a new one
else if (sCount < SCHEDULED_FN_MAX_COUNT)
{
result = new (std::nothrow) scheduled_fn_t;
if (result)
++sCount;
}
return result;
}
static void recycle_fn_unsafe(scheduled_fn_t* fn)
{
fn->mFunc = nullptr; // special overload in c++ std lib
fn->mNext = sUnused;
sUnused = fn;
}
IRAM_ATTR // (not only) called from ISR
bool schedule_function(const std::function<void(void)>& fn)
{
if (!fn)
return false;
esp8266::InterruptLock lockAllInterruptsInThisScope;
scheduled_fn_t* item = get_fn_unsafe();
if (!item)
return false;
item->mFunc = fn;
item->mNext = nullptr;
if (sFirst)
sLast->mNext = item;
else
sFirst = item;
sLast = item;
return true;
}
IRAM_ATTR // (not only) called from ISR
bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
uint32_t repeat_us, const std::function<bool(void)>& alarm)
{
assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
if (!fn)
return false;
recurrent_fn_t* item = new (std::nothrow) recurrent_fn_t(repeat_us);
if (!item)
return false;
item->mFunc = fn;
item->alarm = alarm;
esp8266::InterruptLock lockAllInterruptsInThisScope;
if (rLast)
{
rLast->mNext = item;
}
else
{
rFirst = item;
}
rLast = item;
return true;
}
void run_scheduled_functions()
{
// prevent scheduling of new functions during this run
auto stop = sLast;
bool done = false;
while (sFirst && !done)
{
done = sFirst == stop;
sFirst->mFunc();
{
// remove function from stack
esp8266::InterruptLock lockAllInterruptsInThisScope;
auto to_recycle = sFirst;
// removing rLast
if (sLast == sFirst)
sLast = nullptr;
sFirst = sFirst->mNext;
recycle_fn_unsafe(to_recycle);
}
// scheduled functions might last too long for watchdog etc.
// yield() is allowed in scheduled functions, therefore
// recursion into run_scheduled_recurrent_functions() is permitted
optimistic_yield(100000);
}
}
void run_scheduled_recurrent_functions()
{
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
// 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).
auto current = rFirst;
if (!current)
return;
static bool fence = false;
{
// fence is like a mutex but as we are never called from ISR,
// locking is useless here. Leaving comment for reference.
//esp8266::InterruptLock lockAllInterruptsInThisScope;
if (fence)
// prevent recursive calls from yield()
// (even if they are not allowed)
return;
fence = true;
}
recurrent_fn_t* prev = nullptr;
// prevent scheduling of new functions during this run
auto stop = rLast;
bool done;
do
{
done = current == stop;
const bool wakeup = current->alarm && current->alarm();
bool callNow = current->callNow;
if ((wakeup || callNow) && !current->mFunc())
{
// remove function from stack
esp8266::InterruptLock lockAllInterruptsInThisScope;
auto to_ditch = current;
// removing rLast
if (rLast == current)
rLast = prev;
current = current->mNext;
if (prev)
{
prev->mNext = current;
}
else
{
rFirst = current;
}
delete(to_ditch);
}
else
{
prev = current;
current = current->mNext;
}
if (yieldNow)
{
// because scheduled functions might last too long for watchdog etc,
// this is yield() in cont stack, but need to call cont_suspend directly
// to prevent recursion into run_scheduled_recurrent_functions()
esp_schedule();
cont_suspend(g_pcont);
}
} while (current && !done);
fence = false;
}