mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-25 20:02:37 +03:00
Merge branch 'master' into feature/issue-2246-multi-wifi-hidden
This commit is contained in:
commit
d90015e326
10
.github/workflows/pull-request.yml
vendored
10
.github/workflows/pull-request.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./tools/dist
|
||||
key: key-linux-toolchain
|
||||
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||
- name: Build Sketches
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
@ -62,7 +62,7 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./tools/dist
|
||||
key: key-linux-toolchain
|
||||
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||
- name: Build Sketches
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
@ -90,7 +90,7 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./tools/dist
|
||||
key: key-windows-toolchain
|
||||
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||
- name: Build Sketch
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
@ -122,7 +122,7 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./tools/dist
|
||||
key: key-mac-toolchain
|
||||
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||
- name: Build Sketch
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
@ -260,7 +260,7 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./tools/dist
|
||||
key: key-linux-toolchain
|
||||
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||
- name: Boards.txt diff
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
|
71
.travis.yml
71
.travis.yml
@ -1,71 +0,0 @@
|
||||
# TravisCI left in repo as a backup CI solution in case GitHub CI suffers a
|
||||
# major disruption. Only a few, quick tests are run in order to keep the
|
||||
# TravisCI runtime equal to the GitHub CI time (to avoid bottlenecking on
|
||||
# TravisCI).
|
||||
#
|
||||
# If GitHub CI goes away, it would make sense to drop this .YML file and
|
||||
# use the complete one from release tag 2.7.2
|
||||
|
||||
|
||||
language: bash
|
||||
os: linux
|
||||
dist: bionic
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
submodules: false
|
||||
|
||||
before_install:
|
||||
- git submodule update --init # no recursive update
|
||||
|
||||
stages:
|
||||
- build
|
||||
- deploy
|
||||
|
||||
|
||||
# Run only 5 jobs since TravisCI only allows 5 in parallel
|
||||
jobs:
|
||||
include:
|
||||
- name: "Platform.IO"
|
||||
stage: build
|
||||
script: $TRAVIS_BUILD_DIR/tests/platformio.sh
|
||||
install:
|
||||
- sudo apt-get install python3-pip python3-setuptools
|
||||
env:
|
||||
# PIO is very slow, so do 1/2 as many builds as Arduino
|
||||
- BUILD_PARITY=custom mod=20 rem=5
|
||||
|
||||
- name: "Build 1/4"
|
||||
stage: build
|
||||
script: $TRAVIS_BUILD_DIR/tests/build.sh
|
||||
env:
|
||||
- BUILD_PARITY=custom mod=10 rem=1
|
||||
|
||||
- name: "Build 2/4"
|
||||
stage: build
|
||||
script: $TRAVIS_BUILD_DIR/tests/build.sh
|
||||
env:
|
||||
- BUILD_PARITY=custom mod=10 rem=2
|
||||
|
||||
- name: "Build 3/4"
|
||||
stage: build
|
||||
script: $TRAVIS_BUILD_DIR/tests/build.sh
|
||||
env:
|
||||
- BUILD_PARITY=custom mod=10 rem=3
|
||||
|
||||
- name: "Build 4/4"
|
||||
stage: build
|
||||
script: $TRAVIS_BUILD_DIR/tests/build.sh
|
||||
env:
|
||||
- BUILD_PARITY=custom mod=10 rem=4
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: change
|
||||
on_failure: change
|
||||
webhooks:
|
||||
urls:
|
||||
- secure: "dnSY+KA7NK+KD+Z71copmANDUsyVePrZ0iXvXxmqMEQv+lp3j2Z87G5pHn7j0WNcNZrejJqOdbElJ9Q4QESRaAYxTR7cA6ameJeEKHiFJrQtN/4abvoXb9E1CxpL8aNON/xgnqCk+fycOK3nbWWXlJBodzBm7KN64vrcHO7et+M="
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
@ -30,7 +30,7 @@ Starting with 1.6.4, Arduino allows installation of third-party platform package
|
||||
|
||||
- Install the current upstream Arduino IDE at the 1.8.9 level or later. The current version is on the [Arduino website](https://www.arduino.cc/en/main/software).
|
||||
- Start Arduino and open the Preferences window.
|
||||
- Enter ```https://arduino.esp8266.com/stable/package_esp8266com_index.json``` into the *Additional Board Manager URLs* field. You can add multiple URLs, separating them with commas.
|
||||
- Enter ```https://arduino.esp8266.com/stable/package_esp8266com_index.json``` into the *File>Preferences>Additional Boards Manager URLs* field of the Arduino IDE. You can add multiple URLs, separating them with commas.
|
||||
- Open Boards Manager from Tools > Board menu and install *esp8266* platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation).
|
||||
|
||||
#### Latest release [](https://github.com/esp8266/Arduino/releases/latest/)
|
||||
|
11248
boards.txt
11248
boards.txt
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,7 @@ INC += -I../../tools/sdk/include -I../../tools/sdk/uzlib/src
|
||||
|
||||
CFLAGS += -std=gnu99
|
||||
|
||||
CFLAGS += -Os -fcommon -g -Wall -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mno-text-section-literals -ffunction-sections -fdata-sections
|
||||
CFLAGS += -Os -fcommon -g -Wall -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mno-text-section-literals -ffunction-sections -fdata-sections -free -fipa-pta
|
||||
|
||||
CFLAGS += $(INC)
|
||||
|
||||
|
@ -68,7 +68,9 @@ int load_app_from_flash_raw(const uint32_t flash_addr)
|
||||
load = true;
|
||||
}
|
||||
|
||||
if (address >= 0x40100000 && address < 0x40108000) {
|
||||
// The final IRAM size, once boot has completed, can be either 32K or 48K.
|
||||
// Allow for the higher in range testing.
|
||||
if (address >= 0x40100000 && address < 0x4010C000) {
|
||||
load = true;
|
||||
}
|
||||
|
||||
@ -235,14 +237,14 @@ int main()
|
||||
|
||||
if (cmd.action == ACTION_COPY_RAW) {
|
||||
uint32_t cp = S('c', 'p', ':', 0);
|
||||
ets_printf((const char *)cp);
|
||||
ets_printf((const char *)&cp);
|
||||
|
||||
ets_wdt_disable();
|
||||
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], false);
|
||||
ets_wdt_enable();
|
||||
|
||||
cp = S('0' + res, '\n', 0, 0 );
|
||||
ets_printf((const char *)cp);
|
||||
ets_printf((const char *)&cp);
|
||||
#if 0
|
||||
//devyte: this verify step below (cmp:) only works when the end of copy operation above does not overwrite the
|
||||
//beginning of the image in the empty area, see #7458. Disabling for now.
|
||||
@ -259,7 +261,7 @@ int main()
|
||||
}
|
||||
|
||||
cp = S('0' + res, '\n', 0, 0 );
|
||||
ets_printf((const char *)cp);
|
||||
ets_printf((const char *)&cp);
|
||||
#endif
|
||||
if (res == 0) {
|
||||
cmd.action = ACTION_LOAD_APP;
|
||||
|
Binary file not shown.
@ -225,6 +225,10 @@ void optimistic_yield(uint32_t interval_us);
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
#include "mmu_iram.h"
|
||||
|
||||
|
||||
using std::min;
|
||||
using std::max;
|
||||
using std::round;
|
||||
|
@ -30,14 +30,15 @@ void EspClass::getHeapStats(uint32_t* hfree, uint16_t* hmax, uint8_t* hfrag)
|
||||
// 100 * (1 - sqrt(sum(hole-size²)) / sum(hole-size))
|
||||
|
||||
umm_info(NULL, false);
|
||||
uint8_t block_size = umm_block_size();
|
||||
|
||||
uint32_t free_size = umm_free_heap_size_core(umm_get_current_heap());
|
||||
if (hfree)
|
||||
*hfree = ummHeapInfo.freeBlocks * block_size;
|
||||
*hfree = free_size;
|
||||
if (hmax)
|
||||
*hmax = (uint16_t)ummHeapInfo.maxFreeContiguousBlocks * block_size;
|
||||
*hmax = (uint16_t)umm_max_block_size_core(umm_get_current_heap());
|
||||
if (hfrag) {
|
||||
if (ummHeapInfo.freeBlocks) {
|
||||
*hfrag = 100 - (sqrt32(ummHeapInfo.freeBlocksSquared) * 100) / ummHeapInfo.freeBlocks;
|
||||
if (free_size) {
|
||||
*hfrag = umm_fragmentation_metric_core(umm_get_current_heap());
|
||||
} else {
|
||||
*hfrag = 0;
|
||||
}
|
||||
@ -46,11 +47,5 @@ void EspClass::getHeapStats(uint32_t* hfree, uint16_t* hmax, uint8_t* hfrag)
|
||||
|
||||
uint8_t EspClass::getHeapFragmentation()
|
||||
{
|
||||
#ifdef UMM_INLINE_METRICS
|
||||
return (uint8_t)umm_fragmentation_metric();
|
||||
#else
|
||||
uint8_t hfrag;
|
||||
getHeapStats(nullptr, nullptr, &hfrag);
|
||||
return hfrag;
|
||||
#endif
|
||||
return (uint8_t)umm_fragmentation_metric();
|
||||
}
|
||||
|
@ -28,6 +28,8 @@
|
||||
#include "cont.h"
|
||||
|
||||
#include "coredecls.h"
|
||||
#include "umm_malloc/umm_malloc.h"
|
||||
// #include "core_esp8266_vm.h"
|
||||
#include <pgmspace.h>
|
||||
|
||||
extern "C" {
|
||||
@ -969,3 +971,62 @@ String EspClass::getSketchMD5()
|
||||
result = md5.toString();
|
||||
return result;
|
||||
}
|
||||
|
||||
void EspClass::enableVM()
|
||||
{
|
||||
#ifdef UMM_HEAP_EXTERNAL
|
||||
if (!vmEnabled)
|
||||
install_vm_exception_handler();
|
||||
vmEnabled = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void EspClass::setExternalHeap()
|
||||
{
|
||||
#ifdef UMM_HEAP_EXTERNAL
|
||||
if (vmEnabled) {
|
||||
if (!umm_push_heap(UMM_HEAP_EXTERNAL)) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void EspClass::setIramHeap()
|
||||
{
|
||||
#ifdef UMM_HEAP_IRAM
|
||||
if (!umm_push_heap(UMM_HEAP_IRAM)) {
|
||||
panic();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void EspClass::setDramHeap()
|
||||
{
|
||||
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
|
||||
if (vmEnabled) {
|
||||
if (!umm_push_heap(UMM_HEAP_DRAM)) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
#elif defined(UMM_HEAP_IRAM)
|
||||
if (!umm_push_heap(UMM_HEAP_DRAM)) {
|
||||
panic();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void EspClass::resetHeap()
|
||||
{
|
||||
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
|
||||
if (vmEnabled) {
|
||||
if (!umm_pop_heap()) {
|
||||
panic();
|
||||
}
|
||||
}
|
||||
#elif defined(UMM_HEAP_IRAM)
|
||||
if (!umm_pop_heap()) {
|
||||
panic();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -215,7 +215,46 @@ class EspClass {
|
||||
#else
|
||||
uint32_t getCycleCount();
|
||||
#endif // !defined(CORE_MOCK)
|
||||
/**
|
||||
* @brief Installs VM exception handler to support External memory (Experimental)
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void enableVM();
|
||||
/**
|
||||
* @brief Push current Heap selection and set Heap selection to DRAM.
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void setDramHeap();
|
||||
/**
|
||||
* @brief Push current Heap selection and set Heap selection to IRAM.
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void setIramHeap();
|
||||
/**
|
||||
* @brief Push current Heap selection and set Heap selection to External. (Experimental)
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void setExternalHeap();
|
||||
/**
|
||||
* @brief Restores Heap selection back to value present when
|
||||
* setDramHeap, setIramHeap, or setExternalHeap was called.
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void resetHeap();
|
||||
private:
|
||||
#ifdef UMM_HEAP_EXTERNAL
|
||||
bool vmEnabled = false;
|
||||
#endif
|
||||
/**
|
||||
* @brief Replaces @a byteCount bytes of a 4 byte block on flash
|
||||
*
|
||||
|
@ -46,6 +46,14 @@ int File::available() {
|
||||
return _p->size() - _p->position();
|
||||
}
|
||||
|
||||
int File::availableForWrite() {
|
||||
if (!_p)
|
||||
return false;
|
||||
|
||||
return _p->availableForWrite();
|
||||
}
|
||||
|
||||
|
||||
int File::read() {
|
||||
if (!_p)
|
||||
return -1;
|
||||
@ -60,7 +68,7 @@ int File::read() {
|
||||
|
||||
size_t File::read(uint8_t* buf, size_t size) {
|
||||
if (!_p)
|
||||
return -1;
|
||||
return 0;
|
||||
|
||||
return _p->read(buf, size);
|
||||
}
|
||||
@ -198,6 +206,7 @@ void File::setTimeCallback(time_t (*cb)(void)) {
|
||||
if (!_p)
|
||||
return;
|
||||
_p->setTimeCallback(cb);
|
||||
_timeCallback = cb;
|
||||
}
|
||||
|
||||
File Dir::openFile(const char* mode) {
|
||||
@ -213,7 +222,7 @@ File Dir::openFile(const char* mode) {
|
||||
}
|
||||
|
||||
File f(_impl->openFile(om, am), _baseFS);
|
||||
f.setTimeCallback(timeCallback);
|
||||
f.setTimeCallback(_timeCallback);
|
||||
return f;
|
||||
}
|
||||
|
||||
@ -279,7 +288,7 @@ void Dir::setTimeCallback(time_t (*cb)(void)) {
|
||||
if (!_impl)
|
||||
return;
|
||||
_impl->setTimeCallback(cb);
|
||||
timeCallback = cb;
|
||||
_timeCallback = cb;
|
||||
}
|
||||
|
||||
|
||||
@ -296,7 +305,7 @@ bool FS::begin() {
|
||||
DEBUGV("#error: FS: no implementation");
|
||||
return false;
|
||||
}
|
||||
_impl->setTimeCallback(timeCallback);
|
||||
_impl->setTimeCallback(_timeCallback);
|
||||
bool ret = _impl->begin();
|
||||
DEBUGV("%s\n", ret? "": "#error: FS could not start");
|
||||
return ret;
|
||||
@ -359,7 +368,7 @@ File FS::open(const char* path, const char* mode) {
|
||||
return File();
|
||||
}
|
||||
File f(_impl->open(path, om, am), this);
|
||||
f.setTimeCallback(timeCallback);
|
||||
f.setTimeCallback(_timeCallback);
|
||||
return f;
|
||||
}
|
||||
|
||||
@ -380,7 +389,7 @@ Dir FS::openDir(const char* path) {
|
||||
}
|
||||
DirImplPtr p = _impl->openDir(path);
|
||||
Dir d(p, this);
|
||||
d.setTimeCallback(timeCallback);
|
||||
d.setTimeCallback(_timeCallback);
|
||||
return d;
|
||||
}
|
||||
|
||||
@ -436,6 +445,7 @@ void FS::setTimeCallback(time_t (*cb)(void)) {
|
||||
if (!_impl)
|
||||
return;
|
||||
_impl->setTimeCallback(cb);
|
||||
_timeCallback = cb;
|
||||
}
|
||||
|
||||
|
||||
|
@ -57,6 +57,7 @@ public:
|
||||
// Print methods:
|
||||
size_t write(uint8_t) override;
|
||||
size_t write(const uint8_t *buf, size_t size) override;
|
||||
int availableForWrite() override;
|
||||
|
||||
// Stream methods:
|
||||
int available() override;
|
||||
@ -117,6 +118,7 @@ public:
|
||||
|
||||
protected:
|
||||
FileImplPtr _p;
|
||||
time_t (*_timeCallback)(void) = nullptr;
|
||||
|
||||
// Arduino SD class emulation
|
||||
std::shared_ptr<Dir> _fakeDir;
|
||||
@ -144,7 +146,7 @@ public:
|
||||
protected:
|
||||
DirImplPtr _impl;
|
||||
FS *_baseFS;
|
||||
time_t (*timeCallback)(void) = nullptr;
|
||||
time_t (*_timeCallback)(void) = nullptr;
|
||||
};
|
||||
|
||||
// Backwards compatible, <4GB filesystem usage
|
||||
@ -197,7 +199,7 @@ public:
|
||||
class FS
|
||||
{
|
||||
public:
|
||||
FS(FSImplPtr impl) : _impl(impl) { timeCallback = _defaultTimeCB; }
|
||||
FS(FSImplPtr impl) : _impl(impl) { _timeCallback = _defaultTimeCB; }
|
||||
|
||||
bool setConfig(const FSConfig &cfg);
|
||||
|
||||
@ -239,7 +241,7 @@ public:
|
||||
protected:
|
||||
FSImplPtr _impl;
|
||||
FSImplPtr getImpl() { return _impl; }
|
||||
time_t (*timeCallback)(void);
|
||||
time_t (*_timeCallback)(void) = nullptr;
|
||||
static time_t _defaultTimeCB(void) { return time(NULL); }
|
||||
};
|
||||
|
||||
|
@ -35,6 +35,7 @@ public:
|
||||
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
|
||||
virtual size_t position() const = 0;
|
||||
virtual size_t size() const = 0;
|
||||
virtual int availableForWrite() { return 0; }
|
||||
virtual bool truncate(uint32_t size) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual const char* name() const = 0;
|
||||
@ -44,8 +45,8 @@ public:
|
||||
|
||||
// Filesystems *may* support a timestamp per-file, so allow the user to override with
|
||||
// their own callback for *this specific* file (as opposed to the FSImpl call of the
|
||||
// same name. The default implementation simply returns time(&null)
|
||||
virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
|
||||
// same name. The default implementation simply returns time(null)
|
||||
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
|
||||
|
||||
// Return the last written time for a file. Undefined when called on a writable file
|
||||
// as the FS is allowed to return either the time of the last write() operation or the
|
||||
@ -55,7 +56,7 @@ public:
|
||||
virtual time_t getCreationTime() { return 0; } // Default is to not support timestamps
|
||||
|
||||
protected:
|
||||
time_t (*timeCallback)(void) = nullptr;
|
||||
time_t (*_timeCallback)(void) = nullptr;
|
||||
};
|
||||
|
||||
enum OpenMode {
|
||||
@ -89,11 +90,11 @@ public:
|
||||
|
||||
// Filesystems *may* support a timestamp per-file, so allow the user to override with
|
||||
// their own callback for *this specific* file (as opposed to the FSImpl call of the
|
||||
// same name. The default implementation simply returns time(&null)
|
||||
virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
|
||||
// same name. The default implementation simply returns time(null)
|
||||
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
|
||||
|
||||
protected:
|
||||
time_t (*timeCallback)(void) = nullptr;
|
||||
time_t (*_timeCallback)(void) = nullptr;
|
||||
};
|
||||
|
||||
class FSImpl {
|
||||
@ -117,11 +118,11 @@ public:
|
||||
|
||||
// Filesystems *may* support a timestamp per-file, so allow the user to override with
|
||||
// their own callback for all files on this FS. The default implementation simply
|
||||
// returns the present time as reported by time(&null)
|
||||
virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
|
||||
// returns the present time as reported by time(null)
|
||||
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
|
||||
|
||||
protected:
|
||||
time_t (*timeCallback)(void) = nullptr;
|
||||
time_t (*_timeCallback)(void) = nullptr;
|
||||
};
|
||||
|
||||
} // namespace fs
|
||||
|
@ -150,7 +150,7 @@ public:
|
||||
{
|
||||
return readBytes((char*)buffer, size);
|
||||
}
|
||||
int availableForWrite(void)
|
||||
int availableForWrite(void) override
|
||||
{
|
||||
return static_cast<int>(uart_tx_free(_uart));
|
||||
}
|
||||
|
64
cores/esp8266/LwipDhcpServer-NonOS.cpp
Normal file
64
cores/esp8266/LwipDhcpServer-NonOS.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
lwIPDhcpServer-NonOS.cpp - DHCP server wrapper
|
||||
|
||||
Copyright (c) 2020 esp8266 arduino. All rights reserved.
|
||||
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
|
||||
*/
|
||||
|
||||
// STARTS/STOPS DHCP SERVER ON WIFI AP INTERFACE
|
||||
// these functions must exists as-is with "C" interface,
|
||||
// nonos-sdk calls them at boot time and later
|
||||
|
||||
#include <lwip/init.h> // LWIP_VERSION
|
||||
|
||||
#include <lwip/netif.h>
|
||||
#include "LwipDhcpServer.h"
|
||||
|
||||
extern netif netif_git[2];
|
||||
|
||||
// global DHCP instance for softAP interface
|
||||
DhcpServer dhcpSoftAP(&netif_git[SOFTAP_IF]);
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
void dhcps_start(struct ip_info *info, netif* apnetif)
|
||||
{
|
||||
// apnetif is esp interface, replaced by lwip2's
|
||||
// netif_git[SOFTAP_IF] interface in constructor
|
||||
(void)apnetif;
|
||||
|
||||
#if 0
|
||||
// can't use C++ now, global ctors are not initialized yet
|
||||
dhcpSoftAP.begin(info);
|
||||
#else
|
||||
(void)info;
|
||||
// initial version: emulate nonos-sdk in DhcpServer class before
|
||||
// trying to change legacy behavor
|
||||
// `fw_has_started_softap_dhcps` will be read in DhcpServer::DhcpServer
|
||||
// which is called when c++ ctors are initialized, specifically
|
||||
// dhcpSoftAP intialized with AP interface number above.
|
||||
fw_has_started_softap_dhcps = 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void dhcps_stop()
|
||||
{
|
||||
dhcpSoftAP.end();
|
||||
}
|
||||
|
||||
} // extern "C"
|
1613
cores/esp8266/LwipDhcpServer.cpp
Normal file
1613
cores/esp8266/LwipDhcpServer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
124
cores/esp8266/LwipDhcpServer.h
Normal file
124
cores/esp8266/LwipDhcpServer.h
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
lwIPDhcpServer.h - DHCP server
|
||||
|
||||
Copyright (c) 2016 Espressif. All rights reserved.
|
||||
Copyright (c) 2020 esp8266 arduino. All rights reserved.
|
||||
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
|
||||
|
||||
// original sources (no license provided)
|
||||
// ESP8266_NONOS_SDK/third_party/lwip/app/dhcpserver.c
|
||||
// ESP8266_NONOS_SDK/third_party/include/lwip/app/dhcpserver.h
|
||||
*/
|
||||
|
||||
// lwIPDhcpServer.{cc,h} encapsulate original nonos-sdk dhcp server
|
||||
// nearly as-is. This is an initial version to guaranty legacy behavior
|
||||
// with same default values.
|
||||
|
||||
#ifndef __DHCPS_H__
|
||||
#define __DHCPS_H__
|
||||
|
||||
#include <lwip/init.h> // LWIP_VERSION
|
||||
|
||||
class DhcpServer
|
||||
{
|
||||
public:
|
||||
|
||||
DhcpServer(netif* netif);
|
||||
~DhcpServer();
|
||||
|
||||
void setDns(int num, const ipv4_addr_t* dns);
|
||||
|
||||
bool begin(ip_info* info);
|
||||
void end();
|
||||
bool isRunning();
|
||||
|
||||
// this is the C interface encapsulated in a class
|
||||
// (originally dhcpserver.c in lwIP-v1.4 in NonOS-SDK)
|
||||
// (not changing everything at once)
|
||||
// the API below is subject to change
|
||||
|
||||
// legacy public C structure and API to eventually turn into C++
|
||||
|
||||
void init_dhcps_lease(uint32 ip);
|
||||
bool set_dhcps_lease(struct dhcps_lease *please);
|
||||
bool get_dhcps_lease(struct dhcps_lease *please);
|
||||
bool set_dhcps_offer_option(uint8 level, void* optarg);
|
||||
bool set_dhcps_lease_time(uint32 minute);
|
||||
bool reset_dhcps_lease_time(void);
|
||||
uint32 get_dhcps_lease_time(void);
|
||||
bool add_dhcps_lease(uint8 *macaddr);
|
||||
|
||||
void dhcps_set_dns(int num, const ipv4_addr_t* dns);
|
||||
|
||||
protected:
|
||||
|
||||
// legacy C structure and API to eventually turn into C++
|
||||
|
||||
typedef struct _list_node
|
||||
{
|
||||
void *pnode;
|
||||
struct _list_node *pnext;
|
||||
} list_node;
|
||||
|
||||
void node_insert_to_list(list_node **phead, list_node* pinsert);
|
||||
void node_remove_from_list(list_node **phead, list_node* pdelete);
|
||||
uint8_t* add_msg_type(uint8_t *optptr, uint8_t type);
|
||||
uint8_t* add_offer_options(uint8_t *optptr);
|
||||
uint8_t* add_end(uint8_t *optptr);
|
||||
void create_msg(struct dhcps_msg *m);
|
||||
void send_offer(struct dhcps_msg *m);
|
||||
void send_nak(struct dhcps_msg *m);
|
||||
void send_ack(struct dhcps_msg *m);
|
||||
uint8_t parse_options(uint8_t *optptr, sint16_t len);
|
||||
sint16_t parse_msg(struct dhcps_msg *m, u16_t len);
|
||||
static void S_handle_dhcp(void *arg,
|
||||
struct udp_pcb *pcb,
|
||||
struct pbuf *p,
|
||||
const ip_addr_t *addr,
|
||||
uint16_t port);
|
||||
void handle_dhcp(
|
||||
struct udp_pcb *pcb,
|
||||
struct pbuf *p,
|
||||
const ip_addr_t *addr,
|
||||
uint16_t port);
|
||||
void kill_oldest_dhcps_pool(void);
|
||||
void dhcps_coarse_tmr(void); // CURRENTLY NOT CALLED
|
||||
void dhcps_client_leave(u8 *bssid, struct ipv4_addr *ip, bool force);
|
||||
uint32 dhcps_client_update(u8 *bssid, struct ipv4_addr *ip);
|
||||
|
||||
netif* _netif;
|
||||
|
||||
struct udp_pcb *pcb_dhcps;
|
||||
ip_addr_t broadcast_dhcps;
|
||||
struct ipv4_addr server_address;
|
||||
struct ipv4_addr client_address;
|
||||
struct ipv4_addr dns_address;
|
||||
uint32 dhcps_lease_time;
|
||||
|
||||
struct dhcps_lease dhcps_lease;
|
||||
list_node *plist;
|
||||
uint8 offer;
|
||||
bool renew;
|
||||
|
||||
static const uint32 magic_cookie;
|
||||
};
|
||||
|
||||
// SoftAP DHCP server always exists and is started on boot
|
||||
extern DhcpServer dhcpSoftAP;
|
||||
extern "C" int fw_has_started_softap_dhcps;
|
||||
|
||||
#endif // __DHCPS_H__
|
156
cores/esp8266/LwipIntf.cpp
Normal file
156
cores/esp8266/LwipIntf.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
|
||||
extern "C" {
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/ip_addr.h"
|
||||
#include "lwip/dns.h"
|
||||
#include "lwip/dhcp.h"
|
||||
#include "lwip/init.h" // LWIP_VERSION_
|
||||
#if LWIP_IPV6
|
||||
#include "lwip/netif.h" // struct netif
|
||||
#endif
|
||||
|
||||
#include <user_interface.h>
|
||||
}
|
||||
|
||||
#include "debug.h"
|
||||
#include "LwipIntf.h"
|
||||
|
||||
// args | esp order arduino order
|
||||
// ---- + --------- -------------
|
||||
// local_ip | local_ip local_ip
|
||||
// arg1 | gateway dns1
|
||||
// arg2 | netmask gateway
|
||||
// arg3 | dns1 netmask
|
||||
//
|
||||
// result stored into gateway/netmask/dns1
|
||||
|
||||
bool LwipIntf::ipAddressReorder(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3,
|
||||
IPAddress& gateway, IPAddress& netmask, IPAddress& dns1)
|
||||
{
|
||||
//To allow compatibility, check first octet of 3rd arg. If 255, interpret as ESP order, otherwise Arduino order.
|
||||
gateway = arg1;
|
||||
netmask = arg2;
|
||||
dns1 = arg3;
|
||||
|
||||
if (netmask[0] != 255)
|
||||
{
|
||||
//octet is not 255 => interpret as Arduino order
|
||||
gateway = arg2;
|
||||
netmask = arg3[0] == 0 ? IPAddress(255, 255, 255, 0) : arg3; //arg order is arduino and 4th arg not given => assign it arduino default
|
||||
dns1 = arg1;
|
||||
}
|
||||
|
||||
// check whether all is IPv4 (or gateway not set)
|
||||
if (!(local_ip.isV4() && netmask.isV4() && (!gateway.isSet() || gateway.isV4())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//ip and gateway must be in the same netmask
|
||||
if (gateway.isSet() && (local_ip.v4() & netmask.v4()) != (gateway.v4() & netmask.v4()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
Get ESP8266 station DHCP hostname
|
||||
@return hostname
|
||||
*/
|
||||
String LwipIntf::hostname(void)
|
||||
{
|
||||
return wifi_station_get_hostname();
|
||||
}
|
||||
|
||||
/**
|
||||
Set ESP8266 station DHCP hostname
|
||||
@param aHostname max length:24
|
||||
@return ok
|
||||
*/
|
||||
bool LwipIntf::hostname(const char* aHostname)
|
||||
{
|
||||
/*
|
||||
vvvv RFC952 vvvv
|
||||
ASSUMPTIONS
|
||||
1. A "name" (Net, Host, Gateway, or Domain name) is a text string up
|
||||
to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
|
||||
sign (-), and period (.). Note that periods are only allowed when
|
||||
they serve to delimit components of "domain style names". (See
|
||||
RFC-921, "Domain Name System Implementation Schedule", for
|
||||
background). No blank or space characters are permitted as part of a
|
||||
name. No distinction is made between upper and lower case. The first
|
||||
character must be an alpha character. The last character must not be
|
||||
a minus sign or period. A host which serves as a GATEWAY should have
|
||||
"-GATEWAY" or "-GW" as part of its name. Hosts which do not serve as
|
||||
Internet gateways should not use "-GATEWAY" and "-GW" as part of
|
||||
their names. A host which is a TAC should have "-TAC" as the last
|
||||
part of its host name, if it is a DoD host. Single character names
|
||||
or nicknames are not allowed.
|
||||
^^^^ RFC952 ^^^^
|
||||
|
||||
- 24 chars max
|
||||
- only a..z A..Z 0..9 '-'
|
||||
- no '-' as last char
|
||||
*/
|
||||
|
||||
size_t len = strlen(aHostname);
|
||||
|
||||
if (len == 0 || len > 32)
|
||||
{
|
||||
// nonos-sdk limit is 32
|
||||
// (dhcp hostname option minimum size is ~60)
|
||||
DEBUGV("WiFi.(set)hostname(): empty or large(>32) name\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check RFC compliance
|
||||
bool compliant = (len <= 24);
|
||||
for (size_t i = 0; compliant && i < len; i++)
|
||||
if (!isalnum(aHostname[i]) && aHostname[i] != '-')
|
||||
{
|
||||
compliant = false;
|
||||
}
|
||||
if (aHostname[len - 1] == '-')
|
||||
{
|
||||
compliant = false;
|
||||
}
|
||||
|
||||
if (!compliant)
|
||||
{
|
||||
DEBUGV("hostname '%s' is not compliant with RFC952\n", aHostname);
|
||||
}
|
||||
|
||||
bool ret = wifi_station_set_hostname(aHostname);
|
||||
if (!ret)
|
||||
{
|
||||
DEBUGV("WiFi.hostname(%s): wifi_station_set_hostname() failed\n", aHostname);
|
||||
return false;
|
||||
}
|
||||
|
||||
// now we should inform dhcp server for this change, using lwip_renew()
|
||||
// looping through all existing interface
|
||||
// harmless for AP, also compatible with ethernet adapters (to come)
|
||||
for (netif* intf = netif_list; intf; intf = intf->next)
|
||||
{
|
||||
|
||||
// unconditionally update all known interfaces
|
||||
intf->hostname = wifi_station_get_hostname();
|
||||
|
||||
if (netif_dhcp_data(intf) != nullptr)
|
||||
{
|
||||
// renew already started DHCP leases
|
||||
err_t lwipret = dhcp_renew(intf);
|
||||
if (lwipret != ERR_OK)
|
||||
{
|
||||
DEBUGV("WiFi.hostname(%s): lwIP error %d on interface %c%c (index %d)\n",
|
||||
intf->hostname, (int)lwipret, intf->name[0], intf->name[1], intf->num);
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret && compliant;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
#define _LWIPINTF_H
|
||||
|
||||
#include <lwip/netif.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
@ -12,15 +13,31 @@ public:
|
||||
|
||||
using CBType = std::function <void(netif*)>;
|
||||
|
||||
static bool stateUpCB (LwipIntf::CBType&& cb);
|
||||
static bool stateUpCB(LwipIntf::CBType&& cb);
|
||||
|
||||
private:
|
||||
// reorder WiFi.config() parameters for a esp8266/official Arduino dual-compatibility API
|
||||
// args | esp order arduino order
|
||||
// ---- + --------- -------------
|
||||
// local_ip | local_ip local_ip
|
||||
// arg1 | gateway dns1
|
||||
// arg2 | netmask [Agateway
|
||||
// arg3 | dns1 netmask
|
||||
//
|
||||
// result stored into gateway/netmask/dns1
|
||||
static
|
||||
bool ipAddressReorder(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3,
|
||||
IPAddress& gateway, IPAddress& netmask, IPAddress& dns1);
|
||||
|
||||
LwipIntf () { } // private, cannot be directly allocated
|
||||
String hostname();
|
||||
bool hostname(const String& aHostname)
|
||||
{
|
||||
return hostname(aHostname.c_str());
|
||||
}
|
||||
bool hostname(const char* aHostname);
|
||||
|
||||
protected:
|
||||
|
||||
static bool stateChangeSysCB (LwipIntf::CBType&& cb);
|
||||
static bool stateChangeSysCB(LwipIntf::CBType&& cb);
|
||||
};
|
||||
|
||||
#endif // _LWIPINTF_H
|
||||
|
@ -8,14 +8,16 @@
|
||||
static int netifStatusChangeListLength = 0;
|
||||
LwipIntf::CBType netifStatusChangeList [NETIF_STATUS_CB_SIZE];
|
||||
|
||||
extern "C" void netif_status_changed (struct netif* netif)
|
||||
extern "C" void netif_status_changed(struct netif* netif)
|
||||
{
|
||||
// override the default empty weak function
|
||||
for (int i = 0; i < netifStatusChangeListLength; i++)
|
||||
{
|
||||
netifStatusChangeList[i](netif);
|
||||
}
|
||||
}
|
||||
|
||||
bool LwipIntf::stateChangeSysCB (LwipIntf::CBType&& cb)
|
||||
bool LwipIntf::stateChangeSysCB(LwipIntf::CBType&& cb)
|
||||
{
|
||||
if (netifStatusChangeListLength >= NETIF_STATUS_CB_SIZE)
|
||||
{
|
||||
@ -29,14 +31,14 @@ bool LwipIntf::stateChangeSysCB (LwipIntf::CBType&& cb)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LwipIntf::stateUpCB (LwipIntf::CBType&& cb)
|
||||
bool LwipIntf::stateUpCB(LwipIntf::CBType&& cb)
|
||||
{
|
||||
return stateChangeSysCB([cb](netif* nif)
|
||||
return stateChangeSysCB([cb](netif * nif)
|
||||
{
|
||||
if (netif_is_up(nif))
|
||||
schedule_function([cb, nif]()
|
||||
{
|
||||
cb(nif);
|
||||
});
|
||||
{
|
||||
cb(nif);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
386
cores/esp8266/LwipIntfDev.h
Normal file
386
cores/esp8266/LwipIntfDev.h
Normal file
@ -0,0 +1,386 @@
|
||||
|
||||
#ifndef _LWIPINTFDEV_H
|
||||
#define _LWIPINTFDEV_H
|
||||
|
||||
// TODO:
|
||||
// remove all Serial.print
|
||||
// unchain pbufs
|
||||
|
||||
#include <netif/ethernet.h>
|
||||
#include <lwip/init.h>
|
||||
#include <lwip/netif.h>
|
||||
#include <lwip/etharp.h>
|
||||
#include <lwip/dhcp.h>
|
||||
#include <lwip/apps/sntp.h>
|
||||
|
||||
#include <user_interface.h> // wifi_get_macaddr()
|
||||
|
||||
#include "SPI.h"
|
||||
#include "Schedule.h"
|
||||
#include "LwipIntf.h"
|
||||
#include "wl_definitions.h"
|
||||
|
||||
#ifndef DEFAULT_MTU
|
||||
#define DEFAULT_MTU 1500
|
||||
#endif
|
||||
|
||||
template <class RawDev>
|
||||
class LwipIntfDev: public LwipIntf, public RawDev
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
LwipIntfDev(int8_t cs = SS, SPIClass& spi = SPI, int8_t intr = -1):
|
||||
RawDev(cs, spi, intr),
|
||||
_mtu(DEFAULT_MTU),
|
||||
_intrPin(intr),
|
||||
_started(false),
|
||||
_default(false)
|
||||
{
|
||||
memset(&_netif, 0, sizeof(_netif));
|
||||
}
|
||||
|
||||
boolean config(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3, const IPAddress& dns2);
|
||||
|
||||
// default mac-address is inferred from esp8266's STA interface
|
||||
boolean begin(const uint8_t *macAddress = nullptr, const uint16_t mtu = DEFAULT_MTU);
|
||||
|
||||
const netif* getNetIf() const
|
||||
{
|
||||
return &_netif;
|
||||
}
|
||||
|
||||
IPAddress localIP() const
|
||||
{
|
||||
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr)));
|
||||
}
|
||||
IPAddress subnetMask() const
|
||||
{
|
||||
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.netmask)));
|
||||
}
|
||||
IPAddress gatewayIP() const
|
||||
{
|
||||
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.gw)));
|
||||
}
|
||||
|
||||
void setDefault();
|
||||
|
||||
bool connected()
|
||||
{
|
||||
return !!ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr));
|
||||
}
|
||||
|
||||
// ESP8266WiFi API compatibility
|
||||
|
||||
wl_status_t status();
|
||||
|
||||
protected:
|
||||
|
||||
err_t netif_init();
|
||||
void netif_status_callback();
|
||||
|
||||
static err_t netif_init_s(netif* netif);
|
||||
static err_t linkoutput_s(netif *netif, struct pbuf *p);
|
||||
static void netif_status_callback_s(netif* netif);
|
||||
|
||||
// called on a regular basis or on interrupt
|
||||
err_t handlePackets();
|
||||
|
||||
// members
|
||||
|
||||
netif _netif;
|
||||
|
||||
uint16_t _mtu;
|
||||
int8_t _intrPin;
|
||||
uint8_t _macAddress[6];
|
||||
bool _started;
|
||||
bool _default;
|
||||
|
||||
};
|
||||
|
||||
template <class RawDev>
|
||||
boolean LwipIntfDev<RawDev>::config(const IPAddress& localIP, const IPAddress& gateway, const IPAddress& netmask, const IPAddress& dns1, const IPAddress& dns2)
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
DEBUGV("LwipIntfDev: use config() then begin()\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
IPAddress realGateway, realNetmask, realDns1;
|
||||
if (!ipAddressReorder(localIP, gateway, netmask, dns1, realGateway, realNetmask, realDns1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ip4_addr_set_u32(ip_2_ip4(&_netif.ip_addr), localIP.v4());
|
||||
ip4_addr_set_u32(ip_2_ip4(&_netif.gw), realGateway.v4());
|
||||
ip4_addr_set_u32(ip_2_ip4(&_netif.netmask), realNetmask.v4());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
boolean LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu)
|
||||
{
|
||||
if (mtu)
|
||||
{
|
||||
_mtu = mtu;
|
||||
}
|
||||
|
||||
if (macAddress)
|
||||
{
|
||||
memcpy(_macAddress, macAddress, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
_netif.num = 2;
|
||||
for (auto n = netif_list; n; n = n->next)
|
||||
if (n->num >= _netif.num)
|
||||
{
|
||||
_netif.num = n->num + 1;
|
||||
}
|
||||
|
||||
#if 1
|
||||
// forge a new mac-address from the esp's wifi sta one
|
||||
// I understand this is cheating with an official mac-address
|
||||
wifi_get_macaddr(STATION_IF, (uint8*)_macAddress);
|
||||
#else
|
||||
// https://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
|
||||
memset(_macAddress, 0, 6);
|
||||
_macAddress[0] = 0xEE;
|
||||
#endif
|
||||
_macAddress[3] += _netif.num; // alter base mac address
|
||||
_macAddress[0] &= 0xfe; // set as locally administered, unicast, per
|
||||
_macAddress[0] |= 0x02; // https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local
|
||||
}
|
||||
|
||||
if (!RawDev::begin(_macAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// setup lwIP netif
|
||||
|
||||
_netif.hwaddr_len = sizeof _macAddress;
|
||||
memcpy(_netif.hwaddr, _macAddress, sizeof _macAddress);
|
||||
|
||||
// due to netif_add() api: ...
|
||||
ip_addr_t ip_addr, netmask, gw;
|
||||
ip_addr_copy(ip_addr, _netif.ip_addr);
|
||||
ip_addr_copy(netmask, _netif.netmask);
|
||||
ip_addr_copy(gw, _netif.gw);
|
||||
|
||||
if (!netif_add(&_netif, ip_2_ip4(&ip_addr), ip_2_ip4(&netmask), ip_2_ip4(&gw), this, netif_init_s, ethernet_input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_netif.flags |= NETIF_FLAG_UP;
|
||||
|
||||
if (localIP().v4() == 0)
|
||||
{
|
||||
switch (dhcp_start(&_netif))
|
||||
{
|
||||
case ERR_OK:
|
||||
break;
|
||||
|
||||
case ERR_IF:
|
||||
return false;
|
||||
|
||||
default:
|
||||
netif_remove(&_netif);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_started = true;
|
||||
|
||||
if (_intrPin >= 0)
|
||||
{
|
||||
if (RawDev::interruptIsPossible())
|
||||
{
|
||||
//attachInterrupt(_intrPin, [&]() { this->handlePackets(); }, FALLING);
|
||||
}
|
||||
else
|
||||
{
|
||||
::printf((PGM_P)F("lwIP_Intf: Interrupt not implemented yet, enabling transparent polling\r\n"));
|
||||
_intrPin = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (_intrPin < 0 && !schedule_recurrent_function_us([&]()
|
||||
{
|
||||
this->handlePackets();
|
||||
return true;
|
||||
}, 100))
|
||||
{
|
||||
netif_remove(&_netif);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
wl_status_t LwipIntfDev<RawDev>::status()
|
||||
{
|
||||
return _started ? (connected() ? WL_CONNECTED : WL_DISCONNECTED) : WL_NO_SHIELD;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
err_t LwipIntfDev<RawDev>::linkoutput_s(netif *netif, struct pbuf *pbuf)
|
||||
{
|
||||
LwipIntfDev* ths = (LwipIntfDev*)netif->state;
|
||||
|
||||
if (pbuf->len != pbuf->tot_len || pbuf->next)
|
||||
{
|
||||
Serial.println("ERRTOT\r\n");
|
||||
}
|
||||
|
||||
uint16_t len = ths->sendFrame((const uint8_t*)pbuf->payload, pbuf->len);
|
||||
|
||||
#if PHY_HAS_CAPTURE
|
||||
if (phy_capture)
|
||||
{
|
||||
phy_capture(ths->_netif.num, (const char*)pbuf->payload, pbuf->len, /*out*/1, /*success*/len == pbuf->len);
|
||||
}
|
||||
#endif
|
||||
|
||||
return len == pbuf->len ? ERR_OK : ERR_MEM;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
err_t LwipIntfDev<RawDev>::netif_init_s(struct netif* netif)
|
||||
{
|
||||
return ((LwipIntfDev*)netif->state)->netif_init();
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
void LwipIntfDev<RawDev>::netif_status_callback_s(struct netif* netif)
|
||||
{
|
||||
((LwipIntfDev*)netif->state)->netif_status_callback();
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
err_t LwipIntfDev<RawDev>::netif_init()
|
||||
{
|
||||
_netif.name[0] = 'e';
|
||||
_netif.name[1] = '0' + _netif.num;
|
||||
_netif.mtu = _mtu;
|
||||
_netif.chksum_flags = NETIF_CHECKSUM_ENABLE_ALL;
|
||||
_netif.flags =
|
||||
NETIF_FLAG_ETHARP
|
||||
| NETIF_FLAG_IGMP
|
||||
| NETIF_FLAG_BROADCAST
|
||||
| NETIF_FLAG_LINK_UP;
|
||||
|
||||
// lwIP's doc: This function typically first resolves the hardware
|
||||
// address, then sends the packet. For ethernet physical layer, this is
|
||||
// usually lwIP's etharp_output()
|
||||
_netif.output = etharp_output;
|
||||
|
||||
// lwIP's doc: This function outputs the pbuf as-is on the link medium
|
||||
// (this must points to the raw ethernet driver, meaning: us)
|
||||
_netif.linkoutput = linkoutput_s;
|
||||
|
||||
_netif.status_callback = netif_status_callback_s;
|
||||
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
void LwipIntfDev<RawDev>::netif_status_callback()
|
||||
{
|
||||
if (connected())
|
||||
{
|
||||
if (_default)
|
||||
{
|
||||
netif_set_default(&_netif);
|
||||
}
|
||||
sntp_stop();
|
||||
sntp_init();
|
||||
}
|
||||
else if (netif_default == &_netif)
|
||||
{
|
||||
netif_set_default(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
err_t LwipIntfDev<RawDev>::handlePackets()
|
||||
{
|
||||
int pkt = 0;
|
||||
while (1)
|
||||
{
|
||||
if (++pkt == 10)
|
||||
// prevent starvation
|
||||
{
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
uint16_t tot_len = RawDev::readFrameSize();
|
||||
if (!tot_len)
|
||||
{
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
// from doc: use PBUF_RAM for TX, PBUF_POOL from RX
|
||||
// however:
|
||||
// PBUF_POOL can return chained pbuf (not in one piece)
|
||||
// and WiznetDriver does not have the proper API to deal with that
|
||||
// so in the meantime, we use PBUF_RAM instead which is currently
|
||||
// guarantying to deliver a continuous chunk of memory.
|
||||
// TODO: tweak the wiznet driver to allow copying partial chunk
|
||||
// of received data and use PBUF_POOL.
|
||||
pbuf* pbuf = pbuf_alloc(PBUF_RAW, tot_len, PBUF_RAM);
|
||||
if (!pbuf || pbuf->len < tot_len)
|
||||
{
|
||||
if (pbuf)
|
||||
{
|
||||
pbuf_free(pbuf);
|
||||
}
|
||||
RawDev::discardFrame(tot_len);
|
||||
return ERR_BUF;
|
||||
}
|
||||
|
||||
uint16_t len = RawDev::readFrameData((uint8_t*)pbuf->payload, tot_len);
|
||||
if (len != tot_len)
|
||||
{
|
||||
// tot_len is given by readFrameSize()
|
||||
// and is supposed to be honoured by readFrameData()
|
||||
// todo: ensure this test is unneeded, remove the print
|
||||
Serial.println("read error?\r\n");
|
||||
pbuf_free(pbuf);
|
||||
return ERR_BUF;
|
||||
}
|
||||
|
||||
err_t err = _netif.input(pbuf, &_netif);
|
||||
|
||||
#if PHY_HAS_CAPTURE
|
||||
if (phy_capture)
|
||||
{
|
||||
phy_capture(_netif.num, (const char*)pbuf->payload, tot_len, /*out*/0, /*success*/err == ERR_OK);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (err != ERR_OK)
|
||||
{
|
||||
pbuf_free(pbuf);
|
||||
return err;
|
||||
}
|
||||
// (else) allocated pbuf is now lwIP's responsibility
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
void LwipIntfDev<RawDev>::setDefault()
|
||||
{
|
||||
_default = true;
|
||||
if (connected())
|
||||
{
|
||||
netif_set_default(&_netif);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _LWIPINTFDEV_H
|
@ -75,6 +75,10 @@ class Print {
|
||||
inline size_t write(char c) { return write((uint8_t) c); }
|
||||
inline size_t write(int8_t c) { return write((uint8_t) c); }
|
||||
|
||||
// default to zero, meaning "a single write may block"
|
||||
// should be overriden by subclasses with buffering
|
||||
virtual int availableForWrite() { return 0; }
|
||||
|
||||
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
size_t printf_P(PGM_P format, ...) __attribute__((format(printf, 2, 3)));
|
||||
size_t print(const __FlashStringHelper *);
|
||||
|
@ -102,6 +102,7 @@ bool schedule_function(const std::function<void(void)>& fn)
|
||||
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)
|
||||
{
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include "debug.h"
|
||||
#include "StackThunk.h"
|
||||
#include <ets_sys.h>
|
||||
#include <umm_malloc/umm_malloc.h>
|
||||
#include <umm_malloc/umm_heap_select.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
@ -48,7 +50,14 @@ void stack_thunk_add_ref()
|
||||
{
|
||||
stack_thunk_refcnt++;
|
||||
if (stack_thunk_refcnt == 1) {
|
||||
DBG_MMU_PRINTF("\nStackThunk malloc(%u)\n", _stackSize * sizeof(uint32_t));
|
||||
// The stack must be in DRAM, or an Soft WDT will follow. Not sure why,
|
||||
// maybe too much time is consumed with the non32-bit exception handler.
|
||||
// Also, interrupt handling on an IRAM stack would be very slow.
|
||||
// Strings on the stack would be very slow to access as well.
|
||||
HeapSelectDram ephemeral;
|
||||
stack_thunk_ptr = (uint32_t *)malloc(_stackSize * sizeof(uint32_t));
|
||||
DBG_MMU_PRINTF("StackThunk stack_thunk_ptr: %p\n", stack_thunk_ptr);
|
||||
if (!stack_thunk_ptr) {
|
||||
// This is a fatal error, stop the sketch
|
||||
DEBUGV("Unable to allocate BearSSL stack\n");
|
||||
|
@ -1,10 +1,11 @@
|
||||
|
||||
// autogenerated from https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv
|
||||
// by script <esp8266 arduino core>/tools/TZupdate.sh
|
||||
// Tue Jul 7 07:38:29 UTC 2020
|
||||
// Thu Nov 12 04:07:03 UTC 2020
|
||||
//
|
||||
// This database is autogenerated from IANA timezone database
|
||||
// https://www.iana.org/time-zones
|
||||
// https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv
|
||||
// (using https://www.iana.org/time-zones)
|
||||
// and can be updated on demand in this repository
|
||||
// or by yourself using the above script
|
||||
|
||||
@ -211,10 +212,10 @@
|
||||
#define TZ_America_Winnipeg PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Yakutat PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Yellowknife PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||
#define TZ_Antarctica_Casey PSTR("<+08>-8")
|
||||
#define TZ_Antarctica_Casey PSTR("<+11>-11")
|
||||
#define TZ_Antarctica_Davis PSTR("<+07>-7")
|
||||
#define TZ_Antarctica_DumontDUrville PSTR("<+10>-10")
|
||||
#define TZ_Antarctica_Macquarie PSTR("<+11>-11")
|
||||
#define TZ_Antarctica_Macquarie PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||
#define TZ_Antarctica_Mawson PSTR("<+05>-5")
|
||||
#define TZ_Antarctica_McMurdo PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
|
||||
#define TZ_Antarctica_Palmer PSTR("<-03>3")
|
||||
@ -248,8 +249,8 @@
|
||||
#define TZ_Asia_Dubai PSTR("<+04>-4")
|
||||
#define TZ_Asia_Dushanbe PSTR("<+05>-5")
|
||||
#define TZ_Asia_Famagusta PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Asia_Gaza PSTR("EET-2EEST,M3.5.5/0,M10.5.6/1")
|
||||
#define TZ_Asia_Hebron PSTR("EET-2EEST,M3.5.5/0,M10.5.6/1")
|
||||
#define TZ_Asia_Gaza PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
|
||||
#define TZ_Asia_Hebron PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
|
||||
#define TZ_Asia_Ho_Chi_Minh PSTR("<+07>-7")
|
||||
#define TZ_Asia_Hong_Kong PSTR("HKT-8")
|
||||
#define TZ_Asia_Hovd PSTR("<+07>-7")
|
||||
|
@ -25,15 +25,16 @@
|
||||
#include "core_esp8266_waveform.h"
|
||||
#include "user_interface.h"
|
||||
|
||||
// Which pins have a tone running on them?
|
||||
static uint32_t _toneMap = 0;
|
||||
|
||||
|
||||
static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) {
|
||||
if (_pin > 16) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any analogWrites (PWM) because they are a different generator
|
||||
_stopPWM(_pin);
|
||||
// If there's another Tone or startWaveform on this pin
|
||||
// it will be changed on-the-fly (no need to stop it)
|
||||
|
||||
pinMode(_pin, OUTPUT);
|
||||
|
||||
high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency,
|
||||
@ -42,9 +43,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat
|
||||
duration = microsecondsToClockCycles(duration * 1000UL);
|
||||
duration += high + low - 1;
|
||||
duration -= duration % (high + low);
|
||||
if (startWaveformClockCycles(_pin, high, low, duration)) {
|
||||
_toneMap |= 1 << _pin;
|
||||
}
|
||||
startWaveformClockCycles(_pin, high, low, duration);
|
||||
}
|
||||
|
||||
|
||||
@ -86,6 +85,5 @@ void noTone(uint8_t _pin) {
|
||||
return;
|
||||
}
|
||||
stopWaveform(_pin);
|
||||
_toneMap &= ~(1 << _pin);
|
||||
digitalWrite(_pin, 0);
|
||||
}
|
||||
|
@ -203,6 +203,11 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
||||
return false;
|
||||
}
|
||||
|
||||
// Updating w/o any data is an error we detect here
|
||||
if (!progress()) {
|
||||
_setError(UPDATE_ERROR_NO_DATA);
|
||||
}
|
||||
|
||||
if(hasError() || (!isFinished() && !evenIfRemaining)){
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("premature end: res:%u, pos:%zu/%zu\n"), getError(), progress(), _size);
|
||||
@ -551,6 +556,8 @@ void UpdaterClass::printError(Print &out){
|
||||
out.println(F("Bad Size Given"));
|
||||
} else if(_error == UPDATE_ERROR_STREAM){
|
||||
out.println(F("Stream Read Timeout"));
|
||||
} else if(_error == UPDATE_ERROR_NO_DATA){
|
||||
out.println(F("No data supplied"));
|
||||
} else if(_error == UPDATE_ERROR_MD5){
|
||||
out.printf_P(PSTR("MD5 Failed: expected:%s, calculated:%s\n"), _target_md5.c_str(), _md5.toString().c_str());
|
||||
} else if(_error == UPDATE_ERROR_SIGN){
|
||||
|
@ -19,6 +19,7 @@
|
||||
#define UPDATE_ERROR_MAGIC_BYTE (10)
|
||||
#define UPDATE_ERROR_BOOTSTRAP (11)
|
||||
#define UPDATE_ERROR_SIGN (12)
|
||||
#define UPDATE_ERROR_NO_DATA (13)
|
||||
|
||||
#define U_FLASH 0
|
||||
#define U_FS 100
|
||||
|
@ -32,7 +32,7 @@
|
||||
String::String(const char *cstr) {
|
||||
init();
|
||||
if (cstr)
|
||||
copy(cstr, strlen(cstr));
|
||||
copy(cstr, strlen_P(cstr));
|
||||
}
|
||||
|
||||
String::String(const String &value) {
|
||||
@ -55,14 +55,6 @@ String::String(StringSumHelper &&rval) noexcept {
|
||||
move(rval);
|
||||
}
|
||||
|
||||
String::String(char c) {
|
||||
init();
|
||||
char buf[2];
|
||||
buf[0] = c;
|
||||
buf[1] = 0;
|
||||
*this = buf;
|
||||
}
|
||||
|
||||
String::String(unsigned char value, unsigned char base) {
|
||||
init();
|
||||
char buf[1 + 8 * sizeof(unsigned char)];
|
||||
@ -91,7 +83,7 @@ String::String(unsigned int value, unsigned char base) {
|
||||
String::String(long value, unsigned char base) {
|
||||
init();
|
||||
char buf[2 + 8 * sizeof(long)];
|
||||
if (base==10) {
|
||||
if (base == 10) {
|
||||
sprintf(buf, "%ld", value);
|
||||
} else {
|
||||
ltoa(value, buf, base);
|
||||
@ -118,31 +110,21 @@ String::String(double value, unsigned char decimalPlaces) {
|
||||
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
|
||||
}
|
||||
|
||||
String::~String() {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/*********************************************/
|
||||
/* Memory Management */
|
||||
/*********************************************/
|
||||
|
||||
inline void String::init(void) {
|
||||
setSSO(true);
|
||||
setLen(0);
|
||||
wbuffer()[0] = 0;
|
||||
}
|
||||
|
||||
void String::invalidate(void) {
|
||||
if(!isSSO() && wbuffer())
|
||||
if (!isSSO() && wbuffer())
|
||||
free(wbuffer());
|
||||
init();
|
||||
}
|
||||
|
||||
unsigned char String::reserve(unsigned int size) {
|
||||
if(buffer() && capacity() >= size)
|
||||
if (buffer() && capacity() >= size)
|
||||
return 1;
|
||||
if(changeBuffer(size)) {
|
||||
if(len() == 0)
|
||||
if (changeBuffer(size)) {
|
||||
if (len() == 0)
|
||||
wbuffer()[0] = 0;
|
||||
return 1;
|
||||
}
|
||||
@ -157,35 +139,32 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
||||
uint16_t oldLen = len();
|
||||
setSSO(true);
|
||||
setLen(oldLen);
|
||||
return 1;
|
||||
} else { // if bufptr && !isSSO()
|
||||
// Using bufptr, need to shrink into sso.buff
|
||||
char temp[sizeof(sso.buff)];
|
||||
memcpy(temp, buffer(), maxStrLen);
|
||||
free(wbuffer());
|
||||
const char *temp = buffer();
|
||||
uint16_t oldLen = len();
|
||||
setSSO(true);
|
||||
setLen(oldLen);
|
||||
memcpy(wbuffer(), temp, maxStrLen);
|
||||
return 1;
|
||||
free((void *)temp);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
// Fallthrough to normal allocator
|
||||
size_t newSize = (maxStrLen + 16) & (~0xf);
|
||||
// Make sure we can fit newsize in the buffer
|
||||
if (newSize > CAPACITY_MAX) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
uint16_t oldLen = len();
|
||||
char *newbuffer = (char *) realloc(isSSO() ? nullptr : wbuffer(), newSize);
|
||||
char *newbuffer = (char *)realloc(isSSO() ? nullptr : wbuffer(), newSize);
|
||||
if (newbuffer) {
|
||||
size_t oldSize = capacity() + 1; // include NULL.
|
||||
if (isSSO()) {
|
||||
// Copy the SSO buffer into allocated space
|
||||
memmove_P(newbuffer, sso.buff, sizeof(sso.buff));
|
||||
}
|
||||
if (newSize > oldSize)
|
||||
{
|
||||
if (newSize > oldSize) {
|
||||
memset(newbuffer + oldSize, 0, newSize - oldSize);
|
||||
}
|
||||
setSSO(false);
|
||||
@ -201,7 +180,7 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
||||
/* Copy and Move */
|
||||
/*********************************************/
|
||||
|
||||
String & String::copy(const char *cstr, unsigned int length) {
|
||||
String &String::copy(const char *cstr, unsigned int length) {
|
||||
if (!reserve(length)) {
|
||||
invalidate();
|
||||
return *this;
|
||||
@ -211,7 +190,7 @@ String & String::copy(const char *cstr, unsigned int length) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
String & String::copy(const __FlashStringHelper *pstr, unsigned int length) {
|
||||
String &String::copy(const __FlashStringHelper *pstr, unsigned int length) {
|
||||
if (!reserve(length)) {
|
||||
invalidate();
|
||||
return *this;
|
||||
@ -227,44 +206,35 @@ void String::move(String &rhs) noexcept {
|
||||
rhs.init();
|
||||
}
|
||||
|
||||
String & String::operator =(const String &rhs) {
|
||||
String &String::operator =(const String &rhs) {
|
||||
if (this == &rhs)
|
||||
return *this;
|
||||
|
||||
if (rhs.buffer())
|
||||
copy(rhs.buffer(), rhs.len());
|
||||
else
|
||||
invalidate();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
String & String::operator =(String &&rval) noexcept {
|
||||
String &String::operator =(String &&rval) noexcept {
|
||||
if (this != &rval)
|
||||
move(rval);
|
||||
return *this;
|
||||
}
|
||||
|
||||
String & String::operator =(StringSumHelper &&rval) noexcept {
|
||||
if (this != &rval)
|
||||
move(rval);
|
||||
return *this;
|
||||
}
|
||||
|
||||
String & String::operator =(const char *cstr) {
|
||||
String &String::operator =(const char *cstr) {
|
||||
if (cstr)
|
||||
copy(cstr, strlen(cstr));
|
||||
else
|
||||
invalidate();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
String & String::operator = (const __FlashStringHelper *pstr)
|
||||
{
|
||||
if (pstr) copy(pstr, strlen_P((PGM_P)pstr));
|
||||
else invalidate();
|
||||
|
||||
String &String::operator =(const __FlashStringHelper *pstr) {
|
||||
if (pstr)
|
||||
copy(pstr, strlen_P((PGM_P)pstr));
|
||||
else
|
||||
invalidate();
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -285,7 +255,7 @@ unsigned char String::concat(const String &s) {
|
||||
return 0;
|
||||
memmove_P(wbuffer() + len(), buffer(), len());
|
||||
setLen(newlen);
|
||||
wbuffer()[len()] = 0;
|
||||
wbuffer()[newlen] = 0;
|
||||
return 1;
|
||||
} else {
|
||||
return concat(s.buffer(), s.len());
|
||||
@ -313,22 +283,17 @@ unsigned char String::concat(const char *cstr) {
|
||||
}
|
||||
|
||||
unsigned char String::concat(char c) {
|
||||
char buf[2];
|
||||
buf[0] = c;
|
||||
buf[1] = 0;
|
||||
return concat(buf, 1);
|
||||
return concat(&c, 1);
|
||||
}
|
||||
|
||||
unsigned char String::concat(unsigned char num) {
|
||||
char buf[1 + 3 * sizeof(unsigned char)];
|
||||
sprintf(buf, "%d", num);
|
||||
return concat(buf, strlen(buf));
|
||||
return concat(buf, sprintf(buf, "%d", num));
|
||||
}
|
||||
|
||||
unsigned char String::concat(int num) {
|
||||
char buf[2 + 3 * sizeof(int)];
|
||||
sprintf(buf, "%d", num);
|
||||
return concat(buf, strlen(buf));
|
||||
return concat(buf, sprintf(buf, "%d", num));
|
||||
}
|
||||
|
||||
unsigned char String::concat(unsigned int num) {
|
||||
@ -339,8 +304,7 @@ unsigned char String::concat(unsigned int num) {
|
||||
|
||||
unsigned char String::concat(long num) {
|
||||
char buf[2 + 3 * sizeof(long)];
|
||||
sprintf(buf, "%ld", num);
|
||||
return concat(buf, strlen(buf));
|
||||
return concat(buf, sprintf(buf, "%ld", num));
|
||||
}
|
||||
|
||||
unsigned char String::concat(unsigned long num) {
|
||||
@ -351,22 +315,25 @@ unsigned char String::concat(unsigned long num) {
|
||||
|
||||
unsigned char String::concat(float num) {
|
||||
char buf[20];
|
||||
char* string = dtostrf(num, 4, 2, buf);
|
||||
char *string = dtostrf(num, 4, 2, buf);
|
||||
return concat(string, strlen(string));
|
||||
}
|
||||
|
||||
unsigned char String::concat(double num) {
|
||||
char buf[20];
|
||||
char* string = dtostrf(num, 4, 2, buf);
|
||||
char *string = dtostrf(num, 4, 2, buf);
|
||||
return concat(string, strlen(string));
|
||||
}
|
||||
|
||||
unsigned char String::concat(const __FlashStringHelper * str) {
|
||||
if (!str) return 0;
|
||||
unsigned char String::concat(const __FlashStringHelper *str) {
|
||||
if (!str)
|
||||
return 0;
|
||||
int length = strlen_P((PGM_P)str);
|
||||
if (length == 0) return 1;
|
||||
if (length == 0)
|
||||
return 1;
|
||||
unsigned int newlen = len() + length;
|
||||
if (!reserve(newlen)) return 0;
|
||||
if (!reserve(newlen))
|
||||
return 0;
|
||||
memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1);
|
||||
setLen(newlen);
|
||||
return 1;
|
||||
@ -376,79 +343,78 @@ unsigned char String::concat(const __FlashStringHelper * str) {
|
||||
/* Concatenate */
|
||||
/*********************************************/
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(rhs.buffer(), rhs.len()))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!cstr || !a.concat(cstr, strlen(cstr)))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, char c) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, char c) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(c))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, int num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, int num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, long num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, long num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, float num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, float num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator +(const StringSumHelper &lhs, double num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, double num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs)
|
||||
{
|
||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(rhs))
|
||||
a.invalidate();
|
||||
return a;
|
||||
@ -459,11 +425,11 @@ StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHel
|
||||
/*********************************************/
|
||||
|
||||
int String::compareTo(const String &s) const {
|
||||
if(!buffer() || !s.buffer()) {
|
||||
if(s.buffer() && s.len() > 0)
|
||||
return 0 - *(unsigned char *) s.buffer();
|
||||
if(buffer() && len() > 0)
|
||||
return *(unsigned char *) buffer();
|
||||
if (!buffer() || !s.buffer()) {
|
||||
if (s.buffer() && s.len() > 0)
|
||||
return 0 - *(unsigned char *)s.buffer();
|
||||
if (buffer() && len() > 0)
|
||||
return *(unsigned char *)buffer();
|
||||
return 0;
|
||||
}
|
||||
return strcmp(buffer(), s.buffer());
|
||||
@ -521,7 +487,7 @@ unsigned char String::equalsConstantTime(const String &s2) const {
|
||||
//at this point lengths are the same
|
||||
if (len() == 0)
|
||||
return 1;
|
||||
//at this point lenghts are the same and non-zero
|
||||
//at this point lengths are the same and non-zero
|
||||
const char *p1 = buffer();
|
||||
const char *p2 = s2.buffer();
|
||||
unsigned int equalchars = 0;
|
||||
@ -541,19 +507,19 @@ unsigned char String::equalsConstantTime(const String &s2) const {
|
||||
}
|
||||
|
||||
unsigned char String::startsWith(const String &s2) const {
|
||||
if(len() < s2.len())
|
||||
if (len() < s2.len())
|
||||
return 0;
|
||||
return startsWith(s2, 0);
|
||||
}
|
||||
|
||||
unsigned char String::startsWith(const String &s2, unsigned int offset) const {
|
||||
if(offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer())
|
||||
if (offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer())
|
||||
return 0;
|
||||
return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0;
|
||||
}
|
||||
|
||||
unsigned char String::endsWith(const String &s2) const {
|
||||
if(len() < s2.len() || !buffer() || !s2.buffer())
|
||||
if (len() < s2.len() || !buffer() || !s2.buffer())
|
||||
return 0;
|
||||
return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0;
|
||||
}
|
||||
@ -562,16 +528,12 @@ unsigned char String::endsWith(const String &s2) const {
|
||||
/* Character Access */
|
||||
/*********************************************/
|
||||
|
||||
char String::charAt(unsigned int loc) const {
|
||||
return operator[](loc);
|
||||
}
|
||||
|
||||
void String::setCharAt(unsigned int loc, char c) {
|
||||
if (loc < len())
|
||||
wbuffer()[loc] = c;
|
||||
}
|
||||
|
||||
char & String::operator[](unsigned int index) {
|
||||
char &String::operator[](unsigned int index) {
|
||||
static char dummy_writable_char;
|
||||
if (index >= len() || !buffer()) {
|
||||
dummy_writable_char = 0;
|
||||
@ -596,7 +558,7 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind
|
||||
unsigned int n = bufsize - 1;
|
||||
if (n > len() - index)
|
||||
n = len() - index;
|
||||
strncpy((char *) buf, buffer() + index, n);
|
||||
strncpy((char *)buf, buffer() + index, n);
|
||||
buf[n] = 0;
|
||||
}
|
||||
|
||||
@ -604,46 +566,43 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind
|
||||
/* Search */
|
||||
/*********************************************/
|
||||
|
||||
int String::indexOf(char c) const {
|
||||
return indexOf(c, 0);
|
||||
}
|
||||
|
||||
int String::indexOf(char ch, unsigned int fromIndex) const {
|
||||
if (fromIndex >= len())
|
||||
return -1;
|
||||
const char* temp = strchr(buffer() + fromIndex, ch);
|
||||
const char *temp = strchr(buffer() + fromIndex, ch);
|
||||
if (temp == NULL)
|
||||
return -1;
|
||||
return temp - buffer();
|
||||
}
|
||||
|
||||
int String::indexOf(const String &s2) const {
|
||||
return indexOf(s2, 0);
|
||||
}
|
||||
|
||||
int String::indexOf(const String &s2, unsigned int fromIndex) const {
|
||||
int String::indexOf(const char *s2, unsigned int fromIndex) const {
|
||||
if (fromIndex >= len())
|
||||
return -1;
|
||||
const char *found = strstr(buffer() + fromIndex, s2.buffer());
|
||||
const char *found = strstr_P(buffer() + fromIndex, s2);
|
||||
if (found == NULL)
|
||||
return -1;
|
||||
return found - buffer();
|
||||
}
|
||||
|
||||
int String::lastIndexOf(char theChar) const {
|
||||
return lastIndexOf(theChar, len() - 1);
|
||||
int String::indexOf(const String &s2, unsigned int fromIndex) const {
|
||||
return indexOf(s2.c_str(), fromIndex);
|
||||
}
|
||||
|
||||
int String::lastIndexOf(char ch) const {
|
||||
return lastIndexOf(ch, len() - 1);
|
||||
}
|
||||
|
||||
int String::lastIndexOf(char ch, unsigned int fromIndex) const {
|
||||
if (fromIndex >= len())
|
||||
return -1;
|
||||
char tempchar = buffer()[fromIndex + 1];
|
||||
wbuffer()[fromIndex + 1] = '\0';
|
||||
char* temp = strrchr(wbuffer(), ch);
|
||||
wbuffer()[fromIndex + 1] = tempchar;
|
||||
char *writeTo = wbuffer();
|
||||
char tempchar = writeTo[fromIndex + 1]; // save the replaced character
|
||||
writeTo[fromIndex + 1] = '\0';
|
||||
char *temp = strrchr(writeTo, ch);
|
||||
writeTo[fromIndex + 1] = tempchar; // restore character
|
||||
if (temp == NULL)
|
||||
return -1;
|
||||
return temp - buffer();
|
||||
return temp - writeTo;
|
||||
}
|
||||
|
||||
int String::lastIndexOf(const String &s2) const {
|
||||
@ -656,11 +615,11 @@ int String::lastIndexOf(const String &s2, unsigned int fromIndex) const {
|
||||
if (fromIndex >= len())
|
||||
fromIndex = len() - 1;
|
||||
int found = -1;
|
||||
for (char *p = wbuffer(); p <= wbuffer() + fromIndex; p++) {
|
||||
for (const char *p = buffer(); p <= buffer() + fromIndex; p++) {
|
||||
p = strstr(p, s2.buffer());
|
||||
if (!p)
|
||||
break;
|
||||
if ((unsigned int) (p - wbuffer()) <= fromIndex)
|
||||
if ((unsigned int)(p - buffer()) <= fromIndex)
|
||||
found = p - buffer();
|
||||
}
|
||||
return found;
|
||||
@ -677,10 +636,11 @@ String String::substring(unsigned int left, unsigned int right) const {
|
||||
return out;
|
||||
if (right > len())
|
||||
right = len();
|
||||
char temp = buffer()[right]; // save the replaced character
|
||||
wbuffer()[right] = '\0';
|
||||
out = wbuffer() + left; // pointer arithmetic
|
||||
wbuffer()[right] = temp; //restore character
|
||||
char *writeTo = wbuffer();
|
||||
char tempchar = writeTo[right]; // save the replaced character
|
||||
writeTo[right] = '\0';
|
||||
out = writeTo + left; // pointer arithmetic
|
||||
writeTo[right] = tempchar; // restore character
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -697,7 +657,7 @@ void String::replace(char find, char replace) {
|
||||
}
|
||||
}
|
||||
|
||||
void String::replace(const String& find, const String& replace) {
|
||||
void String::replace(const String &find, const String &replace) {
|
||||
if (len() == 0 || find.len() == 0)
|
||||
return;
|
||||
int diff = replace.len() - find.len();
|
||||
@ -719,7 +679,7 @@ void String::replace(const String& find, const String& replace) {
|
||||
readFrom = foundAt + find.len();
|
||||
setLen(len() + diff);
|
||||
}
|
||||
memmove_P(writeTo, readFrom, strlen(readFrom)+1);
|
||||
memmove_P(writeTo, readFrom, strlen(readFrom) + 1);
|
||||
} else {
|
||||
unsigned int size = len(); // compute size needed for result
|
||||
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
||||
@ -743,13 +703,6 @@ void String::replace(const String& find, const String& replace) {
|
||||
}
|
||||
}
|
||||
|
||||
void String::remove(unsigned int index) {
|
||||
// Pass the biggest integer as the count. The remove method
|
||||
// below will take care of truncating it at the end of the
|
||||
// string.
|
||||
remove(index, (unsigned int) -1);
|
||||
}
|
||||
|
||||
void String::remove(unsigned int index, unsigned int count) {
|
||||
if (index >= len()) {
|
||||
return;
|
||||
@ -812,11 +765,10 @@ long String::toInt(void) const {
|
||||
float String::toFloat(void) const {
|
||||
if (buffer())
|
||||
return atof(buffer());
|
||||
return 0;
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
double String::toDouble(void) const
|
||||
{
|
||||
double String::toDouble(void) const {
|
||||
if (buffer())
|
||||
return atof(buffer());
|
||||
return 0.0;
|
||||
|
@ -53,7 +53,7 @@ class String {
|
||||
// if the initial value is null or invalid, or if memory allocation
|
||||
// fails, the string will be marked as invalid (i.e. "if (s)" will
|
||||
// be false).
|
||||
String() {
|
||||
String() __attribute__((always_inline)) { // See init()
|
||||
init();
|
||||
}
|
||||
String(const char *cstr);
|
||||
@ -61,7 +61,12 @@ class String {
|
||||
String(const __FlashStringHelper *str);
|
||||
String(String &&rval) noexcept;
|
||||
String(StringSumHelper &&rval) noexcept;
|
||||
explicit String(char c);
|
||||
explicit String(char c) {
|
||||
sso.buff[0] = c;
|
||||
sso.buff[1] = 0;
|
||||
sso.len = 1;
|
||||
sso.isHeap = 0;
|
||||
}
|
||||
explicit String(unsigned char, unsigned char base = 10);
|
||||
explicit String(int, unsigned char base = 10);
|
||||
explicit String(unsigned int, unsigned char base = 10);
|
||||
@ -69,35 +74,35 @@ class String {
|
||||
explicit String(unsigned long, unsigned char base = 10);
|
||||
explicit String(float, unsigned char decimalPlaces = 2);
|
||||
explicit String(double, unsigned char decimalPlaces = 2);
|
||||
~String(void);
|
||||
~String() {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// memory management
|
||||
// return true on success, false on failure (in which case, the string
|
||||
// is left unchanged). reserve(0), if successful, will validate an
|
||||
// invalid string (i.e., "if (s)" will be true afterwards)
|
||||
unsigned char reserve(unsigned int size);
|
||||
inline unsigned int length(void) const {
|
||||
if(buffer()) {
|
||||
return len();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
unsigned int length(void) const {
|
||||
return buffer() ? len() : 0;
|
||||
}
|
||||
inline void clear(void) {
|
||||
void clear(void) {
|
||||
setLen(0);
|
||||
}
|
||||
inline bool isEmpty(void) const {
|
||||
bool isEmpty(void) const {
|
||||
return length() == 0;
|
||||
}
|
||||
|
||||
// creates a copy of the assigned value. if the value is null or
|
||||
// invalid, or if the memory allocation fails, the string will be
|
||||
// marked as invalid ("if (s)" will be false).
|
||||
String & operator =(const String &rhs);
|
||||
String & operator =(const char *cstr);
|
||||
String & operator = (const __FlashStringHelper *str);
|
||||
String & operator =(String &&rval) noexcept;
|
||||
String & operator =(StringSumHelper &&rval) noexcept;
|
||||
String &operator =(const String &rhs);
|
||||
String &operator =(const char *cstr);
|
||||
String &operator =(const __FlashStringHelper *str);
|
||||
String &operator =(String &&rval) noexcept;
|
||||
String &operator =(StringSumHelper &&rval) noexcept {
|
||||
return operator =((String &&)rval);
|
||||
}
|
||||
|
||||
// concatenate (works w/ built-in types)
|
||||
|
||||
@ -114,67 +119,67 @@ class String {
|
||||
unsigned char concat(unsigned long num);
|
||||
unsigned char concat(float num);
|
||||
unsigned char concat(double num);
|
||||
unsigned char concat(const __FlashStringHelper * str);
|
||||
unsigned char concat(const __FlashStringHelper *str);
|
||||
unsigned char concat(const char *cstr, unsigned int length);
|
||||
|
||||
// if there's not enough memory for the concatenated value, the string
|
||||
// will be left unchanged (but this isn't signalled in any way)
|
||||
String & operator +=(const String &rhs) {
|
||||
String &operator +=(const String &rhs) {
|
||||
concat(rhs);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator +=(const char *cstr) {
|
||||
String &operator +=(const char *cstr) {
|
||||
concat(cstr);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator +=(char c) {
|
||||
String &operator +=(char c) {
|
||||
concat(c);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator +=(unsigned char num) {
|
||||
String &operator +=(unsigned char num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator +=(int num) {
|
||||
String &operator +=(int num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator +=(unsigned int num) {
|
||||
String &operator +=(unsigned int num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator +=(long num) {
|
||||
String &operator +=(long num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator +=(unsigned long num) {
|
||||
String &operator +=(unsigned long num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator +=(float num) {
|
||||
String &operator +=(float num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator +=(double num) {
|
||||
String &operator +=(double num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String & operator += (const __FlashStringHelper *str){
|
||||
String &operator +=(const __FlashStringHelper *str) {
|
||||
concat(str);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, char c);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, int num);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, long num);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, float num);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, double num);
|
||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, char c);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, int num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, long num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, float num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, double num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
|
||||
|
||||
// comparison (only works w/ Strings and "strings")
|
||||
operator StringIfHelperType() const {
|
||||
@ -202,41 +207,45 @@ class String {
|
||||
unsigned char equalsIgnoreCase(const String &s) const;
|
||||
unsigned char equalsConstantTime(const String &s) const;
|
||||
unsigned char startsWith(const String &prefix) const;
|
||||
unsigned char startsWith(const char * prefix) const {
|
||||
unsigned char startsWith(const char *prefix) const {
|
||||
return this->startsWith(String(prefix));
|
||||
}
|
||||
unsigned char startsWith(const __FlashStringHelper * prefix) const {
|
||||
unsigned char startsWith(const __FlashStringHelper *prefix) const {
|
||||
return this->startsWith(String(prefix));
|
||||
}
|
||||
unsigned char startsWith(const String &prefix, unsigned int offset) const;
|
||||
unsigned char endsWith(const String &suffix) const;
|
||||
unsigned char endsWith(const char * suffix) const {
|
||||
unsigned char endsWith(const char *suffix) const {
|
||||
return this->endsWith(String(suffix));
|
||||
}
|
||||
unsigned char endsWith(const __FlashStringHelper * suffix) const {
|
||||
unsigned char endsWith(const __FlashStringHelper *suffix) const {
|
||||
return this->endsWith(String(suffix));
|
||||
}
|
||||
|
||||
// character access
|
||||
char charAt(unsigned int index) const;
|
||||
char charAt(unsigned int index) const {
|
||||
return operator [](index);
|
||||
}
|
||||
void setCharAt(unsigned int index, char c);
|
||||
char operator [](unsigned int index) const;
|
||||
char& operator [](unsigned int index);
|
||||
char &operator [](unsigned int index);
|
||||
void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const;
|
||||
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
|
||||
getBytes((unsigned char *) buf, bufsize, index);
|
||||
}
|
||||
const char* c_str() const { return buffer(); }
|
||||
char* begin() { return wbuffer(); }
|
||||
char* end() { return wbuffer() + length(); }
|
||||
const char* begin() const { return c_str(); }
|
||||
const char* end() const { return c_str() + length(); }
|
||||
const char *c_str() const { return buffer(); }
|
||||
char *begin() { return wbuffer(); }
|
||||
char *end() { return wbuffer() + length(); }
|
||||
const char *begin() const { return c_str(); }
|
||||
const char *end() const { return c_str() + length(); }
|
||||
|
||||
// search
|
||||
int indexOf(char ch) const;
|
||||
int indexOf(char ch, unsigned int fromIndex) const;
|
||||
int indexOf(const String &str) const;
|
||||
int indexOf(const String &str, unsigned int fromIndex) const;
|
||||
int indexOf(char ch, unsigned int fromIndex = 0) const;
|
||||
int indexOf(const char *str, unsigned int fromIndex = 0) const;
|
||||
int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) const {
|
||||
return indexOf((const char*)str, fromIndex);
|
||||
}
|
||||
int indexOf(const String &str, unsigned int fromIndex = 0) const;
|
||||
int lastIndexOf(char ch) const;
|
||||
int lastIndexOf(char ch, unsigned int fromIndex) const;
|
||||
int lastIndexOf(const String &str) const;
|
||||
@ -244,29 +253,29 @@ class String {
|
||||
String substring(unsigned int beginIndex) const {
|
||||
return substring(beginIndex, len());
|
||||
}
|
||||
;
|
||||
String substring(unsigned int beginIndex, unsigned int endIndex) const;
|
||||
|
||||
// modification
|
||||
void replace(char find, char replace);
|
||||
void replace(const String& find, const String& replace);
|
||||
void replace(const char * find, const String& replace) {
|
||||
void replace(const String &find, const String &replace);
|
||||
void replace(const char *find, const String &replace) {
|
||||
this->replace(String(find), replace);
|
||||
}
|
||||
void replace(const __FlashStringHelper * find, const String& replace) {
|
||||
void replace(const __FlashStringHelper *find, const String &replace) {
|
||||
this->replace(String(find), replace);
|
||||
}
|
||||
void replace(const char * find, const char * replace) {
|
||||
void replace(const char *find, const char *replace) {
|
||||
this->replace(String(find), String(replace));
|
||||
}
|
||||
void replace(const __FlashStringHelper * find, const char * replace) {
|
||||
void replace(const __FlashStringHelper *find, const char *replace) {
|
||||
this->replace(String(find), String(replace));
|
||||
}
|
||||
void replace(const __FlashStringHelper * find, const __FlashStringHelper * replace) {
|
||||
void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) {
|
||||
this->replace(String(find), String(replace));
|
||||
}
|
||||
void remove(unsigned int index);
|
||||
void remove(unsigned int index, unsigned int count);
|
||||
// Pass the biggest integer if the count is not specified.
|
||||
// The remove method below will take care of truncating it at the end of the string.
|
||||
void remove(unsigned int index, unsigned int count = (unsigned int)-1);
|
||||
void toLowerCase(void);
|
||||
void toUpperCase(void);
|
||||
void trim(void);
|
||||
@ -274,7 +283,7 @@ class String {
|
||||
// parsing/conversion
|
||||
long toInt(void) const;
|
||||
float toFloat(void) const;
|
||||
double toDouble(void) const;
|
||||
double toDouble(void) const;
|
||||
|
||||
protected:
|
||||
// Contains the string info when we're not in SSO mode
|
||||
@ -287,8 +296,8 @@ class String {
|
||||
enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more
|
||||
struct _sso {
|
||||
char buff[SSOSIZE];
|
||||
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
|
||||
unsigned char isSSO : 1;
|
||||
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
|
||||
unsigned char isHeap : 1;
|
||||
} __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues
|
||||
enum { CAPACITY_MAX = 65535 }; // If typeof(cap) changed from uint16_t, be sure to update this enum to the max value storable in the type
|
||||
union {
|
||||
@ -296,25 +305,47 @@ class String {
|
||||
struct _sso sso;
|
||||
};
|
||||
// Accessor functions
|
||||
inline bool isSSO() const { return sso.isSSO; }
|
||||
inline unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
|
||||
inline unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
|
||||
inline void setSSO(bool set) { sso.isSSO = set; }
|
||||
inline void setLen(int len) { if (isSSO()) sso.len = len; else ptr.len = len; }
|
||||
inline void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
|
||||
inline void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
|
||||
bool isSSO() const { return !sso.isHeap; }
|
||||
unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
|
||||
unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
|
||||
void setSSO(bool set) { sso.isHeap = !set; }
|
||||
void setLen(int len) {
|
||||
if (isSSO()) {
|
||||
setSSO(true); // Avoid emitting of bitwise EXTRACT-AND-OR ops (store-merging optimization)
|
||||
sso.len = len;
|
||||
} else
|
||||
ptr.len = len;
|
||||
}
|
||||
void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
|
||||
void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
|
||||
// Buffer accessor functions
|
||||
inline const char *buffer() const { return (const char *)(isSSO() ? sso.buff : ptr.buff); }
|
||||
inline char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
|
||||
const char *buffer() const { return wbuffer(); }
|
||||
char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
|
||||
|
||||
protected:
|
||||
void init(void);
|
||||
void init(void) __attribute__((always_inline)) {
|
||||
sso.buff[0] = 0;
|
||||
sso.len = 0;
|
||||
sso.isHeap = 0;
|
||||
// Without the 6 statements shown below, GCC simply emits such as: "MOVI.N aX,0", "S8I aX,a2,0" and "S8I aX,a2,11" (8 bytes in total)
|
||||
sso.buff[1] = 0;
|
||||
sso.buff[2] = 0;
|
||||
sso.buff[3] = 0;
|
||||
sso.buff[8] = 0;
|
||||
sso.buff[9] = 0;
|
||||
sso.buff[10] = 0;
|
||||
// With the above, thanks to store-merging, GCC can use the narrow form of 32-bit store insn ("S32I.N") and emits:
|
||||
// "MOVI.N aX,0", "S32I.N aX,a2,0" and "S32I.N aX,a2,8" (6 bytes in total)
|
||||
// (Literature: Xtensa(R) Instruction Set Reference Manual, "S8I - Store 8-bit" [p.504] and "S32I.N - Narrow Store 32-bit" [p.512])
|
||||
// Unfortunately, GCC seems not to re-evaluate the cost of inlining after the store-merging optimizer stage,
|
||||
// `always_inline` attribute is necessary in order to keep inlining.
|
||||
}
|
||||
void invalidate(void);
|
||||
unsigned char changeBuffer(unsigned int maxStrLen);
|
||||
|
||||
// copy and move
|
||||
String & copy(const char *cstr, unsigned int length);
|
||||
String & copy(const __FlashStringHelper *pstr, unsigned int length);
|
||||
String ©(const char *cstr, unsigned int length);
|
||||
String ©(const __FlashStringHelper *pstr, unsigned int length);
|
||||
void move(String &rhs) noexcept;
|
||||
};
|
||||
|
||||
@ -350,6 +381,9 @@ class StringSumHelper: public String {
|
||||
StringSumHelper(double num) :
|
||||
String(num) {
|
||||
}
|
||||
StringSumHelper(const __FlashStringHelper *s) :
|
||||
String(s) {
|
||||
}
|
||||
};
|
||||
|
||||
extern const String emptyString;
|
||||
|
163
cores/esp8266/aes_unwrap.cpp
Normal file
163
cores/esp8266/aes_unwrap.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Replacement for the ROM aes_unwrap() function. It uses the heap instead of
|
||||
* the static DRAM address at 0x3FFFEA80, which may step on the SYS stack in
|
||||
* special circumstances such as HWDT Stack Dump.
|
||||
*
|
||||
* When not using WPS, the address space 0x3FFFE000 up to 0x40000000 is mostly
|
||||
* available for the stacks. The one known exception is the ROM AES APIs. When
|
||||
* `aes_decrypt_init` is called, it uses memory at 0x3FFFEA80 up to 0x3FFFEB30
|
||||
* for a buffer. At the finish, `aes_decrypt_deinit` zeros out the buffer.
|
||||
*
|
||||
* The NONOS SDK appears to have replacements for most of the ROM's AES APIs.
|
||||
* However, the SDK still calls on the ROM's aes_unwrap function, which uses
|
||||
* the ROM's AES APIs to operate. These calls can overwrite some of the stack
|
||||
* space. To resolve the problem, this module replaces `aes_unwrap`.
|
||||
*
|
||||
* Final note, so far, I have not seen a problem when using the extra 4K heap
|
||||
* option without the "debug HWDT". It is when combined with the HWDT Stack
|
||||
* Dump that a problem shows. This combination adds a Boot ROM stack, which
|
||||
* pushes up the SYS and CONT stacks into the AES Buffer space. Then the
|
||||
* problem shows.
|
||||
*
|
||||
* While debugging with painted stack space, during WiFi Connect, Reconnect,
|
||||
* and about every hour, a block of memory 0x3FFFEA80 - 0x3FFFEB30 (176 bytes)
|
||||
* was zeroed by the Boot ROM function aes_decrypt_init. All other painted
|
||||
* memory in the area was untouched after starting WiFi.
|
||||
*/
|
||||
|
||||
#if defined(KEEP_ROM_AES_UNWRAP)
|
||||
// Using the ROM version of aes_unwrap should be fine for the no extra 4K case
|
||||
// which is usually used in conjunction with WPS.
|
||||
|
||||
#else
|
||||
// This is required for DEBUG_ESP_HWDT.
|
||||
// The need is unconfirmed for the extra 4K heap case.
|
||||
#include "umm_malloc/umm_malloc.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Uses this function from the Boot ROM
|
||||
void rijndaelKeySetupDec(u32 rk[], const u8 cipherKey[]);
|
||||
|
||||
// This replaces the Boot ROM version just for this module
|
||||
// Uses a malloc-ed buffer instead of the static buffer in stack address space.
|
||||
static void *aes_decrypt_init(const u8 *key, size_t len) {
|
||||
if (16u != len) {
|
||||
return 0;
|
||||
}
|
||||
u32 *rk = (u32 *)malloc(16*11);
|
||||
// u32 *rk = (u32 *)0x3FFFEA80u; // This is what the ROM would have used.
|
||||
if (rk) {
|
||||
rijndaelKeySetupDec(rk, key);
|
||||
}
|
||||
return (void *)rk;
|
||||
}
|
||||
|
||||
// This replaces the Boot ROM version just for this module
|
||||
static void aes_decrypt_deinit(void *ctx) {
|
||||
if (ctx) {
|
||||
ets_memset(ctx, 0, 16*11);
|
||||
free(ctx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The NONOS SDK has an override on this function. To replace the aes_unwrap
|
||||
* without changing its behavior too much. We need access to the ROM version of
|
||||
* the AES APIs to make our aes_unwrap functionally equal to the current
|
||||
* environment except for the AES Buffer.
|
||||
*/
|
||||
#ifndef ROM_aes_decrypt
|
||||
#define ROM_aes_decrypt 0x400092d4
|
||||
#endif
|
||||
|
||||
typedef void (*fp_aes_decrypt_t)(void *ctx, const u8 *crypt, u8 *plain);
|
||||
#define AES_DECRYPT (reinterpret_cast<fp_aes_decrypt_t>(ROM_aes_decrypt))
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* This aes_unwrap() function overrides/replaces the Boot ROM version.
|
||||
*
|
||||
* It was adapted from aes_unwrap() found in the ESP8266 RTOS SDK
|
||||
* .../components/wap_supplicant/src/crypto/aes-unwrap.c
|
||||
*
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* AES key unwrap (128-bit KEK, RFC3394)
|
||||
*
|
||||
* Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Alternatively, this software may be distributed under the terms of BSD
|
||||
* license.
|
||||
*
|
||||
* See README and COPYING for more details.
|
||||
*/
|
||||
|
||||
/** based on RTOS SDK
|
||||
* aes_unwrap - Unwrap key with AES Key Wrap Algorithm (128-bit KEK) (RFC3394)
|
||||
* @kek: Key encryption key (KEK)
|
||||
* @n: Length of the plaintext key in 64-bit units; e.g., 2 = 128-bit = 16
|
||||
* bytes
|
||||
* @cipher: Wrapped key to be unwrapped, (n + 1) * 64 bits
|
||||
* @plain: Plaintext key, n * 64 bits
|
||||
* Returns: 0 on success, -1 on failure (e.g., integrity verification failed)
|
||||
*/
|
||||
int aes_unwrap(const u8 *kek, int n, const u8 *cipher, u8 *plain)
|
||||
{
|
||||
u8 a[8], *r, b[16];
|
||||
int i, j;
|
||||
void *ctx;
|
||||
|
||||
/* 1) Initialize variables. */
|
||||
ets_memcpy(a, cipher, 8);
|
||||
r = plain;
|
||||
ets_memcpy(r, cipher + 8, 8 * n);
|
||||
|
||||
ctx = aes_decrypt_init(kek, 16);
|
||||
if (ctx == NULL)
|
||||
return -1;
|
||||
|
||||
/* 2) Compute intermediate values.
|
||||
* For j = 5 to 0
|
||||
* For i = n to 1
|
||||
* B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i
|
||||
* A = MSB(64, B)
|
||||
* R[i] = LSB(64, B)
|
||||
*/
|
||||
for (j = 5; j >= 0; j--) {
|
||||
r = plain + (n - 1) * 8;
|
||||
for (i = n; i >= 1; i--) {
|
||||
ets_memcpy(b, a, 8);
|
||||
b[7] ^= n * j + i;
|
||||
|
||||
ets_memcpy(b + 8, r, 8);
|
||||
AES_DECRYPT(ctx, b, b);
|
||||
ets_memcpy(a, b, 8);
|
||||
ets_memcpy(r, b + 8, 8);
|
||||
r -= 8;
|
||||
}
|
||||
}
|
||||
aes_decrypt_deinit(ctx);
|
||||
|
||||
/* 3) Output results.
|
||||
*
|
||||
* These are already in @plain due to the location of temporary
|
||||
* variables. Just verify that the IV matches with the expected value.
|
||||
*/
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (a[i] != 0xa6)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
#endif
|
@ -64,6 +64,7 @@ typedef struct i2s_state {
|
||||
// Callback function should be defined as 'void ICACHE_RAM_ATTR function_name()',
|
||||
// and be placed in IRAM for faster execution. Avoid long computational tasks in this
|
||||
// function, use it to set flags and process later.
|
||||
bool driveClocks;
|
||||
} i2s_state_t;
|
||||
|
||||
// RX = I2S receive (i.e. microphone), TX = I2S transmit (i.e. DAC)
|
||||
@ -493,6 +494,10 @@ float i2s_get_real_rate(){
|
||||
}
|
||||
|
||||
bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
||||
return i2s_rxtxdrive_begin(enableRx, enableTx, true, true);
|
||||
}
|
||||
|
||||
bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks) {
|
||||
if (tx || rx) {
|
||||
i2s_end(); // Stop and free any ongoing stuff
|
||||
}
|
||||
@ -503,9 +508,12 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
||||
// Nothing to clean up yet
|
||||
return false; // OOM Error!
|
||||
}
|
||||
pinMode(I2SO_WS, FUNCTION_1);
|
||||
tx->driveClocks = driveTxClocks;
|
||||
pinMode(I2SO_DATA, FUNCTION_1);
|
||||
pinMode(I2SO_BCK, FUNCTION_1);
|
||||
if (driveTxClocks) {
|
||||
pinMode(I2SO_WS, FUNCTION_1);
|
||||
pinMode(I2SO_BCK, FUNCTION_1);
|
||||
}
|
||||
}
|
||||
if (enableRx) {
|
||||
rx = (i2s_state_t*)calloc(1, sizeof(*rx));
|
||||
@ -513,12 +521,15 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
||||
i2s_end(); // Clean up any TX or pin changes
|
||||
return false; // OOM error!
|
||||
}
|
||||
pinMode(I2SI_WS, OUTPUT);
|
||||
pinMode(I2SI_BCK, OUTPUT);
|
||||
rx->driveClocks = driveRxClocks;
|
||||
pinMode(I2SI_DATA, INPUT);
|
||||
if (driveRxClocks) {
|
||||
pinMode(I2SI_WS, OUTPUT);
|
||||
pinMode(I2SI_BCK, OUTPUT);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_I2SI_BCK);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_I2SI_WS);
|
||||
}
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_I2SI_DATA);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_I2SI_BCK);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_I2SI_WS);
|
||||
}
|
||||
|
||||
if (!i2s_slc_begin()) {
|
||||
@ -579,15 +590,19 @@ void i2s_end() {
|
||||
|
||||
if (tx) {
|
||||
pinMode(I2SO_DATA, INPUT);
|
||||
pinMode(I2SO_BCK, INPUT);
|
||||
pinMode(I2SO_WS, INPUT);
|
||||
if (tx->driveClocks) {
|
||||
pinMode(I2SO_BCK, INPUT);
|
||||
pinMode(I2SO_WS, INPUT);
|
||||
}
|
||||
free(tx);
|
||||
tx = NULL;
|
||||
}
|
||||
if (rx) {
|
||||
pinMode(I2SI_DATA, INPUT);
|
||||
pinMode(I2SI_BCK, INPUT);
|
||||
pinMode(I2SI_WS, INPUT);
|
||||
if (rx->driveClocks) {
|
||||
pinMode(I2SI_BCK, INPUT);
|
||||
pinMode(I2SI_WS, INPUT);
|
||||
}
|
||||
free(rx);
|
||||
rx = NULL;
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ extern "C" {
|
||||
#include <core_version.h>
|
||||
#include "gdb_hooks.h"
|
||||
#include "flash_quirks.h"
|
||||
#include <umm_malloc/umm_malloc.h>
|
||||
#include <core_esp8266_non32xfer.h>
|
||||
|
||||
|
||||
#define LOOP_TASK_PRIORITY 1
|
||||
#define LOOP_QUEUE_SIZE 1
|
||||
@ -204,7 +207,9 @@ static void loop_wrapper() {
|
||||
static void loop_task(os_event_t *events) {
|
||||
(void) events;
|
||||
s_cycles_at_yield_start = ESP.getCycleCount();
|
||||
ESP.resetHeap();
|
||||
cont_run(g_pcont, &loop_wrapper);
|
||||
ESP.setDramHeap();
|
||||
if (cont_check(g_pcont) != 0) {
|
||||
panic();
|
||||
}
|
||||
@ -256,6 +261,7 @@ void init_done() {
|
||||
std::set_terminate(__unhandled_exception_cpp);
|
||||
do_global_ctors();
|
||||
esp_schedule();
|
||||
ESP.setDramHeap();
|
||||
}
|
||||
|
||||
/* This is the entry point of the application.
|
||||
@ -314,11 +320,11 @@ extern "C" void app_entry_redefinable(void)
|
||||
/* Call the entry point of the SDK code. */
|
||||
call_user_start();
|
||||
}
|
||||
|
||||
static void app_entry_custom (void) __attribute__((weakref("app_entry_redefinable")));
|
||||
|
||||
extern "C" void app_entry (void)
|
||||
{
|
||||
umm_init();
|
||||
return app_entry_custom();
|
||||
}
|
||||
|
||||
@ -342,6 +348,12 @@ extern "C" void user_init(void) {
|
||||
|
||||
cont_init(g_pcont);
|
||||
|
||||
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP)
|
||||
install_non32xfer_exception_handler();
|
||||
#endif
|
||||
#if defined(MMU_IRAM_HEAP)
|
||||
umm_init_iram();
|
||||
#endif
|
||||
preinit(); // Prior to C++ Dynamic Init (not related to above init() ). Meant to be user redefinable.
|
||||
|
||||
ets_task(loop_task,
|
||||
|
219
cores/esp8266/core_esp8266_non32xfer.cpp
Normal file
219
cores/esp8266/core_esp8266_non32xfer.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
/* 020819
|
||||
Based on PR https://github.com/esp8266/Arduino/pull/6978
|
||||
Enhanced to also handle store operations to iRAM and optional range
|
||||
validation. Also improved failed path to generate crash report.
|
||||
And, partially refactored.
|
||||
|
||||
Apologies if this is being pedantic, I was getting confused over these so
|
||||
I tried to understand what makes them different.
|
||||
|
||||
EXCCAUSE_LOAD_STORE_ERROR 3 is a non-32-bit load or store to an address that
|
||||
only supports a full 32-bit aligned transfer like IRAM or ICACHE. i.e., No
|
||||
8-bit char or 16-bit short transfers allowed.
|
||||
|
||||
EXCCAUSE_UNALIGNED 9 is an exception cause when load or store is not on an
|
||||
aligned boundary that matches the element's width.
|
||||
eg. *(short *)0x3FFF8001 = 1; or *(long *)0x3FFF8002 = 1;
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* This exception handler handles EXCCAUSE_LOAD_STORE_ERROR. It allows for a
|
||||
* byte or short access to iRAM or PROGMEM to succeed without causing a crash.
|
||||
* When reading, it is still preferred to use the xxx_P macros when possible
|
||||
* since they are probably 30x faster than this exception handler method.
|
||||
*
|
||||
* Code taken directly from @pvvx's public domain code in
|
||||
* https://github.com/pvvx/esp8266web/blob/master/app/sdklib/system/app_main.c
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#define VERIFY_C_ASM_EXCEPTION_FRAME_STRUCTURE
|
||||
#include <esp8266_undocumented.h>
|
||||
#include <core_esp8266_non32xfer.h>
|
||||
#include <mmu_iram.h>
|
||||
#include <Schedule.h>
|
||||
#include <debug.h>
|
||||
|
||||
// All of these optimization were tried and now work
|
||||
// These results were from irammem.ino using GCC 10.2
|
||||
// DRAM reference uint16 9 AVG cycles/transfer
|
||||
// #pragma GCC optimize("O0") // uint16, 289 AVG cycles/transfer, IRAM: +180
|
||||
// #pragma GCC optimize("O1") // uint16, 241 AVG cycles/transfer, IRAM: +16
|
||||
#pragma GCC optimize("O2") // uint16, 230 AVG cycles/transfer, IRAM: +4
|
||||
// #pragma GCC optimize("O3") // uint16, 230 AVG cycles/transfer, IRAM: +4
|
||||
// #pragma GCC optimize("Ofast") // uint16, 230 AVG cycles/transfer, IRAM: +4
|
||||
// #pragma GCC optimize("Os") // uint16, 233 AVG cycles/transfer, IRAM: 27556 +0
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define LOAD_MASK 0x00f00fu
|
||||
#define L8UI_MATCH 0x000002u
|
||||
#define L16UI_MATCH 0x001002u
|
||||
#define L16SI_MATCH 0x009002u
|
||||
#define S8I_MATCH 0x004002u
|
||||
#define S16I_MATCH 0x005002u
|
||||
|
||||
#define EXCCAUSE_LOAD_STORE_ERROR 3 /* Non 32-bit read/write error */
|
||||
|
||||
static fn_c_exception_handler_t old_c_handler = NULL;
|
||||
|
||||
static
|
||||
IRAM_ATTR void non32xfer_exception_handler(struct __exception_frame *ef, int cause)
|
||||
{
|
||||
do {
|
||||
/*
|
||||
In adapting the public domain version, a crash would come or go away with
|
||||
the slightest unrelated changes elsewhere in the function. Observed that
|
||||
register a15 was used for epc1, then clobbered by `rsr.` I now believe a
|
||||
"&" on the output register would have resolved the problem.
|
||||
|
||||
However, I have refactored the Extended ASM to reduce and consolidate
|
||||
register usage and corrected the issue.
|
||||
|
||||
The positioning of the Extended ASM block (as early as possible in the
|
||||
compiled function) is in part controlled by the immediate need for
|
||||
output variable `insn`. This placement aids in getting excvaddr read as
|
||||
early as possible.
|
||||
*/
|
||||
uint32_t insn, excvaddr;
|
||||
#if 1
|
||||
{
|
||||
uint32_t tmp;
|
||||
__asm__ (
|
||||
"rsr.excvaddr %[vaddr]\n\t" /* Read faulting address as early as possible */
|
||||
"movi.n %[tmp], ~3\n\t" /* prepare a mask for the EPC */
|
||||
"and %[tmp], %[tmp], %[epc]\n\t" /* apply mask for 32-bit aligned base */
|
||||
"ssa8l %[epc]\n\t" /* set up shift register for src op */
|
||||
"l32i %[insn], %[tmp], 0\n\t" /* load part 1 */
|
||||
"l32i %[tmp], %[tmp], 4\n\t" /* load part 2 */
|
||||
"src %[insn], %[tmp], %[insn]\n\t" /* right shift to get faulting instruction */
|
||||
: [vaddr]"=&r"(excvaddr), [insn]"=&r"(insn), [tmp]"=&r"(tmp)
|
||||
: [epc]"r"(ef->epc) :);
|
||||
}
|
||||
|
||||
#else
|
||||
{
|
||||
__asm__ __volatile__ ("rsr.excvaddr %0;" : "=r"(excvaddr):: "memory");
|
||||
/*
|
||||
"C" reference code for the ASM to document intent.
|
||||
May also prove useful when issolating possible issues with Extended ASM,
|
||||
optimizations, new compilers, etc.
|
||||
*/
|
||||
uint32_t epc = ef->epc;
|
||||
uint32_t *pWord = (uint32_t *)(epc & ~3);
|
||||
uint64_t big_word = ((uint64_t)pWord[1] << 32) | pWord[0];
|
||||
uint32_t pos = (epc & 3) * 8;
|
||||
insn = (uint32_t)(big_word >>= pos);
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t what = insn & LOAD_MASK;
|
||||
uint32_t valmask = 0;
|
||||
|
||||
uint32_t is_read = 1;
|
||||
if (L8UI_MATCH == what || S8I_MATCH == what) {
|
||||
valmask = 0xffu;
|
||||
if (S8I_MATCH == what) {
|
||||
is_read = 0;
|
||||
}
|
||||
} else if (L16UI_MATCH == what || L16SI_MATCH == what || S16I_MATCH == what) {
|
||||
valmask = 0xffffu;
|
||||
if (S16I_MATCH == what) {
|
||||
is_read = 0;
|
||||
}
|
||||
} else {
|
||||
continue; /* fail */
|
||||
}
|
||||
|
||||
int regno = (insn & 0x0000f0u) >> 4;
|
||||
if (regno == 1) {
|
||||
continue; /* we can't support storing into a1, just die */
|
||||
} else if (regno != 0) {
|
||||
--regno; /* account for skipped a1 in exception_frame */
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ESP_MMU
|
||||
/* debug option to validate address so we don't hide memory access bugs in APP */
|
||||
if (mmu_is_iram((void *)excvaddr) || (is_read && mmu_is_icache((void *)excvaddr))) {
|
||||
/* all is good */
|
||||
} else {
|
||||
continue; /* fail */
|
||||
}
|
||||
#endif
|
||||
{
|
||||
uint32_t *pWord = (uint32_t *)(excvaddr & ~0x3);
|
||||
uint32_t pos = (excvaddr & 0x3) * 8;
|
||||
uint32_t mem_val = *pWord;
|
||||
|
||||
if (is_read) {
|
||||
/* shift and mask down to correct size */
|
||||
mem_val >>= pos;
|
||||
mem_val &= valmask;
|
||||
|
||||
/* Sign-extend for L16SI, if applicable */
|
||||
if (what == L16SI_MATCH && (mem_val & 0x8000)) {
|
||||
mem_val |= 0xffff0000;
|
||||
}
|
||||
|
||||
ef->a_reg[regno] = mem_val; /* carry out the load */
|
||||
|
||||
} else { /* is write */
|
||||
uint32_t val = ef->a_reg[regno]; /* get value to store from register */
|
||||
val <<= pos;
|
||||
valmask <<= pos;
|
||||
val &= valmask;
|
||||
|
||||
/* mask out field, and merge */
|
||||
mem_val &= (~valmask);
|
||||
mem_val |= val;
|
||||
*pWord = mem_val; /* carry out the store */
|
||||
}
|
||||
}
|
||||
|
||||
ef->epc += 3; /* resume at following instruction */
|
||||
return;
|
||||
|
||||
} while(false);
|
||||
|
||||
/* Fail request, die */
|
||||
/*
|
||||
The old handler points to the SDK. Be alert for HWDT when Calling with
|
||||
INTLEVEL != 0. I cannot create it any more. I thought I saw this as a
|
||||
problem; however, my test case shows no problem ?? Maybe I was confused.
|
||||
*/
|
||||
if (old_c_handler) { // if (0 == (ef->ps & 0x0F)) {
|
||||
DBG_MMU_PRINTF("\ncalling previous load/store handler(%p)\n", old_c_handler);
|
||||
old_c_handler(ef, cause);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Calling _xtos_unhandled_exception(ef, cause) in the Boot ROM, gets us a
|
||||
hardware wdt.
|
||||
|
||||
Use panic instead as a fall back. It will produce a stack trace.
|
||||
*/
|
||||
panic();
|
||||
}
|
||||
|
||||
/*
|
||||
To operate reliably, this module requires the new
|
||||
`_xtos_set_exception_handler` from `exc-sethandler.cpp` and
|
||||
`_xtos_c_wrapper_handler` from `exc-c-wrapper-handler.S`. See comment block in
|
||||
`exc-sethandler.cpp` for details on issues with interrupts being enabled by
|
||||
"C" wrapper.
|
||||
*/
|
||||
void install_non32xfer_exception_handler(void) __attribute__((weak));
|
||||
void install_non32xfer_exception_handler(void) {
|
||||
if (NULL == old_c_handler) {
|
||||
// Set the "C" exception handler the wrapper will call
|
||||
old_c_handler =
|
||||
_xtos_set_exception_handler(EXCCAUSE_LOAD_STORE_ERROR,
|
||||
non32xfer_exception_handler);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
14
cores/esp8266/core_esp8266_non32xfer.h
Normal file
14
cores/esp8266/core_esp8266_non32xfer.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef __CORE_ESP8266_NON32XFER_H
|
||||
#define __CORE_ESP8266_NON32XFER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern void install_non32xfer_exception_handler();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,312 +0,0 @@
|
||||
/*
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||
set to 1-shot mode and is always loaded with the time until the next edge
|
||||
of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||
timer. This allows for removing interrupt jitter and delay as the counter
|
||||
always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
|
||||
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 <Arduino.h>
|
||||
#include "ets_sys.h"
|
||||
#include "core_esp8266_waveform.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Maximum delay between IRQs
|
||||
#define MAXIRQUS (10000)
|
||||
|
||||
// Set/clear GPIO 0-15 by bitmask
|
||||
#define SetGPIO(a) do { GPOS = a; } while (0)
|
||||
#define ClearGPIO(a) do { GPOC = a; } while (0)
|
||||
|
||||
// Waveform generator can create tones, PWM, and servos
|
||||
typedef struct {
|
||||
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
||||
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
|
||||
uint32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform
|
||||
uint32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform
|
||||
} Waveform;
|
||||
|
||||
static Waveform waveform[17]; // State of all possible pins
|
||||
static volatile uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||
static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||
|
||||
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
|
||||
static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
|
||||
static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
|
||||
|
||||
static uint32_t (*timer1CB)() = NULL;
|
||||
|
||||
|
||||
// Non-speed critical bits
|
||||
#pragma GCC optimize ("Os")
|
||||
|
||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCount() {
|
||||
uint32_t ccount;
|
||||
__asm__ __volatile__("esync; rsr %0,ccount":"=a"(ccount));
|
||||
return ccount;
|
||||
}
|
||||
|
||||
// Interrupt on/off control
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
||||
static bool timerRunning = false;
|
||||
|
||||
static void initTimer() {
|
||||
timer1_disable();
|
||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||
timerRunning = true;
|
||||
}
|
||||
|
||||
static void ICACHE_RAM_ATTR deinitTimer() {
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
timerRunning = false;
|
||||
}
|
||||
|
||||
// Set a callback. Pass in NULL to stop it
|
||||
void setTimer1Callback(uint32_t (*fn)()) {
|
||||
timer1CB = fn;
|
||||
if (!timerRunning && fn) {
|
||||
initTimer();
|
||||
timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste
|
||||
} else if (timerRunning && !fn && !waveformEnabled) {
|
||||
deinitTimer();
|
||||
}
|
||||
}
|
||||
|
||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||
// first, then it will immediately begin.
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) {
|
||||
return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS));
|
||||
}
|
||||
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) {
|
||||
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
||||
return false;
|
||||
}
|
||||
Waveform *wave = &waveform[pin];
|
||||
// Adjust to shave off some of the IRQ time, approximately
|
||||
wave->nextTimeHighCycles = timeHighCycles;
|
||||
wave->nextTimeLowCycles = timeLowCycles;
|
||||
wave->expiryCycle = runTimeCycles ? GetCycleCount() + runTimeCycles : 0;
|
||||
if (runTimeCycles && !wave->expiryCycle) {
|
||||
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
|
||||
}
|
||||
|
||||
uint32_t mask = 1<<pin;
|
||||
if (!(waveformEnabled & mask)) {
|
||||
// Actually set the pin high or low in the IRQ service to guarantee times
|
||||
wave->nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(1);
|
||||
waveformToEnable |= mask;
|
||||
if (!timerRunning) {
|
||||
initTimer();
|
||||
timer1_write(microsecondsToClockCycles(10));
|
||||
} else {
|
||||
// Ensure timely service....
|
||||
if (T1L > microsecondsToClockCycles(10)) {
|
||||
timer1_write(microsecondsToClockCycles(10));
|
||||
}
|
||||
}
|
||||
while (waveformToEnable) {
|
||||
delay(0); // Wait for waveform to update
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Speed critical bits
|
||||
#pragma GCC optimize ("O2")
|
||||
// Normally would not want two copies like this, but due to different
|
||||
// optimization levels the inline attribute gets lost if we try the
|
||||
// other version.
|
||||
|
||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
|
||||
uint32_t ccount;
|
||||
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
||||
return ccount;
|
||||
}
|
||||
|
||||
static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) {
|
||||
if (a < b) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// Stops a waveform on a pin
|
||||
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
|
||||
// Can't possibly need to stop anything if there is no timer active
|
||||
if (!timerRunning) {
|
||||
return false;
|
||||
}
|
||||
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
||||
// If they send >=32, then the shift will result in 0 and it will also return false
|
||||
if (waveformEnabled & (1UL << pin)) {
|
||||
waveformToDisable = 1UL << pin;
|
||||
// Must not interfere if Timer is due shortly
|
||||
if (T1L > microsecondsToClockCycles(10)) {
|
||||
timer1_write(microsecondsToClockCycles(10));
|
||||
}
|
||||
while (waveformToDisable) {
|
||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||
}
|
||||
}
|
||||
if (!waveformEnabled && !timer1CB) {
|
||||
deinitTimer();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// The SDK and hardware take some time to actually get to our NMI code, so
|
||||
// decrement the next IRQ's timer value by a bit so we can actually catch the
|
||||
// real CPU cycle counter we want for the waveforms.
|
||||
#if F_CPU == 80000000
|
||||
#define DELTAIRQ (microsecondsToClockCycles(3))
|
||||
#else
|
||||
#define DELTAIRQ (microsecondsToClockCycles(2))
|
||||
#endif
|
||||
|
||||
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
|
||||
// are generating. In the common case (1 PWM) these may be the same pin and
|
||||
// we can avoid looking at the other pins.
|
||||
static int startPin = 0;
|
||||
static int endPin = 0;
|
||||
|
||||
uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
||||
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
||||
|
||||
if (waveformToEnable || waveformToDisable) {
|
||||
// Handle enable/disable requests from main app.
|
||||
waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off
|
||||
waveformState &= ~waveformToEnable; // And clear the state of any just started
|
||||
waveformToEnable = 0;
|
||||
waveformToDisable = 0;
|
||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||
startPin = __builtin_ffs(waveformEnabled) - 1;
|
||||
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
|
||||
endPin = 32 - __builtin_clz(waveformEnabled);
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
if (waveformEnabled) {
|
||||
do {
|
||||
nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
||||
for (int i = startPin; i <= endPin; i++) {
|
||||
uint32_t mask = 1<<i;
|
||||
|
||||
// If it's not on, ignore!
|
||||
if (!(waveformEnabled & mask)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Waveform *wave = &waveform[i];
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
|
||||
// Disable any waveforms that are done
|
||||
if (wave->expiryCycle) {
|
||||
int32_t expiryToGo = wave->expiryCycle - now;
|
||||
if (expiryToGo < 0) {
|
||||
// Done, remove!
|
||||
waveformEnabled &= ~mask;
|
||||
if (i == 16) {
|
||||
GP16O &= ~1;
|
||||
} else {
|
||||
ClearGPIO(mask);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for toggles
|
||||
int32_t cyclesToGo = wave->nextServiceCycle - now;
|
||||
if (cyclesToGo < 0) {
|
||||
waveformState ^= mask;
|
||||
if (waveformState & mask) {
|
||||
if (i == 16) {
|
||||
GP16O |= 1; // GPIO16 write slow as it's RMW
|
||||
} else {
|
||||
SetGPIO(mask);
|
||||
}
|
||||
wave->nextServiceCycle = now + wave->nextTimeHighCycles;
|
||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles);
|
||||
} else {
|
||||
if (i == 16) {
|
||||
GP16O &= ~1; // GPIO16 write slow as it's RMW
|
||||
} else {
|
||||
ClearGPIO(mask);
|
||||
}
|
||||
wave->nextServiceCycle = now + wave->nextTimeLowCycles;
|
||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles);
|
||||
}
|
||||
} else {
|
||||
uint32_t deltaCycles = wave->nextServiceCycle - now;
|
||||
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles);
|
||||
int32_t cyclesLeftTimeout = timeoutCycle - now;
|
||||
done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0);
|
||||
} while (!done);
|
||||
} // if (waveformEnabled)
|
||||
|
||||
if (timer1CB) {
|
||||
nextEventCycles = min_u32(nextEventCycles, timer1CB());
|
||||
}
|
||||
|
||||
if (nextEventCycles < microsecondsToClockCycles(10)) {
|
||||
nextEventCycles = microsecondsToClockCycles(10);
|
||||
}
|
||||
nextEventCycles -= DELTAIRQ;
|
||||
|
||||
// Do it here instead of global function to save time and because we know it's edge-IRQ
|
||||
#if F_CPU == 160000000
|
||||
T1L = nextEventCycles >> 1; // Already know we're in range by MAXIRQUS
|
||||
#else
|
||||
T1L = nextEventCycles; // Already know we're in range by MAXIRQUS
|
||||
#endif
|
||||
TEIE |= TEIE1; // Edge int enable
|
||||
}
|
||||
|
||||
};
|
@ -2,6 +2,7 @@
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
-- Default, PWM locked version --
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
@ -22,6 +23,30 @@
|
||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
----------
|
||||
|
||||
-- Phase locked version --
|
||||
Copyright (c) 2020 Dirk O. Kaar.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||
set to 1-shot mode and is always loaded with the time until the next edge
|
||||
of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||
timer. This allows for removing interrupt jitter and delay as the counter
|
||||
always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
----------
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
@ -47,20 +72,41 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Call this function in your setup() to cause the phase locked version of the generator to
|
||||
// be linked in automatically. Otherwise, the default PWM locked version will be used.
|
||||
void enablePhaseLockedWaveform(void);
|
||||
|
||||
|
||||
// Start or change a waveform of the specified high and low times on specific pin.
|
||||
// If runtimeUS > 0 then automatically stop it after that many usecs.
|
||||
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
|
||||
// full period.
|
||||
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
||||
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
|
||||
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
||||
// under load, for applications where frequency or duty cycle must not change, leave false.
|
||||
// Returns true or false on success or failure.
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS = 0,
|
||||
// Following parameters are ignored unless in PhaseLocked mode
|
||||
int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
|
||||
|
||||
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
|
||||
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles.
|
||||
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next
|
||||
// full period.
|
||||
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
||||
// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that.
|
||||
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
||||
// under load, for applications where frequency or duty cycle must not change, leave false.
|
||||
// Returns true or false on success or failure.
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys = 0,
|
||||
// Following parameters are ignored unless in PhaseLocked mode
|
||||
int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
|
||||
|
||||
// Stop a waveform, if any, on the specified pin.
|
||||
// Returns true or false on success or failure.
|
||||
int stopWaveform(uint8_t pin);
|
||||
|
||||
// Add a callback function to be called on *EVERY* timer1 trigger. The
|
||||
// callback returns the number of microseconds until the next desired call.
|
||||
// callback must return the number of CPU clock cycles until the next desired call.
|
||||
// However, since it is called every timer1 interrupt, it may be called
|
||||
// again before this period. It should therefore use the ESP Cycle Counter
|
||||
// to determine whether or not to perform an operation.
|
||||
@ -69,6 +115,12 @@ int stopWaveform(uint8_t pin);
|
||||
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
|
||||
void setTimer1Callback(uint32_t (*fn)());
|
||||
|
||||
|
||||
// Internal-only calls, not for applications
|
||||
extern void _setPWMFreq(uint32_t freq);
|
||||
extern bool _stopPWM(uint8_t pin);
|
||||
extern bool _setPWM(int pin, uint32_t val, uint32_t range);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
444
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal file
444
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal file
@ -0,0 +1,444 @@
|
||||
/*
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
Copyright (c) 2020 Dirk O. Kaar.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||
set to 1-shot mode and is always loaded with the time until the next edge
|
||||
of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||
timer. This allows for removing interrupt jitter and delay as the counter
|
||||
always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||
clock cycle time, or an interval measured in clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
|
||||
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 "core_esp8266_waveform.h"
|
||||
#include <Arduino.h>
|
||||
#include "debug.h"
|
||||
#include "ets_sys.h"
|
||||
#include <atomic>
|
||||
|
||||
|
||||
extern "C" void enablePhaseLockedWaveform (void)
|
||||
{
|
||||
// Does nothing, added to app to enable linking these versions
|
||||
// of the waveform functions instead of the default.
|
||||
DEBUGV("Enabling phase locked waveform generator\n");
|
||||
}
|
||||
|
||||
// No-op calls to override the PWM implementation
|
||||
extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; }
|
||||
extern "C" bool _stopPWM_weak(int pin) { (void) pin; return false; }
|
||||
extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; }
|
||||
|
||||
|
||||
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
|
||||
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
|
||||
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
|
||||
// Maximum servicing time for any single IRQ
|
||||
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
|
||||
// The latency between in-ISR rearming of the timer and the earliest firing
|
||||
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
|
||||
// The SDK and hardware take some time to actually get to our NMI code
|
||||
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
|
||||
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
|
||||
|
||||
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
|
||||
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
|
||||
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
|
||||
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
|
||||
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3};
|
||||
|
||||
// Waveform generator can create tones, PWM, and servos
|
||||
typedef struct {
|
||||
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count
|
||||
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
|
||||
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
|
||||
int32_t adjDutyCcys; // Temporary correction for next period
|
||||
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
|
||||
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
|
||||
WaveformMode mode;
|
||||
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
|
||||
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
|
||||
} Waveform;
|
||||
|
||||
namespace {
|
||||
|
||||
static struct {
|
||||
Waveform pins[17]; // State of all possible pins
|
||||
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||
|
||||
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
|
||||
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
|
||||
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
|
||||
|
||||
uint32_t(*timer1CB)() = nullptr;
|
||||
|
||||
bool timer1Running = false;
|
||||
|
||||
uint32_t nextEventCcy;
|
||||
} waveform;
|
||||
|
||||
}
|
||||
|
||||
// Interrupt on/off control
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
||||
|
||||
// Non-speed critical bits
|
||||
#pragma GCC optimize ("Os")
|
||||
|
||||
static void initTimer() {
|
||||
timer1_disable();
|
||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||
waveform.timer1Running = true;
|
||||
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
|
||||
}
|
||||
|
||||
static void ICACHE_RAM_ATTR deinitTimer() {
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
waveform.timer1Running = false;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Set a callback. Pass in NULL to stop it
|
||||
void setTimer1Callback_weak(uint32_t (*fn)()) {
|
||||
waveform.timer1CB = fn;
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (!waveform.timer1Running && fn) {
|
||||
initTimer();
|
||||
} else if (waveform.timer1Running && !fn && !waveform.enabled) {
|
||||
deinitTimer();
|
||||
}
|
||||
}
|
||||
|
||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||
// first, then it will immediately begin.
|
||||
int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
|
||||
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
|
||||
uint32_t periodCcys = highCcys + lowCcys;
|
||||
if (periodCcys < MAXIRQTICKSCCYS) {
|
||||
if (!highCcys) {
|
||||
periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
||||
}
|
||||
else if (!lowCcys) {
|
||||
highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
||||
}
|
||||
}
|
||||
// sanity checks, including mixed signed/unsigned arithmetic safety
|
||||
if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
|
||||
static_cast<int32_t>(periodCcys) <= 0 ||
|
||||
static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) {
|
||||
return false;
|
||||
}
|
||||
Waveform& wave = waveform.pins[pin];
|
||||
wave.dutyCcys = highCcys;
|
||||
wave.adjDutyCcys = 0;
|
||||
wave.periodCcys = periodCcys;
|
||||
wave.autoPwm = autoPwm;
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
const uint32_t pinBit = 1UL << pin;
|
||||
if (!(waveform.enabled & pinBit)) {
|
||||
// wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
|
||||
wave.nextPeriodCcy = phaseOffsetCcys;
|
||||
wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
|
||||
wave.mode = WaveformMode::INIT;
|
||||
wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
|
||||
if (!wave.dutyCcys) {
|
||||
// If initially at zero duty cycle, force GPIO off
|
||||
if (pin == 16) {
|
||||
GP16O = 0;
|
||||
}
|
||||
else {
|
||||
GPOC = pinBit;
|
||||
}
|
||||
}
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
waveform.toSetBits = 1UL << pin;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
if (!waveform.timer1Running) {
|
||||
initTimer();
|
||||
}
|
||||
else if (T1V > IRQLATENCYCCYS) {
|
||||
// Must not interfere if Timer is due shortly
|
||||
timer1_write(IRQLATENCYCCYS);
|
||||
}
|
||||
}
|
||||
else {
|
||||
wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
|
||||
if (runTimeCcys) {
|
||||
wave.mode = WaveformMode::UPDATEEXPIRY;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
waveform.toSetBits = 1UL << pin;
|
||||
}
|
||||
}
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
while (waveform.toSetBits) {
|
||||
delay(0); // Wait for waveform to update
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stops a waveform on a pin
|
||||
ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
|
||||
// Can't possibly need to stop anything if there is no timer active
|
||||
if (!waveform.timer1Running) {
|
||||
return false;
|
||||
}
|
||||
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
||||
// If they send >=32, then the shift will result in 0 and it will also return false
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
const uint32_t pinBit = 1UL << pin;
|
||||
if (waveform.enabled & pinBit) {
|
||||
waveform.toDisableBits = 1UL << pin;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
// Must not interfere if Timer is due shortly
|
||||
if (T1V > IRQLATENCYCCYS) {
|
||||
timer1_write(IRQLATENCYCCYS);
|
||||
}
|
||||
while (waveform.toDisableBits) {
|
||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
if (!waveform.enabled && !waveform.timer1CB) {
|
||||
deinitTimer();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Speed critical bits
|
||||
#pragma GCC optimize ("O2")
|
||||
|
||||
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
|
||||
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
|
||||
static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
|
||||
if (ISCPUFREQ160MHZ) {
|
||||
return isCPU2X ? ccys : (ccys >> 1);
|
||||
}
|
||||
else {
|
||||
return isCPU2X ? (ccys << 1) : ccys;
|
||||
}
|
||||
}
|
||||
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||
const uint32_t isrStartCcy = ESP.getCycleCount();
|
||||
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
|
||||
const bool isCPU2X = CPU2X & 1;
|
||||
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
|
||||
// Handle enable/disable requests from main app.
|
||||
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
|
||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||
waveform.toDisableBits = 0;
|
||||
}
|
||||
|
||||
if (waveform.toSetBits) {
|
||||
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
|
||||
Waveform& wave = waveform.pins[toSetPin];
|
||||
switch (wave.mode) {
|
||||
case WaveformMode::INIT:
|
||||
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
|
||||
if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) {
|
||||
wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
wave.nextPeriodCcy = waveform.nextEventCcy;
|
||||
}
|
||||
if (!wave.expiryCcy) {
|
||||
wave.mode = WaveformMode::INFINITE;
|
||||
break;
|
||||
}
|
||||
// fall through
|
||||
case WaveformMode::UPDATEEXPIRY:
|
||||
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
|
||||
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
|
||||
wave.mode = WaveformMode::EXPIRES;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
waveform.toSetBits = 0;
|
||||
}
|
||||
|
||||
// Exit the loop if the next event, if any, is sufficiently distant.
|
||||
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
|
||||
uint32_t busyPins = waveform.enabled;
|
||||
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
|
||||
|
||||
uint32_t now = ESP.getCycleCount();
|
||||
uint32_t isrNextEventCcy = now;
|
||||
while (busyPins) {
|
||||
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
|
||||
waveform.nextEventCcy = isrNextEventCcy;
|
||||
break;
|
||||
}
|
||||
isrNextEventCcy = waveform.nextEventCcy;
|
||||
uint32_t loopPins = busyPins;
|
||||
while (loopPins) {
|
||||
const int pin = __builtin_ffsl(loopPins) - 1;
|
||||
const uint32_t pinBit = 1UL << pin;
|
||||
loopPins ^= pinBit;
|
||||
|
||||
Waveform& wave = waveform.pins[pin];
|
||||
|
||||
if (clockDrift) {
|
||||
wave.endDutyCcy += clockDrift;
|
||||
wave.nextPeriodCcy += clockDrift;
|
||||
wave.expiryCcy += clockDrift;
|
||||
}
|
||||
|
||||
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
|
||||
if (WaveformMode::EXPIRES == wave.mode &&
|
||||
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
|
||||
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
|
||||
// Disable any waveforms that are done
|
||||
waveform.enabled ^= pinBit;
|
||||
busyPins ^= pinBit;
|
||||
}
|
||||
else {
|
||||
const int32_t overshootCcys = now - waveNextEventCcy;
|
||||
if (overshootCcys >= 0) {
|
||||
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
|
||||
if (waveform.states & pinBit) {
|
||||
// active configuration and forward are 100% duty
|
||||
if (wave.periodCcys == wave.dutyCcys) {
|
||||
wave.nextPeriodCcy += periodCcys;
|
||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
if (wave.autoPwm) {
|
||||
wave.adjDutyCcys += overshootCcys;
|
||||
}
|
||||
waveform.states ^= pinBit;
|
||||
if (16 == pin) {
|
||||
GP16O = 0;
|
||||
}
|
||||
else {
|
||||
GPOC = pinBit;
|
||||
}
|
||||
}
|
||||
waveNextEventCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
wave.nextPeriodCcy += periodCcys;
|
||||
if (!wave.dutyCcys) {
|
||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
|
||||
if (dutyCcys <= wave.adjDutyCcys) {
|
||||
dutyCcys >>= 1;
|
||||
wave.adjDutyCcys -= dutyCcys;
|
||||
}
|
||||
else if (wave.adjDutyCcys) {
|
||||
dutyCcys -= wave.adjDutyCcys;
|
||||
wave.adjDutyCcys = 0;
|
||||
}
|
||||
wave.endDutyCcy = now + dutyCcys;
|
||||
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
|
||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
waveform.states |= pinBit;
|
||||
if (16 == pin) {
|
||||
GP16O = 1;
|
||||
}
|
||||
else {
|
||||
GPOS = pinBit;
|
||||
}
|
||||
}
|
||||
waveNextEventCcy = wave.endDutyCcy;
|
||||
}
|
||||
|
||||
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
|
||||
waveNextEventCcy = wave.expiryCcy;
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
|
||||
busyPins ^= pinBit;
|
||||
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
|
||||
waveform.nextEventCcy = waveNextEventCcy;
|
||||
}
|
||||
}
|
||||
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
|
||||
isrNextEventCcy = waveNextEventCcy;
|
||||
}
|
||||
}
|
||||
now = ESP.getCycleCount();
|
||||
}
|
||||
clockDrift = 0;
|
||||
}
|
||||
|
||||
int32_t callbackCcys = 0;
|
||||
if (waveform.timer1CB) {
|
||||
callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X);
|
||||
}
|
||||
now = ESP.getCycleCount();
|
||||
int32_t nextEventCcys = waveform.nextEventCcy - now;
|
||||
// Account for unknown duration of timer1CB().
|
||||
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
|
||||
waveform.nextEventCcy = now + callbackCcys;
|
||||
nextEventCcys = callbackCcys;
|
||||
}
|
||||
|
||||
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||
int32_t deltaIrqCcys = DELTAIRQCCYS;
|
||||
int32_t irqLatencyCcys = IRQLATENCYCCYS;
|
||||
if (isCPU2X) {
|
||||
nextEventCcys >>= 1;
|
||||
deltaIrqCcys >>= 1;
|
||||
irqLatencyCcys >>= 1;
|
||||
}
|
||||
|
||||
// Firing timer too soon, the NMI occurs before ISR has returned.
|
||||
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
|
||||
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
|
||||
nextEventCcys = irqLatencyCcys;
|
||||
}
|
||||
else {
|
||||
nextEventCcys -= deltaIrqCcys;
|
||||
}
|
||||
|
||||
// Register access is fast and edge IRQ was configured before.
|
||||
T1L = nextEventCcys;
|
||||
}
|
666
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal file
666
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal file
@ -0,0 +1,666 @@
|
||||
/*
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1
|
||||
is set to 1-shot mode and is always loaded with the time until the next
|
||||
edge of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not
|
||||
the timer. This allows for removing interrupt jitter and delay as the
|
||||
counter always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||
clock cycle count, or an interval measured in CPU clock cycles, but not
|
||||
TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
|
||||
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 <Arduino.h>
|
||||
#include "ets_sys.h"
|
||||
#include "core_esp8266_waveform.h"
|
||||
#include "user_interface.h"
|
||||
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Maximum delay between IRQs
|
||||
#define MAXIRQUS (10000)
|
||||
|
||||
// Waveform generator can create tones, PWM, and servos
|
||||
typedef struct {
|
||||
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
||||
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
|
||||
uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
|
||||
uint32_t timeLowCycles; //
|
||||
uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
|
||||
uint32_t desiredLowCycles; //
|
||||
uint32_t lastEdge; // Cycle when this generator last changed
|
||||
} Waveform;
|
||||
|
||||
class WVFState {
|
||||
public:
|
||||
Waveform waveform[17]; // State of all possible pins
|
||||
uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||
uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||
|
||||
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
|
||||
uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
|
||||
uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
|
||||
|
||||
uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI
|
||||
uint32_t waveformNewHigh = 0;
|
||||
uint32_t waveformNewLow = 0;
|
||||
|
||||
uint32_t (*timer1CB)() = NULL;
|
||||
|
||||
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
|
||||
// are generating. In the common case (1 PWM) these may be the same pin and
|
||||
// we can avoid looking at the other pins.
|
||||
uint16_t startPin = 0;
|
||||
uint16_t endPin = 0;
|
||||
};
|
||||
static WVFState wvfState;
|
||||
|
||||
|
||||
// Ensure everything is read/written to RAM
|
||||
#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
|
||||
|
||||
// Non-speed critical bits
|
||||
#pragma GCC optimize ("Os")
|
||||
|
||||
// Interrupt on/off control
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
||||
static bool timerRunning = false;
|
||||
|
||||
static __attribute__((noinline)) void initTimer() {
|
||||
if (!timerRunning) {
|
||||
timer1_disable();
|
||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||
timerRunning = true;
|
||||
timer1_write(microsecondsToClockCycles(10));
|
||||
}
|
||||
}
|
||||
|
||||
static ICACHE_RAM_ATTR void forceTimerInterrupt() {
|
||||
if (T1L > microsecondsToClockCycles(10)) {
|
||||
T1L = microsecondsToClockCycles(10);
|
||||
}
|
||||
}
|
||||
|
||||
// PWM implementation using special purpose state machine
|
||||
//
|
||||
// Keep an ordered list of pins with the delta in cycles between each
|
||||
// element, with a terminal entry making up the remainder of the PWM
|
||||
// period. With this method sum(all deltas) == PWM period clock cycles.
|
||||
//
|
||||
// At t=0 set all pins high and set the timeout for the 1st edge.
|
||||
// On interrupt, if we're at the last element reset to t=0 state
|
||||
// Otherwise, clear that pin down and set delay for next element
|
||||
// and so forth.
|
||||
|
||||
constexpr int maxPWMs = 8;
|
||||
|
||||
// PWM machine state
|
||||
typedef struct PWMState {
|
||||
uint32_t mask; // Bitmask of active pins
|
||||
uint32_t cnt; // How many entries
|
||||
uint32_t idx; // Where the state machine is along the list
|
||||
uint8_t pin[maxPWMs + 1];
|
||||
uint32_t delta[maxPWMs + 1];
|
||||
uint32_t nextServiceCycle; // Clock cycle for next step
|
||||
struct PWMState *pwmUpdate; // Set by main code, cleared by ISR
|
||||
} PWMState;
|
||||
|
||||
static PWMState pwmState;
|
||||
static uint32_t _pwmFreq = 1000;
|
||||
static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq;
|
||||
|
||||
|
||||
// If there are no more scheduled activities, shut down Timer 1.
|
||||
// Otherwise, do nothing.
|
||||
static ICACHE_RAM_ATTR void disableIdleTimer() {
|
||||
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
timerRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the NMI that a new PWM state is available through the mailbox.
|
||||
// Wait for mailbox to be emptied (either busy or delay() as needed)
|
||||
static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
|
||||
p->pwmUpdate = nullptr;
|
||||
pwmState.pwmUpdate = p;
|
||||
MEMBARRIER();
|
||||
forceTimerInterrupt();
|
||||
while (pwmState.pwmUpdate) {
|
||||
if (idle) {
|
||||
delay(0);
|
||||
}
|
||||
MEMBARRIER();
|
||||
}
|
||||
}
|
||||
|
||||
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range);
|
||||
|
||||
|
||||
// Called when analogWriteFreq() changed to update the PWM total period
|
||||
extern void _setPWMFreq_weak(uint32_t freq) __attribute__((weak));
|
||||
void _setPWMFreq_weak(uint32_t freq) {
|
||||
_pwmFreq = freq;
|
||||
|
||||
// Convert frequency into clock cycles
|
||||
uint32_t cc = microsecondsToClockCycles(1000000UL) / freq;
|
||||
|
||||
// Simple static adjustment to bring period closer to requested due to overhead
|
||||
// Empirically determined as a constant PWM delay and a function of the number of PWMs
|
||||
#if F_CPU == 80000000
|
||||
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110;
|
||||
#else
|
||||
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75;
|
||||
#endif
|
||||
|
||||
if (cc == _pwmPeriod) {
|
||||
return; // No change
|
||||
}
|
||||
|
||||
_pwmPeriod = cc;
|
||||
|
||||
if (pwmState.cnt) {
|
||||
PWMState p; // The working copy since we can't edit the one in use
|
||||
p.mask = 0;
|
||||
p.cnt = 0;
|
||||
for (uint32_t i = 0; i < pwmState.cnt; i++) {
|
||||
auto pin = pwmState.pin[i];
|
||||
_addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles);
|
||||
}
|
||||
// Update and wait for mailbox to be emptied
|
||||
initTimer();
|
||||
_notifyPWM(&p, true);
|
||||
disableIdleTimer();
|
||||
}
|
||||
}
|
||||
static void _setPWMFreq_bound(uint32_t freq) __attribute__((weakref("_setPWMFreq_weak")));
|
||||
void _setPWMFreq(uint32_t freq) {
|
||||
_setPWMFreq_bound(freq);
|
||||
}
|
||||
|
||||
|
||||
// Helper routine to remove an entry from the state machine
|
||||
// and clean up any marked-off entries
|
||||
static void _cleanAndRemovePWM(PWMState *p, int pin) {
|
||||
uint32_t leftover = 0;
|
||||
uint32_t in, out;
|
||||
for (in = 0, out = 0; in < p->cnt; in++) {
|
||||
if ((p->pin[in] != pin) && (p->mask & (1<<p->pin[in]))) {
|
||||
p->pin[out] = p->pin[in];
|
||||
p->delta[out] = p->delta[in] + leftover;
|
||||
leftover = 0;
|
||||
out++;
|
||||
} else {
|
||||
leftover += p->delta[in];
|
||||
p->mask &= ~(1<<p->pin[in]);
|
||||
}
|
||||
}
|
||||
p->cnt = out;
|
||||
// Final pin is never used: p->pin[out] = 0xff;
|
||||
p->delta[out] = p->delta[in] + leftover;
|
||||
}
|
||||
|
||||
|
||||
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
|
||||
extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak));
|
||||
ICACHE_RAM_ATTR bool _stopPWM_weak(uint8_t pin) {
|
||||
if (!((1<<pin) & pwmState.mask)) {
|
||||
return false; // Pin not actually active
|
||||
}
|
||||
|
||||
PWMState p; // The working copy since we can't edit the one in use
|
||||
p = pwmState;
|
||||
|
||||
// In _stopPWM we just clear the mask but keep everything else
|
||||
// untouched to save IRAM. The main startPWM will handle cleanup.
|
||||
p.mask &= ~(1<<pin);
|
||||
if (!p.mask) {
|
||||
// If all have been stopped, then turn PWM off completely
|
||||
p.cnt = 0;
|
||||
}
|
||||
|
||||
// Update and wait for mailbox to be emptied, no delay (could be in ISR)
|
||||
_notifyPWM(&p, false);
|
||||
// Possibly shut down the timer completely if we're done
|
||||
disableIdleTimer();
|
||||
return true;
|
||||
}
|
||||
static bool _stopPWM_bound(uint8_t pin) __attribute__((weakref("_stopPWM_weak")));
|
||||
bool _stopPWM(uint8_t pin) {
|
||||
return _stopPWM_bound(pin);
|
||||
}
|
||||
|
||||
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) {
|
||||
// Stash the val and range so we can re-evaluate the fraction
|
||||
// should the user change PWM frequency. This allows us to
|
||||
// give as great a precision as possible. We know by construction
|
||||
// that the waveform for this pin will be inactive so we can borrow
|
||||
// memory from that structure.
|
||||
wvfState.waveform[pin].desiredHighCycles = val; // Numerator == high
|
||||
wvfState.waveform[pin].desiredLowCycles = range; // Denominator == low
|
||||
|
||||
uint32_t cc = (_pwmPeriod * val) / range;
|
||||
|
||||
// Clip to sane values in the case we go from OK to not-OK when adjusting frequencies
|
||||
if (cc == 0) {
|
||||
cc = 1;
|
||||
} else if (cc >= _pwmPeriod) {
|
||||
cc = _pwmPeriod - 1;
|
||||
}
|
||||
|
||||
if (p.cnt == 0) {
|
||||
// Starting up from scratch, special case 1st element and PWM period
|
||||
p.pin[0] = pin;
|
||||
p.delta[0] = cc;
|
||||
// Final pin is never used: p.pin[1] = 0xff;
|
||||
p.delta[1] = _pwmPeriod - cc;
|
||||
} else {
|
||||
uint32_t ttl = 0;
|
||||
uint32_t i;
|
||||
// Skip along until we're at the spot to insert
|
||||
for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
|
||||
ttl += p.delta[i];
|
||||
}
|
||||
// Shift everything out by one to make space for new edge
|
||||
for (int32_t j = p.cnt; j >= (int)i; j--) {
|
||||
p.pin[j + 1] = p.pin[j];
|
||||
p.delta[j + 1] = p.delta[j];
|
||||
}
|
||||
int off = cc - ttl; // The delta from the last edge to the one we're inserting
|
||||
p.pin[i] = pin;
|
||||
p.delta[i] = off; // Add the delta to this new pin
|
||||
p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant
|
||||
}
|
||||
p.cnt++;
|
||||
p.mask |= 1<<pin;
|
||||
}
|
||||
|
||||
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
|
||||
extern bool _setPWM_weak(int pin, uint32_t val, uint32_t range) __attribute__((weak));
|
||||
bool _setPWM_weak(int pin, uint32_t val, uint32_t range) {
|
||||
stopWaveform(pin);
|
||||
PWMState p; // Working copy
|
||||
p = pwmState;
|
||||
// Get rid of any entries for this pin
|
||||
_cleanAndRemovePWM(&p, pin);
|
||||
// And add it to the list, in order
|
||||
if (p.cnt >= maxPWMs) {
|
||||
return false; // No space left
|
||||
}
|
||||
|
||||
// Sanity check for all-on/off
|
||||
uint32_t cc = (_pwmPeriod * val) / range;
|
||||
if ((cc == 0) || (cc >= _pwmPeriod)) {
|
||||
digitalWrite(pin, cc ? HIGH : LOW);
|
||||
return true;
|
||||
}
|
||||
|
||||
_addPWMtoList(p, pin, val, range);
|
||||
|
||||
// Set mailbox and wait for ISR to copy it over
|
||||
initTimer();
|
||||
_notifyPWM(&p, true);
|
||||
disableIdleTimer();
|
||||
|
||||
// Potentially recalculate the PWM period if we've added another pin
|
||||
_setPWMFreq(_pwmFreq);
|
||||
|
||||
return true;
|
||||
}
|
||||
static bool _setPWM_bound(int pin, uint32_t val, uint32_t range) __attribute__((weakref("_setPWM_weak")));
|
||||
bool _setPWM(int pin, uint32_t val, uint32_t range) {
|
||||
return _setPWM_bound(pin, val, range);
|
||||
}
|
||||
|
||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||
// first, then it will immediately begin.
|
||||
extern int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weak));
|
||||
int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles,
|
||||
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||
(void) alignPhase;
|
||||
(void) phaseOffsetUS;
|
||||
(void) autoPwm;
|
||||
|
||||
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
||||
return false;
|
||||
}
|
||||
Waveform *wave = &wvfState.waveform[pin];
|
||||
wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0;
|
||||
if (runTimeCycles && !wave->expiryCycle) {
|
||||
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
|
||||
}
|
||||
|
||||
_stopPWM(pin); // Make sure there's no PWM live here
|
||||
|
||||
uint32_t mask = 1<<pin;
|
||||
MEMBARRIER();
|
||||
if (wvfState.waveformEnabled & mask) {
|
||||
// Make sure no waveform changes are waiting to be applied
|
||||
while (wvfState.waveformToChange) {
|
||||
delay(0); // Wait for waveform to update
|
||||
// No mem barrier here, the call to a global function implies global state updated
|
||||
}
|
||||
wvfState.waveformNewHigh = timeHighCycles;
|
||||
wvfState.waveformNewLow = timeLowCycles;
|
||||
MEMBARRIER();
|
||||
wvfState.waveformToChange = mask;
|
||||
// The waveform will be updated some time in the future on the next period for the signal
|
||||
} else { // if (!(wvfState.waveformEnabled & mask)) {
|
||||
wave->timeHighCycles = timeHighCycles;
|
||||
wave->desiredHighCycles = timeHighCycles;
|
||||
wave->timeLowCycles = timeLowCycles;
|
||||
wave->desiredLowCycles = timeLowCycles;
|
||||
wave->lastEdge = 0;
|
||||
wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
|
||||
wvfState.waveformToEnable |= mask;
|
||||
MEMBARRIER();
|
||||
initTimer();
|
||||
forceTimerInterrupt();
|
||||
while (wvfState.waveformToEnable) {
|
||||
delay(0); // Wait for waveform to update
|
||||
// No mem barrier here, the call to a global function implies global state updated
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
static int startWaveformClockCycles_bound(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weakref("startWaveformClockCycles_weak")));
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||
return startWaveformClockCycles_bound(pin, timeHighCycles, timeLowCycles, runTimeCycles, alignPhase, phaseOffsetUS, autoPwm);
|
||||
}
|
||||
|
||||
|
||||
// This version falls-thru to the proper startWaveformClockCycles call and is invariant across waveform generators
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS,
|
||||
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||
return startWaveformClockCycles_bound(pin,
|
||||
microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS),
|
||||
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
|
||||
}
|
||||
|
||||
// Set a callback. Pass in NULL to stop it
|
||||
extern void setTimer1Callback_weak(uint32_t (*fn)()) __attribute__((weak));
|
||||
void setTimer1Callback_weak(uint32_t (*fn)()) {
|
||||
wvfState.timer1CB = fn;
|
||||
if (fn) {
|
||||
initTimer();
|
||||
forceTimerInterrupt();
|
||||
}
|
||||
disableIdleTimer();
|
||||
}
|
||||
static void setTimer1Callback_bound(uint32_t (*fn)()) __attribute__((weakref("setTimer1Callback_weak")));
|
||||
void setTimer1Callback(uint32_t (*fn)()) {
|
||||
setTimer1Callback_bound(fn);
|
||||
}
|
||||
|
||||
// Stops a waveform on a pin
|
||||
extern int stopWaveform_weak(uint8_t pin) __attribute__((weak));
|
||||
ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
|
||||
// Can't possibly need to stop anything if there is no timer active
|
||||
if (!timerRunning) {
|
||||
return false;
|
||||
}
|
||||
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
||||
// If they send >=32, then the shift will result in 0 and it will also return false
|
||||
uint32_t mask = 1<<pin;
|
||||
if (wvfState.waveformEnabled & mask) {
|
||||
wvfState.waveformToDisable = mask;
|
||||
// Cancel any pending updates for this waveform, too.
|
||||
if (wvfState.waveformToChange & mask) {
|
||||
wvfState.waveformToChange = 0;
|
||||
}
|
||||
forceTimerInterrupt();
|
||||
while (wvfState.waveformToDisable) {
|
||||
MEMBARRIER(); // If it wasn't written yet, it has to be by now
|
||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||
}
|
||||
}
|
||||
disableIdleTimer();
|
||||
return true;
|
||||
}
|
||||
static int stopWaveform_bound(uint8_t pin) __attribute__((weakref("stopWaveform_weak")));
|
||||
ICACHE_RAM_ATTR int stopWaveform(uint8_t pin) {
|
||||
return stopWaveform_bound(pin);
|
||||
}
|
||||
|
||||
// Speed critical bits
|
||||
#pragma GCC optimize ("O2")
|
||||
|
||||
// Normally would not want two copies like this, but due to different
|
||||
// optimization levels the inline attribute gets lost if we try the
|
||||
// other version.
|
||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
|
||||
uint32_t ccount;
|
||||
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
||||
return ccount;
|
||||
}
|
||||
|
||||
// Find the earliest cycle as compared to right now
|
||||
static inline ICACHE_RAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
int32_t da = a - now;
|
||||
int32_t db = b - now;
|
||||
return (da < db) ? a : b;
|
||||
}
|
||||
|
||||
// The SDK and hardware take some time to actually get to our NMI code, so
|
||||
// decrement the next IRQ's timer value by a bit so we can actually catch the
|
||||
// real CPU cycle counter we want for the waveforms.
|
||||
|
||||
// The SDK also sometimes is running at a different speed the the Arduino core
|
||||
// so the ESP cycle counter is actually running at a variable speed.
|
||||
// adjust(x) takes care of adjusting a delta clock cycle amount accordingly.
|
||||
#if F_CPU == 80000000
|
||||
#define DELTAIRQ (microsecondsToClockCycles(9)/4)
|
||||
#define adjust(x) ((x) << (turbo ? 1 : 0))
|
||||
#else
|
||||
#define DELTAIRQ (microsecondsToClockCycles(9)/8)
|
||||
#define adjust(x) ((x) >> 0)
|
||||
#endif
|
||||
|
||||
// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
|
||||
#define MINIRQTIME microsecondsToClockCycles(4)
|
||||
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||
// Flag if the core is at 160 MHz, for use by adjust()
|
||||
bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
|
||||
|
||||
uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
|
||||
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
||||
|
||||
if (wvfState.waveformToEnable || wvfState.waveformToDisable) {
|
||||
// Handle enable/disable requests from main app
|
||||
wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off
|
||||
wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started
|
||||
wvfState.waveformToEnable = 0;
|
||||
wvfState.waveformToDisable = 0;
|
||||
// No mem barrier. Globals must be written to RAM on ISR exit.
|
||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||
wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
|
||||
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
|
||||
wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
|
||||
} else if (!pwmState.cnt && pwmState.pwmUpdate) {
|
||||
// Start up the PWM generator by copying from the mailbox
|
||||
pwmState.cnt = 1;
|
||||
pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0
|
||||
pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
|
||||
// No need for mem barrier here. Global must be written by IRQ exit
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
if (wvfState.waveformEnabled || pwmState.cnt) {
|
||||
do {
|
||||
nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
|
||||
|
||||
// PWM state machine implementation
|
||||
if (pwmState.cnt) {
|
||||
int32_t cyclesToGo;
|
||||
do {
|
||||
cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ();
|
||||
if (cyclesToGo < 0) {
|
||||
if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
|
||||
if (pwmState.pwmUpdate) {
|
||||
// Do the memory copy from temp to global and clear mailbox
|
||||
pwmState = *(PWMState*)pwmState.pwmUpdate;
|
||||
}
|
||||
GPOS = pwmState.mask; // Set all active pins high
|
||||
if (pwmState.mask & (1<<16)) {
|
||||
GP16O = 1;
|
||||
}
|
||||
pwmState.idx = 0;
|
||||
} else {
|
||||
do {
|
||||
// Drop the pin at this edge
|
||||
if (pwmState.mask & (1<<pwmState.pin[pwmState.idx])) {
|
||||
GPOC = 1<<pwmState.pin[pwmState.idx];
|
||||
if (pwmState.pin[pwmState.idx] == 16) {
|
||||
GP16O = 0;
|
||||
}
|
||||
}
|
||||
pwmState.idx++;
|
||||
// Any other pins at this same PWM value will have delta==0, drop them too.
|
||||
} while (pwmState.delta[pwmState.idx] == 0);
|
||||
}
|
||||
// Preserve duty cycle over PWM period by using now+xxx instead of += delta
|
||||
cyclesToGo = adjust(pwmState.delta[pwmState.idx]);
|
||||
pwmState.nextServiceCycle = GetCycleCountIRQ() + cyclesToGo;
|
||||
}
|
||||
nextEventCycle = earliest(nextEventCycle, pwmState.nextServiceCycle);
|
||||
} while (pwmState.cnt && (cyclesToGo < 100));
|
||||
}
|
||||
|
||||
for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) {
|
||||
uint32_t mask = 1<<i;
|
||||
|
||||
// If it's not on, ignore!
|
||||
if (!(wvfState.waveformEnabled & mask)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Waveform *wave = &wvfState.waveform[i];
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
|
||||
// Disable any waveforms that are done
|
||||
if (wave->expiryCycle) {
|
||||
int32_t expiryToGo = wave->expiryCycle - now;
|
||||
if (expiryToGo < 0) {
|
||||
// Done, remove!
|
||||
if (i == 16) {
|
||||
GP16O = 0;
|
||||
}
|
||||
GPOC = mask;
|
||||
wvfState.waveformEnabled &= ~mask;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for toggles
|
||||
int32_t cyclesToGo = wave->nextServiceCycle - now;
|
||||
if (cyclesToGo < 0) {
|
||||
uint32_t nextEdgeCycles;
|
||||
uint32_t desired = 0;
|
||||
uint32_t *timeToUpdate;
|
||||
wvfState.waveformState ^= mask;
|
||||
if (wvfState.waveformState & mask) {
|
||||
if (i == 16) {
|
||||
GP16O = 1;
|
||||
}
|
||||
GPOS = mask;
|
||||
|
||||
if (wvfState.waveformToChange & mask) {
|
||||
// Copy over next full-cycle timings
|
||||
wave->timeHighCycles = wvfState.waveformNewHigh;
|
||||
wave->desiredHighCycles = wvfState.waveformNewHigh;
|
||||
wave->timeLowCycles = wvfState.waveformNewLow;
|
||||
wave->desiredLowCycles = wvfState.waveformNewLow;
|
||||
wave->lastEdge = 0;
|
||||
wvfState.waveformToChange = 0;
|
||||
}
|
||||
if (wave->lastEdge) {
|
||||
desired = wave->desiredLowCycles;
|
||||
timeToUpdate = &wave->timeLowCycles;
|
||||
}
|
||||
nextEdgeCycles = wave->timeHighCycles;
|
||||
} else {
|
||||
if (i == 16) {
|
||||
GP16O = 0;
|
||||
}
|
||||
GPOC = mask;
|
||||
desired = wave->desiredHighCycles;
|
||||
timeToUpdate = &wave->timeHighCycles;
|
||||
nextEdgeCycles = wave->timeLowCycles;
|
||||
}
|
||||
if (desired) {
|
||||
desired = adjust(desired);
|
||||
int32_t err = desired - (now - wave->lastEdge);
|
||||
if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal
|
||||
err /= 2;
|
||||
*timeToUpdate += err;
|
||||
}
|
||||
}
|
||||
nextEdgeCycles = adjust(nextEdgeCycles);
|
||||
wave->nextServiceCycle = now + nextEdgeCycles;
|
||||
wave->lastEdge = now;
|
||||
}
|
||||
nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle);
|
||||
}
|
||||
|
||||
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
int32_t cycleDeltaNextEvent = nextEventCycle - now;
|
||||
int32_t cyclesLeftTimeout = timeoutCycle - now;
|
||||
done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0);
|
||||
} while (!done);
|
||||
} // if (wvfState.waveformEnabled)
|
||||
|
||||
if (wvfState.timer1CB) {
|
||||
nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB());
|
||||
}
|
||||
|
||||
int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ();
|
||||
|
||||
if (nextEventCycles < MINIRQTIME) {
|
||||
nextEventCycles = MINIRQTIME;
|
||||
}
|
||||
nextEventCycles -= DELTAIRQ;
|
||||
|
||||
// Do it here instead of global function to save time and because we know it's edge-IRQ
|
||||
T1L = nextEventCycles >> (turbo ? 1 : 0);
|
||||
}
|
||||
|
||||
};
|
@ -39,4 +39,16 @@ extern int __analogRead(uint8_t pin)
|
||||
|
||||
extern int analogRead(uint8_t pin) __attribute__ ((weak, alias("__analogRead")));
|
||||
|
||||
|
||||
void __analogReference(uint8_t mode)
|
||||
{
|
||||
// Only DEFAULT is supported on the ESP8266
|
||||
if (mode != DEFAULT) {
|
||||
DEBUGV("analogReference called with illegal mode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern void analogReference(uint8_t mode) __attribute__ ((weak, alias("__analogReference")));
|
||||
|
||||
};
|
||||
|
@ -82,7 +82,8 @@ extern void __pinMode(uint8_t pin, uint8_t mode) {
|
||||
}
|
||||
|
||||
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
||||
stopWaveform(pin);
|
||||
stopWaveform(pin); // Disable any Tone or startWaveform on this pin
|
||||
_stopPWM(pin); // and any analogWrites (PWM)
|
||||
if(pin < 16){
|
||||
if(val) GPOS = (1 << pin);
|
||||
else GPOC = (1 << pin);
|
||||
|
@ -26,30 +26,21 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
static uint32_t analogMap = 0;
|
||||
static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x
|
||||
|
||||
|
||||
static uint32_t analogMap = 0;
|
||||
static uint16_t analogFreq = 1000;
|
||||
|
||||
extern void __analogWriteRange(uint32_t range) {
|
||||
if ((range >= 15) && (range <= 65535)) {
|
||||
analogScale = range;
|
||||
}
|
||||
}
|
||||
|
||||
extern void __analogWriteResolution(int res) {
|
||||
if ((res >= 4) && (res <= 16)) {
|
||||
analogScale = (1 << res) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
extern void __analogWriteFreq(uint32_t freq) {
|
||||
if (freq < 100) {
|
||||
analogFreq = 100;
|
||||
} else if (freq > 40000) {
|
||||
analogFreq = 40000;
|
||||
} else if (freq > 60000) {
|
||||
analogFreq = 60000;
|
||||
} else {
|
||||
analogFreq = freq;
|
||||
}
|
||||
_setPWMFreq(freq);
|
||||
}
|
||||
|
||||
extern void __analogWrite(uint8_t pin, int val) {
|
||||
@ -63,22 +54,36 @@ extern void __analogWrite(uint8_t pin, int val) {
|
||||
val = analogScale;
|
||||
}
|
||||
|
||||
if (analogMap & 1UL << pin) {
|
||||
// Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
|
||||
// val: the duty cycle: between 0 (always off) and 255 (always on).
|
||||
// So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH)
|
||||
|
||||
analogMap &= ~(1 << pin);
|
||||
analogMap &= ~(1 << pin);
|
||||
}
|
||||
else {
|
||||
pinMode(pin, OUTPUT);
|
||||
}
|
||||
uint32_t high = (analogPeriod * val) / analogScale;
|
||||
uint32_t low = analogPeriod - high;
|
||||
pinMode(pin, OUTPUT);
|
||||
if (low == 0) {
|
||||
digitalWrite(pin, HIGH);
|
||||
} else if (high == 0) {
|
||||
digitalWrite(pin, LOW);
|
||||
} else {
|
||||
if (startWaveformClockCycles(pin, high, low, 0)) {
|
||||
analogMap |= (1 << pin);
|
||||
}
|
||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||
int phaseReference = __builtin_ffs(analogMap) - 1;
|
||||
if (_setPWM(pin, val, analogScale)) {
|
||||
analogMap |= (1 << pin);
|
||||
} else if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
|
||||
analogMap |= (1 << pin);
|
||||
}
|
||||
}
|
||||
|
||||
extern void __analogWriteRange(uint32_t range) {
|
||||
if ((range >= 15) && (range <= 65535)) {
|
||||
analogScale = range;
|
||||
}
|
||||
}
|
||||
|
||||
extern void __analogWriteResolution(int res) {
|
||||
if ((res >= 4) && (res <= 16)) {
|
||||
analogScale = (1 << res) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,10 @@ uint32_t crc32 (const void* data, size_t length, uint32_t crc = 0xffffffff);
|
||||
|
||||
#include <functional>
|
||||
|
||||
using BoolCB = std::function<void(bool)>;
|
||||
using TrivialCB = std::function<void()>;
|
||||
|
||||
void settimeofday_cb (TrivialCB&& cb);
|
||||
void settimeofday_cb (const BoolCB& cb);
|
||||
void settimeofday_cb (const TrivialCB& cb);
|
||||
|
||||
#endif
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "debug.h"
|
||||
#include "osapi.h"
|
||||
|
||||
void ICACHE_RAM_ATTR hexdump(const void *mem, uint32_t len, uint8_t cols) {
|
||||
const uint8_t* src = (const uint8_t*) mem;
|
||||
|
@ -1,9 +1,17 @@
|
||||
// ROM and blob calls without official headers available
|
||||
|
||||
#if !defined(__ESP8266_UNDOCUMENTED_H) && !(defined(_ASMLANGUAGE) || defined(__ASSEMBLER__))
|
||||
#define __ESP8266_UNDOCUMENTED_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef XCHAL_EXCCAUSE_NUM
|
||||
// from tools/xtensa-lx106-elf/include/xtensa/config/core.h:629:#define XCHAL_EXCCAUSE_NUM 64
|
||||
#define XCHAL_EXCCAUSE_NUM 64
|
||||
#endif
|
||||
|
||||
// ROM
|
||||
|
||||
extern void rom_i2c_writeReg_Mask(int, int, int, int, int, int);
|
||||
@ -34,6 +42,238 @@ extern int ets_uart_printf(const char *format, ...) __attribute__ ((format (prin
|
||||
|
||||
extern void ets_delay_us(uint32_t us);
|
||||
|
||||
#ifndef GDBSTUB_H
|
||||
/*
|
||||
GDBSTUB duplicates these with some variances that are not compatible with our
|
||||
references (offsets), which are synced with those used by the BootROM.
|
||||
Specifically, the BootROM does not have register "a1" in the structure where
|
||||
GDBSTUB does.
|
||||
*/
|
||||
|
||||
/*
|
||||
This structure is used in the argument list of "C" callable exception handlers.
|
||||
See `_xtos_set_exception_handler` details below.
|
||||
*/
|
||||
struct __exception_frame
|
||||
{
|
||||
uint32_t epc;
|
||||
uint32_t ps;
|
||||
uint32_t sar;
|
||||
uint32_t unused;
|
||||
union {
|
||||
struct {
|
||||
uint32_t a0;
|
||||
// note: no a1 here!
|
||||
uint32_t a2;
|
||||
uint32_t a3;
|
||||
uint32_t a4;
|
||||
uint32_t a5;
|
||||
uint32_t a6;
|
||||
uint32_t a7;
|
||||
uint32_t a8;
|
||||
uint32_t a9;
|
||||
uint32_t a10;
|
||||
uint32_t a11;
|
||||
uint32_t a12;
|
||||
uint32_t a13;
|
||||
uint32_t a14;
|
||||
uint32_t a15;
|
||||
};
|
||||
uint32_t a_reg[15];
|
||||
};
|
||||
uint32_t cause;
|
||||
};
|
||||
#endif
|
||||
|
||||
/*
|
||||
Most of the comments here are gleamed from the xtensa files found at the site
|
||||
listed below and are mostly unverified:
|
||||
https://github.com/qca/open-ath9k-htc-firmware/tree/master/sboot/magpie_1_1/sboot/athos/src/xtos
|
||||
* exc-c-wrapper-handler.S
|
||||
* exc-sethandler.c
|
||||
*/
|
||||
|
||||
/*
|
||||
The Boot ROM sets up a table of dispatch handlers at 0x3FFFC000. This table
|
||||
has an entry for each of the EXCCAUSE values, 0 through 63. The exception
|
||||
handler at the `User Exception Vector` uses EXCCAUSE with the base address
|
||||
0x3FFFC000 to build a jump address to the respective cause handler. Of the
|
||||
cause handle functions, `_xtos_c_wrapper_handler` and `_xtos_unhandled_exception`
|
||||
are of interest.
|
||||
|
||||
Exception handler entries that do not have a specific handler are set to
|
||||
`_xtos_unhandled_exception`. This handler will execute a `break 1, 1`
|
||||
(0x4000DC4Bu) before doing a `rfe` (return from exception). Since the PC has
|
||||
not changed, the event that caused the 1st exception will likely keep
|
||||
repeating until the HWDT kicks in.
|
||||
|
||||
These exception handling functions are in assembly, and do not conform to the
|
||||
typical "C" function conventions. However, some form of prototype/typedef is
|
||||
needed to reference these function addresses in "C" code. In
|
||||
`RTOS_SDK/components/esp8266/include/xtensa/xtruntime.h`, it uses a compounded
|
||||
definition that equates to `void (*)(...)` for .cpp modules to use. I have
|
||||
noticed this creates sufficient confusion at compilation to get your attention
|
||||
when used in the wrong place. I have copied that definition here.
|
||||
|
||||
Added to eagle.rom.addr.v6.ld:
|
||||
PROVIDE ( _xtos_exc_handler_table = 0x3fffc000 );
|
||||
PROVIDE ( _xtos_c_handler_table = 0x3fffc100 );
|
||||
*/
|
||||
#ifndef XTRUNTIME_H
|
||||
// This is copy/paste from RTOS_SDK/components/esp8266/include/xtensa/xtruntime.h
|
||||
#ifdef __cplusplus
|
||||
typedef void (_xtos_handler_func)(...);
|
||||
#else
|
||||
typedef void (_xtos_handler_func)();
|
||||
#endif
|
||||
typedef _xtos_handler_func *_xtos_handler;
|
||||
|
||||
extern _xtos_handler _xtos_exc_handler_table[XCHAL_EXCCAUSE_NUM];
|
||||
|
||||
/*
|
||||
Assembly-level handler, used in the _xtos_exc_handler_table[]. It is a wrapper
|
||||
for calling registered "C" exception handlers.
|
||||
*/
|
||||
_xtos_handler_func _xtos_c_wrapper_handler;
|
||||
|
||||
/*
|
||||
Assembly-level handler, used in the _xtos_exc_handler_table[]. It is the
|
||||
default handler, for exceptions without a registered handler.
|
||||
*/
|
||||
_xtos_handler_func _xtos_unhandled_exception;
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
// For these definitions, try to be more precise for .cpp module usage.
|
||||
|
||||
/*
|
||||
A detailed typdef for the "C" callable functions found in
|
||||
`_xtos_c_handler_table[]` More details in `_xtos_set_exception_handler`
|
||||
comments below.
|
||||
*/
|
||||
typedef void (*fn_c_exception_handler_t)(struct __exception_frame *ef, int cause);
|
||||
|
||||
/*
|
||||
TMI maybe? However, it may be useful for a deep debugging session.
|
||||
`_xtos_p_none` is the default "C" exception handler that fills the
|
||||
_xtos_c_handler_table[]. It is present when an exception handler has not been
|
||||
registered. It simply consist of a single instruction, `ret`.
|
||||
It is also internally used by `_xtos_set_exception_handler(cause, NULL)` to
|
||||
reset a "C" exception handler back to the unhandled state. The coresponding
|
||||
`_xtos_exc_handler_table` entry will be set to `_xtos_unhandled_exception`.
|
||||
Note, if nesting handlers is desired this must be implemented in the new "C"
|
||||
exception handler(s) being registered.
|
||||
*/
|
||||
extern void _xtos_p_none(struct __exception_frame *ef, int cause);
|
||||
|
||||
/*
|
||||
TMI maybe?
|
||||
For `extern _xtos_handler _xtos_c_handler_table[XCHAL_EXCCAUSE_NUM];`, defined
|
||||
in in `xtensa/xtos/exc-sethandler.c`. _xtos_handler is a generalized
|
||||
definition that doesn't match the actual function definition of those
|
||||
assigned to `_xtos_c_handler_table` entries.
|
||||
|
||||
At this time we do not require direct access to this table. We perform updates
|
||||
by calling the ROM function `_xtos_set_exception_handler`.
|
||||
|
||||
A corrected version for .cpp would look like this:
|
||||
*/
|
||||
extern fn_c_exception_handler_t _xtos_c_handler_table[XCHAL_EXCCAUSE_NUM];
|
||||
|
||||
/*
|
||||
ROM API function `_xtos_set_exception_handler` registers a "C" callable
|
||||
exception handler for a specified general exception, (EXCCAUSE value). (source
|
||||
in xtensa/xtos/exc-sethandler.c)
|
||||
* If `cause`/reason (EXCCAUSE) is out of range, >=64, it returns NULL.
|
||||
* If the new exception handler is installed, it returns the previous handler.
|
||||
* If the previous handler was `_xtos_unhandled_exception`/`_xtos_p_none`, it
|
||||
returns NULL.
|
||||
|
||||
Note, the installed "C" exception handler is noramlly called from the ROM
|
||||
function _xtos_c_wrapper_handler with IRQs enabled. This build now includes a
|
||||
replacement wrapper that is used with the "C" exception handler for
|
||||
EXCCAUSE_LOAD_STORE_ERROR (3), Non 32-bit read/write error.
|
||||
|
||||
This prototype has been corrected (changed from a generalized to specific
|
||||
argument list) for the .cpp files in this projects; however, it does not match
|
||||
the over generalized version in some Xtensa .h files (not currently part of
|
||||
this project)
|
||||
|
||||
To aid against future conflicts, keep these new defines limited to .cpp with
|
||||
`#ifdef __cplusplus`.
|
||||
*/
|
||||
extern fn_c_exception_handler_t _xtos_set_exception_handler(int cause, fn_c_exception_handler_t fn);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(VERIFY_C_ASM_EXCEPTION_FRAME_STRUCTURE) || defined(_ASMLANGUAGE) || defined(__ASSEMBLER__)
|
||||
/*
|
||||
Extracted from information at
|
||||
From https://github.com/fdivitto/ESPWebFramework/blob/master/SDK/xtensa-lx106-elf/xtensa-lx106-elf/lib/libhandlers-null.txt
|
||||
|
||||
The UEXC_... values are create by the macro STRUCT_FIELD in `xtruntime-frames.h`
|
||||
|
||||
These VERIFY_... values are used to confirm that the "C" structure offsets
|
||||
match those generated in exc-c-wrapper-handler.S.
|
||||
*/
|
||||
#define VERIFY_UEXC_pc 0x0000
|
||||
#define VERIFY_UEXC_ps 0x0004
|
||||
#define VERIFY_UEXC_sar 0x0008
|
||||
#define VERIFY_UEXC_vpri 0x000c
|
||||
#define VERIFY_UEXC_a0 0x0010
|
||||
#define VERIFY_UEXC_a2 0x0014
|
||||
#define VERIFY_UEXC_a3 0x0018
|
||||
#define VERIFY_UEXC_a4 0x001c
|
||||
#define VERIFY_UEXC_a5 0x0020
|
||||
#define VERIFY_UEXC_a6 0x0024
|
||||
#define VERIFY_UEXC_a7 0x0028
|
||||
#define VERIFY_UEXC_a8 0x002c
|
||||
#define VERIFY_UEXC_a9 0x0030
|
||||
#define VERIFY_UEXC_a10 0x0034
|
||||
#define VERIFY_UEXC_a11 0x0038
|
||||
#define VERIFY_UEXC_a12 0x003c
|
||||
#define VERIFY_UEXC_a13 0x0040
|
||||
#define VERIFY_UEXC_a14 0x0044
|
||||
#define VERIFY_UEXC_a15 0x0048
|
||||
#define VERIFY_UEXC_exccause 0x004c
|
||||
#define VERIFY_UserFrameSize 0x0050
|
||||
#define VERIFY_UserFrameTotalSize 0x0100
|
||||
#endif
|
||||
|
||||
#if defined(VERIFY_C_ASM_EXCEPTION_FRAME_STRUCTURE) && !(defined(_ASMLANGUAGE) || defined(__ASSEMBLER__))
|
||||
/*
|
||||
A set of static_asserts test to confirm both "C" and ASM structures match.
|
||||
|
||||
This only needs to be verified once.
|
||||
We use `#define VERIFY_C_ASM_EXCEPTION_FRAME_STRUCTURE` to limit number of
|
||||
times tested in a build. Testing is done from core_esp8266_non32xfer.cpp.
|
||||
|
||||
ASM structure defines are verified in exc-c-wrapper-handler.S
|
||||
*/
|
||||
static_assert(offsetof(struct __exception_frame, epc) == VERIFY_UEXC_pc, "offsetof(struct __exception_frame, epc) != VERIFY_UEXC_pc, expected 0x0000");
|
||||
static_assert(offsetof(struct __exception_frame, ps) == VERIFY_UEXC_ps, "offsetof(struct __exception_frame, ps) != VERIFY_UEXC_ps, expected 0x0004");
|
||||
static_assert(offsetof(struct __exception_frame, sar) == VERIFY_UEXC_sar, "offsetof(struct __exception_frame, sar) != VERIFY_UEXC_sar, expected 0x0008");
|
||||
static_assert(offsetof(struct __exception_frame, unused) == VERIFY_UEXC_vpri, "offsetof(struct __exception_frame, unused) != VERIFY_UEXC_vpri, expected 0x000c");
|
||||
static_assert(offsetof(struct __exception_frame, a0) == VERIFY_UEXC_a0, "offsetof(struct __exception_frame, a0) != VERIFY_UEXC_a0, expected 0x0010");
|
||||
static_assert(offsetof(struct __exception_frame, a2) == VERIFY_UEXC_a2, "offsetof(struct __exception_frame, a2) != VERIFY_UEXC_a2, expected 0x0014");
|
||||
static_assert(offsetof(struct __exception_frame, a3) == VERIFY_UEXC_a3, "offsetof(struct __exception_frame, a3) != VERIFY_UEXC_a3, expected 0x0018");
|
||||
static_assert(offsetof(struct __exception_frame, a4) == VERIFY_UEXC_a4, "offsetof(struct __exception_frame, a4) != VERIFY_UEXC_a4, expected 0x001c");
|
||||
static_assert(offsetof(struct __exception_frame, a5) == VERIFY_UEXC_a5, "offsetof(struct __exception_frame, a5) != VERIFY_UEXC_a5, expected 0x0020");
|
||||
static_assert(offsetof(struct __exception_frame, a6) == VERIFY_UEXC_a6, "offsetof(struct __exception_frame, a6) != VERIFY_UEXC_a6, expected 0x0024");
|
||||
static_assert(offsetof(struct __exception_frame, a7) == VERIFY_UEXC_a7, "offsetof(struct __exception_frame, a7) != VERIFY_UEXC_a7, expected 0x0028");
|
||||
static_assert(offsetof(struct __exception_frame, a8) == VERIFY_UEXC_a8, "offsetof(struct __exception_frame, a8) != VERIFY_UEXC_a8, expected 0x002c");
|
||||
static_assert(offsetof(struct __exception_frame, a9) == VERIFY_UEXC_a9, "offsetof(struct __exception_frame, a9) != VERIFY_UEXC_a9, expected 0x0030");
|
||||
static_assert(offsetof(struct __exception_frame, a10) == VERIFY_UEXC_a10, "offsetof(struct __exception_frame, a10) != VERIFY_UEXC_a10, expected 0x0034");
|
||||
static_assert(offsetof(struct __exception_frame, a11) == VERIFY_UEXC_a11, "offsetof(struct __exception_frame, a11) != VERIFY_UEXC_a11, expected 0x0038");
|
||||
static_assert(offsetof(struct __exception_frame, a12) == VERIFY_UEXC_a12, "offsetof(struct __exception_frame, a12) != VERIFY_UEXC_a12, expected 0x003c");
|
||||
static_assert(offsetof(struct __exception_frame, a13) == VERIFY_UEXC_a13, "offsetof(struct __exception_frame, a13) != VERIFY_UEXC_a13, expected 0x0040");
|
||||
static_assert(offsetof(struct __exception_frame, a14) == VERIFY_UEXC_a14, "offsetof(struct __exception_frame, a14) != VERIFY_UEXC_a14, expected 0x0044");
|
||||
static_assert(offsetof(struct __exception_frame, a15) == VERIFY_UEXC_a15, "offsetof(struct __exception_frame, a15) != VERIFY_UEXC_a15, expected 0x0048");
|
||||
static_assert(offsetof(struct __exception_frame, cause) == VERIFY_UEXC_exccause, "offsetof(struct __exception_frame, cause) != VERIFY_UEXC_exccause, expected 0x004c");
|
||||
#endif
|
||||
|
213
cores/esp8266/exc-c-wrapper-handler.S
Normal file
213
cores/esp8266/exc-c-wrapper-handler.S
Normal file
@ -0,0 +1,213 @@
|
||||
// exc-c-wrapper-handler.S, this is a reduced version of the original file at
|
||||
// https://github.com/qca/open-ath9k-htc-firmware/blob/master/sboot/magpie_1_1/sboot/athos/src/xtos/exc-c-wrapper-handler.S#L62-L67
|
||||
//
|
||||
|
||||
// exc-c-wrapper-handler.S - General Exception Handler that Dispatches C Handlers
|
||||
|
||||
// Copyright (c) 2002-2004, 2006-2007, 2010 Tensilica Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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 AUTHORS 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 IN THE SOFTWARE.
|
||||
|
||||
#include <xtensa/coreasm.h>
|
||||
#include <xtensa/corebits.h>
|
||||
#include <xtensa/config/specreg.h>
|
||||
// #include "xtos-internal.h"
|
||||
// #ifdef SIMULATOR
|
||||
// #include <xtensa/simcall.h>
|
||||
// #endif
|
||||
|
||||
#include "xtruntime-frames.h"
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Verified that the ASM generated UEXC_xxx values match, the corresponding
|
||||
// values in `struct __exception_frame` used in the "C" code.
|
||||
//
|
||||
#include "esp8266_undocumented.h"
|
||||
.if (UEXC_pc != VERIFY_UEXC_pc)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_ps != VERIFY_UEXC_ps)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_sar != VERIFY_UEXC_sar)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_vpri != VERIFY_UEXC_vpri)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a0 != VERIFY_UEXC_a0)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a2 != VERIFY_UEXC_a2)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a3 != VERIFY_UEXC_a3)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a4 != VERIFY_UEXC_a4)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a5 != VERIFY_UEXC_a5)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a6 != VERIFY_UEXC_a6)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a7 != VERIFY_UEXC_a7)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a8 != VERIFY_UEXC_a8)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a9 != VERIFY_UEXC_a9)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a10 != VERIFY_UEXC_a10)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a11 != VERIFY_UEXC_a11)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a12 != VERIFY_UEXC_a12)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a13 != VERIFY_UEXC_a13)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a14 != VERIFY_UEXC_a14)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a15 != VERIFY_UEXC_a15)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_exccause != VERIFY_UEXC_exccause)
|
||||
.err
|
||||
.endif
|
||||
.if (UserFrameSize != VERIFY_UserFrameSize)
|
||||
.err
|
||||
.endif
|
||||
.if (UserFrameTotalSize != VERIFY_UserFrameTotalSize)
|
||||
.err
|
||||
.endif
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* This is the general exception assembly-level handler that dispatches C handlers.
|
||||
*/
|
||||
.section .iram.text
|
||||
.align 4
|
||||
.literal_position
|
||||
.global _xtos_c_wrapper_handler
|
||||
_xtos_c_wrapper_handler:
|
||||
|
||||
// HERE: a2, a3, a4 have been saved to exception stack frame allocated with a1 (sp).
|
||||
// a2 contains EXCCAUSE.
|
||||
s32i a5, a1, UEXC_a5 // a5 will get clobbered by ENTRY after the pseudo-CALL4
|
||||
// (a4..a15 spilled as needed; save if modified)
|
||||
|
||||
//NOTA: Possible future improvement:
|
||||
// keep interrupts disabled until we get into the handler, such that
|
||||
// we don't have to save other critical state such as EXCVADDR here.
|
||||
// @mhightower83 - This promise was broken by an "rsil a13, 0" below.
|
||||
//rsr a3, EXCVADDR
|
||||
s32i a2, a1, UEXC_exccause
|
||||
//s32i a3, a1, UEXC_excvaddr
|
||||
|
||||
// Set PS fields:
|
||||
// EXCM = 0
|
||||
// WOE = __XTENSA_CALL0_ABI__ ? 0 : 1
|
||||
// UM = 1
|
||||
// INTLEVEL = EXCM_LEVEL = 1
|
||||
// CALLINC = __XTENSA_CALL0_ABI__ ? 0 : 1
|
||||
// OWB = 0 (really, a dont care if !__XTENSA_CALL0_ABI__)
|
||||
|
||||
// movi a2, 0x23 // 0x21, PS_UM|PS_INTLEVEL(XCHAL_EXCM_LEVEL)
|
||||
// @mhightower83 - use INTLEVEL 15 instead of 3 for Arduino like interrupt support??
|
||||
movi a2, 0x2F // 0x21, PS_UM|PS_INTLEVEL(15)
|
||||
rsr a3, EPC_1
|
||||
// @mhightower83 - I assume PS.EXCM was set and now is being cleared, thus
|
||||
// allowing new exceptions and interrupts within PS_INTLEVEL to be possible.
|
||||
// We have set INTLEVEL to 15 to block any possible interrupts.
|
||||
xsr a2, PS
|
||||
|
||||
// HERE: window overflows enabled, but NOT SAFE because we're not quite
|
||||
// in a valid windowed context (haven't restored a1 yet...);
|
||||
// so don't cause any (keep to a0..a3) until we've saved critical state and restored a1:
|
||||
|
||||
// NOTE: MUST SAVE EPC1 before causing any overflows, because overflows corrupt EPC1.
|
||||
s32i a3, a1, UEXC_pc
|
||||
s32i a2, a1, UEXC_ps
|
||||
s32i a0, a1, UEXC_a0 // save the rest of the registers
|
||||
s32i a6, a1, UEXC_a6
|
||||
s32i a7, a1, UEXC_a7
|
||||
s32i a8, a1, UEXC_a8
|
||||
s32i a9, a1, UEXC_a9
|
||||
s32i a10, a1, UEXC_a10
|
||||
s32i a11, a1, UEXC_a11
|
||||
s32i a12, a1, UEXC_a12
|
||||
s32i a13, a1, UEXC_a13
|
||||
s32i a14, a1, UEXC_a14
|
||||
s32i a15, a1, UEXC_a15
|
||||
rsync // wait for WSR to PS to complete
|
||||
rsr a12, SAR
|
||||
|
||||
// @mhightower83 - I think, after the next instruction, we have the potential of
|
||||
// losing UEXC_excvaddr. Which the earlier comment said we need to preserve for
|
||||
// the exception handler. We keep interrupts off when calling the "C" exception
|
||||
// handler. For the use cases that I am looking at, this is a must. If there are
|
||||
// future use cases that need interrupts enabled, those "C" exception handlers
|
||||
// can turn them on.
|
||||
//
|
||||
// rsil a13, 0
|
||||
|
||||
movi a13, _xtos_c_handler_table // &table
|
||||
l32i a15, a1, UEXC_exccause // arg2: exccause
|
||||
s32i a12, a1, UEXC_sar
|
||||
addx4 a12, a15, a13 // a12 = table[exccause]
|
||||
l32i a12, a12, 0 // ...
|
||||
mov a2, a1 // arg1: exception parameters
|
||||
mov a3, a15 // arg2: exccause
|
||||
beqz a12, 1f // null handler => skip call
|
||||
callx0 a12 // call C exception handler for this exception
|
||||
1:
|
||||
// Now exit the handler.
|
||||
|
||||
// Restore special registers
|
||||
l32i a14, a1, UEXC_sar
|
||||
|
||||
// load early - saves two cycles - @mhightower83
|
||||
movi a0, _xtos_return_from_exc
|
||||
|
||||
// @mhightower83 - For compatibility with Arduino interrupt architecture, we
|
||||
// keep interrupts 100% disabled.
|
||||
// /*
|
||||
// * Disable interrupts while returning from the pseudo-CALL setup above,
|
||||
// * for the same reason they were disabled while doing the pseudo-CALL:
|
||||
// * this sequence restores SP such that it doesn't reflect the allocation
|
||||
// * of the exception stack frame, which we still need to return from
|
||||
// * the exception.
|
||||
// */
|
||||
// rsil a12, 1 // XCHAL_EXCM_LEVEL
|
||||
rsil a12, 15 // All levels blocked.
|
||||
wsr a14, SAR
|
||||
jx a0
|
||||
|
||||
/* FIXME: what about _GeneralException ? */
|
||||
.size _xtos_c_wrapper_handler, . - _xtos_c_wrapper_handler
|
113
cores/esp8266/exc-sethandler.cpp
Normal file
113
cores/esp8266/exc-sethandler.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Adaptation of _xtos_set_exception_handler for Arduino ESP8266 core
|
||||
*
|
||||
* This replacement for the Boot ROM `_xtos_set_exception_handler` is used to
|
||||
* install our replacement `_xtos_c_wrapper_handler`. This change protects the
|
||||
* value of `excvaddr` from corruption.
|
||||
*
|
||||
*
|
||||
* Details
|
||||
*
|
||||
* The issue, the Boot ROM "C" wrapper for exception handlers,
|
||||
* `_xtos_c_wrapper_handler`, turns interrupts back on. This leaves `excvaddr`
|
||||
* exposed to possible overwrite before it is read. For example, if an interrupt
|
||||
* is taken during the exception handler processing and the ISR handler
|
||||
* generates a new exception, the original value of `excvaddr` is lost. To
|
||||
* address this issue we have a replacement `_xtos_c_wrapper_handler` in file
|
||||
* `exc-c-wrapper-handler.S`.
|
||||
*
|
||||
* An overview, of an exception at entry: New interrupts are blocked by EXCM
|
||||
* being set. Once cleared, interrupts above the current INTLEVEL and exceptions
|
||||
* (w/o creating a DoubleException) can occur.
|
||||
*
|
||||
* Using our replacement for `_xtos_c_wrapper_handler`, INTLEVEL is raised to 15
|
||||
* with EXCM cleared.
|
||||
*
|
||||
* The original Boot ROM `_xtos_c_wrapper_handler` at entry would set INTLEVEL
|
||||
* to 3 with EXCM cleared, save registers, then do a `rsil 0` (interrupts fully
|
||||
* enabled!) just before calling the registered "C" Exception handler. Our
|
||||
* replacement keeps INTLEVEL at 15. This is needed to support the Arduino model
|
||||
* of interrupts disabled while an ISR runs.
|
||||
*
|
||||
* And we also need it for umm_malloc to work safely with an IRAM heap from an
|
||||
* ISR call. While malloc() will supply DRAM for all allocation from an ISR, we
|
||||
* want free() to safely operate from an ISR to avoid a leak potential.
|
||||
*
|
||||
* If an exception handler needs interrupts enabled, it would be done after it
|
||||
* has consumed the value of `excvaddr`. Whether such action is safe is left to
|
||||
* the exception handler writer to determine. However, with our current
|
||||
* architecture, I am not convinced it can be done safely.
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP) || defined(NEW_EXC_C_WRAPPER)
|
||||
|
||||
/*
|
||||
* The original module source code came from:
|
||||
* https://github.com/qca/open-ath9k-htc-firmware/blob/master/sboot/magpie_1_1/sboot/athos/src/xtos/exc-sethandler.c
|
||||
*
|
||||
* It has been revised to use Arduino ESP8266 core includes, types, and
|
||||
* formating.
|
||||
*/
|
||||
|
||||
/* exc-sethandler.c - register an exception handler in XTOS */
|
||||
|
||||
/*
|
||||
* Copyright (c) 1999-2006 Tensilica Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* 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 AUTHORS 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 IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "esp8266_undocumented.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Register a C handler for the specified general exception
|
||||
* (specified EXCCAUSE value).
|
||||
*/
|
||||
fn_c_exception_handler_t _xtos_set_exception_handler(int cause, fn_c_exception_handler_t fn)
|
||||
{
|
||||
fn_c_exception_handler_t ret;
|
||||
|
||||
if( (unsigned) cause >= XCHAL_EXCCAUSE_NUM )
|
||||
return 0;
|
||||
|
||||
if( fn == 0 )
|
||||
fn = &_xtos_p_none;
|
||||
|
||||
ret = _xtos_c_handler_table[cause];
|
||||
|
||||
_xtos_exc_handler_table[cause] = ( (fn == &_xtos_p_none)
|
||||
? &_xtos_unhandled_exception
|
||||
: &_xtos_c_wrapper_handler );
|
||||
|
||||
_xtos_c_handler_table[cause] = fn;
|
||||
|
||||
if( ret == &_xtos_p_none )
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@ -5,6 +5,11 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "umm_malloc/umm_malloc.h"
|
||||
|
||||
// Need FORCE_ALWAYS_INLINE to put HeapSelect class constructor/deconstructor in IRAM
|
||||
#define FORCE_ALWAYS_INLINE_HEAP_SELECT
|
||||
#include "umm_malloc/umm_heap_select.h"
|
||||
|
||||
#include <c_types.h>
|
||||
#include <sys/reent.h>
|
||||
#include <user_interface.h>
|
||||
@ -16,6 +21,7 @@ extern "C" {
|
||||
#define UMM_CALLOC(n,s) umm_poison_calloc(n,s)
|
||||
#define UMM_REALLOC_FL(p,s,f,l) umm_poison_realloc_fl(p,s,f,l)
|
||||
#define UMM_FREE_FL(p,f,l) umm_poison_free_fl(p,f,l)
|
||||
#define STATIC_ALWAYS_INLINE
|
||||
|
||||
#undef realloc
|
||||
#undef free
|
||||
@ -25,6 +31,7 @@ extern "C" {
|
||||
#define UMM_CALLOC(n,s) umm_calloc(n,s)
|
||||
#define UMM_REALLOC_FL(p,s,f,l) umm_realloc(p,s)
|
||||
#define UMM_FREE_FL(p,f,l) umm_free(p)
|
||||
#define STATIC_ALWAYS_INLINE
|
||||
|
||||
#undef realloc
|
||||
#undef free
|
||||
@ -34,6 +41,10 @@ extern "C" {
|
||||
#define UMM_CALLOC(n,s) calloc(n,s)
|
||||
#define UMM_REALLOC_FL(p,s,f,l) realloc(p,s)
|
||||
#define UMM_FREE_FL(p,f,l) free(p)
|
||||
|
||||
// STATIC_ALWAYS_INLINE only applys to the non-debug build path,
|
||||
// it must not be enabled on the debug build path.
|
||||
#define STATIC_ALWAYS_INLINE static ALWAYS_INLINE
|
||||
#endif
|
||||
|
||||
|
||||
@ -259,8 +270,8 @@ void ICACHE_RAM_ATTR free(void* p)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void* ICACHE_RAM_ATTR pvPortMalloc(size_t size, const char* file, int line)
|
||||
STATIC_ALWAYS_INLINE
|
||||
void* ICACHE_RAM_ATTR heap_pvPortMalloc(size_t size, const char* file, int line)
|
||||
{
|
||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||
POISON_CHECK__PANIC_FL(file, line);
|
||||
@ -270,7 +281,8 @@ void* ICACHE_RAM_ATTR pvPortMalloc(size_t size, const char* file, int line)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* ICACHE_RAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file, int line)
|
||||
STATIC_ALWAYS_INLINE
|
||||
void* ICACHE_RAM_ATTR heap_pvPortCalloc(size_t count, size_t size, const char* file, int line)
|
||||
{
|
||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||
POISON_CHECK__PANIC_FL(file, line);
|
||||
@ -280,7 +292,8 @@ void* ICACHE_RAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file,
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, int line)
|
||||
STATIC_ALWAYS_INLINE
|
||||
void* ICACHE_RAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line)
|
||||
{
|
||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||
void* ret = UMM_REALLOC_FL(ptr, size, file, line);
|
||||
@ -290,7 +303,8 @@ void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, in
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* ICACHE_RAM_ATTR pvPortZalloc(size_t size, const char* file, int line)
|
||||
STATIC_ALWAYS_INLINE
|
||||
void* ICACHE_RAM_ATTR heap_pvPortZalloc(size_t size, const char* file, int line)
|
||||
{
|
||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||
POISON_CHECK__PANIC_FL(file, line);
|
||||
@ -300,7 +314,8 @@ void* ICACHE_RAM_ATTR pvPortZalloc(size_t size, const char* file, int line)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR vPortFree(void *ptr, const char* file, int line)
|
||||
STATIC_ALWAYS_INLINE
|
||||
void ICACHE_RAM_ATTR heap_vPortFree(void *ptr, const char* file, int line)
|
||||
{
|
||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||
UMM_FREE_FL(ptr, file, line);
|
||||
@ -314,7 +329,47 @@ size_t ICACHE_RAM_ATTR xPortWantedSizeAlign(size_t size)
|
||||
|
||||
void system_show_malloc(void)
|
||||
{
|
||||
HeapSelectDram ephemeral;
|
||||
umm_info(NULL, true);
|
||||
}
|
||||
|
||||
/*
|
||||
NONOS SDK and lwIP do not handle IRAM heap well. Since they also use portable
|
||||
malloc calls pvPortMalloc, ... we can leverage that for this solution.
|
||||
Force pvPortMalloc, ... APIs to serve DRAM only.
|
||||
*/
|
||||
void* ICACHE_RAM_ATTR pvPortMalloc(size_t size, const char* file, int line)
|
||||
{
|
||||
HeapSelectDram ephemeral;
|
||||
return heap_pvPortMalloc(size, file, line);;
|
||||
}
|
||||
|
||||
void* ICACHE_RAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file, int line)
|
||||
{
|
||||
HeapSelectDram ephemeral;
|
||||
return heap_pvPortCalloc(count, size, file, line);
|
||||
}
|
||||
|
||||
void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, int line)
|
||||
{
|
||||
HeapSelectDram ephemeral;
|
||||
return heap_pvPortRealloc(ptr, size, file, line);
|
||||
}
|
||||
|
||||
void* ICACHE_RAM_ATTR pvPortZalloc(size_t size, const char* file, int line)
|
||||
{
|
||||
HeapSelectDram ephemeral;
|
||||
return heap_pvPortZalloc(size, file, line);
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR vPortFree(void *ptr, const char* file, int line)
|
||||
{
|
||||
#if defined(DEBUG_ESP_OOM) || defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE) || defined(UMM_INTEGRITY_CHECK)
|
||||
// This is only needed for debug checks to ensure they are performed in
|
||||
// correct context. umm_malloc free internally determines the correct heap.
|
||||
HeapSelectDram ephemeral;
|
||||
#endif
|
||||
return heap_vPortFree(ptr, file, line);
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -21,6 +21,8 @@
|
||||
#ifndef I2S_h
|
||||
#define I2S_h
|
||||
|
||||
#define I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS 1
|
||||
|
||||
/*
|
||||
How does this work? Basically, to get sound, you need to:
|
||||
- Connect an I2S codec to the I2S pins on the ESP.
|
||||
@ -42,6 +44,7 @@ extern "C" {
|
||||
|
||||
void i2s_begin(); // Enable TX only, for compatibility
|
||||
bool i2s_rxtx_begin(bool enableRx, bool enableTx); // Allow TX and/or RX, returns false on OOM error
|
||||
bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks);
|
||||
void i2s_end();
|
||||
void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000)
|
||||
void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate
|
||||
|
192
cores/esp8266/mmu_iram.cpp
Normal file
192
cores/esp8266/mmu_iram.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2020 M Hightower
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (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/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "mmu_iram.h"
|
||||
#include <user_interface.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
#if (MMU_ICACHE_SIZE == 0x4000)
|
||||
#define SOC_CACHE_SIZE 0 // 16KB
|
||||
#pragma message("ICACHE size 16K")
|
||||
#else
|
||||
#define SOC_CACHE_SIZE 1 // 32KB
|
||||
#endif
|
||||
|
||||
#if (MMU_ICACHE_SIZE == 0x4000)
|
||||
/*
|
||||
* "Cache_Read_Enable" as in Instruction Read Cache enable, ICACHE.
|
||||
*
|
||||
* The Boot ROM "Cache_Read_Enable" API enables virtual execution of code in
|
||||
* flash memory via an instruction cache, ICACHE. The cache size can be set to
|
||||
* 16K or 32K, and the NONOS SDK 2.x will always set ICACHE to 32K during
|
||||
* initialization.
|
||||
*
|
||||
* When you select a 16K vs. a 32K ICACHE size, you get 48K contiguous IRAM to
|
||||
* work with. The NONOS SDK 2.x does not have an option to select 16K/32K. This
|
||||
* is where this Boot ROM wrapper for Cache_Read_Enable comes in.
|
||||
* Note, there is support for 16K/32K cache size in NONOS SDK 3.0; however, I
|
||||
* do not see an option to have it has part of your general IRAM. That SDK adds
|
||||
* it to the heap.
|
||||
*
|
||||
* With this wrapper function, we override the SDK's ICACHE size.
|
||||
* A build-time define MMU_ICACHE_SIZE selects 16K or 32K ICACHE size.
|
||||
*
|
||||
* mmu_status is used to help understand calling behavior. At some point, it
|
||||
* should be trimmed down to the essentials.
|
||||
*
|
||||
* During NONOS SDK init, it will call to enable. Then call later, to process a
|
||||
* spi_flash_get_id request, it will disable/enable around the Boot ROM SPI calls.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Arguments for Cache_Read_Enable
|
||||
*
|
||||
* The first two arguments appear to specify which 1MB block of the flash to
|
||||
* access with the ICACHE.
|
||||
*
|
||||
* The first argument, map, is partly understood. It has three values 0, 1,
|
||||
* and 2+. The value 0 selects the even 1MB block, and 1 selects the odd 1MB
|
||||
* block, in other words, bit20 of the flash address. No guesses for a value
|
||||
* of 2 or greater.
|
||||
*
|
||||
* The second argument, p, bit 21 of the flash address. Or, it may be bits 23,
|
||||
* 22, 21 of the flash address. A three-bit field is cleared in the register
|
||||
* for this argument; however, I have not seen any examples of it being used
|
||||
* that way.
|
||||
*
|
||||
* The third argument, v, holds our center of attention. A value of 0 selects
|
||||
* 16K, and a value of 1 selects a 32K ICACHE. This is the only parameter we
|
||||
* need to modify on Cache_Read_Enable calls.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Clues and Information sources
|
||||
*
|
||||
* "Cache_Read_Enable" is underdocumented. Main sources of information were from
|
||||
* rboot, zboot, https://richard.burtons.org/2015/06/12/esp8266-cache_read_enable/,
|
||||
* and other places. And some additional expermentation.
|
||||
*
|
||||
* Searching through the NONOS SDK shows nothing on this API; however, some
|
||||
* clues on what the NONOS SDK might be doing with ICACHE related calls can be
|
||||
* found in the RTOS SDK.
|
||||
* eg. ESP8266_RTOS_SDK/blob/master/components/spi_flash/src/spi_flash_raw.c
|
||||
* also calls to it in the bootloader.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ROM_Cache_Read_Enable
|
||||
#define ROM_Cache_Read_Enable 0x40004678U
|
||||
#endif
|
||||
|
||||
typedef void (*fp_Cache_Read_Enable_t)(uint8_t map, uint8_t p, uint8_t v);
|
||||
#define real_Cache_Read_Enable (reinterpret_cast<fp_Cache_Read_Enable_t>(ROM_Cache_Read_Enable))
|
||||
|
||||
void IRAM_ATTR Cache_Read_Enable(uint8_t map, uint8_t p, uint8_t v) {
|
||||
(void)v;
|
||||
real_Cache_Read_Enable(map, p, SOC_CACHE_SIZE);
|
||||
}
|
||||
|
||||
#ifdef DEV_DEBUG_PRINT
|
||||
|
||||
#if 0
|
||||
#ifndef ROM_Cache_Read_Disable
|
||||
#define ROM_Cache_Read_Disable 0x400047f0
|
||||
#endif
|
||||
|
||||
typedef void (*fp_Cache_Read_Disable_t)(void);
|
||||
#define real_Cache_Read_Disable (reinterpret_cast<fp_Cache_Read_Disable_t>(ROM_Cache_Read_Disable))
|
||||
/*
|
||||
*
|
||||
*/
|
||||
void IRAM_ATTR Cache_Read_Disable(void) {
|
||||
real_Cache_Read_Disable();
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Early adjustment for CPU crystal frequency, so debug printing will work.
|
||||
* This should not be left enabled all the time in Cashe_Read..., I am concerned
|
||||
* that there may be unknown interference with the NONOS SDK startup.
|
||||
*
|
||||
* Inspired by:
|
||||
* https://github.com/pvvx/esp8266web/blob/2e25559bc489487747205db2ef171d48326b32d4/app/sdklib/system/app_main.c#L581-L591
|
||||
*/
|
||||
extern "C" uint8_t rom_i2c_readReg(uint8_t block, uint8_t host_id, uint8_t reg_add);
|
||||
extern "C" void rom_i2c_writeReg(uint8_t block, uint8_t host_id, uint8_t reg_add, uint8_t data);
|
||||
|
||||
extern "C" void IRAM_ATTR set_pll(void)
|
||||
{
|
||||
#if !defined(F_CRYSTAL)
|
||||
#define F_CRYSTAL 26000000
|
||||
#endif
|
||||
if (F_CRYSTAL != 40000000) {
|
||||
// At Boot ROM(-BIOS) start, it assumes a 40MHz crystal.
|
||||
// If it is not, we assume a 26MHz crystal.
|
||||
// There is no support for 24MHz crustal at this time.
|
||||
if(rom_i2c_readReg(103,4,1) != 136) { // 8: 40MHz, 136: 26MHz
|
||||
// Assume 26MHz crystal
|
||||
// soc_param0: 0: 40MHz, 1: 26MHz, 2: 24MHz
|
||||
// set 80MHz PLL CPU
|
||||
rom_i2c_writeReg(103,4,1,136);
|
||||
rom_i2c_writeReg(103,4,2,145);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//C This was used to probe at different stages of boot the state of the PLL
|
||||
//C register. I think we can get rid of this one.
|
||||
extern "C" void IRAM_ATTR dbg_set_pll(void)
|
||||
{
|
||||
char r103_4_1 = rom_i2c_readReg(103,4,1);
|
||||
char r103_4_2 = rom_i2c_readReg(103,4,2);
|
||||
set_pll();
|
||||
ets_uart_printf("\nrom_i2c_readReg(103,4,1) == %u\n", r103_4_1);
|
||||
ets_uart_printf( "rom_i2c_readReg(103,4,2) == %u\n", r103_4_2);
|
||||
}
|
||||
|
||||
/*
|
||||
This helps keep the UART enabled at user_init() so we can get a few more
|
||||
messages printed.
|
||||
*/
|
||||
extern struct rst_info resetInfo;
|
||||
extern "C" void __pinMode( uint8_t pin, uint8_t mode );
|
||||
|
||||
inline bool is_gpio_persistent(void) {
|
||||
return REASON_EXCEPTION_RST <= resetInfo.reason &&
|
||||
REASON_SOFT_RESTART >= resetInfo.reason;
|
||||
}
|
||||
|
||||
extern "C" void pinMode( uint8_t pin, uint8_t mode ) {
|
||||
static bool in_initPins = true;
|
||||
if (in_initPins && (1 == pin)) {
|
||||
if (!is_gpio_persistent()) {
|
||||
/* Restore pin to TX after Power-on and EXT_RST */
|
||||
__pinMode(pin, FUNCTION_0);
|
||||
}
|
||||
in_initPins = false;
|
||||
return;
|
||||
}
|
||||
|
||||
__pinMode( pin, mode );
|
||||
}
|
||||
#endif // #ifdef DEV_DEBUG_PRINT
|
||||
|
||||
#endif // #if (MMU_ICACHE_SIZE == 0x4000)
|
||||
|
||||
};
|
221
cores/esp8266/mmu_iram.h
Normal file
221
cores/esp8266/mmu_iram.h
Normal file
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2020 M Hightower
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (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/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __MMU_IRAM_H
|
||||
#define __MMU_IRAM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <c_types.h>
|
||||
#include <assert.h>
|
||||
#include <esp8266_undocumented.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//C This turns on range checking. Is this the value you want to trigger it?
|
||||
#ifdef DEBUG_ESP_CORE
|
||||
#define DEBUG_ESP_MMU
|
||||
#endif
|
||||
|
||||
#if defined(CORE_MOCK)
|
||||
#define ets_uart_printf(...) do {} while(false)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* DEV_DEBUG_PRINT:
|
||||
* Debug printing macros for printing before before, during, and after
|
||||
* NONOS SDK initializes. May or maynot be safe during NONOS SDK
|
||||
* initialization. As in printing from functions called on by the SDK
|
||||
* during the SDK initialization.
|
||||
*
|
||||
#define DEV_DEBUG_PRINT
|
||||
*/
|
||||
|
||||
#if defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
|
||||
#include <esp8266_peri.h>
|
||||
|
||||
#define DBG_MMU_FLUSH(a) while((USS(a) >> USTXC) & 0xff) {}
|
||||
|
||||
#if defined(DEV_DEBUG_PRINT)
|
||||
extern void set_pll(void);
|
||||
extern void dbg_set_pll(void);
|
||||
|
||||
#define DBG_MMU_PRINTF(fmt, ...) \
|
||||
set_pll(); \
|
||||
uart_buff_switch(0); \
|
||||
ets_uart_printf(fmt, ##__VA_ARGS__); \
|
||||
DBG_MMU_FLUSH(0)
|
||||
|
||||
#else // ! defined(DEV_DEBUG_PRINT)
|
||||
#define DBG_MMU_PRINTF(fmt, ...) ets_uart_printf(fmt, ##__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#else // ! defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
|
||||
#define DBG_MMU_FLUSH(...) do {} while(false)
|
||||
#define DBG_MMU_PRINTF(...) do {} while(false)
|
||||
#endif // defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
bool mmu_is_iram(const void *addr) {
|
||||
#define IRAM_START 0x40100000UL
|
||||
#ifndef MMU_IRAM_SIZE
|
||||
#if defined(__GNUC__) && !defined(CORE_MOCK)
|
||||
#warning "MMU_IRAM_SIZE was undefined, setting to 0x8000UL!"
|
||||
#endif
|
||||
#define MMU_IRAM_SIZE 0x8000UL
|
||||
#endif
|
||||
#define IRAM_END (IRAM_START + MMU_IRAM_SIZE)
|
||||
|
||||
return (IRAM_START <= (uintptr_t)addr && IRAM_END > (uintptr_t)addr);
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
bool mmu_is_dram(const void *addr) {
|
||||
#define DRAM_START 0x3FF80000UL
|
||||
#define DRAM_END 0x40000000UL
|
||||
|
||||
return (DRAM_START <= (uintptr_t)addr && DRAM_END > (uintptr_t)addr);
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
bool mmu_is_icache(const void *addr) {
|
||||
#define ICACHE_START 0x40200000UL
|
||||
#define ICACHE_END (ICACHE_START + 0x100000UL)
|
||||
|
||||
return (ICACHE_START <= (uintptr_t)addr && ICACHE_END > (uintptr_t)addr);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ESP_MMU
|
||||
#define ASSERT_RANGE_TEST_WRITE(a) \
|
||||
if (mmu_is_iram(a) || mmu_is_dram(a)) { \
|
||||
} else { \
|
||||
DBG_MMU_PRINTF("\nexcvaddr: %p\n", a); \
|
||||
assert(("Outside of Range - Write" && false)); \
|
||||
}
|
||||
|
||||
#define ASSERT_RANGE_TEST_READ(a) \
|
||||
if (mmu_is_iram(a) || mmu_is_dram(a) || mmu_is_icache(a)) { \
|
||||
} else { \
|
||||
DBG_MMU_PRINTF("\nexcvaddr: %p\n", a); \
|
||||
assert(("Outside of Range - Read" && false)); \
|
||||
}
|
||||
|
||||
#else
|
||||
#define ASSERT_RANGE_TEST_WRITE(a) do {} while(false)
|
||||
#define ASSERT_RANGE_TEST_READ(a) do {} while(false)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Some inlines to allow faster random access to non32bit access of iRAM or
|
||||
* iCACHE data elements. These remove the extra time and stack space that would
|
||||
* have occured by relying on exception processing.
|
||||
*/
|
||||
static inline __attribute__((always_inline))
|
||||
uint8_t mmu_get_uint8(const void *p8) {
|
||||
ASSERT_RANGE_TEST_READ(p8);
|
||||
uint32_t val = (*(uint32_t *)((uintptr_t)p8 & ~0x3));
|
||||
uint32_t pos = ((uintptr_t)p8 & 0x3) * 8;
|
||||
val >>= pos;
|
||||
return (uint8_t)val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
uint16_t mmu_get_uint16(const uint16_t *p16) {
|
||||
ASSERT_RANGE_TEST_READ(p16);
|
||||
uint32_t val = (*(uint32_t *)((uintptr_t)p16 & ~0x3));
|
||||
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||
val >>= pos;
|
||||
return (uint16_t)val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
int16_t mmu_get_int16(const int16_t *p16) {
|
||||
ASSERT_RANGE_TEST_READ(p16);
|
||||
uint32_t val = (*(uint32_t *)((uintptr_t)p16 & ~0x3));
|
||||
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||
val >>= pos;
|
||||
return (int16_t)val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
uint8_t mmu_set_uint8(void *p8, const uint8_t val) {
|
||||
ASSERT_RANGE_TEST_WRITE(p8);
|
||||
uint32_t pos = ((uintptr_t)p8 & 0x3) * 8;
|
||||
uint32_t sval = val << pos;
|
||||
uint32_t valmask = 0x0FF << pos;
|
||||
|
||||
uint32_t *p32 = (uint32_t *)((uintptr_t)p8 & ~0x3);
|
||||
uint32_t ival = *p32;
|
||||
ival &= (~valmask);
|
||||
ival |= sval;
|
||||
*p32 = ival;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
uint16_t mmu_set_uint16(uint16_t *p16, const uint16_t val) {
|
||||
ASSERT_RANGE_TEST_WRITE(p16);
|
||||
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||
uint32_t sval = val << pos;
|
||||
uint32_t valmask = 0x0FFFF << pos;
|
||||
|
||||
uint32_t *p32 = (uint32_t *)((uintptr_t)p16 & ~0x3);
|
||||
uint32_t ival = *p32;
|
||||
ival &= (~valmask);
|
||||
ival |= sval;
|
||||
*p32 = ival;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
int16_t mmu_set_int16(int16_t *p16, const int16_t val) {
|
||||
ASSERT_RANGE_TEST_WRITE(p16);
|
||||
uint32_t sval = (uint16_t)val;
|
||||
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||
sval <<= pos;
|
||||
uint32_t valmask = 0x0FFFF << pos;
|
||||
|
||||
uint32_t *p32 = (uint32_t *)((uintptr_t)p16 & ~0x3);
|
||||
uint32_t ival = *p32;
|
||||
ival &= (~valmask);
|
||||
ival |= sval;
|
||||
*p32 = ival;
|
||||
return val;
|
||||
}
|
||||
|
||||
#if (MMU_IRAM_SIZE > 32*1024) && !defined(MMU_SEC_HEAP)
|
||||
extern void _text_end(void);
|
||||
#define MMU_SEC_HEAP mmu_sec_heap()
|
||||
#define MMU_SEC_HEAP_SIZE mmu_sec_heap_size()
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void *mmu_sec_heap(void) {
|
||||
uint32_t sec_heap = (uint32_t)_text_end + 32;
|
||||
return (void *)(sec_heap &= ~7);
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
size_t mmu_sec_heap_size(void) {
|
||||
return (size_t)0xC000UL - ((size_t)mmu_sec_heap() - 0x40100000UL);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -26,8 +26,6 @@
|
||||
*/
|
||||
#include <limits>
|
||||
#include "FS.h"
|
||||
#undef max
|
||||
#undef min
|
||||
#include "FSImpl.h"
|
||||
extern "C" {
|
||||
#include "spiffs/spiffs.h"
|
||||
|
@ -23,6 +23,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <../include/time.h> // See issue #6714
|
||||
#include <sys/time.h>
|
||||
extern "C" {
|
||||
#include <sys/_tz_structs.h>
|
||||
};
|
||||
#include <sys/reent.h>
|
||||
#include <errno.h>
|
||||
|
||||
@ -204,14 +207,14 @@ void configTime(const char* tz, const char* server1, const char* server2, const
|
||||
sntp_init();
|
||||
}
|
||||
|
||||
static TrivialCB _settimeofday_cb;
|
||||
|
||||
void settimeofday_cb (TrivialCB&& cb)
|
||||
{
|
||||
_settimeofday_cb = std::move(cb);
|
||||
}
|
||||
static BoolCB _settimeofday_cb;
|
||||
|
||||
void settimeofday_cb (const TrivialCB& cb)
|
||||
{
|
||||
_settimeofday_cb = [cb](bool sntp) { (void)sntp; cb(); };
|
||||
}
|
||||
|
||||
void settimeofday_cb (const BoolCB& cb)
|
||||
{
|
||||
_settimeofday_cb = cb;
|
||||
}
|
||||
@ -222,6 +225,20 @@ extern "C" {
|
||||
|
||||
int settimeofday(const struct timeval* tv, const struct timezone* tz)
|
||||
{
|
||||
bool from_sntp;
|
||||
if (tz == (struct timezone*)0xFeedC0de)
|
||||
{
|
||||
// This special constant is used by lwip2/SNTP calling
|
||||
// settimeofday(sntp-time, 0xfeedc0de), secretly using the
|
||||
// obsolete-but-yet-still-there `tz` field.
|
||||
// It allows to avoid duplicating this function and inform user
|
||||
// about the source time change.
|
||||
tz = nullptr;
|
||||
from_sntp = true;
|
||||
}
|
||||
else
|
||||
from_sntp = false;
|
||||
|
||||
if (tz || !tv)
|
||||
// tz is obsolete (cf. man settimeofday)
|
||||
return EINVAL;
|
||||
@ -230,7 +247,7 @@ int settimeofday(const struct timeval* tv, const struct timezone* tz)
|
||||
tune_timeshift64(tv->tv_sec * 1000000ULL + tv->tv_usec);
|
||||
|
||||
if (_settimeofday_cb)
|
||||
schedule_recurrent_function_us([](){ _settimeofday_cb(); return false; }, 0);
|
||||
schedule_recurrent_function_us([from_sntp](){ _settimeofday_cb(from_sntp); return false; }, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -506,8 +506,10 @@ uart_write(uart_t* uart, const char* buf, size_t size)
|
||||
|
||||
size_t ret = size;
|
||||
const int uart_nr = uart->uart_nr;
|
||||
while (size--)
|
||||
while (size--) {
|
||||
uart_do_write_char(uart_nr, pgm_read_byte(buf++));
|
||||
optimistic_yield(10000UL);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
102
cores/esp8266/umm_malloc/umm_heap_select.h
Normal file
102
cores/esp8266/umm_malloc/umm_heap_select.h
Normal file
@ -0,0 +1,102 @@
|
||||
#ifndef UMM_MALLOC_SELECT_H
|
||||
#define UMM_MALLOC_SELECT_H
|
||||
|
||||
#include <umm_malloc/umm_malloc.h>
|
||||
|
||||
#ifndef ALWAYS_INLINE
|
||||
#define ALWAYS_INLINE inline __attribute__ ((always_inline))
|
||||
#endif
|
||||
|
||||
// Use FORCE_ALWAYS_INLINE to ensure HeapSelect... construtor/deconstructor
|
||||
// are placed in IRAM
|
||||
#ifdef FORCE_ALWAYS_INLINE_HEAP_SELECT
|
||||
#define MAYBE_ALWAYS_INLINE ALWAYS_INLINE
|
||||
#else
|
||||
#define MAYBE_ALWAYS_INLINE
|
||||
#endif
|
||||
|
||||
/*
|
||||
This class is modeled after interrupts.h
|
||||
|
||||
HeapSelectIram is used to temporarily select an alternate Heap.
|
||||
|
||||
{
|
||||
{
|
||||
HeapSelectIram lock;
|
||||
// allocate memory here
|
||||
}
|
||||
allocations here are from the old Heap selection
|
||||
}
|
||||
*/
|
||||
|
||||
class HeapSelect {
|
||||
public:
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelect(size_t id) { (void)id; }
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelect() {}
|
||||
#else
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelect(size_t id) : _heap_id(umm_get_current_heap_id()) {
|
||||
umm_set_heap_by_id(id);
|
||||
}
|
||||
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelect() {
|
||||
umm_set_heap_by_id(_heap_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t _heap_id;
|
||||
#endif
|
||||
};
|
||||
|
||||
class HeapSelectIram {
|
||||
public:
|
||||
#ifdef UMM_HEAP_IRAM
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelectIram() : _heap_id(umm_get_current_heap_id()) {
|
||||
umm_set_heap_by_id(UMM_HEAP_IRAM);
|
||||
}
|
||||
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelectIram() {
|
||||
umm_set_heap_by_id(_heap_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t _heap_id;
|
||||
|
||||
#else
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelectIram() {}
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelectIram() {}
|
||||
#endif
|
||||
};
|
||||
|
||||
class HeapSelectDram {
|
||||
public:
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelectDram() {}
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelectDram() {}
|
||||
#else
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelectDram() : _heap_id(umm_get_current_heap_id()) {
|
||||
umm_set_heap_by_id(UMM_HEAP_DRAM);
|
||||
}
|
||||
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelectDram() {
|
||||
umm_set_heap_by_id(_heap_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t _heap_id;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // UMM_MALLOC_SELECT_H
|
@ -23,25 +23,25 @@
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
UMM_HEAP_INFO ummHeapInfo;
|
||||
// UMM_HEAP_INFO ummHeapInfo;
|
||||
|
||||
void *umm_info( void *ptr, bool force ) {
|
||||
UMM_CRITICAL_DECL(id_info);
|
||||
|
||||
if(umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
uint16_t blockNo = 0;
|
||||
|
||||
/* Protect the critical section... */
|
||||
UMM_CRITICAL_ENTRY(id_info);
|
||||
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
|
||||
/*
|
||||
* Clear out all of the entries in the ummHeapInfo structure before doing
|
||||
* any calculations..
|
||||
*/
|
||||
memset( &ummHeapInfo, 0, sizeof( ummHeapInfo ) );
|
||||
memset( &_context->info, 0, sizeof( _context->info ) );
|
||||
|
||||
DBGLOG_FORCE( force, "\n" );
|
||||
DBGLOG_FORCE( force, "+----------+-------+--------+--------+-------+--------+--------+\n" );
|
||||
@ -65,18 +65,18 @@ void *umm_info( void *ptr, bool force ) {
|
||||
while( UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK ) {
|
||||
size_t curBlocks = (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo;
|
||||
|
||||
++ummHeapInfo.totalEntries;
|
||||
ummHeapInfo.totalBlocks += curBlocks;
|
||||
++_context->info.totalEntries;
|
||||
_context->info.totalBlocks += curBlocks;
|
||||
|
||||
/* Is this a free block? */
|
||||
|
||||
if( UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK ) {
|
||||
++ummHeapInfo.freeEntries;
|
||||
ummHeapInfo.freeBlocks += curBlocks;
|
||||
ummHeapInfo.freeBlocksSquared += (curBlocks * curBlocks);
|
||||
++_context->info.freeEntries;
|
||||
_context->info.freeBlocks += curBlocks;
|
||||
_context->info.freeBlocksSquared += (curBlocks * curBlocks);
|
||||
|
||||
if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) {
|
||||
ummHeapInfo.maxFreeContiguousBlocks = curBlocks;
|
||||
if (_context->info.maxFreeContiguousBlocks < curBlocks) {
|
||||
_context->info.maxFreeContiguousBlocks = curBlocks;
|
||||
}
|
||||
|
||||
DBGLOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|NF %5d|PF %5d|\n",
|
||||
@ -98,8 +98,8 @@ void *umm_info( void *ptr, bool force ) {
|
||||
return( ptr );
|
||||
}
|
||||
} else {
|
||||
++ummHeapInfo.usedEntries;
|
||||
ummHeapInfo.usedBlocks += curBlocks;
|
||||
++_context->info.usedEntries;
|
||||
_context->info.usedBlocks += curBlocks;
|
||||
|
||||
DBGLOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|\n",
|
||||
DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)),
|
||||
@ -131,35 +131,35 @@ void *umm_info( void *ptr, bool force ) {
|
||||
DBGLOG_FORCE( force, "+----------+-------+--------+--------+-------+--------+--------+\n" );
|
||||
|
||||
DBGLOG_FORCE( force, "Total Entries %5d Used Entries %5d Free Entries %5d\n",
|
||||
ummHeapInfo.totalEntries,
|
||||
ummHeapInfo.usedEntries,
|
||||
ummHeapInfo.freeEntries );
|
||||
_context->info.totalEntries,
|
||||
_context->info.usedEntries,
|
||||
_context->info.freeEntries );
|
||||
|
||||
DBGLOG_FORCE( force, "Total Blocks %5d Used Blocks %5d Free Blocks %5d\n",
|
||||
ummHeapInfo.totalBlocks,
|
||||
ummHeapInfo.usedBlocks,
|
||||
ummHeapInfo.freeBlocks );
|
||||
_context->info.totalBlocks,
|
||||
_context->info.usedBlocks,
|
||||
_context->info.freeBlocks );
|
||||
|
||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||
|
||||
DBGLOG_FORCE( force, "Usage Metric: %5d\n", umm_usage_metric());
|
||||
DBGLOG_FORCE( force, "Fragmentation Metric: %5d\n", umm_fragmentation_metric());
|
||||
DBGLOG_FORCE( force, "Usage Metric: %5d\n", umm_usage_metric_core(_context));
|
||||
DBGLOG_FORCE( force, "Fragmentation Metric: %5d\n", umm_fragmentation_metric_core(_context));
|
||||
|
||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||
|
||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||
#if !defined(UMM_INLINE_METRICS)
|
||||
if (ummHeapInfo.freeBlocks == ummStats.free_blocks) {
|
||||
if (_context->info.freeBlocks == _context->stats.free_blocks) {
|
||||
DBGLOG_FORCE( force, "heap info Free blocks and heap statistics Free blocks match.\n");
|
||||
} else {
|
||||
DBGLOG_FORCE( force, "\nheap info Free blocks %5d != heap statistics Free Blocks %5d\n\n",
|
||||
ummHeapInfo.freeBlocks,
|
||||
ummStats.free_blocks );
|
||||
_context->info.freeBlocks,
|
||||
_context->stats.free_blocks );
|
||||
}
|
||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||
#endif
|
||||
|
||||
print_stats(force);
|
||||
umm_print_stats(force);
|
||||
#endif
|
||||
|
||||
/* Release the critical section... */
|
||||
@ -170,20 +170,29 @@ void *umm_info( void *ptr, bool force ) {
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
size_t umm_free_heap_size_core( umm_heap_context_t *_context ) {
|
||||
return (size_t)_context->info.freeBlocks * sizeof(umm_block);
|
||||
}
|
||||
|
||||
size_t umm_free_heap_size( void ) {
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
umm_info(NULL, false);
|
||||
#endif
|
||||
return (size_t)ummHeapInfo.freeBlocks * sizeof(umm_block);
|
||||
|
||||
return umm_free_heap_size_core(umm_get_current_heap());
|
||||
}
|
||||
|
||||
//C Breaking change in upstream umm_max_block_size() was changed to
|
||||
//C umm_max_free_block_size() keeping old function name for (dot) releases.
|
||||
//C TODO: update at next major release.
|
||||
//C size_t umm_max_free_block_size( void ) {
|
||||
size_t umm_max_block_size_core( umm_heap_context_t *_context ) {
|
||||
return _context->info.maxFreeContiguousBlocks * sizeof(umm_block);
|
||||
}
|
||||
|
||||
size_t umm_max_block_size( void ) {
|
||||
umm_info(NULL, false);
|
||||
return ummHeapInfo.maxFreeContiguousBlocks * sizeof(umm_block);
|
||||
return umm_max_block_size_core(umm_get_current_heap());
|
||||
}
|
||||
|
||||
/*
|
||||
@ -191,50 +200,62 @@ size_t umm_max_block_size( void ) {
|
||||
umm_fragmentation_metric() must to be preceeded by a call to umm_info(NULL, false)
|
||||
for updated results.
|
||||
*/
|
||||
int umm_usage_metric( void ) {
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
umm_info(NULL, false);
|
||||
#endif
|
||||
DBGLOG_DEBUG( "usedBlocks %d totalBlocks %d\n", umm_metrics.usedBlocks, ummHeapInfo.totalBlocks);
|
||||
if (ummHeapInfo.freeBlocks)
|
||||
return (int)((ummHeapInfo.usedBlocks * 100)/(ummHeapInfo.freeBlocks));
|
||||
int umm_usage_metric_core( umm_heap_context_t *_context ) {
|
||||
//C Note, umm_metrics also appears in the upstrean w/o definition. I suspect it is suppose to be ummHeapInfo.
|
||||
// DBGLOG_DEBUG( "usedBlocks %d totalBlocks %d\n", umm_metrics.usedBlocks, ummHeapInfo.totalBlocks);
|
||||
DBGLOG_DEBUG( "usedBlocks %d totalBlocks %d\n", _context->info.usedBlocks, _context->info.totalBlocks);
|
||||
if (_context->info.freeBlocks)
|
||||
return (int)((_context->info.usedBlocks * 100)/(_context->info.freeBlocks));
|
||||
|
||||
return -1; // no freeBlocks
|
||||
}
|
||||
|
||||
int umm_usage_metric( void ) {
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
umm_info(NULL, false);
|
||||
#endif
|
||||
|
||||
return umm_usage_metric_core(umm_get_current_heap());
|
||||
}
|
||||
uint32_t sqrt32 (uint32_t n);
|
||||
|
||||
int umm_fragmentation_metric_core( umm_heap_context_t *_context ) {
|
||||
// DBGLOG_DEBUG( "freeBlocks %d freeBlocksSquared %d\n", umm_metrics.freeBlocks, ummHeapInfo.freeBlocksSquared);
|
||||
DBGLOG_DEBUG( "freeBlocks %d freeBlocksSquared %d\n", _context->info.freeBlocks, _context->info.freeBlocksSquared);
|
||||
if (0 == _context->info.freeBlocks) {
|
||||
return 0;
|
||||
} else {
|
||||
//upstream version: return (100 - (((uint32_t)(sqrtf(ummHeapInfo.freeBlocksSquared)) * 100)/(ummHeapInfo.freeBlocks)));
|
||||
return (100 - (((uint32_t)(sqrt32(_context->info.freeBlocksSquared)) * 100)/(_context->info.freeBlocks)));
|
||||
}
|
||||
}
|
||||
|
||||
int umm_fragmentation_metric( void ) {
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
umm_info(NULL, false);
|
||||
#endif
|
||||
DBGLOG_DEBUG( "freeBlocks %d freeBlocksSquared %d\n", umm_metrics.freeBlocks, ummHeapInfo.freeBlocksSquared);
|
||||
if (0 == ummHeapInfo.freeBlocks) {
|
||||
return 0;
|
||||
} else {
|
||||
//upstream version: return (100 - (((uint32_t)(sqrtf(ummHeapInfo.freeBlocksSquared)) * 100)/(ummHeapInfo.freeBlocks)));
|
||||
return (100 - (((uint32_t)(sqrt32(ummHeapInfo.freeBlocksSquared)) * 100)/(ummHeapInfo.freeBlocks)));
|
||||
}
|
||||
|
||||
return umm_fragmentation_metric_core(umm_get_current_heap());
|
||||
}
|
||||
|
||||
#ifdef UMM_INLINE_METRICS
|
||||
static void umm_fragmentation_metric_init( void ) {
|
||||
ummHeapInfo.freeBlocks = UMM_NUMBLOCKS - 2;
|
||||
ummHeapInfo.freeBlocksSquared = ummHeapInfo.freeBlocks * ummHeapInfo.freeBlocks;
|
||||
static void umm_fragmentation_metric_init( umm_heap_context_t *_context ) {
|
||||
_context->info.freeBlocks = UMM_NUMBLOCKS - 2;
|
||||
_context->info.freeBlocksSquared = _context->info.freeBlocks * _context->info.freeBlocks;
|
||||
}
|
||||
|
||||
static void umm_fragmentation_metric_add( uint16_t c ) {
|
||||
static void umm_fragmentation_metric_add( umm_heap_context_t *_context, uint16_t c ) {
|
||||
uint16_t blocks = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) - c;
|
||||
DBGLOG_DEBUG( "Add block %d size %d to free metric\n", c, blocks);
|
||||
ummHeapInfo.freeBlocks += blocks;
|
||||
ummHeapInfo.freeBlocksSquared += (blocks * blocks);
|
||||
_context->info.freeBlocks += blocks;
|
||||
_context->info.freeBlocksSquared += (blocks * blocks);
|
||||
}
|
||||
|
||||
static void umm_fragmentation_metric_remove( uint16_t c ) {
|
||||
static void umm_fragmentation_metric_remove( umm_heap_context_t *_context, uint16_t c ) {
|
||||
uint16_t blocks = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) - c;
|
||||
DBGLOG_DEBUG( "Remove block %d size %d from free metric\n", c, blocks);
|
||||
ummHeapInfo.freeBlocks -= blocks;
|
||||
ummHeapInfo.freeBlocksSquared -= (blocks * blocks);
|
||||
_context->info.freeBlocks -= blocks;
|
||||
_context->info.freeBlocksSquared -= (blocks * blocks);
|
||||
}
|
||||
#endif // UMM_INLINE_METRICS
|
||||
|
||||
|
@ -33,13 +33,14 @@ bool umm_integrity_check(void) {
|
||||
uint16_t prev;
|
||||
uint16_t cur;
|
||||
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
/* Iterate through all free blocks */
|
||||
prev = 0;
|
||||
UMM_CRITICAL_ENTRY(id_integrity);
|
||||
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
|
||||
while(1) {
|
||||
cur = UMM_NFREE(prev);
|
||||
|
||||
|
@ -42,7 +42,7 @@ bool ICACHE_FLASH_ATTR get_umm_get_perf_data(UMM_TIME_STATS *p, size_t size)
|
||||
#if defined(UMM_POISON_CHECK_LITE)
|
||||
// We skip this when doing the full poison check.
|
||||
|
||||
static bool check_poison_neighbors( uint16_t cur ) {
|
||||
static bool check_poison_neighbors( umm_heap_context_t *_context, uint16_t cur ) {
|
||||
uint16_t c;
|
||||
|
||||
if ( 0 == cur )
|
||||
@ -96,12 +96,16 @@ static void *get_unpoisoned_check_neighbors( void *vptr, const char* file, int l
|
||||
UMM_CRITICAL_DECL(id_poison);
|
||||
uint16_t c;
|
||||
bool poison = false;
|
||||
|
||||
umm_heap_context_t *_context = umm_get_ptr_context( vptr );
|
||||
if (NULL == _context) {
|
||||
panic();
|
||||
return NULL;
|
||||
}
|
||||
/* Figure out which block we're in. Note the use of truncated division... */
|
||||
c = (ptr - (uintptr_t)(&(umm_heap[0])))/sizeof(umm_block);
|
||||
c = (ptr - (uintptr_t)(&(_context->heap[0])))/sizeof(umm_block);
|
||||
|
||||
UMM_CRITICAL_ENTRY(id_poison);
|
||||
poison = check_poison_block(&UMM_BLOCK(c)) && check_poison_neighbors(c);
|
||||
poison = check_poison_block(&UMM_BLOCK(c)) && check_poison_neighbors(_context, c);
|
||||
UMM_CRITICAL_EXIT(id_poison);
|
||||
|
||||
if (!poison) {
|
||||
@ -157,17 +161,13 @@ size_t umm_block_size( void ) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (!defined(UMM_INLINE_METRICS) && defined(UMM_STATS)) || defined(UMM_STATS_FULL)
|
||||
UMM_STATISTICS ummStats;
|
||||
#endif
|
||||
|
||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||
// Keep complete call path in IRAM
|
||||
size_t umm_free_heap_size_lw( void ) {
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
return (size_t)UMM_FREE_BLOCKS * sizeof(umm_block);
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return (size_t)_context->UMM_FREE_BLOCKS * sizeof(umm_block);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -186,14 +186,17 @@ size_t xPortGetFreeHeapSize(void) __attribute__ ((alias("umm_free_heap_size")));
|
||||
#endif
|
||||
|
||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||
void print_stats(int force) {
|
||||
void umm_print_stats(int force) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
|
||||
DBGLOG_FORCE( force, "umm heap statistics:\n");
|
||||
DBGLOG_FORCE( force, " Raw Free Space %5u\n", UMM_FREE_BLOCKS * sizeof(umm_block));
|
||||
DBGLOG_FORCE( force, " OOM Count %5u\n", UMM_OOM_COUNT);
|
||||
DBGLOG_FORCE( force, " Heap ID %5u\n", _context->id);
|
||||
DBGLOG_FORCE( force, " Free Space %5u\n", _context->UMM_FREE_BLOCKS * sizeof(umm_block));
|
||||
DBGLOG_FORCE( force, " OOM Count %5u\n", _context->UMM_OOM_COUNT);
|
||||
#if defined(UMM_STATS_FULL)
|
||||
DBGLOG_FORCE( force, " Low Watermark %5u\n", ummStats.free_blocks_min * sizeof(umm_block));
|
||||
DBGLOG_FORCE( force, " Low Watermark ISR %5u\n", ummStats.free_blocks_isr_min * sizeof(umm_block));
|
||||
DBGLOG_FORCE( force, " MAX Alloc Request %5u\n", ummStats.alloc_max_size);
|
||||
DBGLOG_FORCE( force, " Low Watermark %5u\n", _context->stats.free_blocks_min * sizeof(umm_block));
|
||||
DBGLOG_FORCE( force, " Low Watermark ISR %5u\n", _context->stats.free_blocks_isr_min * sizeof(umm_block));
|
||||
DBGLOG_FORCE( force, " MAX Alloc Request %5u\n", _context->stats.alloc_max_size);
|
||||
#endif
|
||||
DBGLOG_FORCE( force, " Size of umm_block %5u\n", sizeof(umm_block));
|
||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||
@ -215,4 +218,85 @@ int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) {
|
||||
return result;
|
||||
}
|
||||
|
||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||
size_t ICACHE_FLASH_ATTR umm_get_oom_count( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->UMM_OOM_COUNT;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef UMM_STATS_FULL
|
||||
// TODO - Did I mix something up
|
||||
//
|
||||
// umm_free_heap_size_min is the same code as
|
||||
// umm_free_heap_size_lw_min
|
||||
//
|
||||
// If this is correct use alias.
|
||||
//
|
||||
size_t ICACHE_FLASH_ATTR umm_free_heap_size_lw_min( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.free_blocks_min * umm_block_size();
|
||||
}
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_free_heap_size_min_reset( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
_context->stats.free_blocks_min = _context->UMM_FREE_BLOCKS;
|
||||
return _context->stats.free_blocks_min * umm_block_size();
|
||||
}
|
||||
|
||||
#if 0 // TODO - Don't understand this why do both umm_free_heap_size_(lw_)min exist
|
||||
size_t umm_free_heap_size_min(void) __attribute__ ((alias("umm_free_heap_size_lw_min")));
|
||||
#else
|
||||
size_t ICACHE_FLASH_ATTR umm_free_heap_size_min( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.free_blocks_min * umm_block_size();
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_free_heap_size_isr_min( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.free_blocks_isr_min * umm_block_size();
|
||||
}
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_get_max_alloc_size( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.alloc_max_size;
|
||||
}
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_get_last_alloc_size( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.last_alloc_size;
|
||||
}
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_get_malloc_count( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.id_malloc_count;
|
||||
}
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_get_malloc_zero_count( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.id_malloc_zero_count;
|
||||
}
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_get_realloc_count( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.id_realloc_count;
|
||||
}
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_get_realloc_zero_count( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.id_realloc_zero_count;
|
||||
}
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_get_free_count( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.id_free_count;
|
||||
}
|
||||
|
||||
size_t ICACHE_FLASH_ATTR umm_get_free_null_count( void ) {
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
return _context->stats.id_free_null_count;
|
||||
}
|
||||
#endif // UMM_STATS_FULL
|
||||
|
||||
#endif // BUILD_UMM_MALLOC_C
|
||||
|
@ -37,12 +37,12 @@
|
||||
|
||||
|
||||
#if defined(UMM_POISON_CHECK_LITE)
|
||||
static bool check_poison_neighbors( uint16_t cur );
|
||||
static bool check_poison_neighbors( umm_heap_context_t *_context, uint16_t cur );
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||
void ICACHE_FLASH_ATTR print_stats(int force);
|
||||
void ICACHE_FLASH_ATTR umm_print_stats(int force);
|
||||
#endif
|
||||
|
||||
|
||||
@ -51,4 +51,21 @@ int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) __attribute__
|
||||
#define UMM_INFO_PRINTF(fmt, ...) umm_info_safe_printf_P(PSTR4(fmt), ##__VA_ARGS__)
|
||||
// use PSTR4() instead of PSTR() to ensure 4-bytes alignment in Flash, whatever the default alignment of PSTR_ALIGN
|
||||
|
||||
|
||||
typedef struct umm_block_t umm_block;
|
||||
|
||||
struct UMM_HEAP_CONTEXT {
|
||||
umm_block *heap;
|
||||
void *heap_end;
|
||||
#if (!defined(UMM_INLINE_METRICS) && defined(UMM_STATS)) || defined(UMM_STATS_FULL)
|
||||
UMM_STATISTICS stats;
|
||||
#endif
|
||||
#ifdef UMM_INFO
|
||||
UMM_HEAP_INFO info;
|
||||
#endif
|
||||
unsigned short int numblocks;
|
||||
unsigned char id;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -63,6 +63,11 @@ extern "C" {
|
||||
#define DBGLOG_LEVEL 0
|
||||
#endif
|
||||
|
||||
// Save 104 bytes by calling umm_init() early once from app_entry()
|
||||
// Some minor UMM_CRITICAL_METRICS counts will be lost through CRT0 init.
|
||||
// #define UMM_INIT_HEAP if (!umm_heap) { umm_init(); }
|
||||
#define UMM_INIT_HEAP (void)0
|
||||
|
||||
#include "dbglog/dbglog.h"
|
||||
|
||||
//C This change is new in upstream umm_malloc.I think this would have created a
|
||||
@ -101,24 +106,146 @@ UMM_H_ATTPACKPRE typedef struct umm_block_t {
|
||||
#define UMM_BLOCKNO_MASK ((uint16_t)(0x7FFF))
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
umm_heap_context_t heap_context[UMM_NUM_HEAPS] __attribute__((section(".noinit")));
|
||||
// void *umm_heap = NULL;
|
||||
|
||||
umm_block *umm_heap = NULL;
|
||||
uint16_t umm_numblocks = 0;
|
||||
/* A stack allowing push/popping of heaps for library use */
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
|
||||
#define UMM_NUMBLOCKS (umm_numblocks)
|
||||
#else
|
||||
static size_t umm_heap_cur = UMM_HEAP_DRAM;
|
||||
static int umm_heap_stack_ptr = 0;
|
||||
static unsigned char umm_heap_stack[UMM_HEAP_STACK_DEPTH];
|
||||
#endif
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/*
|
||||
* Methods to get heap id or context
|
||||
*
|
||||
*/
|
||||
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
size_t umm_get_current_heap_id(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_get_current_heap(void) {
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
static umm_heap_context_t *umm_get_heap_by_id( size_t which ) {
|
||||
(void)which;
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_set_heap_by_id( size_t which ) {
|
||||
(void)which;
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
#else
|
||||
size_t umm_get_current_heap_id(void) {
|
||||
return umm_heap_cur;
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_get_current_heap(void) {
|
||||
return &heap_context[umm_heap_cur];
|
||||
}
|
||||
|
||||
static umm_heap_context_t *umm_get_heap_by_id( size_t which ) {
|
||||
if (which < UMM_NUM_HEAPS) {
|
||||
return &heap_context[which];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_set_heap_by_id( size_t which ) {
|
||||
umm_heap_context_t *_context = umm_get_heap_by_id(which);
|
||||
if (_context && _context->heap) {
|
||||
umm_heap_cur = which;
|
||||
return _context;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
umm_heap_context_t *umm_push_heap( size_t which ) {
|
||||
(void)which;
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_pop_heap( void ) {
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
int umm_get_heap_stack_index( void ) {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
umm_heap_context_t *umm_push_heap( size_t which ) {
|
||||
if (umm_heap_stack_ptr < UMM_HEAP_STACK_DEPTH) {
|
||||
umm_heap_stack[umm_heap_stack_ptr++] = umm_heap_cur;
|
||||
return umm_set_heap_by_id( which );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
umm_heap_context_t *umm_pop_heap( void ) {
|
||||
if (umm_heap_stack_ptr > 0 ) {
|
||||
return umm_set_heap_by_id(umm_heap_stack[--umm_heap_stack_ptr]);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Intended for diagnosic use
|
||||
int umm_get_heap_stack_index( void ) {
|
||||
return umm_heap_stack_ptr;
|
||||
}
|
||||
#endif
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/*
|
||||
* Returns the correct heap context for a given pointer. Useful for
|
||||
* realloc or free since you may not be in the right heap to handle it.
|
||||
*
|
||||
*/
|
||||
static bool test_ptr_context( size_t which, void *ptr ) {
|
||||
return
|
||||
heap_context[which].heap &&
|
||||
ptr >= (void *)heap_context[which].heap &&
|
||||
ptr < heap_context[which].heap_end;
|
||||
}
|
||||
|
||||
static umm_heap_context_t *umm_get_ptr_context(void *ptr) {
|
||||
for (size_t i = 0; i < UMM_NUM_HEAPS; i++) {
|
||||
if (test_ptr_context( i, ptr ) ) {
|
||||
return umm_get_heap_by_id( i );
|
||||
}
|
||||
}
|
||||
|
||||
panic();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define UMM_NUMBLOCKS (_context->numblocks)
|
||||
#define UMM_BLOCK_LAST (UMM_NUMBLOCKS - 1)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* These macros evaluate to the address of the block and data respectively
|
||||
*/
|
||||
|
||||
#define UMM_BLOCK(b) (umm_heap[b])
|
||||
#define UMM_BLOCK(b) (_context->heap[b])
|
||||
#define UMM_DATA(b) (UMM_BLOCK(b).body.data)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* These macros evaluate to the index of the block - NOT the address!!!
|
||||
*/
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
#define UMM_NBLOCK(b) (UMM_BLOCK(b).header.used.next)
|
||||
#define UMM_PBLOCK(b) (UMM_BLOCK(b).header.used.prev)
|
||||
#define UMM_NFREE(b) (UMM_BLOCK(b).body.free.next)
|
||||
@ -172,7 +299,9 @@ static uint16_t umm_blocks( size_t size ) {
|
||||
*
|
||||
* Note that free pointers are NOT modified by this function.
|
||||
*/
|
||||
static void umm_split_block( uint16_t c,
|
||||
static void umm_split_block(
|
||||
umm_heap_context_t *_context,
|
||||
uint16_t c,
|
||||
uint16_t blocks,
|
||||
uint16_t new_freemask ) {
|
||||
|
||||
@ -185,7 +314,7 @@ static void umm_split_block( uint16_t c,
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static void umm_disconnect_from_free_list( uint16_t c ) {
|
||||
static void umm_disconnect_from_free_list( umm_heap_context_t *_context, uint16_t c ) {
|
||||
/* Disconnect this block from the FREE list */
|
||||
|
||||
UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c);
|
||||
@ -202,7 +331,7 @@ static void umm_disconnect_from_free_list( uint16_t c ) {
|
||||
* next block is free.
|
||||
*/
|
||||
|
||||
static void umm_assimilate_up( uint16_t c ) {
|
||||
static void umm_assimilate_up( umm_heap_context_t *_context, uint16_t c ) {
|
||||
|
||||
if( UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK ) {
|
||||
|
||||
@ -217,7 +346,7 @@ static void umm_assimilate_up( uint16_t c ) {
|
||||
|
||||
/* Disconnect the next block from the FREE list */
|
||||
|
||||
umm_disconnect_from_free_list( UMM_NBLOCK(c) );
|
||||
umm_disconnect_from_free_list( _context, UMM_NBLOCK(c) );
|
||||
|
||||
/* Assimilate the next block with this one */
|
||||
|
||||
@ -232,7 +361,7 @@ static void umm_assimilate_up( uint16_t c ) {
|
||||
* up before assimilating down.
|
||||
*/
|
||||
|
||||
static uint16_t umm_assimilate_down( uint16_t c, uint16_t freemask ) {
|
||||
static uint16_t umm_assimilate_down( umm_heap_context_t *_context, uint16_t c, uint16_t freemask ) {
|
||||
|
||||
// We are going to assimilate down to the previous block because
|
||||
// it was free, so remove it from the fragmentation metric
|
||||
@ -257,23 +386,18 @@ static uint16_t umm_assimilate_down( uint16_t c, uint16_t freemask ) {
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
void umm_init( void ) {
|
||||
/* init heap pointer and size, and memset it to 0 */
|
||||
umm_heap = (umm_block *)UMM_MALLOC_CFG_HEAP_ADDR;
|
||||
umm_numblocks = (UMM_MALLOC_CFG_HEAP_SIZE / sizeof(umm_block));
|
||||
memset(umm_heap, 0x00, UMM_MALLOC_CFG_HEAP_SIZE);
|
||||
|
||||
static void umm_init_stage_2( umm_heap_context_t *_context ) {
|
||||
/* setup initial blank heap structure */
|
||||
UMM_FRAGMENTATION_METRIC_INIT();
|
||||
|
||||
/* init ummStats.free_blocks */
|
||||
/* init stats.free_blocks */
|
||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||
#if defined(UMM_STATS_FULL)
|
||||
ummStats.free_blocks_min =
|
||||
ummStats.free_blocks_isr_min = UMM_NUMBLOCKS - 2;
|
||||
_context->stats.free_blocks_min =
|
||||
_context->stats.free_blocks_isr_min = UMM_NUMBLOCKS - 2;
|
||||
#endif
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
ummStats.free_blocks = UMM_NUMBLOCKS - 2;
|
||||
_context->stats.free_blocks = UMM_NUMBLOCKS - 2;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -314,15 +438,89 @@ void umm_init( void ) {
|
||||
UMM_PBLOCK(UMM_BLOCK_LAST) = 1;
|
||||
}
|
||||
|
||||
|
||||
void umm_init_common( size_t id, void *start_addr, size_t size, bool zero ) {
|
||||
/* Preserve internal setup */
|
||||
umm_heap_context_t *_context = umm_get_heap_by_id(id);
|
||||
if (NULL == start_addr || NULL == _context || _context->heap) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* init heap pointer and size, and memset it to 0 */
|
||||
_context->id = id;
|
||||
_context->heap = (umm_block *)start_addr;
|
||||
_context->heap_end = (void *)((uintptr_t)start_addr + size);
|
||||
_context->numblocks = (size / sizeof(umm_block));
|
||||
|
||||
// An option for blocking the zeroing of extra heaps allows for performing
|
||||
// post-crash discovery.
|
||||
if (zero) {
|
||||
memset(_context->heap, 0x00, size);
|
||||
#if (!defined(UMM_INLINE_METRICS) && defined(UMM_STATS)) || defined(UMM_STATS_FULL)
|
||||
memset(&_context->stats, 0x00, sizeof(_context->stats));
|
||||
#endif
|
||||
|
||||
/* Set up internal data structures */
|
||||
umm_init_stage_2(_context);
|
||||
}
|
||||
}
|
||||
|
||||
void umm_init( void ) {
|
||||
// if (umm_heap) {
|
||||
// return;
|
||||
// }
|
||||
for (size_t i = 0; i < UMM_NUM_HEAPS; i++) {
|
||||
heap_context[i].heap = NULL;
|
||||
}
|
||||
memset(&heap_context[0], 0, sizeof(heap_context));
|
||||
umm_init_common( UMM_HEAP_DRAM, (void *)UMM_MALLOC_CFG_HEAP_ADDR, UMM_MALLOC_CFG_HEAP_SIZE, true );
|
||||
// umm_heap = (void *)&heap_context;
|
||||
}
|
||||
|
||||
#ifdef UMM_HEAP_IRAM
|
||||
void umm_init_iram_ex( void *addr, unsigned int size, bool zero ) {
|
||||
/* We need the main, internal heap set up first */
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
umm_init_common(UMM_HEAP_IRAM, addr, size, zero);
|
||||
}
|
||||
|
||||
void _text_end(void);
|
||||
void umm_init_iram(void) __attribute__((weak));
|
||||
|
||||
/*
|
||||
By using a weak link, it is possible to reduce the IRAM heap size with a
|
||||
user-supplied init function. This would allow the creation of a block of IRAM
|
||||
dedicated to a sketch and possibly used/preserved across reboots.
|
||||
*/
|
||||
void umm_init_iram(void) {
|
||||
umm_init_iram_ex(mmu_sec_heap(), mmu_sec_heap_size(), true);
|
||||
}
|
||||
#endif // #ifdef UMM_HEAP_IRAM
|
||||
|
||||
#ifdef UMM_HEAP_EXTERNAL
|
||||
void umm_init_vm( void *vmaddr, unsigned int vmsize ) {
|
||||
/* We need the main, internal (DRAM) heap set up first */
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
umm_init_common(UMM_HEAP_EXTERNAL, vmaddr, vmsize, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Must be called only from within critical sections guarded by
|
||||
* UMM_CRITICAL_ENTRY() and UMM_CRITICAL_EXIT().
|
||||
*/
|
||||
|
||||
static void umm_free_core( void *ptr ) {
|
||||
static void umm_free_core( umm_heap_context_t *_context, void *ptr ) {
|
||||
|
||||
uint16_t c;
|
||||
|
||||
if (NULL == _context) {
|
||||
panic();
|
||||
return;
|
||||
}
|
||||
|
||||
STATS__FREE_REQUEST(id_free);
|
||||
/*
|
||||
* FIXME: At some point it might be a good idea to add a check to make sure
|
||||
@ -335,7 +533,7 @@ static void umm_free_core( void *ptr ) {
|
||||
|
||||
/* Figure out which block we're in. Note the use of truncated division... */
|
||||
|
||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(umm_heap[0])))/sizeof(umm_block);
|
||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(_context->heap[0])))/sizeof(umm_block);
|
||||
|
||||
DBGLOG_DEBUG( "Freeing block %6d\n", c );
|
||||
|
||||
@ -344,7 +542,7 @@ static void umm_free_core( void *ptr ) {
|
||||
|
||||
/* Now let's assimilate this block with the next one if possible. */
|
||||
|
||||
umm_assimilate_up( c );
|
||||
umm_assimilate_up( _context, c );
|
||||
|
||||
/* Then assimilate with the previous block if possible */
|
||||
|
||||
@ -352,7 +550,7 @@ static void umm_free_core( void *ptr ) {
|
||||
|
||||
DBGLOG_DEBUG( "Assimilate down to previous block, which is FREE\n" );
|
||||
|
||||
c = umm_assimilate_down(c, UMM_FREELIST_MASK);
|
||||
c = umm_assimilate_down(_context, c, UMM_FREELIST_MASK);
|
||||
} else {
|
||||
/*
|
||||
* The previous block is not a free block, so add this one to the head
|
||||
@ -376,9 +574,7 @@ static void umm_free_core( void *ptr ) {
|
||||
void umm_free( void *ptr ) {
|
||||
UMM_CRITICAL_DECL(id_free);
|
||||
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
/* If we're being asked to free a NULL pointer, well that's just silly! */
|
||||
|
||||
@ -393,7 +589,8 @@ void umm_free( void *ptr ) {
|
||||
|
||||
UMM_CRITICAL_ENTRY(id_free);
|
||||
|
||||
umm_free_core( ptr );
|
||||
/* Need to be in the heap in which this block lives */
|
||||
umm_free_core( umm_get_ptr_context( ptr ), ptr );
|
||||
|
||||
UMM_CRITICAL_EXIT(id_free);
|
||||
}
|
||||
@ -403,7 +600,7 @@ void umm_free( void *ptr ) {
|
||||
* UMM_CRITICAL_ENTRY() and UMM_CRITICAL_EXIT().
|
||||
*/
|
||||
|
||||
static void *umm_malloc_core( size_t size ) {
|
||||
static void *umm_malloc_core( umm_heap_context_t *_context, size_t size ) {
|
||||
uint16_t blocks;
|
||||
uint16_t blockSize = 0;
|
||||
|
||||
@ -414,6 +611,11 @@ static void *umm_malloc_core( size_t size ) {
|
||||
|
||||
STATS__ALLOC_REQUEST(id_malloc, size);
|
||||
|
||||
if (NULL == _context) {
|
||||
panic();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
blocks = umm_blocks( size );
|
||||
|
||||
/*
|
||||
@ -474,7 +676,8 @@ static void *umm_malloc_core( size_t size ) {
|
||||
|
||||
/* Disconnect this block from the FREE list */
|
||||
|
||||
umm_disconnect_from_free_list( cf );
|
||||
umm_disconnect_from_free_list( _context, cf );
|
||||
|
||||
} else {
|
||||
|
||||
/* It's not an exact fit and we need to split off a block. */
|
||||
@ -484,7 +687,7 @@ static void *umm_malloc_core( size_t size ) {
|
||||
* split current free block `cf` into two blocks. The first one will be
|
||||
* returned to user, so it's not free, and the second one will be free.
|
||||
*/
|
||||
umm_split_block( cf, blocks, UMM_FREELIST_MASK /*new block is free*/ );
|
||||
umm_split_block( _context, cf, blocks, UMM_FREELIST_MASK /*new block is free*/ );
|
||||
|
||||
UMM_FRAGMENTATION_METRIC_ADD(UMM_NBLOCK(cf));
|
||||
|
||||
@ -525,9 +728,61 @@ void *umm_malloc( size_t size ) {
|
||||
|
||||
void *ptr = NULL;
|
||||
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
/*
|
||||
* "Is it safe"
|
||||
*
|
||||
* Is it safe to call from an ISR? Is there a point during a malloc that a
|
||||
* an interrupt and subsequent call to malloc result in undesired results?
|
||||
*
|
||||
* Heap selection in managed by the functions umm_push_heap, umm_pop_heap,
|
||||
* umm_get_current_heap_id, and umm_set_heap_by_id. These functions are
|
||||
* responsible for getting/setting the module static variable umm_heap_cur.
|
||||
* The umm_heap_cur variable is an index that is used to select the current
|
||||
* heap context. Depending on the situation this selection can be overriddened.
|
||||
*
|
||||
* All variables for a specific Heap are in a single structure. `heap_context`
|
||||
* is an array of these structures. Each heap API function uses a function
|
||||
* local variable `_context` to hold a pointer to the selected heap structure.
|
||||
* This local pointer is referenced for all the "selected heap" operations.
|
||||
* Coupled with critical sections around global data should allow the API
|
||||
* functions to be reentrant.
|
||||
*
|
||||
* Using the `_context` name throughout made it easy to incorporate the
|
||||
* context into existing macros.
|
||||
*
|
||||
* For allocating APIs `umm_heap_cur` is used to index and select a value for
|
||||
* `_context`. If an allocation is made from an ISR, this value is ignored and
|
||||
* the heap context for DRAM is loaded. For APIs that require operating on an
|
||||
* existing allcation such as realloc and free, the heap context selected is
|
||||
* done by matching the allocation's address with that of one of the heap
|
||||
* address ranges.
|
||||
*
|
||||
* I think we are safe with multiple heaps when the non32-bit exception
|
||||
* handler is used, as long as interrupts don't get enabled. There was a
|
||||
* window in the Boot ROM "C" Exception Wrapper that would enable interrupts
|
||||
* when running our non32-exception handler; however, that should be resolved
|
||||
* by our replacement wrapper. For more information on exception handling
|
||||
* issues for IRAM see comments above `_set_exception_handler_wrapper()` in
|
||||
* `core_esp8266_non32xfer.cpp`.
|
||||
*
|
||||
* ISRs should not try and change heaps. umm_malloc will ignore the change.
|
||||
* All should be fine as long as the caller puts the heap back the way it was.
|
||||
* On return, everything must be the same. The foreground thread will continue
|
||||
* with the same information that was there before the interrupt. All malloc()
|
||||
* requests made from an ISR are fulfilled with DRAM.
|
||||
*
|
||||
* For umm_malloc, heap selection involves changing a single variable that is
|
||||
* on the calling context stack. From the umm_mallac side, that variable is
|
||||
* used to load a context pointer by index, heap ID. While an umm_malloc API
|
||||
* function is running, all heap related variables are in the context variable
|
||||
* pointer, registers, or the current stack as the request is processed. With
|
||||
* a single variable to reference for heap selection, I think it is unlikely
|
||||
* that umm_malloc can be called, with things in an unusable transition state.
|
||||
*/
|
||||
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
|
||||
/*
|
||||
* the very first thing we do is figure out if we're being asked to allocate
|
||||
@ -547,7 +802,21 @@ void *umm_malloc( size_t size ) {
|
||||
|
||||
UMM_CRITICAL_ENTRY(id_malloc);
|
||||
|
||||
ptr = umm_malloc_core( size );
|
||||
/*
|
||||
* We handle the realloc of an existing IRAM allocation from an ISR with IRAM,
|
||||
* while a new malloc from an ISR will always supply DRAM. That said, realloc
|
||||
* from an ISR is not generally safe without special locking mechanisms and is
|
||||
* not formally supported.
|
||||
*
|
||||
* Additionally, to avoid extending the IRQs disabled period, it is best to
|
||||
* use DRAM for an ISR. Each 16-bit access to IRAM that umm_malloc has to make
|
||||
* requires a pass through the exception handling logic.
|
||||
*/
|
||||
if (UMM_CRITICAL_WITHINISR(id_malloc)) {
|
||||
_context = umm_get_heap_by_id(UMM_HEAP_DRAM);
|
||||
}
|
||||
|
||||
ptr = umm_malloc_core( _context, size );
|
||||
|
||||
UMM_CRITICAL_EXIT(id_malloc);
|
||||
|
||||
@ -568,9 +837,7 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
|
||||
size_t curSize;
|
||||
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
/*
|
||||
* This code looks after the case of a NULL value for ptr. The ANSI C
|
||||
@ -592,6 +859,13 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
* we should operate the same as free.
|
||||
*/
|
||||
|
||||
/* Need to be in the heap in which this block lives */
|
||||
umm_heap_context_t *_context = umm_get_ptr_context( ptr );
|
||||
if (NULL == _context) {
|
||||
panic();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( 0 == size ) {
|
||||
DBGLOG_DEBUG( "realloc to 0 size, just free the block\n" );
|
||||
STATS__ZERO_ALLOC_REQUEST(id_realloc, size);
|
||||
@ -616,7 +890,7 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
|
||||
/* Figure out which block we're in. Note the use of truncated division... */
|
||||
|
||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(umm_heap[0])))/sizeof(umm_block);
|
||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(_context->heap[0])))/sizeof(umm_block);
|
||||
|
||||
/* Figure out how big this block is ... the free bit is not set :-) */
|
||||
|
||||
@ -713,15 +987,15 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
// Case 3 - prev block NOT free and block + next block fits
|
||||
} else if ((0 == prevBlockSize) && (blockSize + nextBlockSize) >= blocks) {
|
||||
DBGLOG_DEBUG( "realloc using next block - %i\n", blocks );
|
||||
umm_assimilate_up( c );
|
||||
umm_assimilate_up( _context, c );
|
||||
STATS__FREE_BLOCKS_UPDATE( - nextBlockSize );
|
||||
blockSize += nextBlockSize;
|
||||
|
||||
// Case 4 - prev block + block fits
|
||||
} else if ((prevBlockSize + blockSize) >= blocks) {
|
||||
DBGLOG_DEBUG( "realloc using prev block - %i\n", blocks );
|
||||
umm_disconnect_from_free_list( UMM_PBLOCK(c) );
|
||||
c = umm_assimilate_down(c, 0);
|
||||
umm_disconnect_from_free_list( _context, UMM_PBLOCK(c) );
|
||||
c = umm_assimilate_down(_context, c, 0);
|
||||
STATS__FREE_BLOCKS_UPDATE( - prevBlockSize );
|
||||
STATS__FREE_BLOCKS_ISR_MIN();
|
||||
blockSize += prevBlockSize;
|
||||
@ -732,14 +1006,14 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
// Case 5 - prev block + block + next block fits
|
||||
} else if ((prevBlockSize + blockSize + nextBlockSize) >= blocks) {
|
||||
DBGLOG_DEBUG( "realloc using prev and next block - %d\n", blocks );
|
||||
umm_assimilate_up( c );
|
||||
umm_disconnect_from_free_list( UMM_PBLOCK(c) );
|
||||
c = umm_assimilate_down(c, 0);
|
||||
umm_assimilate_up( _context, c );
|
||||
umm_disconnect_from_free_list( _context, UMM_PBLOCK(c) );
|
||||
c = umm_assimilate_down(_context, c, 0);
|
||||
STATS__FREE_BLOCKS_UPDATE( - prevBlockSize - nextBlockSize );
|
||||
#ifdef UMM_LIGHTWEIGHT_CPU
|
||||
if ((prevBlockSize + blockSize + nextBlockSize) > blocks) {
|
||||
umm_split_block( c, blocks, 0 );
|
||||
umm_free_core( (void *)&UMM_DATA(c+blocks) );
|
||||
umm_split_block( _context, c, blocks, 0 );
|
||||
umm_free_core( _context, (void *)&UMM_DATA(c+blocks) );
|
||||
}
|
||||
STATS__FREE_BLOCKS_ISR_MIN();
|
||||
blockSize = blocks;
|
||||
@ -755,16 +1029,16 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
} else {
|
||||
DBGLOG_DEBUG( "realloc a completely new block %i\n", blocks );
|
||||
void *oldptr = ptr;
|
||||
if( (ptr = umm_malloc_core( size )) ) {
|
||||
if( (ptr = umm_malloc_core( _context, size )) ) {
|
||||
DBGLOG_DEBUG( "realloc %i to a bigger block %i, copy, and free the old\n", blockSize, blocks );
|
||||
UMM_CRITICAL_SUSPEND(id_realloc);
|
||||
memcpy( ptr, oldptr, curSize );
|
||||
UMM_CRITICAL_RESUME(id_realloc);
|
||||
umm_free_core( oldptr );
|
||||
umm_free_core( _context, oldptr );
|
||||
} else {
|
||||
DBGLOG_DEBUG( "realloc %i to a bigger block %i failed - return NULL and leave the old block!\n", blockSize, blocks );
|
||||
/* This space intentionally left blnk */
|
||||
STATS__OOM_UPDATE();
|
||||
/* STATS__OOM_UPDATE() has already been called by umm_malloc_core - don't duplicate count */
|
||||
}
|
||||
/* This is not accurate for OOM case; however, it will work for
|
||||
* stopping a call to free before return.
|
||||
@ -798,8 +1072,8 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
* requested number of blocks and add what's left to the free list.
|
||||
*/
|
||||
if (prevBlockSize && (prevBlockSize + blockSize + nextBlockSize) >= blocks) { // 1
|
||||
umm_disconnect_from_free_list( UMM_PBLOCK(c) );
|
||||
c = umm_assimilate_down(c, 0);
|
||||
umm_disconnect_from_free_list( _context, UMM_PBLOCK(c) );
|
||||
c = umm_assimilate_down( _context, c, 0 );
|
||||
STATS__FREE_BLOCKS_UPDATE( - prevBlockSize );
|
||||
blockSize += prevBlockSize;
|
||||
if (blockSize >= blocks) {
|
||||
@ -807,13 +1081,13 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
STATS__FREE_BLOCKS_ISR_MIN();
|
||||
} else {
|
||||
DBGLOG_DEBUG( "realloc using prev and next block - %d\n", blocks );
|
||||
umm_assimilate_up( c );
|
||||
umm_assimilate_up( _context, c );
|
||||
STATS__FREE_BLOCKS_UPDATE( - nextBlockSize );
|
||||
blockSize += nextBlockSize;
|
||||
#ifdef UMM_LIGHTWEIGHT_CPU
|
||||
if (blockSize > blocks) {
|
||||
umm_split_block( c, blocks, 0 );
|
||||
umm_free_core( (void *)&UMM_DATA(c+blocks) );
|
||||
umm_split_block( _context, c, blocks, 0 );
|
||||
umm_free_core( _context, (void *)&UMM_DATA(c+blocks) );
|
||||
}
|
||||
STATS__FREE_BLOCKS_ISR_MIN();
|
||||
blockSize = blocks;
|
||||
@ -828,22 +1102,22 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
/* This space intentionally left blank */
|
||||
} else if ((blockSize + nextBlockSize) >= blocks) { // 3
|
||||
DBGLOG_DEBUG( "realloc using next block - %d\n", blocks );
|
||||
umm_assimilate_up( c );
|
||||
umm_assimilate_up( _context, c );
|
||||
STATS__FREE_BLOCKS_UPDATE( - nextBlockSize );
|
||||
blockSize += nextBlockSize;
|
||||
} else { // 4
|
||||
DBGLOG_DEBUG( "realloc a completely new block %d\n", blocks );
|
||||
void *oldptr = ptr;
|
||||
if( (ptr = umm_malloc_core( size )) ) {
|
||||
if( (ptr = umm_malloc_core( _context, size )) ) {
|
||||
DBGLOG_DEBUG( "realloc %d to a bigger block %d, copy, and free the old\n", blockSize, blocks );
|
||||
UMM_CRITICAL_SUSPEND(id_realloc);
|
||||
memcpy( ptr, oldptr, curSize );
|
||||
UMM_CRITICAL_RESUME(id_realloc);
|
||||
umm_free_core( oldptr);
|
||||
umm_free_core( _context, oldptr);
|
||||
} else {
|
||||
DBGLOG_DEBUG( "realloc %d to a bigger block %d failed - return NULL and leave the old block!\n", blockSize, blocks );
|
||||
/* This space intentionally left blnk */
|
||||
STATS__OOM_UPDATE();
|
||||
/* STATS__OOM_UPDATE() has already been called by umm_malloc_core - don't duplicate count */
|
||||
}
|
||||
/* This is not accurate for OOM case; however, it will work for
|
||||
* stopping a call to free before return.
|
||||
@ -859,16 +1133,16 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
} else {
|
||||
DBGLOG_DEBUG( "realloc a completely new block %d\n", blocks );
|
||||
void *oldptr = ptr;
|
||||
if( (ptr = umm_malloc_core( size )) ) {
|
||||
if( (ptr = umm_malloc_core( _context, size )) ) {
|
||||
DBGLOG_DEBUG( "realloc %d to a bigger block %d, copy, and free the old\n", blockSize, blocks );
|
||||
UMM_CRITICAL_SUSPEND(id_realloc);
|
||||
memcpy( ptr, oldptr, curSize );
|
||||
UMM_CRITICAL_RESUME(id_realloc);
|
||||
umm_free_core( oldptr );
|
||||
umm_free_core( _context, oldptr );
|
||||
} else {
|
||||
DBGLOG_DEBUG( "realloc %d to a bigger block %d failed - return NULL and leave the old block!\n", blockSize, blocks );
|
||||
/* This space intentionally left blnk */
|
||||
STATS__OOM_UPDATE();
|
||||
/* STATS__OOM_UPDATE() has already been called by umm_malloc_core - don't duplicate count */
|
||||
}
|
||||
/* This is not accurate for OOM case; however, it will work for
|
||||
* stopping a call to free before return.
|
||||
@ -882,8 +1156,8 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
|
||||
if (blockSize > blocks ) {
|
||||
DBGLOG_DEBUG( "split and free %d blocks from %d\n", blocks, blockSize );
|
||||
umm_split_block( c, blocks, 0 );
|
||||
umm_free_core( (void *)&UMM_DATA(c+blocks) );
|
||||
umm_split_block( _context, c, blocks, 0 );
|
||||
umm_free_core( _context, (void *)&UMM_DATA(c+blocks) );
|
||||
}
|
||||
|
||||
STATS__FREE_BLOCKS_MIN();
|
||||
|
@ -17,6 +17,14 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef UMM_HEAP_EXTERNAL
|
||||
extern void umm_init_vm( void *vmaddr, unsigned int vmsize );
|
||||
#endif
|
||||
#ifdef UMM_HEAP_IRAM
|
||||
extern void umm_init_iram(void);
|
||||
extern void umm_init_iram_ex( void *addr, unsigned int size, bool zero );
|
||||
#endif
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
extern void umm_init( void );
|
||||
@ -27,6 +35,13 @@ extern void umm_free( void *ptr );
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
extern umm_heap_context_t *umm_push_heap( size_t heap_number );
|
||||
extern umm_heap_context_t *umm_pop_heap( void );
|
||||
extern int umm_get_heap_stack_index( void );
|
||||
extern umm_heap_context_t *umm_set_heap_by_id( size_t which );
|
||||
extern size_t umm_get_current_heap_id( void );
|
||||
extern umm_heap_context_t *umm_get_current_heap( void );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <pgmspace.h>
|
||||
#include <mmu_iram.h>
|
||||
#include "../debug.h"
|
||||
#include "../esp8266_undocumented.h"
|
||||
|
||||
@ -32,6 +33,46 @@ extern "C" {
|
||||
|
||||
#include "c_types.h"
|
||||
|
||||
/*
|
||||
* Define active Heaps
|
||||
*/
|
||||
#if defined(MMU_IRAM_HEAP)
|
||||
#define UMM_HEAP_IRAM
|
||||
#else
|
||||
#undef UMM_HEAP_IRAM
|
||||
#endif
|
||||
|
||||
// #define UMM_HEAP_EXTERNAL
|
||||
|
||||
/*
|
||||
* Assign IDs to active Heaps and tally. DRAM is always active.
|
||||
*/
|
||||
#define UMM_HEAP_DRAM 0
|
||||
#define UMM_HEAP_DRAM_DEFINED 1
|
||||
|
||||
#ifdef UMM_HEAP_IRAM
|
||||
#undef UMM_HEAP_IRAM
|
||||
#define UMM_HEAP_IRAM_DEFINED 1
|
||||
#define UMM_HEAP_IRAM UMM_HEAP_DRAM_DEFINED
|
||||
#else
|
||||
#define UMM_HEAP_IRAM_DEFINED 0
|
||||
#endif
|
||||
|
||||
#ifdef UMM_HEAP_EXTERNAL
|
||||
#undef UMM_HEAP_EXTERNAL
|
||||
#define UMM_HEAP_EXTERNAL_DEFINED 1
|
||||
#define UMM_HEAP_EXTERNAL (UMM_HEAP_DRAM_DEFINED + UMM_HEAP_IRAM_DEFINED)
|
||||
#else
|
||||
#define UMM_HEAP_EXTERNAL_DEFINED 0
|
||||
#endif
|
||||
|
||||
#define UMM_NUM_HEAPS (UMM_HEAP_DRAM_DEFINED + UMM_HEAP_IRAM_DEFINED + UMM_HEAP_EXTERNAL_DEFINED)
|
||||
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
#else
|
||||
#define UMM_HEAP_STACK_DEPTH 32
|
||||
#endif
|
||||
|
||||
/*
|
||||
* There are a number of defines you can set at compile time that affect how
|
||||
* the memory allocator will operate.
|
||||
@ -134,8 +175,9 @@ extern char test_umm_heap[];
|
||||
#else
|
||||
/* Start addresses and the size of the heap */
|
||||
extern char _heap_start[];
|
||||
#define UMM_HEAP_END_ADDR 0x3FFFC000UL
|
||||
#define UMM_MALLOC_CFG_HEAP_ADDR ((uint32_t)&_heap_start[0])
|
||||
#define UMM_MALLOC_CFG_HEAP_SIZE ((size_t)(0x3fffc000 - UMM_MALLOC_CFG_HEAP_ADDR))
|
||||
#define UMM_MALLOC_CFG_HEAP_SIZE ((size_t)(UMM_HEAP_END_ADDR - UMM_MALLOC_CFG_HEAP_ADDR))
|
||||
#endif
|
||||
|
||||
/* A couple of macros to make packing structures less compiler dependent */
|
||||
@ -158,9 +200,9 @@ extern char _heap_start[];
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#ifdef UMM_INLINE_METRICS
|
||||
#define UMM_FRAGMENTATION_METRIC_INIT() umm_fragmentation_metric_init()
|
||||
#define UMM_FRAGMENTATION_METRIC_ADD(c) umm_fragmentation_metric_add(c)
|
||||
#define UMM_FRAGMENTATION_METRIC_REMOVE(c) umm_fragmentation_metric_remove(c)
|
||||
#define UMM_FRAGMENTATION_METRIC_INIT() umm_fragmentation_metric_init(_context)
|
||||
#define UMM_FRAGMENTATION_METRIC_ADD(c) umm_fragmentation_metric_add(_context, c)
|
||||
#define UMM_FRAGMENTATION_METRIC_REMOVE(c) umm_fragmentation_metric_remove(_context, c)
|
||||
#ifndef UMM_INFO
|
||||
#define UMM_INFO
|
||||
#endif
|
||||
@ -194,14 +236,16 @@ extern char _heap_start[];
|
||||
unsigned int freeBlocksSquared;
|
||||
#ifdef UMM_INLINE_METRICS
|
||||
size_t oom_count;
|
||||
#define UMM_OOM_COUNT ummHeapInfo.oom_count
|
||||
#define UMM_FREE_BLOCKS ummHeapInfo.freeBlocks
|
||||
#define UMM_OOM_COUNT info.oom_count
|
||||
#define UMM_FREE_BLOCKS info.freeBlocks
|
||||
#endif
|
||||
unsigned int maxFreeContiguousBlocks;
|
||||
}
|
||||
UMM_HEAP_INFO;
|
||||
|
||||
extern UMM_HEAP_INFO ummHeapInfo;
|
||||
// extern UMM_HEAP_INFO ummHeapInfo;
|
||||
struct UMM_HEAP_CONTEXT;
|
||||
typedef struct UMM_HEAP_CONTEXT umm_heap_context_t;
|
||||
|
||||
extern ICACHE_FLASH_ATTR void *umm_info( void *ptr, bool force );
|
||||
#ifdef UMM_INLINE_METRICS
|
||||
@ -213,12 +257,19 @@ extern char _heap_start[];
|
||||
extern ICACHE_FLASH_ATTR size_t umm_max_block_size( void );
|
||||
extern ICACHE_FLASH_ATTR int umm_usage_metric( void );
|
||||
extern ICACHE_FLASH_ATTR int umm_fragmentation_metric( void );
|
||||
extern ICACHE_FLASH_ATTR size_t umm_free_heap_size_core( umm_heap_context_t *_context );
|
||||
extern ICACHE_FLASH_ATTR size_t umm_max_block_size_core( umm_heap_context_t *_context );
|
||||
extern ICACHE_FLASH_ATTR int umm_usage_metric_core( umm_heap_context_t *_context );
|
||||
extern ICACHE_FLASH_ATTR int umm_fragmentation_metric_core( umm_heap_context_t *_context );
|
||||
#else
|
||||
#define umm_info(p,b)
|
||||
#define umm_free_heap_size() (0)
|
||||
#define umm_max_block_size() (0)
|
||||
#define umm_fragmentation_metric() (0)
|
||||
#define umm_usage_metric() (0)
|
||||
#define umm_free_heap_size_core() (0)
|
||||
#define umm_max_block_size_core() (0)
|
||||
#define umm_fragmentation_metric_core() (0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -263,8 +314,8 @@ typedef struct UMM_STATISTICS_t {
|
||||
// Otherwise it is defined here.
|
||||
size_t free_blocks;
|
||||
size_t oom_count;
|
||||
#define UMM_OOM_COUNT ummStats.oom_count
|
||||
#define UMM_FREE_BLOCKS ummStats.free_blocks
|
||||
#define UMM_OOM_COUNT stats.oom_count
|
||||
#define UMM_FREE_BLOCKS stats.free_blocks
|
||||
#endif
|
||||
#ifdef UMM_STATS_FULL
|
||||
size_t free_blocks_min;
|
||||
@ -280,21 +331,17 @@ typedef struct UMM_STATISTICS_t {
|
||||
#endif
|
||||
}
|
||||
UMM_STATISTICS;
|
||||
extern UMM_STATISTICS ummStats;
|
||||
|
||||
#ifdef UMM_INLINE_METRICS
|
||||
#define STATS__FREE_BLOCKS_UPDATE(s) (void)(s)
|
||||
#else
|
||||
#define STATS__FREE_BLOCKS_UPDATE(s) ummStats.free_blocks += (s)
|
||||
#define STATS__FREE_BLOCKS_UPDATE(s) _context->stats.free_blocks += (s)
|
||||
#endif
|
||||
|
||||
#define STATS__OOM_UPDATE() UMM_OOM_COUNT += 1
|
||||
#define STATS__OOM_UPDATE() _context->UMM_OOM_COUNT += 1
|
||||
|
||||
extern size_t umm_free_heap_size_lw( void );
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_get_oom_count( void ) {
|
||||
return UMM_OOM_COUNT;
|
||||
}
|
||||
extern size_t umm_get_oom_count( void );
|
||||
|
||||
#else // not UMM_STATS or UMM_STATS_FULL
|
||||
#define STATS__FREE_BLOCKS_UPDATE(s) (void)(s)
|
||||
@ -308,87 +355,53 @@ size_t ICACHE_FLASH_ATTR umm_block_size( void );
|
||||
#ifdef UMM_STATS_FULL
|
||||
#define STATS__FREE_BLOCKS_MIN() \
|
||||
do { \
|
||||
if (UMM_FREE_BLOCKS < ummStats.free_blocks_min) \
|
||||
ummStats.free_blocks_min = UMM_FREE_BLOCKS; \
|
||||
if (_context->UMM_FREE_BLOCKS < _context->stats.free_blocks_min) \
|
||||
_context->stats.free_blocks_min = _context->UMM_FREE_BLOCKS; \
|
||||
} while(false)
|
||||
|
||||
#define STATS__FREE_BLOCKS_ISR_MIN() \
|
||||
do { \
|
||||
if (UMM_FREE_BLOCKS < ummStats.free_blocks_isr_min) \
|
||||
ummStats.free_blocks_isr_min = UMM_FREE_BLOCKS; \
|
||||
if (_context->UMM_FREE_BLOCKS < _context->stats.free_blocks_isr_min) \
|
||||
_context->stats.free_blocks_isr_min = _context->UMM_FREE_BLOCKS; \
|
||||
} while(false)
|
||||
|
||||
#define STATS__ALLOC_REQUEST(tag, s) \
|
||||
do { \
|
||||
ummStats.tag##_count += 1; \
|
||||
ummStats.last_alloc_size = s; \
|
||||
if (ummStats.alloc_max_size < s) \
|
||||
ummStats.alloc_max_size = s; \
|
||||
_context->stats.tag##_count += 1; \
|
||||
_context->stats.last_alloc_size = s; \
|
||||
if (_context->stats.alloc_max_size < s) \
|
||||
_context->stats.alloc_max_size = s; \
|
||||
} while(false)
|
||||
|
||||
#define STATS__ZERO_ALLOC_REQUEST(tag, s) \
|
||||
do { \
|
||||
ummStats.tag##_zero_count += 1; \
|
||||
_context->stats.tag##_zero_count += 1; \
|
||||
} while(false)
|
||||
|
||||
#define STATS__NULL_FREE_REQUEST(tag) \
|
||||
do { \
|
||||
ummStats.tag##_null_count += 1; \
|
||||
umm_heap_context_t *_context = umm_get_current_heap(); \
|
||||
_context->stats.tag##_null_count += 1; \
|
||||
} while(false)
|
||||
|
||||
#define STATS__FREE_REQUEST(tag) \
|
||||
do { \
|
||||
ummStats.tag##_count += 1; \
|
||||
_context->stats.tag##_count += 1; \
|
||||
} while(false)
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_free_heap_size_lw_min( void ) {
|
||||
return (size_t)ummStats.free_blocks_min * umm_block_size();
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_free_heap_size_min_reset( void ) {
|
||||
ummStats.free_blocks_min = UMM_FREE_BLOCKS;
|
||||
return (size_t)ummStats.free_blocks_min * umm_block_size();
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_free_heap_size_min( void ) {
|
||||
return ummStats.free_blocks_min * umm_block_size();
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_free_heap_size_isr_min( void ) {
|
||||
return ummStats.free_blocks_isr_min * umm_block_size();
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_get_max_alloc_size( void ) {
|
||||
return ummStats.alloc_max_size;
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_get_last_alloc_size( void ) {
|
||||
return ummStats.last_alloc_size;
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_get_malloc_count( void ) {
|
||||
return ummStats.id_malloc_count;
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_get_malloc_zero_count( void ) {
|
||||
return ummStats.id_malloc_zero_count;
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_get_realloc_count( void ) {
|
||||
return ummStats.id_realloc_count;
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_get_realloc_zero_count( void ) {
|
||||
return ummStats.id_realloc_zero_count;
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_get_free_count( void ) {
|
||||
return ummStats.id_free_count;
|
||||
}
|
||||
|
||||
static inline size_t ICACHE_FLASH_ATTR umm_get_free_null_count( void ) {
|
||||
return ummStats.id_free_null_count;
|
||||
}
|
||||
size_t umm_free_heap_size_lw_min( void );
|
||||
size_t umm_free_heap_size_min_reset( void );
|
||||
size_t umm_free_heap_size_min( void );
|
||||
size_t umm_free_heap_size_isr_min( void );
|
||||
size_t umm_get_max_alloc_size( void );
|
||||
size_t umm_get_last_alloc_size( void );
|
||||
size_t umm_get_malloc_count( void );
|
||||
size_t umm_get_malloc_zero_count( void );
|
||||
size_t umm_get_realloc_count( void );
|
||||
size_t umm_get_realloc_zero_count( void );
|
||||
size_t umm_get_free_count( void );
|
||||
size_t umm_get_free_null_count( void );
|
||||
|
||||
#else // Not UMM_STATS_FULL
|
||||
#define STATS__FREE_BLOCKS_MIN() (void)0
|
||||
@ -492,6 +505,7 @@ static inline void _critical_exit(UMM_TIME_STAT *p, uint32_t *saved_ps) {
|
||||
#define UMM_CRITICAL_DECL(tag) uint32_t _saved_ps_##tag
|
||||
#define UMM_CRITICAL_ENTRY(tag)_critical_entry(&time_stats.tag, &_saved_ps_##tag)
|
||||
#define UMM_CRITICAL_EXIT(tag) _critical_exit(&time_stats.tag, &_saved_ps_##tag)
|
||||
#define UMM_CRITICAL_WITHINISR(tag) (0 != (_saved_ps_##tag & 0x0F))
|
||||
|
||||
#else // ! UMM_CRITICAL_METRICS
|
||||
// This method preserves the intlevel on entry and restores the
|
||||
@ -499,6 +513,7 @@ static inline void _critical_exit(UMM_TIME_STAT *p, uint32_t *saved_ps) {
|
||||
#define UMM_CRITICAL_DECL(tag) uint32_t _saved_ps_##tag
|
||||
#define UMM_CRITICAL_ENTRY(tag) _saved_ps_##tag = xt_rsil(DEFAULT_CRITICAL_SECTION_INTLEVEL)
|
||||
#define UMM_CRITICAL_EXIT(tag) xt_wsr_ps(_saved_ps_##tag)
|
||||
#define UMM_CRITICAL_WITHINISR(tag) (0 != (_saved_ps_##tag & 0x0F))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -666,7 +681,7 @@ static inline void _critical_exit(UMM_TIME_STAT *p, uint32_t *saved_ps) {
|
||||
# define POISON_CHECK() 1
|
||||
# define POISON_CHECK_NEIGHBORS(c) \
|
||||
do {\
|
||||
if(!check_poison_neighbors(c)) \
|
||||
if(!check_poison_neighbors(_context, c)) \
|
||||
panic();\
|
||||
} while(false)
|
||||
#else
|
||||
@ -759,6 +774,9 @@ void free_loc (void* p, const char* file, int line);
|
||||
#else // !defined(ESP_DEBUG_OOM)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -774,28 +792,29 @@ extern "C" {
|
||||
// Arduino.h recall us to redefine them
|
||||
#include <pgmspace.h>
|
||||
// Reuse pvPort* calls, since they already support passing location information.
|
||||
void* ICACHE_RAM_ATTR pvPortMalloc(size_t size, const char* file, int line);
|
||||
void* ICACHE_RAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file, int line);
|
||||
void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, int line);
|
||||
void* ICACHE_RAM_ATTR pvPortZalloc(size_t size, const char* file, int line);
|
||||
void ICACHE_RAM_ATTR vPortFree(void *ptr, const char* file, int line);
|
||||
// Specificly the debug version (heap_...) that does not force DRAM heap.
|
||||
void* ICACHE_RAM_ATTR heap_pvPortMalloc(size_t size, const char* file, int line);
|
||||
void* ICACHE_RAM_ATTR heap_pvPortCalloc(size_t count, size_t size, const char* file, int line);
|
||||
void* ICACHE_RAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line);
|
||||
void* ICACHE_RAM_ATTR heap_pvPortZalloc(size_t size, const char* file, int line);
|
||||
void ICACHE_RAM_ATTR heap_vPortFree(void *ptr, const char* file, int line);
|
||||
|
||||
#define malloc(s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; pvPortMalloc(s, mem_debug_file, __LINE__); })
|
||||
#define calloc(n,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; pvPortCalloc(n, s, mem_debug_file, __LINE__); })
|
||||
#define realloc(p,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; pvPortRealloc(p, s, mem_debug_file, __LINE__); })
|
||||
#define malloc(s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortMalloc(s, mem_debug_file, __LINE__); })
|
||||
#define calloc(n,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortCalloc(n, s, mem_debug_file, __LINE__); })
|
||||
#define realloc(p,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortRealloc(p, s, mem_debug_file, __LINE__); })
|
||||
|
||||
#if defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE)
|
||||
#define dbg_heap_free(p) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; vPortFree(p, mem_debug_file, __LINE__); })
|
||||
#define dbg_heap_free(p) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_vPortFree(p, mem_debug_file, __LINE__); })
|
||||
#else
|
||||
#define dbg_heap_free(p) free(p)
|
||||
#endif
|
||||
|
||||
#elif defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE)
|
||||
#include <pgmspace.h>
|
||||
void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, int line);
|
||||
#define realloc(p,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; pvPortRealloc(p, s, mem_debug_file, __LINE__); })
|
||||
void* ICACHE_RAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line);
|
||||
#define realloc(p,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortRealloc(p, s, mem_debug_file, __LINE__); })
|
||||
|
||||
void ICACHE_RAM_ATTR vPortFree(void *ptr, const char* file, int line);
|
||||
void ICACHE_RAM_ATTR heap_vPortFree(void *ptr, const char* file, int line);
|
||||
//C - to be discussed
|
||||
/*
|
||||
Problem, I would like to report the file and line number with the umm poison
|
||||
@ -810,7 +829,7 @@ void ICACHE_RAM_ATTR vPortFree(void *ptr, const char* file, int line);
|
||||
Create dbg_heap_free() as an alternative for free() when you need a little
|
||||
more help in debugging the more challenging problems.
|
||||
*/
|
||||
#define dbg_heap_free(p) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; vPortFree(p, mem_debug_file, __LINE__); })
|
||||
#define dbg_heap_free(p) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_vPortFree(p, mem_debug_file, __LINE__); })
|
||||
|
||||
#else
|
||||
#define dbg_heap_free(p) free(p)
|
||||
|
@ -137,8 +137,13 @@ static void *get_unpoisoned( void *vptr ) {
|
||||
|
||||
ptr -= (sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE);
|
||||
|
||||
umm_heap_context_t *_context = umm_get_ptr_context( vptr );
|
||||
if (NULL == _context) {
|
||||
panic();
|
||||
return NULL;
|
||||
}
|
||||
/* Figure out which block we're in. Note the use of truncated division... */
|
||||
c = (ptr - (uintptr_t)(&(umm_heap[0])))/sizeof(umm_block);
|
||||
c = (ptr - (uintptr_t)(&(_context->heap[0])))/sizeof(umm_block);
|
||||
|
||||
check_poison_block(&UMM_BLOCK(c));
|
||||
}
|
||||
@ -214,11 +219,10 @@ bool umm_poison_check(void) {
|
||||
bool ok = true;
|
||||
uint16_t cur;
|
||||
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
UMM_CRITICAL_ENTRY(id_poison);
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
|
||||
/* Now iterate through the blocks list */
|
||||
cur = UMM_NBLOCK(0) & UMM_BLOCKNO_MASK;
|
||||
|
@ -69,7 +69,7 @@ enum wl_enc_type { /* Values map to 802.11 encryption suites... */
|
||||
ENC_TYPE_AUTO = 8
|
||||
};
|
||||
|
||||
#if !defined(LWIP_INTERNAL) && !defined(__LWIP_TCP_H__)
|
||||
#if !defined(LWIP_INTERNAL) && !defined(__LWIP_TCP_H__) && !defined(LWIP_HDR_TCPBASE_H)
|
||||
enum wl_tcp_state {
|
||||
CLOSED = 0,
|
||||
LISTEN = 1,
|
162
cores/esp8266/xtruntime-frames.h
Normal file
162
cores/esp8266/xtruntime-frames.h
Normal file
@ -0,0 +1,162 @@
|
||||
/* xtruntime-frames.h - exception stack frames for single-threaded run-time */
|
||||
/* $Id: //depot/rel/Eaglenest/Xtensa/OS/include/xtensa/xtruntime-frames.h#1 $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2002-2012 Tensilica Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* 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 AUTHORS 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 IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _XTRUNTIME_FRAMES_H_
|
||||
#define _XTRUNTIME_FRAMES_H_
|
||||
|
||||
#include <xtensa/config/core.h>
|
||||
|
||||
/* Macros that help define structures for both C and assembler: */
|
||||
#if defined(_ASMLANGUAGE) || defined(__ASSEMBLER__)
|
||||
#define STRUCT_BEGIN .pushsection .text; .struct 0
|
||||
#define STRUCT_FIELD(ctype,size,pre,name) pre##name: .space size
|
||||
#define STRUCT_AFIELD(ctype,size,pre,name,n) pre##name: .if n ; .space (size)*(n) ; .endif
|
||||
#define STRUCT_AFIELD_A(ctype,size,align,pre,name,n) .balign align ; pre##name: .if n ; .space (size)*(n) ; .endif
|
||||
#define STRUCT_END(sname) sname##Size:; .popsection
|
||||
#else /*_ASMLANGUAGE||__ASSEMBLER__*/
|
||||
#define STRUCT_BEGIN typedef struct {
|
||||
#define STRUCT_FIELD(ctype,size,pre,name) ctype name;
|
||||
#define STRUCT_AFIELD(ctype,size,pre,name,n) ctype name[n];
|
||||
#define STRUCT_AFIELD_A(ctype,size,align,pre,name,n) ctype name[n] __attribute__((aligned(align)));
|
||||
#define STRUCT_END(sname) } sname;
|
||||
#endif /*_ASMLANGUAGE||__ASSEMBLER__*/
|
||||
|
||||
|
||||
/*
|
||||
* Kernel vector mode exception stack frame.
|
||||
*
|
||||
* NOTE: due to the limited range of addi used in the current
|
||||
* kernel exception vector, and the fact that historically
|
||||
* the vector is limited to 12 bytes, the size of this
|
||||
* stack frame is limited to 128 bytes (currently at 64).
|
||||
*/
|
||||
STRUCT_BEGIN
|
||||
STRUCT_FIELD (long,4,KEXC_,pc) /* "parm" */
|
||||
STRUCT_FIELD (long,4,KEXC_,ps)
|
||||
STRUCT_AFIELD(long,4,KEXC_,areg, 4) /* a12 .. a15 */
|
||||
STRUCT_FIELD (long,4,KEXC_,sar) /* "save" */
|
||||
#if XCHAL_HAVE_LOOPS
|
||||
STRUCT_FIELD (long,4,KEXC_,lcount)
|
||||
STRUCT_FIELD (long,4,KEXC_,lbeg)
|
||||
STRUCT_FIELD (long,4,KEXC_,lend)
|
||||
#endif
|
||||
#if XCHAL_HAVE_MAC16
|
||||
STRUCT_FIELD (long,4,KEXC_,acclo)
|
||||
STRUCT_FIELD (long,4,KEXC_,acchi)
|
||||
STRUCT_AFIELD(long,4,KEXC_,mr, 4)
|
||||
#endif
|
||||
STRUCT_END(KernelFrame)
|
||||
|
||||
|
||||
/*
|
||||
* User vector mode exception stack frame:
|
||||
*
|
||||
* WARNING: if you modify this structure, you MUST modify the
|
||||
* computation of the pad size (ALIGNPAD) accordingly.
|
||||
*/
|
||||
STRUCT_BEGIN
|
||||
STRUCT_FIELD (long,4,UEXC_,pc)
|
||||
STRUCT_FIELD (long,4,UEXC_,ps)
|
||||
STRUCT_FIELD (long,4,UEXC_,sar)
|
||||
STRUCT_FIELD (long,4,UEXC_,vpri)
|
||||
#ifdef __XTENSA_CALL0_ABI__
|
||||
STRUCT_FIELD (long,4,UEXC_,a0)
|
||||
#endif
|
||||
STRUCT_FIELD (long,4,UEXC_,a2)
|
||||
STRUCT_FIELD (long,4,UEXC_,a3)
|
||||
STRUCT_FIELD (long,4,UEXC_,a4)
|
||||
STRUCT_FIELD (long,4,UEXC_,a5)
|
||||
#ifdef __XTENSA_CALL0_ABI__
|
||||
STRUCT_FIELD (long,4,UEXC_,a6)
|
||||
STRUCT_FIELD (long,4,UEXC_,a7)
|
||||
STRUCT_FIELD (long,4,UEXC_,a8)
|
||||
STRUCT_FIELD (long,4,UEXC_,a9)
|
||||
STRUCT_FIELD (long,4,UEXC_,a10)
|
||||
STRUCT_FIELD (long,4,UEXC_,a11)
|
||||
STRUCT_FIELD (long,4,UEXC_,a12)
|
||||
STRUCT_FIELD (long,4,UEXC_,a13)
|
||||
STRUCT_FIELD (long,4,UEXC_,a14)
|
||||
STRUCT_FIELD (long,4,UEXC_,a15)
|
||||
#endif
|
||||
STRUCT_FIELD (long,4,UEXC_,exccause) /* NOTE: can probably rid of this one (pass direct) */
|
||||
#if XCHAL_HAVE_LOOPS
|
||||
STRUCT_FIELD (long,4,UEXC_,lcount)
|
||||
STRUCT_FIELD (long,4,UEXC_,lbeg)
|
||||
STRUCT_FIELD (long,4,UEXC_,lend)
|
||||
#endif
|
||||
#if XCHAL_HAVE_MAC16
|
||||
STRUCT_FIELD (long,4,UEXC_,acclo)
|
||||
STRUCT_FIELD (long,4,UEXC_,acchi)
|
||||
STRUCT_AFIELD(long,4,UEXC_,mr, 4)
|
||||
#endif
|
||||
/* ALIGNPAD is the 16-byte alignment padding. */
|
||||
#ifdef __XTENSA_CALL0_ABI__
|
||||
# define CALL0_ABI 1
|
||||
#else
|
||||
# define CALL0_ABI 0
|
||||
#endif
|
||||
#define ALIGNPAD ((3 + XCHAL_HAVE_LOOPS*1 + XCHAL_HAVE_MAC16*2 + CALL0_ABI*1) & 3)
|
||||
#if ALIGNPAD
|
||||
STRUCT_AFIELD(long,4,UEXC_,pad, ALIGNPAD) /* 16-byte alignment padding */
|
||||
#endif
|
||||
/*STRUCT_AFIELD_A(char,1,XCHAL_CPEXTRA_SA_ALIGN,UEXC_,ureg, (XCHAL_CPEXTRA_SA_SIZE+3)&-4)*/ /* not used */
|
||||
STRUCT_END(UserFrame)
|
||||
|
||||
|
||||
#if defined(_ASMLANGUAGE) || defined(__ASSEMBLER__)
|
||||
|
||||
|
||||
/* Check for UserFrameSize small enough not to require rounding...: */
|
||||
/* Skip 16-byte save area, then 32-byte space for 8 regs of call12
|
||||
* (which overlaps with 16-byte GCC nested func chaining area),
|
||||
* then exception stack frame: */
|
||||
.set UserFrameTotalSize, 16+32+UserFrameSize
|
||||
/* Greater than 112 bytes? (max range of ADDI, both signs, when aligned to 16 bytes): */
|
||||
.ifgt UserFrameTotalSize-112
|
||||
/* Round up to 256-byte multiple to accelerate immediate adds: */
|
||||
.set UserFrameTotalSize, ((UserFrameTotalSize+255) & 0xFFFFFF00)
|
||||
.endif
|
||||
# define ESF_TOTALSIZE UserFrameTotalSize
|
||||
|
||||
#endif /* _ASMLANGUAGE || __ASSEMBLER__ */
|
||||
|
||||
|
||||
#if XCHAL_NUM_CONTEXTS > 1
|
||||
/* Structure of info stored on new context's stack for setup: */
|
||||
STRUCT_BEGIN
|
||||
STRUCT_FIELD (long,4,INFO_,sp)
|
||||
STRUCT_FIELD (long,4,INFO_,arg1)
|
||||
STRUCT_FIELD (long,4,INFO_,funcpc)
|
||||
STRUCT_FIELD (long,4,INFO_,prevps)
|
||||
STRUCT_END(SetupInfo)
|
||||
#endif
|
||||
|
||||
|
||||
#define KERNELSTACKSIZE 1024
|
||||
|
||||
|
||||
#endif /* _XTRUNTIME_FRAMES_H_ */
|
||||
|
@ -362,8 +362,8 @@ links:
|
||||
- ESP8285 datasheet: https://www.espressif.com/sites/default/files/0a-esp8285_datasheet_en_v1.0_20160422.pdf
|
||||
- Voltage regulator datasheet: http://pdf-datasheet.datasheet.netdna-cdn.com/pdf-down/M/E/6/ME6211-Microne.pdf
|
||||
|
||||
WeMos D1 R1
|
||||
-----------
|
||||
LOLIN(WeMos) D1 R1
|
||||
------------------
|
||||
|
||||
Product page: https://www.wemos.cc/
|
||||
|
||||
|
@ -8,7 +8,7 @@ Implements a TLS encrypted server with optional client certificate validation.
|
||||
setBufferSizes(int recv, int xmit)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Similar to the `BearSSL::WiFiClientSecure` method, sets the receive and transmit buffer sizes. Note that servers cannot request a buffer size from the client, so if these are shrunk and the client tries to send a chunk larger than the receive buffer, it will always fail. This must be called before the server is
|
||||
Similar to the `BearSSL::WiFiClientSecure` method, sets the receive and transmit buffer sizes. Note that servers cannot request a buffer size from the client, so if these are shrunk and the client tries to send a chunk larger than the receive buffer, it will always fail. Needs to be called before `begin()`
|
||||
|
||||
Setting Server Certificates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -33,6 +33,26 @@ setECCert(const BearSSL::X509List \*chain, unsigned cert_issuer_key_type, const
|
||||
|
||||
Sets an elliptic curve certificate and key for the server. Needs to be called before `begin()`.
|
||||
|
||||
Client sessions (Resuming connections fast)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The TLS handshake process takes a long time because of all the back and forth between the client and the server. You can shorten it by caching the clients' sessions which will skip a few steps in the TLS handshake. In order for this to work, your client also needs to cache the session. `BearSSL::WiFiClientSecure <bearssl-client-secure-class.rst#sessions-resuming-connections-fast>`__ can do that as well as modern web browers.
|
||||
|
||||
Here are the kind of performance improvements that you'll be able to see for TLS handshakes with an ESP8266 with it's clock set at 160MHz on a network with fairly low latency:
|
||||
|
||||
* With an EC key of 256 bits, a request taking ~360ms without caching takes ~60ms with caching.
|
||||
* With an RSA key of 2048 bits, a request taking ~1850ms without caching takes ~70ms with caching.
|
||||
|
||||
setCache(BearSSL::ServerSessions \*cache)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Sets the cache for the server's sessions. When choosing the size of the cache, remember that each client session takes 100 bytes. If you setup a cache for 10 sessions, it will take 1000 bytes. Needs to be called before `begin()`
|
||||
|
||||
When creating the cache, you can use any of the 2 available constructors:
|
||||
|
||||
* `BearSSL::ServerSessions(ServerSession *sessions, uint32_t size)`: Creates a cache with the given buffer and number of sessions.
|
||||
* `BearSSL::ServerSessions(uint32_t size)`: Dynamically allocates a cache for the given number of sessions.
|
||||
|
||||
Requiring Client Certificates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -39,17 +39,19 @@ Then let's write a short function ``prepareHtmlPage()``, that will return a ``St
|
||||
|
||||
String prepareHtmlPage()
|
||||
{
|
||||
String htmlPage =
|
||||
String("HTTP/1.1 200 OK\r\n") +
|
||||
"Content-Type: text/html\r\n" +
|
||||
"Connection: close\r\n" + // the connection will be closed after completion of the response
|
||||
"Refresh: 5\r\n" + // refresh the page automatically every 5 sec
|
||||
"\r\n" +
|
||||
"<!DOCTYPE HTML>" +
|
||||
"<html>" +
|
||||
"Analog input: " + String(analogRead(A0)) +
|
||||
"</html>" +
|
||||
"\r\n";
|
||||
String htmlPage;
|
||||
htmlPage.reserve(1024); // prevent ram fragmentation
|
||||
htmlPage = F("HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/html\r\n"
|
||||
"Connection: close\r\n" // the connection will be closed after completion of the response
|
||||
"Refresh: 5\r\n" // refresh the page automatically every 5 sec
|
||||
"\r\n"
|
||||
"<!DOCTYPE HTML>"
|
||||
"<html>"
|
||||
"Analog input: ");
|
||||
htmlPage += analogRead(A0);
|
||||
htmlPage += F("</html>"
|
||||
"\r\n");
|
||||
return htmlPage;
|
||||
}
|
||||
|
||||
@ -79,7 +81,7 @@ The content contains two basic `HTML <https://www.w3schools.com/html/>`__ tags,
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
String(analogRead(A0))
|
||||
analogRead(A0)
|
||||
|
||||
The Page is Served
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
@ -126,6 +128,18 @@ The whole process is concluded by stopping the connection with client:
|
||||
|
||||
client.stop();
|
||||
|
||||
But before that, we must not interrupt client's request:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
while (client.available()) {
|
||||
// but first, let client finish its request
|
||||
// that's diplomatic compliance to protocols
|
||||
// (and otherwise some clients may complain, like curl)
|
||||
// (that is an example, prefer using a proper webserver library)
|
||||
client.read();
|
||||
}
|
||||
|
||||
Put it Together
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
@ -163,17 +177,19 @@ Complete sketch is presented below.
|
||||
// prepare a web page to be send to a client (web browser)
|
||||
String prepareHtmlPage()
|
||||
{
|
||||
String htmlPage =
|
||||
String("HTTP/1.1 200 OK\r\n") +
|
||||
"Content-Type: text/html\r\n" +
|
||||
"Connection: close\r\n" + // the connection will be closed after completion of the response
|
||||
"Refresh: 5\r\n" + // refresh the page automatically every 5 sec
|
||||
"\r\n" +
|
||||
"<!DOCTYPE HTML>" +
|
||||
"<html>" +
|
||||
"Analog input: " + String(analogRead(A0)) +
|
||||
"</html>" +
|
||||
"\r\n";
|
||||
String htmlPage;
|
||||
htmlPage.reserve(1024); // prevent ram fragmentation
|
||||
htmlPage = F("HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/html\r\n"
|
||||
"Connection: close\r\n" // the connection will be closed after completion of the response
|
||||
"Refresh: 5\r\n" // refresh the page automatically every 5 sec
|
||||
"\r\n"
|
||||
"<!DOCTYPE HTML>"
|
||||
"<html>"
|
||||
"Analog input: ");
|
||||
htmlPage += analogRead(A0);
|
||||
htmlPage += F("</html>"
|
||||
"\r\n");
|
||||
return htmlPage;
|
||||
}
|
||||
|
||||
@ -200,7 +216,14 @@ Complete sketch is presented below.
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(1); // give the web browser time to receive the data
|
||||
|
||||
while (client.available()) {
|
||||
// but first, let client finish its request
|
||||
// that's diplomatic compliance to protocols
|
||||
// (and otherwise some clients may complain, like curl)
|
||||
// (that is an example, prefer using a proper webserver library)
|
||||
client.read();
|
||||
}
|
||||
|
||||
// close the connection:
|
||||
client.stop();
|
||||
@ -243,6 +266,6 @@ Conclusion
|
||||
|
||||
The above example shows that a web server on ESP8266 can be set up in almost no time. Such server can easily stand up requests from much more powerful hardware and software like a PC with a web browser. Check out other classes like `ESP8266WebServer <https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer>`__ that let you program more advanced applications.
|
||||
|
||||
If you like to try another server example, check out `WiFiWebServer.ino <https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/WiFiWebServer/WiFiWebServer.ino>`__, that provides functionality of toggling the GPIO pin on and off out of a web browser.
|
||||
If you like to try another server example, check out `WiFiManualWebServer.ino <https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/WiFiManualWebServer/WiFiManualWebServer.ino>`__, that provides functionality of toggling the GPIO pin on and off out of a web browser.
|
||||
|
||||
For the list of functions provided to implement and manage servers, please refer to the `Server Class <server-class.rst>`__ documentation.
|
||||
|
@ -65,12 +65,12 @@ If you are using generic ESP modules, please follow
|
||||
`recommendations <Generic%20ESP8266%20modules>`__ on power supply and
|
||||
boot strapping resistors.
|
||||
|
||||
For boards with integrated USB to serial converter and power supply,
|
||||
For boards with integrated USB-to-serial converter and power supply,
|
||||
usually it is enough to connect it to an USB hub that provides standard
|
||||
0.5A and is not shared with other USB devices.
|
||||
|
||||
In any case make sure that your module is able to stable run standard
|
||||
example sketches that establish Wi-Fi connection like e.g.
|
||||
In any case, make sure that your module is able to stably run standard
|
||||
example sketches that establish Wi-Fi connection like, e.g.,
|
||||
`HelloServer.ino <https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer/examples/HelloServer>`__.
|
||||
|
||||
What is the Cause of Restart?
|
||||
@ -82,13 +82,13 @@ is wrong, it restarts itself to tell you about it.
|
||||
|
||||
There are two typical scenarios that trigger ESP restarts:
|
||||
|
||||
- **Exception** - when code is performing `illegal
|
||||
- **Exception** - when the code attempts an `illegal
|
||||
operation <../exception_causes.rst>`__,
|
||||
like trying to write to non-existent memory location.
|
||||
- **Watchdog** - if code is `locked
|
||||
up <https://en.wikipedia.org/wiki/Watchdog_timer>`__ staying too long
|
||||
in a loop or processing some task, so vital processes like Wi-Fi
|
||||
communication are not able to run.
|
||||
- **Watchdog** - if the code `locks
|
||||
up <https://en.wikipedia.org/wiki/Watchdog_timer>`__, staying too long
|
||||
in a loop or processing any other task without any pauses, which would
|
||||
prevent vital processes like Wi-Fi communication from running.
|
||||
|
||||
Please check below how to recognize `exception <#exception>`__ and
|
||||
`watchdog <#watchdog>`__ scenarios and what to do about it.
|
||||
@ -127,7 +127,7 @@ up.
|
||||
`SDK <https://bbs.espressif.com/viewforum.php?f=46>`__, that is part
|
||||
of `esp8266 / arduino <https://github.com/esp8266/Arduino>`__ core
|
||||
loaded to module together with your application.
|
||||
- **Hardware Watchdog** - build in ESP8266 hardware and acting if
|
||||
- **Hardware Watchdog** - built-in ESP8266 hardware, acting if the
|
||||
software watchdog is disabled for too long, in case it fails, or if
|
||||
it is not provided at all.
|
||||
|
||||
@ -312,9 +312,10 @@ Memory, memory, memory
|
||||
Stack
|
||||
The amount of stack in the ESP is tiny at only 4KB. For normal development in large systems, it
|
||||
is good practice to use and abuse the stack, because it is faster for allocation/deallocation, the scope of the object is well defined, and deallocation automatically happens in reverse order as allocation, which means no mem fragmentation. However, with the tiny amount of stack available in the ESP, that practice is not really viable, at least not for big objects.
|
||||
|
||||
* Large objects that have internally managed memory, such as String, std::string, std::vector, etc, are ok on the stack, because they internally allocate their buffers on the heap.
|
||||
* Large arrays on the stack, such as uint8_t buffer[2048] should be avoided on the stack and be dynamically allocated (consider smart pointers).
|
||||
* Objects that have large data members, such as large arrays, should be avoided on the stack, and be dynamically allocated (consider smart pointers).
|
||||
* Large arrays on the stack, such as uint8_t buffer[2048] should be avoided on the stack and should be dynamically allocated instead (consider smart pointers).
|
||||
* Objects that have large data members, such as large arrays, should also be avoided on the stack, and should be dynamically allocated (consider smart pointers).
|
||||
|
||||
|
||||
If at the Wall, Enter an Issue Report
|
||||
@ -330,37 +331,38 @@ author of code in his / her repository.
|
||||
|
||||
If there are no guidelines, include in your report the following:
|
||||
|
||||
- [ ] Exact steps by step instructions to reproduce the issue
|
||||
- [ ] Exact step-by-step instructions to reproduce the issue
|
||||
- [ ] Your exact hardware configuration including the schematic
|
||||
- [ ] If the issue concerns standard, commercially available ESP board
|
||||
- [ ] If the issue concerns a standard, commercially available ESP board
|
||||
with power supply and USB interface, without extra h/w attached, then
|
||||
provide just the board type or link to description
|
||||
provide just the board type or a link to its description
|
||||
- [ ] Configuration settings in Arduino IDE used to upload the
|
||||
application
|
||||
- [ ] Error log & messages produced by the application (enable
|
||||
debugging for more details)
|
||||
- [ ] Decoded stack trace
|
||||
- [ ] Copy of your sketch
|
||||
- [ ] Copy of all the libraries used by the sketch
|
||||
- [ ] If you are using standard libraries available in Library Manager,
|
||||
then provide just version numbers
|
||||
- [ ] Copy of all the libraries used by the sketch (if you are using
|
||||
standard libraries available in the Arduino Library Manager,
|
||||
then provide just version numbers)
|
||||
- [ ] Version of `esp8266 /
|
||||
Arduino <https://github.com/esp8266/Arduino>`__ core
|
||||
- [ ] Name and version of your programming IDE and O/S
|
||||
|
||||
With plenty of ESP module types available, several versions of libraries
|
||||
or `esp8266 / Arduino <https://github.com/esp8266/Arduino>`__ core,
|
||||
types and versions of O/S, you need to provide exact information what
|
||||
your application is about. Only then people willing to look into your
|
||||
issue may be able to refer it to configuration they have. If you are
|
||||
lucky, they may even attempt to reproduce your issue on their equipment.
|
||||
This will be far more difficult if you are providing only vague details,
|
||||
types and versions of O/S, you need to provide exact information on what
|
||||
your application is about. Only then, people willing to look into your
|
||||
issue may be able to compare it to a configuration they are familiar with.
|
||||
If you are lucky, they may even attempt to reproduce your issue on their
|
||||
own equipment!
|
||||
This will be far more difficult if you provide only vague details,
|
||||
so somebody would need to ask you to find out what is really happening.
|
||||
|
||||
On the other hand if you flood your issue report with hundreds lines of
|
||||
code, you may also have difficulty to find somebody willing to analyze
|
||||
it. Therefore reduce your code to the bare minimum that is still causing
|
||||
the issue. It will help you as well to isolate the issue and pin done
|
||||
On the other hand, if you flood your issue report with hundreds lines of
|
||||
code, you may also have difficulty finding somebody willing to analyze
|
||||
it. Therefore, reduce your code to the bare minimum that is still causing
|
||||
the issue. This will also help to isolate the issue and pin down
|
||||
the root cause.
|
||||
|
||||
Conclusion
|
||||
@ -371,8 +373,8 @@ Do not be afraid to troubleshoot ESP exception and watchdog restarts.
|
||||
detailed diagnostics that will help you pin down the issue. Before
|
||||
checking the s/w, get your h/w right. Use `ESP Exception
|
||||
Decoder <https://github.com/me-no-dev/EspExceptionDecoder>`__ to find
|
||||
out where the code fails. If you do you homework and still unable to
|
||||
identify the root cause, enter the issue report. Provide enough details.
|
||||
out where the code fails. If you do you homework and are still unable to
|
||||
identify the root cause, submit an issue report. Provide enough details.
|
||||
Be specific and isolate the issue. Then ask community for support. There
|
||||
are plenty of people that like to work with ESP and willing to help with
|
||||
your problem.
|
||||
|
@ -13,6 +13,7 @@ Welcome to ESP8266 Arduino Core's documentation!
|
||||
OTA Updates <ota_updates/readme>
|
||||
PROGMEM <PROGMEM>
|
||||
Using GDB to debug <gdb>
|
||||
MMU <mmu>
|
||||
|
||||
Boards <boards>
|
||||
FAQ <faq/readme>
|
||||
|
237
doc/mmu.rst
Normal file
237
doc/mmu.rst
Normal file
@ -0,0 +1,237 @@
|
||||
MMU - Adjust the Ratio of ICACHE to IRAM
|
||||
========================================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ESP8266 has a total of 64K of instruction memory, IRAM. This 64K of
|
||||
IRAM is composed of one dedicated 32K block of IRAM and two 16K blocks
|
||||
of IRAM. The last two 16K blocks of IRAM are flexible in the sense that
|
||||
it can be used as a transparent cache for external flash memory. These
|
||||
blocks can either be used for IRAM or an instruction cache for executing
|
||||
code out of flash, ICACHE.
|
||||
|
||||
The code generated for a sketch is divided up into two groups, ICACHE
|
||||
and IRAM. IRAM offers faster execution. It is used for interrupt service
|
||||
routines, exception handling, and time-critical code. The ICACHE allows
|
||||
for the execution of up to 1MB of code stored in flash. On a cache miss,
|
||||
a delay occurs as the instructions are read from flash via the SPI bus.
|
||||
|
||||
There is 98KB of DRAM space. This memory can be accessed as byte, short,
|
||||
or a 32-bit word. Access must be aligned according to the data type
|
||||
size. A 16bit short must be on a multiple of 2-byte address boundary.
|
||||
Likewise, a 32-bit word must be on a multiple of 4-byte address
|
||||
boundary. In contrast, data access in IRAM or ICACHE must always be a
|
||||
full 32-bit word and aligned. We will discuss a non32-bit exception
|
||||
handler for this later.
|
||||
|
||||
Option Summary
|
||||
--------------
|
||||
|
||||
The Arduino IDE Tools menu option, ``MMU`` has the following selections:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. ``32KB cache + 32KB IRAM (balanced)``
|
||||
|
||||
- This is the legacy ratio.
|
||||
- Try this option 1st.
|
||||
|
||||
2. ``16KB cache + 48KB IRAM (IRAM)``
|
||||
|
||||
- With just 16KB cache, execution of code out of flash may be slowed
|
||||
by more cache misses when compared to 32KB. The slowness will vary
|
||||
with the sketch.
|
||||
- Use this if you need a little more IRAM space, and you have enough
|
||||
DRAM space.
|
||||
|
||||
3. ``16KB cache + 48KB IRAM and 2nd Heap (shared)``
|
||||
|
||||
- This option builds on the previous option and creates a 2nd Heap
|
||||
made with IRAM.
|
||||
- The 2nd Heap size will vary with free IRAM.
|
||||
- This option is flexible. IRAM usage for code can overflow into the
|
||||
additional 16KB IRAM region, shrinking the 2nd Heap below 16KB. Or
|
||||
IRAM can be under 32KB, allowing the 2nd Heap to be larger than
|
||||
16KB.
|
||||
- Installs a Non-32-Bit Access handler for IRAM. This allows for
|
||||
byte and 16-bit aligned short access.
|
||||
- This 2nd Heap is supported by the standard ``malloc`` APIs.
|
||||
- Heap selection is handled through a ``HeapSelect`` class. This
|
||||
allows a specific heap selection for the duration of a scope.
|
||||
- Use this option, if you are still running out of DRAM space after
|
||||
you have moved as many of your constant strings/data elements that
|
||||
you can to PROGMEM.
|
||||
|
||||
4. ``16KB cache + 32KB IRAM + 16KB 2nd Heap (not shared)``
|
||||
|
||||
- Not managed by the ``umm_malloc`` heap library
|
||||
- If required, non-32-Bit Access for IRAM must be enabled
|
||||
separately.
|
||||
- Enables a 16KB block of unmanaged IRAM memory
|
||||
- Data persist across reboots, but not deep sleep.
|
||||
- Works well for when you need a simple large chunk of memory. This
|
||||
option will reduce the resources required to support a shared 2nd
|
||||
Heap.
|
||||
|
||||
MMU related build defines and possible values. These values change as
|
||||
indicated with the menu options above:
|
||||
|
||||
+-------------------------+--------------+--------------+------------------------------------+------------------------------+
|
||||
| ``#define`` | balanced | IRAM | shared (IRAM and Heap) | not shared (IRAM and Heap) |
|
||||
+=========================+==============+==============+====================================+==============================+
|
||||
| ``MMU_IRAM_SIZE`` | ``0x8000`` | ``0xC000`` | ``0xC000`` | ``0x8000`` |
|
||||
+-------------------------+--------------+--------------+------------------------------------+------------------------------+
|
||||
| ``MMU_ICACHE_SIZE`` | ``0x8000`` | ``0x4000`` | ``0x4000`` | ``0x4000`` |
|
||||
+-------------------------+--------------+--------------+------------------------------------+------------------------------+
|
||||
| ``MMU_IRAM_HEAP`` | -- | -- | defined, enables\ ``umm_malloc`` | -- |
|
||||
+-------------------------+--------------+--------------+------------------------------------+------------------------------+
|
||||
| ``MMU_SEC_HEAP`` | -- | \*\* | \*\* | ``0x40108000`` |
|
||||
+-------------------------+--------------+--------------+------------------------------------+------------------------------+
|
||||
| ``MMU_SEC_HEAP_SIZE`` | -- | \*\* | \*\* | ``0x4000`` |
|
||||
+-------------------------+--------------+--------------+------------------------------------+------------------------------+
|
||||
|
||||
\*\* This define is to an inline function that calculates the value,
|
||||
based on unused code space, requires ``#include <mmu_iram.h>``.
|
||||
|
||||
The Arduino IDE Tools menu option, ``Non-32-Bit Access`` has the following selections:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``Use pgm_read macros for IRAM/PROGMEM``
|
||||
- ``Byte/Word access to IRAM/PROGMEM (very slow)``
|
||||
|
||||
- This option adds a non32-bit exception handler to your build.
|
||||
- Handles read/writes to IRAM and reads to ICACHE.
|
||||
- Supports short and byte access to IRAM
|
||||
- Not recommended for high-frequency access data, use DRAM if you
|
||||
can.
|
||||
- Expect it to be slower than DRAM, each character access, will
|
||||
require a complete save and restore of all 16+ registers.
|
||||
- Processing an exception uses 256 bytes of stack space just to get
|
||||
started. The actual handler will add a little more.
|
||||
- This option is implicitly enabled and required when you select MMU
|
||||
option ``16KB cache + 48KB IRAM and 2nd Heap (shared)``.
|
||||
|
||||
IRAM, unlike DRAM, must be accessed as aligned full 32-bit words, no
|
||||
byte or short access. The pgm\_read macros are an option; however, the
|
||||
store operation remains an issue. For a block copy, ets\_memcpy appears
|
||||
to work well as long as the byte count is rounded up to be evenly
|
||||
divided by 4, and source and destination addresses are 4 bytes aligned.
|
||||
|
||||
A word of caution, I have seen one case with the new toolchain 10.1
|
||||
where code that reads a 32-bit word to extract a byte was optimized away
|
||||
to be a byte read. Using ``volatile`` on the pointer stopped the
|
||||
over-optimization.
|
||||
|
||||
To get a sense of how memory access time is effected, see examples
|
||||
``MMU48K`` and ``irammem`` in ``ESP8266``.
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
For calls to ``umm_malloc`` with interrupts disabled.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``malloc`` will always allocate from the ``DRAM`` heap when called
|
||||
with interrupts disabled.
|
||||
- ``realloc`` with a NULL pointer will use ``malloc`` and return a
|
||||
``DRAM`` heap allocation. Note, calling ``realloc`` with interrupts
|
||||
disabled is **not** officially supported. You are on your own if you
|
||||
do this.
|
||||
- If you must use IRAM memory in your ISR, allocate the memory in your
|
||||
init code. To reduce the time spent in the ISR, avoid non32-bit
|
||||
access that would trigger the exception handler. For short or byte
|
||||
access, consider using the inline functions described in section
|
||||
"Performance Functions" below.
|
||||
|
||||
How to Select Heap
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``MMU`` selection ``16KB cache + 48KB IRAM and 2nd Heap (shared)``
|
||||
allows you to use the standard heap API function calls (``malloc``,
|
||||
``calloc``, ``free``, ... ). to allocate memory from DRAM or IRAM. This
|
||||
selection can be made by instantiating the class ``HeapSelectIram`` or
|
||||
``HeapSelectDram``. The usage is similar to that of the
|
||||
``InterruptLock`` class. The default/initial heap source is DRAM. The
|
||||
class is in ``umm_malloc/umm_heap_select.h``.
|
||||
|
||||
::
|
||||
|
||||
...
|
||||
char *bufferDram;
|
||||
bufferDram = (char *)malloc(33);
|
||||
char *bufferIram;
|
||||
{
|
||||
HeapSelectIram ephemeral;
|
||||
bufferIram = (char *)malloc(33);
|
||||
}
|
||||
...
|
||||
free(bufferIram);
|
||||
free(bufferDram);
|
||||
...
|
||||
|
||||
``free`` will always return memory to the correct heap. There is no need
|
||||
for tracking and selecting before freeing.
|
||||
|
||||
``realloc`` with a non-NULL pointer will always resize the allocation
|
||||
from the original heap it was allocated from. When the supplied pointer
|
||||
is NULL, then the current heap selection is used.
|
||||
|
||||
Low-level primitives for selecting a heap. These are used by the above
|
||||
Classes:
|
||||
|
||||
- ``umm_get_current_heap_id()``
|
||||
- ``umm_set_heap_by_id( ID value )``
|
||||
- Possible ID values
|
||||
- ``UMM_HEAP_DRAM``
|
||||
- ``UMM_HEAP_IRAM``
|
||||
|
||||
Also, an alternate stack select method API is available. This is not as
|
||||
easy as the class method; however, for some small set of cases, it may
|
||||
provide some additional control:
|
||||
|
||||
- ``ESP.setIramHeap()`` Pushes current heap ID onto a stack and sets
|
||||
Heap API for an IRAM selection.
|
||||
- ``ESP.setDramHeap()`` Pushes current heap ID onto a stack and sets
|
||||
Heap API for a DRAM selection.
|
||||
- ``ESP.resetHeap()`` Restores previously pushed heap. ### Identify
|
||||
Memory
|
||||
|
||||
These always inlined functions can be used to determine the resource of
|
||||
a pointer:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
bool mmu_is_iram(const void *addr);
|
||||
bool mmu_is_dram(const void *addr);
|
||||
bool mmu_is_icache(const void *addr);
|
||||
|
||||
Performance Functions
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While these always inlined functions, will bypass the need for the
|
||||
exception handler reducing execution time and stack use, it comes at the
|
||||
cost of increased code size.
|
||||
|
||||
These are an alternative to the ``pgm_read`` macros for reading from
|
||||
IRAM. When compiled with 'Debug Level: core' range checks are performed
|
||||
on the pointer value to make sure you are reading from the address range
|
||||
of IRAM, DRAM, or ICACHE.
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
uint8_t mmu_get_uint8(const void *p8);
|
||||
uint16_t mmu_get_uint16(const uint16_t *p16);
|
||||
int16_t mmu_get_int16(const int16_t *p16);
|
||||
|
||||
While these functions are intended for writing to IRAM, they will work
|
||||
with DRAM. When compiled with 'Debug Level: core', range checks are
|
||||
performed on the pointer value to make sure you are writing to the
|
||||
address range of IRAM or DRAM.
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
uint8_t mmu_set_uint8(void *p8, const uint8_t val);
|
||||
uint16_t mmu_set_uint16(uint16_t *p16, const uint16_t val);
|
||||
int16_t mmu_set_int16(int16_t *p16, const int16_t val);
|
||||
|
||||
::
|
@ -44,9 +44,9 @@ Make your own risk analysis and, depending on the application, decide what libra
|
||||
Advanced Security - Signed Updates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While the above password-based security will dissuade casual hacking attempts, it is not highly secure. For applications where a higher level of security is needed, cryptographically signed OTA updates can be required. This uses SHA256 hashing in place of MD5 (which is known to be cryptographically broken) and RSA-2048 bit level encryption to guarantee that only the holder of a cryptographic private key can generate code accepted by the OTA update mechanisms.
|
||||
While the above password-based security will dissuade casual hacking attempts, it is not highly secure. For applications where a higher level of security is needed, cryptographically signed OTA updates can be required. This uses SHA256 hashing in place of MD5 (which is known to be cryptographically broken) and RSA-2048 bit level public-key encryption to guarantee that only the holder of a cryptographic private key can produce signed updates accepted by the OTA update mechanisms.
|
||||
|
||||
Signed updates are updates whose compiled binaries are signed with a private key (held by the developer) and verified with a public key (stored in the application and available for all to see). The signing process computes a hash of the binary code, encrypts the hash with the developer's private key, and appends this encrypted hash to the binary that is uploaded (via OTA, web, or HTTP server). If the code is modified or replaced in any way by anyone except the holder of the developer's private key, the hash will not match and the ESP8266 will reject the upload.
|
||||
Signed updates are updates whose compiled binaries are signed with a private key (held by the developer) and verified with a public key (stored in the application and available for all to see). The signing process computes a hash of the binary code, encrypts the hash with the developer's private key, and appends this encrypted hash (also called a signature) to the binary that is uploaded (via OTA, web, or HTTP server). If the code is modified or replaced in any way by anyone except the holder of the developer's private key, the signature will not match and the ESP8266 will reject the upload.
|
||||
|
||||
Cryptographic signing only protects against tampering with binaries delivered via OTA. If someone has physical access, they will always be able to flash the device over the serial port. Signing also does not encrypt anything but the hash (so that it can't be modified), so this does not protect code inside the device: if a user has physical access they can read out your program.
|
||||
|
||||
@ -57,11 +57,11 @@ Signed Binary Format
|
||||
|
||||
The format of a signed binary is compatible with the standard binary format, and can be uploaded to a non-signed ESP8266 via serial or OTA without any conditions. Note, however, that once an unsigned OTA app is overwritten by this signed version, further updates will require signing.
|
||||
|
||||
As shown below, the signed hash is appended to the unsigned binary, followed by the total length of the signed hash (i.e., if the signed hash was 64 bytes, then this uint32 data segment will contain 64). This format allows for extensibility (such as adding a CA-based validation scheme allowing multiple signing keys all based on a trust anchor). Pull requests are always welcome.
|
||||
As shown below, the signed hash is appended to the unsigned binary, followed by the total length of the signed hash (i.e., if the signed hash was 64 bytes, then this uint32 data segment will contain 64). This format allows for extensibility (such as adding a CA-based validation scheme allowing multiple signing keys all based on a trust anchor). Pull requests are always welcome. (currently it uses SHA256 with RSASSA-PKCS1-V1_5-SIGN signature scheme from RSA PKCS #1 v1.5)
|
||||
|
||||
.. code:: bash
|
||||
|
||||
NORMAL-BINARY <SIGNED HASH> <uint32 LENGTH-OF-SIGNING-DATA-INCLUDING-THIS-32-BITS>
|
||||
NORMAL-BINARY <SIGNATURE> <uint32 LENGTH-OF-SIGNATURE>
|
||||
|
||||
Signed Binary Prerequisites
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -13,6 +13,7 @@ analogWriteFreq KEYWORD2
|
||||
analogWriteRange KEYWORD2
|
||||
baudrate KEYWORD2
|
||||
swap KEYWORD2
|
||||
enablePhaseLockedWaveform KEYWORD2
|
||||
|
||||
######################################
|
||||
# Constants (LITERAL1)
|
||||
|
@ -359,8 +359,12 @@ uint8_t ESP8266AVRISP::flash_read(uint8_t hilo, int addr) {
|
||||
0);
|
||||
}
|
||||
|
||||
void ESP8266AVRISP::flash_read_page(int length) {
|
||||
bool ESP8266AVRISP::flash_read_page(int length) {
|
||||
uint8_t *data = (uint8_t *) malloc(length + 1);
|
||||
if (!data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int x = 0; x < length; x += 2) {
|
||||
*(data + x) = flash_read(LOW, here);
|
||||
*(data + x + 1) = flash_read(HIGH, here);
|
||||
@ -369,12 +373,16 @@ void ESP8266AVRISP::flash_read_page(int length) {
|
||||
*(data + length) = Resp_STK_OK;
|
||||
_client.write((const uint8_t *)data, (size_t)(length + 1));
|
||||
free(data);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ESP8266AVRISP::eeprom_read_page(int length) {
|
||||
bool ESP8266AVRISP::eeprom_read_page(int length) {
|
||||
// here again we have a word address
|
||||
uint8_t *data = (uint8_t *) malloc(length + 1);
|
||||
if (!data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int start = here * 2;
|
||||
for (int x = 0; x < length; x++) {
|
||||
int addr = start + x;
|
||||
@ -384,7 +392,7 @@ void ESP8266AVRISP::eeprom_read_page(int length) {
|
||||
*(data + length) = Resp_STK_OK;
|
||||
_client.write((const uint8_t *)data, (size_t)(length + 1));
|
||||
free(data);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ESP8266AVRISP::read_page() {
|
||||
|
@ -89,8 +89,8 @@ protected:
|
||||
void commit(int addr);
|
||||
void program_page();
|
||||
uint8_t flash_read(uint8_t hilo, int addr);
|
||||
void flash_read_page(int length);
|
||||
void eeprom_read_page(int length);
|
||||
bool flash_read_page(int length);
|
||||
bool eeprom_read_page(int length);
|
||||
void read_page();
|
||||
void read_signature();
|
||||
|
||||
|
@ -725,7 +725,7 @@ int HTTPClient::writeToStream(Stream * stream)
|
||||
int ret = 0;
|
||||
|
||||
if(_transferEncoding == HTTPC_TE_IDENTITY) {
|
||||
if(len > 0) {
|
||||
if(len > 0 || len == -1) {
|
||||
ret = writeToStreamDataBlock(stream, len);
|
||||
|
||||
// have we an error?
|
||||
@ -1125,18 +1125,18 @@ int HTTPClient::handleHeaderResponse()
|
||||
if(transferEncoding.equalsIgnoreCase(F("chunked"))) {
|
||||
_transferEncoding = HTTPC_TE_CHUNKED;
|
||||
} else {
|
||||
return HTTPC_ERROR_ENCODING;
|
||||
_returnCode = HTTPC_ERROR_ENCODING;
|
||||
return _returnCode;
|
||||
}
|
||||
} else {
|
||||
_transferEncoding = HTTPC_TE_IDENTITY;
|
||||
}
|
||||
|
||||
if(_returnCode) {
|
||||
return _returnCode;
|
||||
} else {
|
||||
if(_returnCode <= 0) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Remote host is not an HTTP Server!");
|
||||
return HTTPC_ERROR_NO_HTTP_SERVER;
|
||||
_returnCode = HTTPC_ERROR_NO_HTTP_SERVER;
|
||||
}
|
||||
return _returnCode;
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -1185,6 +1185,12 @@ int HTTPClient::writeToStreamDataBlock(Stream * stream, int size)
|
||||
readBytes = buff_size;
|
||||
}
|
||||
|
||||
// len == -1 or len > what is available, read only what is available
|
||||
int av = _client->available();
|
||||
if (readBytes < 0 || readBytes > av) {
|
||||
readBytes = av;
|
||||
}
|
||||
|
||||
// read data
|
||||
int bytesRead = _client->readBytes(buff, readBytes);
|
||||
if (!bytesRead)
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit b240d2231a117bbd89b79902eb54cae948ee2f42
|
||||
Subproject commit 0a46e4ebb2de585c5a64f981dbc2b2223a438984
|
@ -13,6 +13,8 @@
|
||||
#include <WiFiClient.h>
|
||||
#include <ESP8266WebServerSecure.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <umm_malloc/umm_malloc.h>
|
||||
#include <umm_malloc/umm_heap_select.h>
|
||||
|
||||
#ifndef STASSID
|
||||
#define STASSID "your-ssid"
|
||||
@ -23,6 +25,7 @@ const char* ssid = STASSID;
|
||||
const char* password = STAPSK;
|
||||
|
||||
BearSSL::ESP8266WebServerSecure server(443);
|
||||
BearSSL::ServerSessions serverCache(5);
|
||||
|
||||
static const char serverCert[] PROGMEM = R"EOF(
|
||||
-----BEGIN CERTIFICATE-----
|
||||
@ -130,6 +133,9 @@ void setup(void){
|
||||
|
||||
server.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey));
|
||||
|
||||
// Cache SSL sessions to accelerate the TLS handshake.
|
||||
server.getServer().setCache(&serverCache);
|
||||
|
||||
server.on("/", handleRoot);
|
||||
|
||||
server.on("/inline", [](){
|
||||
@ -142,7 +148,68 @@ void setup(void){
|
||||
Serial.println("HTTPS server started");
|
||||
}
|
||||
|
||||
extern "C" void stack_thunk_dump_stack();
|
||||
|
||||
void processKey(Print& out, int hotKey) {
|
||||
switch (hotKey) {
|
||||
case 'd': {
|
||||
HeapSelectDram ephemeral;
|
||||
umm_info(NULL, true);
|
||||
break;
|
||||
}
|
||||
case 'i': {
|
||||
HeapSelectIram ephemeral;
|
||||
umm_info(NULL, true);
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
{
|
||||
HeapSelectIram ephemeral;
|
||||
Serial.printf(PSTR("IRAM ESP.getFreeHeap: %u\n"), ESP.getFreeHeap());
|
||||
}
|
||||
{
|
||||
HeapSelectDram ephemeral;
|
||||
Serial.printf(PSTR("DRAM ESP.getFreeHeap: %u\n"), ESP.getFreeHeap());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'P':
|
||||
out.println(F("Calling stack_thunk_dump_stack();"));
|
||||
stack_thunk_dump_stack();
|
||||
break;
|
||||
case 'R':
|
||||
out.printf_P(PSTR("Restart, ESP.restart(); ...\r\n"));
|
||||
ESP.restart();
|
||||
break;
|
||||
case '\r':
|
||||
out.println();
|
||||
case '\n':
|
||||
break;
|
||||
case '?':
|
||||
out.println();
|
||||
out.println(F("Press a key + <enter>"));
|
||||
out.println(F(" h - Free Heap Report;"));
|
||||
out.println(F(" i - iRAM umm_info(null, true);"));
|
||||
out.println(F(" d - dRAM umm_info(null, true);"));
|
||||
out.println(F(" p - call stack_thunk_dump_stack();"));
|
||||
out.println(F(" R - Restart, ESP.restart();"));
|
||||
out.println(F(" ? - Print Help"));
|
||||
out.println();
|
||||
break;
|
||||
default:
|
||||
out.printf_P(PSTR("\"%c\" - Not an option? / ? - help"), hotKey);
|
||||
out.println();
|
||||
processKey(out, '?');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void loop(void){
|
||||
server.handleClient();
|
||||
MDNS.update();
|
||||
if (Serial.available() > 0) {
|
||||
int hotKey = Serial.read();
|
||||
processKey(Serial, hotKey);
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "WiFiClient.h"
|
||||
#include "ESP8266WebServer.h"
|
||||
#include "FS.h"
|
||||
#include "base64.h"
|
||||
#include "detail/RequestHandlersImpl.h"
|
||||
|
||||
static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization";
|
||||
@ -33,6 +34,7 @@ static const char qop_auth[] PROGMEM = "qop=auth";
|
||||
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
|
||||
static const char WWW_Authenticate[] PROGMEM = "WWW-Authenticate";
|
||||
static const char Content_Length[] PROGMEM = "Content-Length";
|
||||
static const char ETAG_HEADER[] PROGMEM = "If-None-Match";
|
||||
|
||||
namespace esp8266webserver {
|
||||
|
||||
@ -98,21 +100,19 @@ bool ESP8266WebServerTemplate<ServerType>::authenticate(const char * username, c
|
||||
authReq = "";
|
||||
return false;
|
||||
}
|
||||
char *encoded = new (std::nothrow) char[base64_encode_expected_len(toencodeLen)+1];
|
||||
if(encoded == NULL){
|
||||
sprintf(toencode, "%s:%s", username, password);
|
||||
String encoded = base64::encode((uint8_t *)toencode, toencodeLen, false);
|
||||
if(!encoded){
|
||||
authReq = "";
|
||||
delete[] toencode;
|
||||
return false;
|
||||
}
|
||||
sprintf(toencode, "%s:%s", username, password);
|
||||
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) {
|
||||
if(authReq.equalsConstantTime(encoded)) {
|
||||
authReq = "";
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
return true;
|
||||
}
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
} else if(authReq.startsWith(F("Digest"))) {
|
||||
String _realm = _extractParam(authReq, F("realm=\""));
|
||||
String _H1 = credentialHash((String)username,_realm,(String)password);
|
||||
@ -255,7 +255,18 @@ void ESP8266WebServerTemplate<ServerType>::_addRequestHandler(RequestHandlerType
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
|
||||
_addRequestHandler(new StaticRequestHandler<ServerType>(fs, path, uri, cache_header));
|
||||
bool is_file = false;
|
||||
|
||||
if (fs.exists(path)) {
|
||||
File file = fs.open(path, "r");
|
||||
is_file = file && file.isFile();
|
||||
file.close();
|
||||
}
|
||||
|
||||
if(is_file)
|
||||
_addRequestHandler(new StaticFileRequestHandler<ServerType>(fs, path, uri, cache_header));
|
||||
else
|
||||
_addRequestHandler(new StaticDirectoryRequestHandler<ServerType>(fs, path, uri, cache_header));
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
@ -607,15 +618,18 @@ const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) c
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
|
||||
template<typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
|
||||
_headerKeysCount = headerKeysCount + 1;
|
||||
if (_currentHeaders)
|
||||
delete[]_currentHeaders;
|
||||
_headerKeysCount = headerKeysCount + 2;
|
||||
if (_currentHeaders){
|
||||
delete[] _currentHeaders;
|
||||
}
|
||||
_currentHeaders = new RequestArgument[_headerKeysCount];
|
||||
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
||||
for (int i = 1; i < _headerKeysCount; i++){
|
||||
_currentHeaders[i].key = headerKeys[i-1];
|
||||
_currentHeaders[1].key = FPSTR(ETAG_HEADER);
|
||||
for (int i = 2; i < _headerKeysCount; i++){
|
||||
_currentHeaders[i].key = headerKeys[i-2];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,7 +246,7 @@ protected:
|
||||
bool _parseForm(ClientType& client, const String& boundary, uint32_t len);
|
||||
bool _parseFormUploadAborted();
|
||||
void _uploadWriteByte(uint8_t b);
|
||||
uint8_t _uploadReadByte(ClientType& client);
|
||||
int _uploadReadByte(ClientType& client);
|
||||
void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
|
||||
bool _collectHeader(const char* headerName, const char* headerValue);
|
||||
|
||||
@ -308,5 +308,4 @@ protected:
|
||||
using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate<WiFiServer>;
|
||||
using RequestHandler = esp8266webserver::RequestHandler<WiFiServer>;
|
||||
|
||||
|
||||
#endif //ESP8266WEBSERVER_H
|
@ -347,16 +347,17 @@ void ESP8266WebServerTemplate<ServerType>::_uploadWriteByte(uint8_t b){
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
uint8_t ESP8266WebServerTemplate<ServerType>::_uploadReadByte(ClientType& client){
|
||||
int ESP8266WebServerTemplate<ServerType>::_uploadReadByte(ClientType& client){
|
||||
int res = client.read();
|
||||
if(res == -1){
|
||||
while(!client.available() && client.connected())
|
||||
yield();
|
||||
res = client.read();
|
||||
}
|
||||
return (uint8_t)res;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
template <typename ServerType>
|
||||
bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const String& boundary, uint32_t len){
|
||||
(void) len;
|
||||
@ -442,74 +443,59 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
_currentUpload->status = UPLOAD_FILE_WRITE;
|
||||
uint8_t argByte = _uploadReadByte(client);
|
||||
readfile:
|
||||
while(argByte != 0x0D){
|
||||
if (!client.connected()) return _parseFormUploadAborted();
|
||||
_uploadWriteByte(argByte);
|
||||
argByte = _uploadReadByte(client);
|
||||
}
|
||||
|
||||
argByte = _uploadReadByte(client);
|
||||
int fastBoundaryLen = 4 /* \r\n-- */ + boundary.length() + 1 /* \0 */;
|
||||
char fastBoundary[ fastBoundaryLen ];
|
||||
snprintf(fastBoundary, fastBoundaryLen, "\r\n--%s", boundary.c_str());
|
||||
int boundaryPtr = 0;
|
||||
while ( true ) {
|
||||
int ret = _uploadReadByte(client);
|
||||
if (ret < 0) {
|
||||
// Unexpected, we should have had data available per above
|
||||
return _parseFormUploadAborted();
|
||||
}
|
||||
char in = (char) ret;
|
||||
if (in == fastBoundary[ boundaryPtr ]) {
|
||||
// The input matched the current expected character, advance and possibly exit this file
|
||||
boundaryPtr++;
|
||||
if (boundaryPtr == fastBoundaryLen - 1) {
|
||||
// We read the whole boundary line, we're done here!
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// The char doesn't match what we want, so dump whatever matches we had, the read in char, and reset ptr to start
|
||||
for (int i = 0; i < boundaryPtr; i++) {
|
||||
_uploadWriteByte( fastBoundary[ i ] );
|
||||
}
|
||||
if (in == fastBoundary[ 0 ]) {
|
||||
// This could be the start of the real end, mark it so and don't emit/skip it
|
||||
boundaryPtr = 1;
|
||||
} else {
|
||||
// Not the 1st char of our pattern, so emit and ignore
|
||||
_uploadWriteByte( in );
|
||||
boundaryPtr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Found the boundary string, finish processing this file upload
|
||||
if (_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->status = UPLOAD_FILE_END;
|
||||
if (_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
DBGWS("End File: %s Type: %s Size: %d\n",
|
||||
_currentUpload->filename.c_str(),
|
||||
_currentUpload->type.c_str(),
|
||||
(int)_currentUpload->totalSize);
|
||||
if (!client.connected()) return _parseFormUploadAborted();
|
||||
if (argByte == 0x0A){
|
||||
argByte = _uploadReadByte(client);
|
||||
if (!client.connected()) return _parseFormUploadAborted();
|
||||
if ((char)argByte != '-'){
|
||||
//continue reading the file
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
goto readfile;
|
||||
} else {
|
||||
argByte = _uploadReadByte(client);
|
||||
if (!client.connected()) return _parseFormUploadAborted();
|
||||
if ((char)argByte != '-'){
|
||||
//continue reading the file
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
goto readfile;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t endBuf[boundary.length()];
|
||||
client.readBytes(endBuf, boundary.length());
|
||||
|
||||
if (strstr((const char*)endBuf, boundary.c_str()) != NULL){
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->status = UPLOAD_FILE_END;
|
||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
DBGWS("End File: %s Type: %s Size: %d\n",
|
||||
_currentUpload->filename.c_str(),
|
||||
_currentUpload->type.c_str(),
|
||||
(int)_currentUpload->totalSize);
|
||||
line = client.readStringUntil(0x0D);
|
||||
client.readStringUntil(0x0A);
|
||||
if (line == "--"){
|
||||
DBGWS("Done Parsing POST\n");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
_uploadWriteByte(0x0D);
|
||||
_uploadWriteByte(0x0A);
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
_uploadWriteByte((uint8_t)('-'));
|
||||
uint32_t i = 0;
|
||||
while(i < boundary.length()){
|
||||
_uploadWriteByte(endBuf[i++]);
|
||||
}
|
||||
argByte = _uploadReadByte(client);
|
||||
goto readfile;
|
||||
}
|
||||
} else {
|
||||
_uploadWriteByte(0x0D);
|
||||
goto readfile;
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line == "--") { // extra two dashes mean we reached the end of all form fields
|
||||
DBGWS("Done Parsing POST\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,83 +72,11 @@ public:
|
||||
, _path(path)
|
||||
, _cache_header(cache_header)
|
||||
{
|
||||
if (fs.exists(path)) {
|
||||
File file = fs.open(path, "r");
|
||||
_isFile = file && file.isFile();
|
||||
file.close();
|
||||
}
|
||||
else {
|
||||
_isFile = false;
|
||||
}
|
||||
|
||||
DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header == __null ? "" : cache_header);
|
||||
_baseUriLength = _uri.length();
|
||||
DEBUGV("StaticRequestHandler: path=%s uri=%s, cache_header=%s\r\n", path, uri, cache_header == __null ? "" : cache_header);
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, const String& requestUri) override {
|
||||
if ((requestMethod != HTTP_GET) && (requestMethod != HTTP_HEAD))
|
||||
return false;
|
||||
|
||||
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServerType& server, HTTPMethod requestMethod, const String& requestUri) override {
|
||||
|
||||
if (!canHandle(requestMethod, requestUri))
|
||||
return false;
|
||||
|
||||
DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
||||
|
||||
String path;
|
||||
path.reserve(_path.length() + requestUri.length() + 32);
|
||||
path = _path;
|
||||
|
||||
if (!_isFile) {
|
||||
|
||||
// Append whatever follows this URI in request to get the file path.
|
||||
path += requestUri.substring(_baseUriLength);
|
||||
|
||||
// Base URI doesn't point to a file.
|
||||
// If a directory is requested, look for index file.
|
||||
if (path.endsWith("/"))
|
||||
path += F("index.htm");
|
||||
|
||||
// If neither <blah> nor <blah>.gz exist, and <blah> is a file.htm, try it with file.html instead
|
||||
// For the normal case this will give a search order of index.htm, index.htm.gz, index.html, index.html.gz
|
||||
if (!_fs.exists(path) && !_fs.exists(path + ".gz") && path.endsWith(".htm")) {
|
||||
path += 'l';
|
||||
}
|
||||
}
|
||||
DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
||||
|
||||
String contentType = mime::getContentType(path);
|
||||
|
||||
using namespace mime;
|
||||
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
|
||||
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
|
||||
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
|
||||
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
|
||||
if(_fs.exists(pathWithGz))
|
||||
path += FPSTR(mimeTable[gz].endsWith);
|
||||
}
|
||||
|
||||
File f = _fs.open(path, "r");
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
if (!f.isFile()) {
|
||||
f.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_cache_header.length() != 0)
|
||||
server.sendHeader("Cache-Control", _cache_header);
|
||||
|
||||
server.streamFile(f, contentType, requestMethod);
|
||||
return true;
|
||||
bool validMethod(HTTPMethod requestMethod){
|
||||
return (requestMethod == HTTP_GET) || (requestMethod == HTTP_HEAD);
|
||||
}
|
||||
|
||||
/* Deprecated version. Please use mime::getContentType instead */
|
||||
@ -161,10 +89,144 @@ protected:
|
||||
String _uri;
|
||||
String _path;
|
||||
String _cache_header;
|
||||
bool _isFile;
|
||||
};
|
||||
|
||||
|
||||
template<typename ServerType>
|
||||
class StaticDirectoryRequestHandler : public StaticRequestHandler<ServerType> {
|
||||
|
||||
using SRH = StaticRequestHandler<ServerType>;
|
||||
using WebServerType = ESP8266WebServerTemplate<ServerType>;
|
||||
|
||||
public:
|
||||
StaticDirectoryRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
|
||||
:
|
||||
SRH(fs, path, uri, cache_header),
|
||||
_baseUriLength{SRH::_uri.length()}
|
||||
{}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, const String& requestUri) override {
|
||||
return SRH::validMethod(requestMethod) && requestUri.startsWith(SRH::_uri);
|
||||
}
|
||||
|
||||
bool handle(WebServerType& server, HTTPMethod requestMethod, const String& requestUri) override {
|
||||
|
||||
if (!canHandle(requestMethod, requestUri))
|
||||
return false;
|
||||
|
||||
DEBUGV("DirectoryRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), SRH::_uri.c_str());
|
||||
|
||||
String path;
|
||||
path.reserve(SRH::_path.length() + requestUri.length() + 32);
|
||||
path = SRH::_path;
|
||||
|
||||
// Append whatever follows this URI in request to get the file path.
|
||||
path += requestUri.substring(_baseUriLength);
|
||||
|
||||
// Base URI doesn't point to a file.
|
||||
// If a directory is requested, look for index file.
|
||||
if (path.endsWith("/"))
|
||||
path += F("index.htm");
|
||||
|
||||
// If neither <blah> nor <blah>.gz exist, and <blah> is a file.htm, try it with file.html instead
|
||||
// For the normal case this will give a search order of index.htm, index.htm.gz, index.html, index.html.gz
|
||||
if (!SRH::_fs.exists(path) && !SRH::_fs.exists(path + ".gz") && path.endsWith(".htm")) {
|
||||
path += 'l';
|
||||
}
|
||||
|
||||
DEBUGV("DirectoryRequestHandler::handle: path=%s\r\n", path.c_str());
|
||||
|
||||
String contentType = mime::getContentType(path);
|
||||
|
||||
using namespace mime;
|
||||
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
|
||||
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
|
||||
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !SRH::_fs.exists(path)) {
|
||||
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
|
||||
if(SRH::_fs.exists(pathWithGz))
|
||||
path += FPSTR(mimeTable[gz].endsWith);
|
||||
}
|
||||
|
||||
File f = SRH::_fs.open(path, "r");
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
if (!f.isFile()) {
|
||||
f.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SRH::_cache_header.length() != 0)
|
||||
server.sendHeader("Cache-Control", SRH::_cache_header);
|
||||
|
||||
server.streamFile(f, contentType, requestMethod);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t _baseUriLength;
|
||||
};
|
||||
|
||||
template<typename ServerType>
|
||||
class StaticFileRequestHandler
|
||||
:
|
||||
public StaticRequestHandler<ServerType> {
|
||||
|
||||
using SRH = StaticRequestHandler<ServerType>;
|
||||
using WebServerType = ESP8266WebServerTemplate<ServerType>;
|
||||
|
||||
public:
|
||||
StaticFileRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
|
||||
:
|
||||
StaticRequestHandler<ServerType>{fs, path, uri, cache_header}
|
||||
{
|
||||
File f = SRH::_fs.open(path, "r");
|
||||
MD5Builder calcMD5;
|
||||
calcMD5.begin();
|
||||
calcMD5.addStream(f, f.size());
|
||||
calcMD5.calculate();
|
||||
calcMD5.getBytes(_ETag_md5);
|
||||
f.close();
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, const String& requestUri) override {
|
||||
return SRH::validMethod(requestMethod) && requestUri == SRH::_uri;
|
||||
}
|
||||
|
||||
bool handle(WebServerType& server, HTTPMethod requestMethod, const String & requestUri) override {
|
||||
if (!canHandle(requestMethod, requestUri))
|
||||
return false;
|
||||
|
||||
const String etag = "\"" + base64::encode(_ETag_md5, 16, false) + "\"";
|
||||
|
||||
if(server.header("If-None-Match") == etag){
|
||||
server.send(304);
|
||||
return true;
|
||||
}
|
||||
|
||||
File f = SRH::_fs.open(SRH::_path, "r");
|
||||
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
if (!f.isFile()) {
|
||||
f.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SRH::_cache_header.length() != 0)
|
||||
server.sendHeader("Cache-Control", SRH::_cache_header);
|
||||
|
||||
server.sendHeader("ETag", etag);
|
||||
|
||||
server.streamFile(f, mime::getContentType(SRH::_path), requestMethod);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t _ETag_md5[16];
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif //REQUESTHANDLERSIMPL_H
|
@ -138,6 +138,21 @@ GBEnkz4KpKv7TkHoW+j7F5EMcLcSrUIpyw==
|
||||
|
||||
#endif
|
||||
|
||||
#define CACHE_SIZE 5 // Number of sessions to cache.
|
||||
#define USE_CACHE // Enable SSL session caching.
|
||||
// Caching SSL sessions shortens the length of the SSL handshake.
|
||||
// You can see the performance improvement by looking at the
|
||||
// Network tab of the developper tools of your browser.
|
||||
//#define DYNAMIC_CACHE // Whether to dynamically allocate the cache.
|
||||
|
||||
#if defined(USE_CACHE) && defined(DYNAMIC_CACHE)
|
||||
// Dynamically allocated cache.
|
||||
BearSSL::ServerSessions serverCache(CACHE_SIZE);
|
||||
#elif defined(USE_CACHE)
|
||||
// Statically allocated cache.
|
||||
ServerSession store[CACHE_SIZE];
|
||||
BearSSL::ServerSessions serverCache(store, CACHE_SIZE);
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
@ -169,6 +184,11 @@ void setup() {
|
||||
server.setECCert(serverCertList, BR_KEYTYPE_KEYX|BR_KEYTYPE_SIGN, serverPrivKey);
|
||||
#endif
|
||||
|
||||
// Set the server's cache
|
||||
#if defined(USE_CACHE)
|
||||
server.setCache(&serverCache);
|
||||
#endif
|
||||
|
||||
// Actually start accepting connections
|
||||
server.begin();
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <lwip/napt.h>
|
||||
#include <lwip/dns.h>
|
||||
#include <dhcpserver.h>
|
||||
#include <LwipDhcpServer.h>
|
||||
|
||||
#define NAPT 1000
|
||||
#define NAPT_PORT 10
|
||||
@ -57,8 +57,8 @@ void setup() {
|
||||
WiFi.dnsIP(1).toString().c_str());
|
||||
|
||||
// give DNS servers to AP side
|
||||
dhcps_set_dns(0, WiFi.dnsIP(0));
|
||||
dhcps_set_dns(1, WiFi.dnsIP(1));
|
||||
dhcpSoftAP.dhcps_set_dns(0, WiFi.dnsIP(0));
|
||||
dhcpSoftAP.dhcps_set_dns(1, WiFi.dnsIP(1));
|
||||
|
||||
WiFi.softAPConfig( // enable AP, with android-compatible google domain
|
||||
IPAddress(172, 217, 28, 254),
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <LwipDhcpServer.h>
|
||||
|
||||
/* Set these to your desired credentials. */
|
||||
const char *ssid = "ESPap";
|
||||
@ -75,8 +76,8 @@ void setup() {
|
||||
...
|
||||
any client not listed will use next IP address available from the range (here 192.168.0.102 and more)
|
||||
*/
|
||||
wifi_softap_add_dhcps_lease(mac_CAM); // always 192.168.0.100
|
||||
wifi_softap_add_dhcps_lease(mac_PC); // always 192.168.0.101
|
||||
dhcpSoftAP.add_dhcps_lease(mac_CAM); // always 192.168.0.100
|
||||
dhcpSoftAP.add_dhcps_lease(mac_PC); // always 192.168.0.101
|
||||
/* Start Access Point. You can remove the password parameter if you want the AP to be open. */
|
||||
WiFi.softAP(ssid, password);
|
||||
Serial.print("AP IP address: ");
|
||||
|
@ -3,43 +3,57 @@
|
||||
The API is almost the same as with the WiFi Shield library,
|
||||
the most obvious difference being the different file you need to include:
|
||||
*/
|
||||
#include "ESP8266WiFi.h"
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println(F("\nESP8266 WiFi scan example"));
|
||||
|
||||
// Set WiFi to station mode and disconnect from an AP if it was previously connected
|
||||
// Set WiFi to station mode
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
// Disconnect from an AP if it was previously connected
|
||||
WiFi.disconnect();
|
||||
delay(100);
|
||||
|
||||
Serial.println("Setup done");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Serial.println("scan start");
|
||||
String ssid;
|
||||
int32_t rssi;
|
||||
uint8_t encryptionType;
|
||||
uint8_t* bssid;
|
||||
int32_t channel;
|
||||
bool hidden;
|
||||
int scanResult;
|
||||
|
||||
// WiFi.scanNetworks will return the number of networks found
|
||||
int n = WiFi.scanNetworks();
|
||||
Serial.println("scan done");
|
||||
if (n == 0) {
|
||||
Serial.println("no networks found");
|
||||
} else {
|
||||
Serial.print(n);
|
||||
Serial.println(" networks found");
|
||||
for (int i = 0; i < n; ++i) {
|
||||
// Print SSID and RSSI for each network found
|
||||
Serial.print(i + 1);
|
||||
Serial.print(": ");
|
||||
Serial.print(WiFi.SSID(i));
|
||||
Serial.print(" (");
|
||||
Serial.print(WiFi.RSSI(i));
|
||||
Serial.print(")");
|
||||
Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? " " : "*");
|
||||
delay(10);
|
||||
Serial.println(F("Starting WiFi scan..."));
|
||||
|
||||
scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true);
|
||||
|
||||
if (scanResult == 0) {
|
||||
Serial.println(F("No networks found"));
|
||||
} else if (scanResult > 0) {
|
||||
Serial.printf(PSTR("%d networks found:\n"), scanResult);
|
||||
|
||||
// Print unsorted scan results
|
||||
for (int8_t i = 0; i < scanResult; i++) {
|
||||
WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden);
|
||||
|
||||
Serial.printf(PSTR(" %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %c %s\n"),
|
||||
i,
|
||||
channel,
|
||||
bssid[0], bssid[1], bssid[2],
|
||||
bssid[3], bssid[4], bssid[5],
|
||||
rssi,
|
||||
(encryptionType == ENC_TYPE_NONE) ? ' ' : '*',
|
||||
hidden ? 'H' : 'V',
|
||||
ssid.c_str());
|
||||
delay(0);
|
||||
}
|
||||
} else {
|
||||
Serial.printf(PSTR("WiFi scan error %d"), scanResult);
|
||||
}
|
||||
Serial.println("");
|
||||
|
||||
// Wait a bit before scanning again
|
||||
delay(5000);
|
||||
|
@ -173,10 +173,10 @@ void loop() {
|
||||
|
||||
// determine maximum output size "fair TCP use"
|
||||
// client.availableForWrite() returns 0 when !client.connected()
|
||||
size_t maxToTcp = 0;
|
||||
int maxToTcp = 0;
|
||||
for (int i = 0; i < MAX_SRV_CLIENTS; i++)
|
||||
if (serverClients[i]) {
|
||||
size_t afw = serverClients[i].availableForWrite();
|
||||
int afw = serverClients[i].availableForWrite();
|
||||
if (afw) {
|
||||
if (!maxToTcp) {
|
||||
maxToTcp = afw;
|
||||
@ -190,11 +190,11 @@ void loop() {
|
||||
}
|
||||
|
||||
//check UART for data
|
||||
size_t len = std::min((size_t)Serial.available(), maxToTcp);
|
||||
size_t len = std::min(Serial.available(), maxToTcp);
|
||||
len = std::min(len, (size_t)STACK_PROTECTOR);
|
||||
if (len) {
|
||||
uint8_t sbuf[len];
|
||||
size_t serial_got = Serial.readBytes(sbuf, len);
|
||||
int serial_got = Serial.readBytes(sbuf, len);
|
||||
// push UART data to all connected telnet clients
|
||||
for (int i = 0; i < MAX_SRV_CLIENTS; i++)
|
||||
// if client.availableForWrite() was 0 (congested)
|
||||
|
@ -24,6 +24,8 @@ X509List KEYWORD1
|
||||
PrivateKey KEYWORD1
|
||||
PublicKey KEYWORD1
|
||||
Session KEYWORD1
|
||||
ServerSession KEYWORD1
|
||||
ServerSessions KEYWORD1
|
||||
ESP8266WiFiGratuitous KEYWORD1
|
||||
|
||||
|
||||
@ -191,10 +193,14 @@ getMFLNStatus KEYWORD2
|
||||
setRSACert KEYWORD2
|
||||
setECCert KEYWORD2
|
||||
setClientTrustAnchor KEYWORD2
|
||||
setCache KEYWORD2
|
||||
|
||||
#CertStoreBearSSL
|
||||
initCertStore KEYWORD2
|
||||
|
||||
#ServerSessions
|
||||
size KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
@ -526,6 +526,10 @@ namespace brssl {
|
||||
case BR_KEYTYPE_EC:
|
||||
ek = br_skey_decoder_get_ec(dc.get());
|
||||
sk = (private_key*)malloc(sizeof * sk);
|
||||
if (!sk)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
sk->key_type = BR_KEYTYPE_EC;
|
||||
sk->key.ec.curve = ek->curve;
|
||||
sk->key.ec.x = (uint8_t*)malloc(ek->xlen);
|
||||
@ -626,6 +630,17 @@ namespace brssl {
|
||||
return pk;
|
||||
}
|
||||
|
||||
static uint8_t *loadStream(Stream& stream, size_t size) {
|
||||
uint8_t *dest = (uint8_t *)malloc(size);
|
||||
if (!dest) {
|
||||
return nullptr; // OOM error
|
||||
}
|
||||
if (size != stream.readBytes(dest, size)) {
|
||||
free(dest); // Error during read
|
||||
return nullptr;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -648,6 +663,15 @@ PublicKey::PublicKey(const uint8_t *derKey, size_t derLen) {
|
||||
parse(derKey, derLen);
|
||||
}
|
||||
|
||||
PublicKey::PublicKey(Stream &stream, size_t size) {
|
||||
_key = nullptr;
|
||||
auto buff = brssl::loadStream(stream, size);
|
||||
if (buff) {
|
||||
parse(buff, size);
|
||||
free(buff);
|
||||
}
|
||||
}
|
||||
|
||||
PublicKey::~PublicKey() {
|
||||
if (_key) {
|
||||
brssl::free_public_key(_key);
|
||||
@ -711,6 +735,15 @@ PrivateKey::PrivateKey(const uint8_t *derKey, size_t derLen) {
|
||||
parse(derKey, derLen);
|
||||
}
|
||||
|
||||
PrivateKey::PrivateKey(Stream &stream, size_t size) {
|
||||
_key = nullptr;
|
||||
auto buff = brssl::loadStream(stream, size);
|
||||
if (buff) {
|
||||
parse(buff, size);
|
||||
free(buff);
|
||||
}
|
||||
}
|
||||
|
||||
PrivateKey::~PrivateKey() {
|
||||
if (_key) {
|
||||
brssl::free_private_key(_key);
|
||||
@ -781,6 +814,17 @@ X509List::X509List(const uint8_t *derCert, size_t derLen) {
|
||||
append(derCert, derLen);
|
||||
}
|
||||
|
||||
X509List::X509List(Stream &stream, size_t size) {
|
||||
_count = 0;
|
||||
_cert = nullptr;
|
||||
_ta = nullptr;
|
||||
auto buff = brssl::loadStream(stream, size);
|
||||
if (buff) {
|
||||
append(buff, size);
|
||||
free(buff);
|
||||
}
|
||||
}
|
||||
|
||||
X509List::~X509List() {
|
||||
brssl::free_certificates(_cert, _count); // also frees cert
|
||||
for (size_t i = 0; i < _count; i++) {
|
||||
@ -832,6 +876,22 @@ bool X509List::append(const uint8_t *derCert, size_t derLen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ServerSessions::~ServerSessions() {
|
||||
if (_isDynamic && _store != nullptr)
|
||||
delete _store;
|
||||
}
|
||||
|
||||
ServerSessions::ServerSessions(ServerSession *sessions, uint32_t size, bool isDynamic) :
|
||||
_size(sessions != nullptr ? size : 0),
|
||||
_store(sessions), _isDynamic(isDynamic) {
|
||||
if (_size > 0)
|
||||
br_ssl_session_cache_lru_init(&_cache, (uint8_t*)_store, size * sizeof(ServerSession));
|
||||
}
|
||||
|
||||
const br_ssl_session_cache_class **ServerSessions::getCache() {
|
||||
return _size > 0 ? &_cache.vtable : nullptr;
|
||||
}
|
||||
|
||||
// SHA256 hash for updater
|
||||
void HashSHA256::begin() {
|
||||
br_sha256_init( &_cc );
|
||||
|
@ -43,6 +43,8 @@ class PublicKey {
|
||||
PublicKey();
|
||||
PublicKey(const char *pemKey);
|
||||
PublicKey(const uint8_t *derKey, size_t derLen);
|
||||
PublicKey(Stream& stream, size_t size);
|
||||
PublicKey(Stream& stream) : PublicKey(stream, stream.available()) { };
|
||||
~PublicKey();
|
||||
|
||||
bool parse(const char *pemKey);
|
||||
@ -69,6 +71,8 @@ class PrivateKey {
|
||||
PrivateKey();
|
||||
PrivateKey(const char *pemKey);
|
||||
PrivateKey(const uint8_t *derKey, size_t derLen);
|
||||
PrivateKey(Stream& stream, size_t size);
|
||||
PrivateKey(Stream& stream) : PrivateKey(stream, stream.available()) { };
|
||||
~PrivateKey();
|
||||
|
||||
bool parse(const char *pemKey);
|
||||
@ -98,6 +102,8 @@ class X509List {
|
||||
X509List();
|
||||
X509List(const char *pemCert);
|
||||
X509List(const uint8_t *derCert, size_t derLen);
|
||||
X509List(Stream& stream, size_t size);
|
||||
X509List(Stream& stream) : X509List(stream, stream.available()) { };
|
||||
~X509List();
|
||||
|
||||
bool append(const char *pemCert);
|
||||
@ -127,17 +133,61 @@ class X509List {
|
||||
// significantly faster. Completely optional.
|
||||
class WiFiClientSecure;
|
||||
|
||||
// Cache for a TLS session with a server
|
||||
// Use with BearSSL::WiFiClientSecure::setSession
|
||||
// to accelerate the TLS handshake
|
||||
class Session {
|
||||
friend class WiFiClientSecure;
|
||||
friend class WiFiClientSecureCtx;
|
||||
|
||||
public:
|
||||
Session() { memset(&_session, 0, sizeof(_session)); }
|
||||
private:
|
||||
br_ssl_session_parameters *getSession() { return &_session; }
|
||||
// The actual BearSSL ession information
|
||||
// The actual BearSSL session information
|
||||
br_ssl_session_parameters _session;
|
||||
};
|
||||
|
||||
// Represents a single server session.
|
||||
// Use with BearSSL::ServerSessions.
|
||||
typedef uint8_t ServerSession[100];
|
||||
|
||||
// Cache for the TLS sessions of multiple clients.
|
||||
// Use with BearSSL::WiFiServerSecure::setCache
|
||||
class ServerSessions {
|
||||
friend class WiFiClientSecureCtx;
|
||||
|
||||
public:
|
||||
// Uses the given buffer to cache the given number of sessions and initializes it.
|
||||
ServerSessions(ServerSession *sessions, uint32_t size) : ServerSessions(sessions, size, false) {}
|
||||
|
||||
// Dynamically allocates a cache for the given number of sessions and initializes it.
|
||||
// If the allocation of the buffer wasn't successfull, the value
|
||||
// returned by size() will be 0.
|
||||
ServerSessions(uint32_t size) : ServerSessions(size > 0 ? new ServerSession[size] : nullptr, size, true) {}
|
||||
|
||||
~ServerSessions();
|
||||
|
||||
// Returns the number of sessions the cache can hold.
|
||||
uint32_t size() { return _size; }
|
||||
|
||||
private:
|
||||
ServerSessions(ServerSession *sessions, uint32_t size, bool isDynamic);
|
||||
|
||||
// Returns the cache's vtable or null if the cache has no capacity.
|
||||
const br_ssl_session_cache_class **getCache();
|
||||
|
||||
// Size of the store in sessions.
|
||||
uint32_t _size;
|
||||
// Store where the informations for the sessions are stored.
|
||||
ServerSession *_store;
|
||||
// Whether the store is dynamically allocated.
|
||||
// If this is true, the store needs to be freed in the destructor.
|
||||
bool _isDynamic;
|
||||
|
||||
// Cache of the server using the _store.
|
||||
br_ssl_session_cache_lru _cache;
|
||||
};
|
||||
|
||||
// Updater SHA256 hash and signature verification
|
||||
class HashSHA256 : public UpdaterHashClass {
|
||||
public:
|
||||
|
@ -80,7 +80,7 @@ CertStore::CertInfo CertStore::_preprocessCert(uint32_t length, uint32_t offset,
|
||||
|
||||
// The certs.ar file is a UNIX ar format file, concatenating all the
|
||||
// individual certificates into a single blob in a space-efficient way.
|
||||
int CertStore::initCertStore(FS &fs, const char *indexFileName, const char *dataFileName) {
|
||||
int CertStore::initCertStore(fs::FS &fs, const char *indexFileName, const char *dataFileName) {
|
||||
int count = 0;
|
||||
uint32_t offset = 0;
|
||||
|
||||
@ -101,12 +101,12 @@ int CertStore::initCertStore(FS &fs, const char *indexFileName, const char *data
|
||||
memcpy_P(_indexName, indexFileName, strlen_P(indexFileName) + 1);
|
||||
memcpy_P(_dataName, dataFileName, strlen_P(dataFileName) + 1);
|
||||
|
||||
File index = _fs->open(_indexName, "w");
|
||||
fs::File index = _fs->open(_indexName, "w");
|
||||
if (!index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
File data = _fs->open(_dataName, "r");
|
||||
fs::File data = _fs->open(_dataName, "r");
|
||||
if (!data) {
|
||||
index.close();
|
||||
return 0;
|
||||
@ -179,7 +179,7 @@ const br_x509_trust_anchor *CertStore::findHashedTA(void *ctx, void *hashed_dn,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
File index = cs->_fs->open(cs->_indexName, "r");
|
||||
fs::File index = cs->_fs->open(cs->_indexName, "r");
|
||||
if (!index) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -191,12 +191,12 @@ const br_x509_trust_anchor *CertStore::findHashedTA(void *ctx, void *hashed_dn,
|
||||
if (!der) {
|
||||
return nullptr;
|
||||
}
|
||||
File data = cs->_fs->open(cs->_dataName, "r");
|
||||
fs::File data = cs->_fs->open(cs->_dataName, "r");
|
||||
if (!data) {
|
||||
free(der);
|
||||
return nullptr;
|
||||
}
|
||||
if (!data.seek(ci.offset, SeekSet)) {
|
||||
if (!data.seek(ci.offset, fs::SeekSet)) {
|
||||
data.close();
|
||||
free(der);
|
||||
return nullptr;
|
||||
|
@ -31,7 +31,15 @@
|
||||
|
||||
namespace BearSSL {
|
||||
|
||||
class CertStore {
|
||||
class CertStoreBase {
|
||||
public:
|
||||
virtual ~CertStoreBase() {}
|
||||
|
||||
// Installs the cert store into the X509 decoder (normally via static function callbacks)
|
||||
virtual void installCertStore(br_x509_minimal_context *ctx) = 0;
|
||||
};
|
||||
|
||||
class CertStore: public CertStoreBase {
|
||||
public:
|
||||
CertStore() { };
|
||||
~CertStore();
|
||||
|
@ -25,7 +25,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
extern "C" {
|
||||
#include "include/wl_definitions.h"
|
||||
#include <wl_definitions.h>
|
||||
}
|
||||
|
||||
#include "IPAddress.h"
|
||||
|
@ -33,11 +33,11 @@ extern "C" {
|
||||
#include "osapi.h"
|
||||
#include "mem.h"
|
||||
#include "user_interface.h"
|
||||
#include <lwip/init.h> // LWIP_VERSION_*
|
||||
}
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
|
||||
#include "LwipDhcpServer.h"
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------------
|
||||
// ---------------------------------------------------- Private functions ------------------------------------------------
|
||||
@ -156,13 +156,7 @@ bool ESP8266WiFiAPClass::softAP(const char* ssid, const char* passphrase, int ch
|
||||
DEBUG_WIFI("[AP] softap config unchanged\n");
|
||||
}
|
||||
|
||||
if(wifi_softap_dhcps_status() != DHCP_STARTED) {
|
||||
DEBUG_WIFI("[AP] DHCP not started, starting...\n");
|
||||
if(!wifi_softap_dhcps_start()) {
|
||||
DEBUG_WIFI("[AP] wifi_softap_dhcps_start failed!\n");
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
dhcpSoftAP.end();
|
||||
|
||||
// check IP config
|
||||
struct ip_info ip;
|
||||
@ -182,6 +176,8 @@ bool ESP8266WiFiAPClass::softAP(const char* ssid, const char* passphrase, int ch
|
||||
ret = false;
|
||||
}
|
||||
|
||||
dhcpSoftAP.begin(&ip);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -237,19 +233,22 @@ bool ESP8266WiFiAPClass::softAPConfig(IPAddress local_ip, IPAddress gateway, IPA
|
||||
dhcp_lease.end_ip.addr = ip.v4();
|
||||
DEBUG_WIFI("[APConfig] DHCP IP end: %s\n", ip.toString().c_str());
|
||||
|
||||
if(!wifi_softap_set_dhcps_lease(&dhcp_lease)) {
|
||||
if(!dhcpSoftAP.set_dhcps_lease(&dhcp_lease))
|
||||
{
|
||||
DEBUG_WIFI("[APConfig] wifi_set_ip_info failed!\n");
|
||||
ret = false;
|
||||
}
|
||||
|
||||
// set lease time to 720min --> 12h
|
||||
if(!wifi_softap_set_dhcps_lease_time(720)) {
|
||||
if(!dhcpSoftAP.set_dhcps_lease_time(720))
|
||||
{
|
||||
DEBUG_WIFI("[APConfig] wifi_softap_set_dhcps_lease_time failed!\n");
|
||||
ret = false;
|
||||
}
|
||||
|
||||
uint8 mode = info.gw.addr ? 1 : 0;
|
||||
if(!wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode)) {
|
||||
if(!dhcpSoftAP.set_dhcps_offer_option(OFFER_ROUTER, &mode))
|
||||
{
|
||||
DEBUG_WIFI("[APConfig] wifi_softap_set_dhcps_offer_option failed!\n");
|
||||
ret = false;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
ESP8266WiFiGeneric.h - esp8266 Wifi support.
|
||||
Based on WiFi.h from Ardiono WiFi shield library.
|
||||
Based on WiFi.h from Arduino WiFi shield library.
|
||||
Copyright (c) 2011-2014 Arduino. All right reserved.
|
||||
Modified by Ivan Grokhotkov, December 2014
|
||||
Reworked by Markus Sattler, December 2015
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "ESP8266WiFiGeneric.h"
|
||||
#include "ESP8266WiFiSTA.h"
|
||||
#include "PolledTimeout.h"
|
||||
#include "LwipIntf.h"
|
||||
|
||||
#include "c_types.h"
|
||||
#include "ets_sys.h"
|
||||
@ -281,28 +282,9 @@ bool ESP8266WiFiSTAClass::config(IPAddress local_ip, IPAddress arg1, IPAddress a
|
||||
return true;
|
||||
}
|
||||
|
||||
//To allow compatibility, check first octet of 3rd arg. If 255, interpret as ESP order, otherwise Arduino order.
|
||||
IPAddress gateway = arg1;
|
||||
IPAddress subnet = arg2;
|
||||
IPAddress dns1 = arg3;
|
||||
|
||||
if(subnet[0] != 255)
|
||||
{
|
||||
//octet is not 255 => interpret as Arduino order
|
||||
gateway = arg2;
|
||||
subnet = arg3[0] == 0 ? IPAddress(255,255,255,0) : arg3; //arg order is arduino and 4th arg not given => assign it arduino default
|
||||
dns1 = arg1;
|
||||
}
|
||||
|
||||
// check whether all is IPv4 (or gateway not set)
|
||||
if (!(local_ip.isV4() && subnet.isV4() && (!gateway.isSet() || gateway.isV4()))) {
|
||||
IPAddress gateway, subnet, dns1;
|
||||
if (!ipAddressReorder(local_ip, arg1, arg2, arg3, gateway, subnet, dns1))
|
||||
return false;
|
||||
}
|
||||
|
||||
//ip and gateway must be in the same subnet
|
||||
if((local_ip.v4() & subnet.v4()) != (gateway.v4() & subnet.v4())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !CORE_MOCK
|
||||
// get current->previous IP address
|
||||
@ -522,94 +504,6 @@ IPAddress ESP8266WiFiSTAClass::dnsIP(uint8_t dns_no) {
|
||||
return IPAddress(dns_getserver(dns_no));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get ESP8266 station DHCP hostname
|
||||
* @return hostname
|
||||
*/
|
||||
String ESP8266WiFiSTAClass::hostname(void) {
|
||||
return wifi_station_get_hostname();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ESP8266 station DHCP hostname
|
||||
* @param aHostname max length:24
|
||||
* @return ok
|
||||
*/
|
||||
bool ESP8266WiFiSTAClass::hostname(const char* aHostname) {
|
||||
/*
|
||||
vvvv RFC952 vvvv
|
||||
ASSUMPTIONS
|
||||
1. A "name" (Net, Host, Gateway, or Domain name) is a text string up
|
||||
to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
|
||||
sign (-), and period (.). Note that periods are only allowed when
|
||||
they serve to delimit components of "domain style names". (See
|
||||
RFC-921, "Domain Name System Implementation Schedule", for
|
||||
background). No blank or space characters are permitted as part of a
|
||||
name. No distinction is made between upper and lower case. The first
|
||||
character must be an alpha character. The last character must not be
|
||||
a minus sign or period. A host which serves as a GATEWAY should have
|
||||
"-GATEWAY" or "-GW" as part of its name. Hosts which do not serve as
|
||||
Internet gateways should not use "-GATEWAY" and "-GW" as part of
|
||||
their names. A host which is a TAC should have "-TAC" as the last
|
||||
part of its host name, if it is a DoD host. Single character names
|
||||
or nicknames are not allowed.
|
||||
^^^^ RFC952 ^^^^
|
||||
|
||||
- 24 chars max
|
||||
- only a..z A..Z 0..9 '-'
|
||||
- no '-' as last char
|
||||
*/
|
||||
|
||||
size_t len = strlen(aHostname);
|
||||
|
||||
if (len == 0 || len > 32) {
|
||||
// nonos-sdk limit is 32
|
||||
// (dhcp hostname option minimum size is ~60)
|
||||
DEBUG_WIFI_GENERIC("WiFi.(set)hostname(): empty or large(>32) name\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check RFC compliance
|
||||
bool compliant = (len <= 24);
|
||||
for (size_t i = 0; compliant && i < len; i++)
|
||||
if (!isalnum(aHostname[i]) && aHostname[i] != '-')
|
||||
compliant = false;
|
||||
if (aHostname[len - 1] == '-')
|
||||
compliant = false;
|
||||
|
||||
if (!compliant) {
|
||||
DEBUG_WIFI_GENERIC("hostname '%s' is not compliant with RFC952\n", aHostname);
|
||||
}
|
||||
|
||||
bool ret = wifi_station_set_hostname(aHostname);
|
||||
if (!ret) {
|
||||
DEBUG_WIFI_GENERIC("WiFi.hostname(%s): wifi_station_set_hostname() failed\n", aHostname);
|
||||
return false;
|
||||
}
|
||||
|
||||
// now we should inform dhcp server for this change, using lwip_renew()
|
||||
// looping through all existing interface
|
||||
// harmless for AP, also compatible with ethernet adapters (to come)
|
||||
for (netif* intf = netif_list; intf; intf = intf->next) {
|
||||
|
||||
// unconditionally update all known interfaces
|
||||
intf->hostname = wifi_station_get_hostname();
|
||||
|
||||
if (netif_dhcp_data(intf) != nullptr) {
|
||||
// renew already started DHCP leases
|
||||
err_t lwipret = dhcp_renew(intf);
|
||||
if (lwipret != ERR_OK) {
|
||||
DEBUG_WIFI_GENERIC("WiFi.hostname(%s): lwIP error %d on interface %c%c (index %d)\n",
|
||||
intf->hostname, (int)lwipret, intf->name[0], intf->name[1], intf->num);
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret && compliant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Connection status.
|
||||
* @return one of the value defined in wl_status_t
|
||||
|
@ -27,9 +27,10 @@
|
||||
#include "ESP8266WiFiType.h"
|
||||
#include "ESP8266WiFiGeneric.h"
|
||||
#include "user_interface.h"
|
||||
#include "LwipIntf.h"
|
||||
|
||||
|
||||
class ESP8266WiFiSTAClass {
|
||||
class ESP8266WiFiSTAClass: public LwipIntf {
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// ---------------------------------------- STA function ----------------------------------------
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
@ -69,10 +70,6 @@ class ESP8266WiFiSTAClass {
|
||||
IPAddress gatewayIP();
|
||||
IPAddress dnsIP(uint8_t dns_no = 0);
|
||||
|
||||
String hostname();
|
||||
bool hostname(const String& aHostname) { return hostname(aHostname.c_str()); }
|
||||
bool hostname(const char* aHostname);
|
||||
|
||||
// STA WiFi info
|
||||
wl_status_t status();
|
||||
String SSID() const;
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "include/wl_definitions.h"
|
||||
#include "wl_definitions.h"
|
||||
#include "osapi.h"
|
||||
#include "ets_sys.h"
|
||||
}
|
||||
@ -195,7 +195,7 @@ bool WiFiClient::getSync() const
|
||||
return _client->getSync();
|
||||
}
|
||||
|
||||
size_t WiFiClient::availableForWrite ()
|
||||
int WiFiClient::availableForWrite ()
|
||||
{
|
||||
return _client? _client->availableForWrite(): 0;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public:
|
||||
WiFiClient(const WiFiClient&);
|
||||
WiFiClient& operator=(const WiFiClient&);
|
||||
|
||||
uint8_t status();
|
||||
virtual uint8_t status();
|
||||
virtual int connect(IPAddress ip, uint16_t port) override;
|
||||
virtual int connect(const char *host, uint16_t port) override;
|
||||
virtual int connect(const String& host, uint16_t port);
|
||||
@ -86,7 +86,7 @@ public:
|
||||
|
||||
static void setLocalPortStart(uint16_t port) { _localPort = port; }
|
||||
|
||||
size_t availableForWrite();
|
||||
int availableForWrite() override;
|
||||
|
||||
friend class WiFiServer;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user