1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-25 20:02:37 +03:00

Merge branch 'master' into master

This commit is contained in:
david gauchard 2021-03-07 09:43:49 +01:00 committed by GitHub
commit 7e1fb05f6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
367 changed files with 15115 additions and 29505 deletions

View File

@ -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 }}
@ -102,7 +102,7 @@ jobs:
run: |
# Windows has python3 already installed, but it's called "python".
# Copy python.exe to the proper name so scripts "just work".
copy (get-command python).source (get-command python).source.Replace("python.exe", "python3.exe")
try { Get-Command python3 } catch { copy (get-command python).source (get-command python).source.Replace("python.exe", "python3.exe") }
bash ./tests/build.sh
@ -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 }}
@ -268,3 +268,4 @@ jobs:
run: |
bash ./tests/ci/build_boards.sh
bash ./tests/ci/eboot_test.sh
bash ./tests/ci/pkgrefs_test.sh

2
.gitmodules vendored
View File

@ -24,4 +24,4 @@
url = https://github.com/arduino-libraries/Ethernet.git
[submodule "tools/sdk/uzlib"]
path = tools/sdk/uzlib
url = https://github.com/earlephilhower/uzlib.git
url = https://github.com/pfalcon/uzlib.git

View File

@ -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 [![Latest release](https://img.shields.io/github/release/esp8266/Arduino.svg)](https://github.com/esp8266/Arduino/releases/latest/)

File diff suppressed because it is too large Load Diff

View File

@ -21,9 +21,9 @@ OBJDUMP := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-objdump
INC += -I../../tools/sdk/include -I../../tools/sdk/uzlib/src
CFLAGS += -std=gnu99
CFLAGS += -std=gnu17
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)
@ -40,17 +40,17 @@ APP_FW := eboot.bin
all: $(APP_OUT)
tinflate.o: $(UZLIB_PATH)/tinflate.c $(UZLIB_PATH)/uzlib.h $(UZLIB_PATH)/uzlib_conf.h
tinflate.o: $(UZLIB_PATH)/tinflate.c $(UZLIB_PATH)/uzlib.h $(UZLIB_PATH)/uzlib_conf.h Makefile
$(CC) $(CFLAGS) -c -o tinflate.o $(UZLIB_PATH)/tinflate.c
tinfgzip.o: $(UZLIB_PATH)/tinfgzip.c $(UZLIB_PATH)/uzlib.h $(UZLIB_PATH)/uzlib_conf.h
tinfgzip.o: $(UZLIB_PATH)/tinfgzip.c $(UZLIB_PATH)/uzlib.h $(UZLIB_PATH)/uzlib_conf.h Makefile
$(CC) $(CFLAGS) -c -o tinfgzip.o $(UZLIB_PATH)/tinfgzip.c
$(APP_AR): $(TARGET_OBJ_PATHS) tinflate.o tinfgzip.o
$(APP_AR): $(TARGET_OBJ_PATHS) tinflate.o tinfgzip.o Makefile
$(AR) cru $@ $^
$(APP_OUT): $(APP_AR) eboot.ld | Makefile
$(LD) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group -Wl,--whole-archive $(APP_AR) -Wl,--end-group -o $@
$(LD) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group -Wl,--sort-common $(APP_AR) -Wl,--end-group -o $@
clean:
rm -f *.o

View File

@ -14,28 +14,19 @@
#include "eboot_command.h"
#include <uzlib.h>
extern unsigned char _gzip_dict;
#define SWRST do { (*((volatile uint32_t*) 0x60000700)) |= 0x80000000; } while(0);
extern void ets_wdt_enable(void);
extern void ets_wdt_disable(void);
// Converts bit of a string into a uint32
#define S(a,b,c,d) ( (((uint32_t)a) & 0xff) | (((uint32_t)b) << 8) | (((uint32_t)c) << 16) | (((uint32_t)d)<<24) )
int print_version(const uint32_t flash_addr)
{
uint32_t ver;
if (SPIRead(flash_addr + APP_START_OFFSET + sizeof(image_header_t) + sizeof(section_header_t), &ver, sizeof(ver))) {
return 1;
}
// We don't have BSS and can't print from flash, so build up string
// 4 chars at a time. Smaller code than byte-wise assignment.
uint32_t fmt[2];
fmt[0] = S('v', '%', '0', '8');
fmt[1] = S('x', '\n', 0, 0);
ets_printf((const char*) fmt, ver);
ets_printf("v%08x\n", ver);
return 0;
}
@ -68,7 +59,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;
}
@ -220,6 +213,16 @@ int main()
bool clear_cmd = false;
struct eboot_command cmd;
// BSS init commented out for now to save space. If any static variables set
// to 0 are used, need to uncomment it or else the BSS will not be cleared and
// the static vars will power on with random values.
#if 0
// Clear BSS ourselves, we don't have handy C runtime
extern char _bss_start;
extern char _bss_end;
ets_bzero(&_bss_start, &_bss_end - &_bss_start);
#endif
print_version(0);
if (eboot_command_read(&cmd) == 0) {
@ -234,32 +237,26 @@ int main()
}
if (cmd.action == ACTION_COPY_RAW) {
uint32_t cp = S('c', 'p', ':', 0);
ets_printf((const char *)&cp);
ets_printf("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("%d\n", res);
#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.
//TODO: replace the below verify with hash type, crc, or similar.
// Verify the copy
uint32_t v[2];
v[0] = S('c', 'm', 'p', ':');
v[1] = 0;
ets_printf(const char *)v);
ets_printf("cmp:");
if (res == 0) {
ets_wdt_disable();
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], true);
ets_wdt_enable();
}
cp = S('0' + res, '\n', 0, 0 );
ets_printf((const char *)&cp);
ets_printf("%d\n", res);
#endif
if (res == 0) {
cmd.action = ACTION_LOAD_APP;
@ -272,13 +269,10 @@ int main()
}
if (cmd.action == ACTION_LOAD_APP) {
ets_putc('l'); ets_putc('d'); ets_putc('\n');
ets_printf("ld\n");
res = load_app_from_flash_raw(cmd.args[0]);
// We will get to this only on load fail
uint32_t e[2];
e[0] = S('e', ':', '0' + res, '\n' );
e[1] = 0;
ets_printf((const char*)e);
ets_printf("e:%d\n", res);
}
if (res) {

Binary file not shown.

View File

@ -42,53 +42,13 @@ PROVIDE(_memmap_cacheattr_reset = _memmap_cacheattr_wb_trapnull);
SECTIONS
{
.dport0.rodata : ALIGN(4)
.globals : ALIGN(4)
{
_dport0_rodata_start = ABSOLUTE(.);
*(.dport0.rodata)
*(.dport.rodata)
_dport0_rodata_end = ABSOLUTE(.);
} >dport0_0_seg :dport0_0_phdr
.dport0.literal : ALIGN(4)
{
_dport0_literal_start = ABSOLUTE(.);
*(.dport0.literal)
*(.dport.literal)
_dport0_literal_end = ABSOLUTE(.);
} >dport0_0_seg :dport0_0_phdr
.dport0.data : ALIGN(4)
{
_dport0_data_start = ABSOLUTE(.);
*(.dport0.data)
*(.dport.data)
_dport0_data_end = ABSOLUTE(.);
} >dport0_0_seg :dport0_0_phdr
*(COMMON) /* Global vars */
} >dram0_0_seg :dram0_0_bss_phdr
.data : ALIGN(4)
{
*(COMMON) /* Global vars */
. = ALIGN(4);
_heap_start = ABSOLUTE(.);
/* _stack_sentry = ALIGN(0x8); */
} >dram0_0_seg :dram0_0_bss_phdr
/* __stack = 0x3ffc8000; */
.text : ALIGN(4)
{
_stext = .;
_text_start = ABSOLUTE(.);
*(.entry.text)
*(.init.literal)
*(.init)
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
*(.fini.literal)
*(.fini)
*(.gnu.version)
_text_end = ABSOLUTE(.);
_etext = .;
. = ALIGN (8);
_data_start = ABSOLUTE(.);
*(.data)
*(.data.*)
@ -102,7 +62,10 @@ SECTIONS
*(.gnu.linkonce.s2.*)
*(.jcr)
_data_end = ABSOLUTE(.);
. = ALIGN (8);
} >dram0_0_seg :dram0_0_bss_phdr
.rodata : ALIGN(4)
{
_rodata_start = ABSOLUTE(.);
*(.rodata)
*(.rodata.*)
@ -131,14 +94,11 @@ SECTIONS
*(.xt_except_desc_end)
*(.dynamic)
*(.gnu.version_d)
. = ALIGN(4); /* this table MUST be 4-byte aligned */
_bss_table_start = ABSOLUTE(.);
LONG(_bss_start)
LONG(_bss_end)
_bss_table_end = ABSOLUTE(.);
_rodata_end = ABSOLUTE(.);
} >dram0_0_seg :dram0_0_bss_phdr
. = ALIGN (8);
.bss : ALIGN(4)
{
_bss_start = ABSOLUTE(.);
*(.dynsbss)
*(.sbss)
@ -152,26 +112,24 @@ SECTIONS
*(.bss)
*(.bss.*)
*(.gnu.linkonce.b.*)
. = ALIGN (8);
_bss_end = ABSOLUTE(.);
_free_space = 4096 - 17 - (. - _stext);
/*
The boot loader checksum must be before the CRC, which is written by elf2bin.py.
This leaves 16 bytes after the checksum for the CRC placed at the end of the
4096-byte sector. */
_cs_here = (ALIGN((. + 1), 16) == ALIGN(16)) ? (ALIGN(16) - 1) : (. + 0x0F);
} >dram0_0_seg :dram0_0_bss_phdr
/*
The filling (padding) and values for _crc_size and _crc_val are handled by
elf2bin.py. With this, we give values to the symbols without explicitly
assigning space. This avoids the linkers back *fill* operation that causes
trouble.
The CRC info is stored in last 8 bytes. */
_crc_size = _stext + 4096 - 8;
_crc_val = _stext + 4096 - 4;
ASSERT((4096 > (17 + (. - _stext))), "Error: No space for CS and CRC in bootloader sector.");
ASSERT((_crc_size > _cs_here), "Error: CRC must be located after CS.");
.text : ALIGN(4)
{
_stext = .;
_text_start = ABSOLUTE(.);
*(.entry.text)
*(.init.literal)
*(.init)
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
*(.fini.literal)
*(.fini)
*(.gnu.version)
_text_end = ABSOLUTE(.);
_etext = .;
. = ALIGN (4); /* Ensure 32b alignment since this is written to IRAM */
} >iram1_0_seg :iram1_0_phdr
.lit4 : ALIGN(4)

View File

@ -171,6 +171,7 @@ int digitalRead(uint8_t pin);
int analogRead(uint8_t pin);
void analogReference(uint8_t mode);
void analogWrite(uint8_t pin, int val);
void analogWriteMode(uint8_t pin, int val, bool openDrain);
void analogWriteFreq(uint32_t freq);
void analogWriteResolution(int res);
void analogWriteRange(uint32_t range);
@ -225,6 +226,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;

View File

@ -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();
}

View File

@ -28,7 +28,10 @@
#include "cont.h"
#include "coredecls.h"
#include "umm_malloc/umm_malloc.h"
// #include "core_esp8266_vm.h"
#include <pgmspace.h>
#include "reboot_uart_dwnld.h"
extern "C" {
#include "user_interface.h"
@ -201,6 +204,15 @@ void EspClass::restart(void)
esp_yield();
}
[[noreturn]] void EspClass::rebootIntoUartDownloadMode()
{
wdtDisable();
/* disable hardware watchdog */
CLEAR_PERI_REG_MASK(PERIPHS_HW_WDT, 0x1);
esp8266RebootIntoUartDownloadMode();
}
uint16_t EspClass::getVcc(void)
{
esp8266::InterruptLock lock;
@ -442,22 +454,24 @@ bool EspClass::checkFlashConfig(bool needsEquals) {
return false;
}
// These are defined in the linker script, and filled in by the elf2bin.py util
extern "C" uint32_t __crc_len;
extern "C" uint32_t __crc_val;
bool EspClass::checkFlashCRC() {
// The CRC and total length are placed in extra space at the end of the 4K chunk
// of flash occupied by the bootloader. If the bootloader grows to >4K-8 bytes,
// we'll need to adjust this.
uint32_t flashsize = *((uint32_t*)(0x40200000 + 4088)); // Start of PROGMEM plus 4K-8
uint32_t flashcrc = *((uint32_t*)(0x40200000 + 4092)); // Start of PROGMEM plus 4K-4
// Dummy CRC fill
uint32_t z[2];
z[0] = z[1] = 0;
uint32_t firstPart = (uintptr_t)&__crc_len - 0x40200000; // How many bytes to check before the 1st CRC val
// Start the checksum
uint32_t crc = crc32((const void*)0x40200000, 4096-8, 0xffffffff);
uint32_t crc = crc32((const void*)0x40200000, firstPart, 0xffffffff);
// Pretend the 2 words of crc/len are zero to be idempotent
crc = crc32(z, 8, crc);
// Finish the CRC calculation over the rest of flash
crc = crc32((const void*)0x40201000, flashsize-4096, crc);
return crc == flashcrc;
crc = crc32((const void*)(0x40200000 + firstPart + 8), __crc_len - (firstPart + 8), crc);
return crc == __crc_val;
}
@ -516,45 +530,45 @@ uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) co
{
/**
* The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266):
*
*
* "When used correctly, every 32-bit value the system reads from the RNG_DATA_REG register of the random number generator is a true random number.
* These true random numbers are generated based on the noise in the Wi-Fi/BT RF system.
* When Wi-Fi and BT are disabled, the random number generator will give out pseudo-random numbers.
*
*
* When Wi-Fi or BT is enabled, the random number generator is fed two bits of entropy every APB clock cycle (normally 80 MHz).
* Thus, for the maximum amount of entropy, it is advisable to read the random register at a maximum rate of 5 MHz.
* A data sample of 2 GB, read from the random number generator with Wi-Fi enabled and the random register read at 5 MHz,
* has been tested using the Dieharder Random Number Testsuite (version 3.31.1).
* The sample passed all tests."
*
*
* Since ESP32 is the sequal to ESP8266 it is unlikely that the ESP8266 is able to generate random numbers more quickly than 5 MHz when run at a 80 MHz frequency.
* A maximum random number frequency of 0.5 MHz is used here to leave some margin for possibly inferior components in the ESP8266.
* It should be noted that the ESP8266 has no Bluetooth functionality, so turning the WiFi off is likely to cause RANDOM_REG32 to use pseudo-random numbers.
*
* It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available.
*
* It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available.
* However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during number generation.
* Thus only delayMicroseconds() is used below.
*/
*/
constexpr uint8_t cooldownMicros = 2;
static uint32_t lastCalledMicros = micros() - cooldownMicros;
uint32_t randomNumber = 0;
for(size_t byteIndex = 0; byteIndex < outputSizeBytes; ++byteIndex)
{
if(byteIndex % 4 == 0)
{
// Old random number has been used up (random number could be exactly 0, so we can't check for that)
uint32_t timeSinceLastCall = micros() - lastCalledMicros;
if(timeSinceLastCall < cooldownMicros)
delayMicroseconds(cooldownMicros - timeSinceLastCall);
randomNumber = RANDOM_REG32;
lastCalledMicros = micros();
}
resultArray[byteIndex] = randomNumber;
randomNumber >>= 8;
}
@ -969,3 +983,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
}

View File

@ -103,6 +103,12 @@ class EspClass {
void reset();
void restart();
/**
* @brief When calling this method the ESP8266 reboots into the UART download mode without
* the need of any external wiring. This is the same mode which can also be entered by
* pulling GPIO0=low, GPIO2=high, GPIO15=low and resetting the ESP8266.
*/
[[noreturn]] void rebootIntoUartDownloadMode();
uint16_t getVcc();
uint32_t getChipId();
@ -123,7 +129,7 @@ class EspClass {
uint8_t getBootMode();
#if defined(F_CPU) || defined(CORE_MOCK)
constexpr
constexpr
#endif
inline uint8_t getCpuFreqMHz() const __attribute__((always_inline))
{
@ -215,7 +221,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
*

View File

@ -68,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);
}
@ -206,6 +206,7 @@ void File::setTimeCallback(time_t (*cb)(void)) {
if (!_p)
return;
_p->setTimeCallback(cb);
_timeCallback = cb;
}
File Dir::openFile(const char* mode) {
@ -221,7 +222,7 @@ File Dir::openFile(const char* mode) {
}
File f(_impl->openFile(om, am), _baseFS);
f.setTimeCallback(timeCallback);
f.setTimeCallback(_timeCallback);
return f;
}
@ -287,7 +288,7 @@ void Dir::setTimeCallback(time_t (*cb)(void)) {
if (!_impl)
return;
_impl->setTimeCallback(cb);
timeCallback = cb;
_timeCallback = cb;
}
@ -304,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;
@ -367,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;
}
@ -388,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;
}
@ -440,10 +441,18 @@ bool FS::rename(const String& pathFrom, const String& pathTo) {
return rename(pathFrom.c_str(), pathTo.c_str());
}
time_t FS::getCreationTime() {
if (!_impl) {
return 0;
}
return _impl->getCreationTime();
}
void FS::setTimeCallback(time_t (*cb)(void)) {
if (!_impl)
return;
_impl->setTimeCallback(cb);
_timeCallback = cb;
}

View File

@ -117,6 +117,7 @@ public:
protected:
FileImplPtr _p;
time_t (*_timeCallback)(void) = nullptr;
// Arduino SD class emulation
std::shared_ptr<Dir> _fakeDir;
@ -144,7 +145,7 @@ public:
protected:
DirImplPtr _impl;
FS *_baseFS;
time_t (*timeCallback)(void) = nullptr;
time_t (*_timeCallback)(void) = nullptr;
};
// Backwards compatible, <4GB filesystem usage
@ -197,7 +198,7 @@ public:
class FS
{
public:
FS(FSImplPtr impl) : _impl(impl) { timeCallback = _defaultTimeCB; }
FS(FSImplPtr impl) : _impl(impl) { _timeCallback = _defaultTimeCB; }
bool setConfig(const FSConfig &cfg);
@ -233,13 +234,15 @@ public:
bool gc();
bool check();
time_t getCreationTime();
void setTimeCallback(time_t (*cb)(void));
friend class ::SDClass; // More of a frenemy, but SD needs internal implementation to get private FAT bits
protected:
FSImplPtr _impl;
FSImplPtr getImpl() { return _impl; }
time_t (*timeCallback)(void);
time_t (*_timeCallback)(void) = nullptr;
static time_t _defaultTimeCB(void) { return time(NULL); }
};

View File

@ -45,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
@ -56,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 {
@ -90,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 {
@ -115,14 +115,15 @@ public:
virtual bool rmdir(const char* path) = 0;
virtual bool gc() { return true; } // May not be implemented in all file systems.
virtual bool check() { return true; } // May not be implemented in all file systems.
virtual time_t getCreationTime() { return 0; } // May not be implemented in all file systems.
// 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

View 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"

File diff suppressed because it is too large Load Diff

View 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__

165
cores/esp8266/LwipIntf.cpp Normal file
View File

@ -0,0 +1,165 @@
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();
}
/**
Get ESP8266 station DHCP hostname
@return hostname
*/
const char* LwipIntf::getHostname(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;
}

View File

@ -3,6 +3,7 @@
#define _LWIPINTF_H
#include <lwip/netif.h>
#include <IPAddress.h>
#include <functional>
@ -12,15 +13,37 @@ 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);
// ESP32 API compatibility
bool setHostname(const char* aHostName)
{
return hostname(aHostName);
}
const char* getHostname();
protected:
static bool stateChangeSysCB (LwipIntf::CBType&& cb);
static bool stateChangeSysCB(LwipIntf::CBType&& cb);
};
#endif // _LWIPINTF_H

View File

@ -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
View 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

View File

@ -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");

View File

@ -30,10 +30,8 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat
return;
}
#ifndef WAVEFORM_LOCKED_PHASE
// Stop any analogWrites (PWM) because they are a different generator
_stopPWM(_pin);
#endif
// If there's another Tone or startWaveform on this pin
// it will be changed on-the-fly (no need to stop it)

View File

@ -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){

View File

@ -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

View File

@ -98,6 +98,32 @@ String::String(unsigned long value, unsigned char base) {
*this = buf;
}
String::String(long long value) {
init();
char buf[2 + 8 * sizeof(long long)];
sprintf(buf, "%lld", value);
*this = buf;
}
String::String(unsigned long long value) {
init();
char buf[1 + 8 * sizeof(unsigned long long)];
sprintf(buf, "%llu", value);
*this = buf;
}
String::String(long long value, unsigned char base) {
init();
char buf[2 + 8 * sizeof(long long)];
*this = lltoa(value, buf, sizeof(buf), base);
}
String::String(unsigned long long value, unsigned char base) {
init();
char buf[1 + 8 * sizeof(unsigned long long)];
*this = ulltoa(value, buf, sizeof(buf), base);
}
String::String(float value, unsigned char decimalPlaces) {
init();
char buf[33];
@ -313,6 +339,16 @@ unsigned char String::concat(unsigned long num) {
return concat(buf, strlen(buf));
}
unsigned char String::concat(long long num) {
char buf[2 + 3 * sizeof(long long)];
return concat(buf, sprintf(buf, "%lld", num));
}
unsigned char String::concat(unsigned long long num) {
char buf[1 + 3 * sizeof(unsigned long long)];
return concat(buf, sprintf(buf, "%llu", num));
}
unsigned char String::concat(float num) {
char buf[20];
char *string = dtostrf(num, 4, 2, buf);
@ -399,6 +435,20 @@ StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) {
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, long long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long 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);
if (!a.concat(num))

View File

@ -72,6 +72,10 @@ class String {
explicit String(unsigned int, unsigned char base = 10);
explicit String(long, unsigned char base = 10);
explicit String(unsigned long, unsigned char base = 10);
explicit String(long long /* base 10 */);
explicit String(long long, unsigned char base);
explicit String(unsigned long long /* base 10 */);
explicit String(unsigned long long, unsigned char base);
explicit String(float, unsigned char decimalPlaces = 2);
explicit String(double, unsigned char decimalPlaces = 2);
~String() {
@ -117,6 +121,8 @@ class String {
unsigned char concat(unsigned int num);
unsigned char concat(long num);
unsigned char concat(unsigned long num);
unsigned char concat(long long num);
unsigned char concat(unsigned long long num);
unsigned char concat(float num);
unsigned char concat(double num);
unsigned char concat(const __FlashStringHelper *str);
@ -156,6 +162,14 @@ class String {
concat(num);
return *this;
}
String &operator +=(long long num) {
concat(num);
return *this;
}
String &operator +=(unsigned long long num) {
concat(num);
return *this;
}
String &operator +=(float num) {
concat(num);
return *this;
@ -177,6 +191,8 @@ class String {
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, long long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long 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);
@ -375,6 +391,12 @@ class StringSumHelper: public String {
StringSumHelper(unsigned long num) :
String(num) {
}
StringSumHelper(long long num) :
String(num) {
}
StringSumHelper(unsigned long long num) :
String(num) {
}
StringSumHelper(float num) :
String(num) {
}

View 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

View File

@ -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)
@ -72,6 +73,7 @@ static i2s_state_t *tx = NULL;
// Last I2S sample rate requested
static uint32_t _i2s_sample_rate;
static int _i2s_bits = 16;
// IOs used for I2S. Not defined in i2s.h, unfortunately.
// Note these are internal GPIO numbers and not pins on an
@ -83,6 +85,14 @@ static uint32_t _i2s_sample_rate;
#define I2SI_BCK 13
#define I2SI_WS 14
bool i2s_set_bits(int bits) {
if (tx || rx || (bits != 16 && bits != 24)) {
return false;
}
_i2s_bits = bits;
return true;
}
static bool _i2s_is_full(const i2s_state_t *ch) {
if (!ch) {
return false;
@ -440,7 +450,7 @@ void i2s_set_rate(uint32_t rate) { //Rate in HZ
}
_i2s_sample_rate = rate;
uint32_t scaled_base_freq = I2SBASEFREQ/32;
uint32_t scaled_base_freq = I2SBASEFREQ / (_i2s_bits * 2);
float delta_best = scaled_base_freq;
uint8_t sbd_div_best=1;
@ -482,6 +492,9 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) {
// div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this
i2sc_temp |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD);
// Adjust the shift count for 16/24b output
i2sc_temp |= (_i2s_bits == 24 ? 8 : 0) << I2SBM;
I2SC = i2sc_temp;
i2sc_temp &= ~(I2STXR); // Release reset
@ -489,10 +502,14 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) {
}
float i2s_get_real_rate(){
return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
return (float)I2SBASEFREQ/(_i2s_bits * 2)/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
}
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 +520,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 +533,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()) {
@ -538,6 +561,9 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
// I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data shifted in/out
I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only)
if (_i2s_bits == 24) {
I2SFC |= (2 << I2STXFM) | (2 << I2SRXFM);
}
I2SFC |= I2SDE; // Enable DMA
// I2STXCMM, I2SRXCMM=0 => Dual channel mode
@ -579,15 +605,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;
}

View File

@ -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,

View 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);
}
}
};

View 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

View File

@ -1,7 +1,128 @@
// Wrapper to include both versions of the waveform generator
/*
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
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).
----------
-- 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
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>
#ifndef __ESP8266_WAVEFORM_H
#define __ESP8266_WAVEFORM_H
#ifdef __cplusplus
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, 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 = 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, 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 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 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.
// Pass in NULL to disable the callback and, if no other waveforms being
// generated, stop the timer as well.
// 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
#ifdef WAVEFORM_LOCKED_PHASE
#include "core_esp8266_waveform_phase.h"
#else
#include "core_esp8266_waveform_pwm.h"
#endif

View File

@ -39,13 +39,26 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef WAVEFORM_LOCKED_PHASE
#include "core_esp8266_waveform_phase.h"
#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
@ -122,7 +135,7 @@ static void ICACHE_RAM_ATTR deinitTimer() {
extern "C" {
// Set a callback. Pass in NULL to stop it
void setTimer1Callback(uint32_t (*fn)()) {
void setTimer1Callback_weak(uint32_t (*fn)()) {
waveform.timer1CB = fn;
std::atomic_thread_fence(std::memory_order_acq_rel);
if (!waveform.timer1Running && fn) {
@ -132,17 +145,10 @@ void setTimer1Callback(uint32_t (*fn)()) {
}
}
int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS,
uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
return startWaveformClockCycles(pin,
microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS),
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
}
// 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(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
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) {
@ -212,7 +218,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
}
// Stops a waveform on a pin
int ICACHE_RAM_ATTR stopWaveform(uint8_t 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;
@ -436,5 +442,3 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
// Register access is fast and edge IRQ was configured before.
T1L = nextEventCcys;
}
#endif // WAVEFORM_LOCKED_PHASE

View File

@ -1,93 +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.
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
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
*/
#ifdef WAVEFORM_LOCKED_PHASE
#include <Arduino.h>
#ifndef __ESP8266_WAVEFORM_H
#define __ESP8266_WAVEFORM_H
#ifdef __cplusplus
extern "C" {
#endif
// 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, 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 = 0, 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, 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 timeHighCcys, uint32_t timeLowCcys,
uint32_t runTimeCcys = 0, 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 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.
// Pass in NULL to disable the callback and, if no other waveforms being
// generated, stop the timer as well.
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
void setTimer1Callback(uint32_t (*fn)());
#ifdef __cplusplus
}
#endif
#endif // __ESP8266_WAVEFORM_H
#endif // WAVEFORM_LOCKED_PHASE

View File

@ -38,12 +38,13 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef WAVEFORM_LOCKED_PHASE
#include <Arduino.h>
#include "ets_sys.h"
#include "core_esp8266_waveform_pwm.h"
#include "core_esp8266_waveform.h"
#include "user_interface.h"
extern "C" {
// Maximum delay between IRQs
@ -169,8 +170,10 @@ static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range);
// Called when analogWriteFreq() changed to update the PWM total period
void _setPWMFreq(uint32_t freq) {
extern void _setPWMFreq_weak(uint32_t freq) __attribute__((weak));
void _setPWMFreq_weak(uint32_t freq) {
_pwmFreq = freq;
// Convert frequency into clock cycles
@ -204,6 +207,11 @@ void _setPWMFreq(uint32_t freq) {
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
@ -228,7 +236,8 @@ static void _cleanAndRemovePWM(PWMState *p, int pin) {
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
ICACHE_RAM_ATTR bool _stopPWM(int pin) {
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
}
@ -250,6 +259,10 @@ ICACHE_RAM_ATTR bool _stopPWM(int pin) {
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
@ -297,7 +310,8 @@ static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) {
}
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
bool _setPWM(int pin, uint32_t val, uint32_t range) {
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;
@ -327,15 +341,21 @@ bool _setPWM(int pin, uint32_t val, uint32_t range) {
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.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) {
return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS));
}
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;
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) {
if ((pin > 16) || isFlashInterfacePin(pin)) {
return false;
}
@ -379,10 +399,23 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time
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
void setTimer1Callback(uint32_t (*fn)()) {
extern void setTimer1Callback_weak(uint32_t (*fn)()) __attribute__((weak));
void setTimer1Callback_weak(uint32_t (*fn)()) {
wvfState.timer1CB = fn;
if (fn) {
initTimer();
@ -390,9 +423,14 @@ void setTimer1Callback(uint32_t (*fn)()) {
}
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
int ICACHE_RAM_ATTR stopWaveform(uint8_t 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;
@ -415,6 +453,10 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
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")
@ -622,5 +664,3 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
}
};
#endif

View File

@ -1,87 +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
*/
#ifndef WAVEFORM_LOCKED_PHASE
#include <Arduino.h>
#ifndef __ESP8266_WAVEFORM_H
#define __ESP8266_WAVEFORM_H
#ifdef __cplusplus
extern "C" {
#endif
// 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.
// Returns true or false on success or failure.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
// 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.
// Returns true or false on success or failure.
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
// 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 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.
// Pass in NULL to disable the callback and, if no other waveforms being
// generated, stop the timer as well.
// 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(int pin);
extern bool _setPWM(int pin, uint32_t val, uint32_t range);
#ifdef __cplusplus
}
#endif
#endif
#endif

View File

@ -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")));
};

View File

@ -82,10 +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); // Disable any tone
#ifndef WAVEFORM_LOCKED_PHASE
_stopPWM(pin); // ...and any analogWrite
#endif
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);

View File

@ -28,7 +28,6 @@ extern "C" {
static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x
#ifdef WAVEFORM_LOCKED_PHASE
static uint32_t analogMap = 0;
static uint16_t analogFreq = 1000;
@ -41,9 +40,14 @@ extern void __analogWriteFreq(uint32_t freq) {
} else {
analogFreq = freq;
}
_setPWMFreq(freq);
}
extern void __analogWrite(uint8_t pin, int val) {
analogWriteMode(pin, val, false);
}
extern void __analogWriteMode(uint8_t pin, int val, bool openDrain) {
if (pin > 16) {
return;
}
@ -62,51 +66,23 @@ extern void __analogWrite(uint8_t pin, int val) {
analogMap &= ~(1 << pin);
}
else {
pinMode(pin, OUTPUT);
if(openDrain) {
pinMode(pin, OUTPUT_OPEN_DRAIN);
} else {
pinMode(pin, OUTPUT);
}
}
uint32_t high = (analogPeriod * val) / analogScale;
uint32_t low = analogPeriod - high;
// 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 (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
if (_setPWM(pin, val, analogScale)) {
analogMap |= (1 << pin);
} else if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
analogMap |= (1 << pin);
}
}
#else // !WAVEFORM_LOCKED_PHASE
extern void __analogWriteFreq(uint32_t freq) {
if (freq < 100) {
freq = 100;
} else if (freq > 60000) {
freq = 60000;
} else {
freq = freq;
}
_setPWMFreq(freq);
}
extern void __analogWrite(uint8_t pin, int val) {
if (pin > 16) {
return;
}
if (val < 0) {
val = 0;
} else if (val > analogScale) {
val = analogScale;
}
// 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)
pinMode(pin, OUTPUT);
_setPWM(pin, val, analogScale);
}
#endif // WAVEFORM_LOCKED_PHASE
extern void __analogWriteRange(uint32_t range) {
if ((range >= 15) && (range <= 65535)) {
analogScale = range;
@ -120,6 +96,7 @@ extern void __analogWriteResolution(int res) {
}
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));
extern void analogWriteMode(uint8_t pin, int val, bool openDrain) __attribute__((weak, alias("__analogWriteMode")));
extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq")));
extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange")));
extern void analogWriteResolution(int res) __attribute__((weak, alias("__analogWriteResolution")));

View File

@ -1,36 +1,53 @@
/*
debug.cpp - debug helper functions
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
debug.cpp - debug helper functions
Copyright (c) 2015 Markus Sattler. 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 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.
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
*/
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 "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;
os_printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
for(uint32_t i = 0; i < len; i++) {
if(i % cols == 0) {
os_printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
yield();
IRAM_ATTR
void hexdump(const void *mem, uint32_t len, uint8_t cols)
{
const char* src = (const char*)mem;
os_printf("\n[HEXDUMP] Address: %p len: 0x%X (%d)", src, len, len);
while (len > 0)
{
uint32_t linesize = cols > len ? len : cols;
os_printf("\n[%p] 0x%04x: ", src, (int)(src - (const char*)mem));
for (uint32_t i = 0; i < linesize; i++)
{
os_printf("%02x ", *(src + i));
}
os_printf("%02X ", *src);
src++;
os_printf(" ");
for (uint32_t i = linesize; i < cols; i++)
{
os_printf(" ");
}
for (uint32_t i = 0; i < linesize; i++)
{
unsigned char c = *(src + i);
os_putc(isprint(c) ? c : '.');
}
src += linesize;
len -= linesize;
yield();
}
os_printf("\n");
}

View File

@ -13,7 +13,7 @@
#endif
#ifdef __cplusplus
void hexdump(const void *mem, uint32_t len, uint8_t cols = 16);
extern "C" void hexdump(const void *mem, uint32_t len, uint8_t cols = 16);
#else
void hexdump(const void *mem, uint32_t len, uint8_t cols);
#endif

View File

@ -1,8 +1,31 @@
// 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
#include <stdint.h>
#include <eagle_soc.h>
#include <spi_flash.h>
#define PERIPHS_DPORT_18 (PERIPHS_DPORT_BASEADDR + 0x018)
#define PERIPHS_DPORT_ICACHE_ENABLE (PERIPHS_DPORT_BASEADDR + 0x024)
/* When enabled 16K IRAM starting at 0x4010C000 is unmapped */
#define ICACHE_ENABLE_FIRST_16K BIT3
/* When enabled 16K IRAM starting at 0x40108000 is unmapped */
#define ICACHE_ENABLE_SECOND_16K BIT4
#define PERIPHS_HW_WDT (0x60000900)
#define PERIPHS_I2C_48 (0x60000a00 + 0x348)
extern void (*user_start_fptr)();
#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
@ -11,6 +34,12 @@ extern int rom_i2c_readReg_Mask(int, int, int, int, int);
extern int uart_baudrate_detect(int, int);
/* SDK/Flash contains also an implementation of this function
* but for reboot into UART download mode the version from ROM
* has to be used because flash is not accessible.
*/
extern void rom_uart_div_modify(uint8 uart_no, uint32 DivLatchValue);
/*
ROM function, uart_buff_switch(), is used to switch printing between UART0 and
UART1. It updates a structure that only controls a select group of print
@ -32,8 +61,254 @@ extern void uart_buff_switch(uint8_t);
*/
extern int ets_uart_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
extern void user_uart_wait_tx_fifo_empty(uint32_t ch, uint32_t arg2);
extern void uartAttach();
extern void Uart_Init(uint32_t uart_no);
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
extern uint32_t Wait_SPI_Idle(SpiFlashChip *fc);
extern void Cache_Read_Disable();
extern int32_t system_func1(uint32_t);
extern void clockgate_watchdog(uint32_t);
extern void pm_open_rf();
extern void ets_install_uart_printf(uint32_t uart_no);
extern void UartDwnLdProc(uint8_t* ram_addr, uint32_t size, void (**user_start_ptr)());
extern int boot_from_flash();
extern void ets_run() __attribute__((noreturn));
#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

View 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

View 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

View File

@ -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
@ -183,8 +194,8 @@ void ICACHE_RAM_ATTR print_oom_size(size_t size)
}
}
#define OOM_CHECK__PRINT_OOM(p, s) if (!p) print_oom_size(s)
#define OOM_CHECK__PRINT_LOC(p, s, f, l) if (!p) print_loc(s, f, l)
#define OOM_CHECK__PRINT_OOM(p, s) if ((s) && !(p)) print_oom_size(s)
#define OOM_CHECK__PRINT_LOC(p, s, f, l) if ((s) && !(p)) print_loc(s, f, l)
#else // ! DEBUG_ESP_OOM
@ -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);
}
};

View File

@ -21,10 +21,13 @@
#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.
- Start up a thread that's going to do the sound output
- Call i2s_set_bits() if you want to enable 24-bit mode
- Call i2s_begin()
- Call i2s_set_rate() with the sample rate you want.
- Generate sound and call i2s_write_sample() with 32-bit samples.
@ -40,8 +43,13 @@ speed.
extern "C" {
#endif
bool i2s_set_bits(int bits); // Set bits per sample, only 16 or 24 supported. Call before begin.
// Note that in 24 bit mode each sample must be left-aligned (i.e. 0x00000000 .. 0xffffff00) as the
// hardware shifts starting at bit 31, not bit 23.
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
View 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
View 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

View File

@ -0,0 +1,151 @@
/*
ESP8266-specific implementation of the UART download mode
Copyright (c) 2021 Timo Wischer <twischer@freenet.de>
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
This implementation is based on the original implementation of the ROM.
It was shortend to reduce the memory usage. The complete version and the
development history can be found in:
https://github.com/twischer/Arduino/tree/reboot_uart_download_full
This might be usefull in case of issues.
*/
#include "reboot_uart_dwnld.h"
#include <stdnoreturn.h>
#include <user_interface.h>
#include <esp8266_undocumented.h>
static inline uint32_t __rsil_1() {
uint32_t program_state;
asm volatile("rsil %0, 1" : "=r" (program_state));
return program_state;
}
static inline void __wsr_intenable(uint32_t interupt_enable) {
asm volatile("wsr.intenable %0" :: "r" (interupt_enable));
}
static inline void __wsr_litbase(uint32_t literal_base) {
asm volatile("wsr.litbase %0" :: "r" (literal_base));
}
static inline void __wsr_ps(uint32_t program_state) {
asm volatile("wsr.ps %0" :: "r" (program_state));
}
static inline void __wsr_vecbase(uint32_t vector_base) {
asm volatile("wsr.vecbase %0" :: "r" (vector_base));
}
[[noreturn]] void ICACHE_RAM_ATTR esp8266UartDownloadMode()
{
/* reverse engineered from system_restart_core() */
/* Before disabling instruction cache and restoring instruction RAM to a
* power-on like state, SPI bus must be idle.
*/
Wait_SPI_Idle(flashchip);
Cache_Read_Disable();
/* This will disable the 32kB instruction cache and extend the IRAM by 32kB.
* Therefore the full 64kB of IRAM will be available for boot.
* Cache_Read_Enable() sets those bits but Cache_Read_Disable() does not clear
* them. On hardware reset those bits are cleared. Therefore clear them also
* for this reboot.
*/
CLEAR_PERI_REG_MASK(PERIPHS_DPORT_ICACHE_ENABLE,
ICACHE_ENABLE_FIRST_16K | ICACHE_ENABLE_SECOND_16K);
/* reverse engineered from _ResetHandler() */
/* disable all level 1 interrupts */
__wsr_intenable(0);
/* Clear the literal base to use an offset of 0 for
* Load 32-bit PC-Relative(L32R) instructions
*/
__wsr_litbase(0);
asm volatile("rsync");
/* Set interrupt vector base address to system ROM */
__wsr_vecbase(0x40000000);
/* Set interrupt level to 1. Therefore disable interrupts of level 1.
* Above levels like level 2,... might still be active if available
* on ESP8266.
*/
__rsil_1();
/* reverse engineered from _start() */
/* Set stack pointer to upper end of data RAM */
const uint32_t stack_pointer = 0x40000000;
asm volatile("mov a1, %0" :: "r" (stack_pointer));
/* Set the program state register
* Name Value Description
* Interrupt level disable 0 enable all interrupt levels
* Exception mode 0 normal operation
* User vector mode 1 user vector mode, exceptions need to switch stacks
* Privilege level 0 Set to Ring 0
*/
__wsr_ps(0x20);
asm volatile("rsync");
/* reverse engineered from main() */
const uint32_t uart_no = 0;
uartAttach();
Uart_Init(uart_no);
ets_install_uart_printf(uart_no);
/* reverse engineered from boot_from_something() */
const uint16_t divlatch = uart_baudrate_detect(uart_no, 0);
rom_uart_div_modify(uart_no, divlatch);
UartDwnLdProc((uint8_t*)0x3fffa000, 0x2000, &user_start_fptr);
/* reverse engineered from main() */
if (user_start_fptr == NULL) {
if (boot_from_flash() != 0) {
ets_printf("boot_from_flash() failed\n");
while (true);
}
}
if (user_start_fptr) {
user_start_fptr();
}
ets_printf("user code done\n");
ets_run();
}
[[noreturn]] void esp8266RebootIntoUartDownloadMode()
{
/* reverse engineered from system_restart_local() */
if (system_func1(0x4) == -1) {
clockgate_watchdog(0);
SET_PERI_REG_MASK(PERIPHS_DPORT_18, 0xffff00ff);
pm_open_rf();
}
user_uart_wait_tx_fifo_empty(0, 0x7a120);
user_uart_wait_tx_fifo_empty(1, 0x7a120);
ets_intr_lock();
SET_PERI_REG_MASK(PERIPHS_DPORT_18, 0x7500);
CLEAR_PERI_REG_MASK(PERIPHS_DPORT_18, 0x7500);
SET_PERI_REG_MASK(PERIPHS_I2C_48, 0x2);
CLEAR_PERI_REG_MASK(PERIPHS_I2C_48, 0x2);
esp8266UartDownloadMode();
}

View File

@ -0,0 +1,23 @@
/*
ESP8266-specific implementation of the UART download mode
Copyright (c) 2021 Timo Wischer <twischer@freenet.de>
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
*/
#include <stdnoreturn.h>
[[noreturn]] void esp8266RebootIntoUartDownloadMode();

View File

@ -26,8 +26,6 @@
*/
#include <limits>
#include "FS.h"
#undef max
#undef min
#include "FSImpl.h"
extern "C" {
#include "spiffs/spiffs.h"

View File

@ -0,0 +1,62 @@
/*
stdlib_noniso.h - nonstandard (but usefull) conversion functions
Copyright (c) 2021 David Gauchard. 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
*/
#include "stdlib_noniso.h"
// ulltoa() is slower than std::to_char() (1.6 times)
// but is smaller by ~800B/flash and ~250B/rodata
// ulltoa fills str backwards and can return a pointer different from str
char* ulltoa(unsigned long long val, char* str, int slen, unsigned int radix)
{
str += --slen;
*str = 0;
do
{
auto mod = val % radix;
val /= radix;
*--str = mod + ((mod > 9) ? ('a' - 10) : '0');
} while (--slen && val);
return val? nullptr: str;
}
// lltoa fills str backwards and can return a pointer different from str
char* lltoa (long long val, char* str, int slen, unsigned int radix)
{
bool neg;
if (val < 0)
{
val = -val;
neg = true;
}
else
{
neg = false;
}
char* ret = ulltoa(val, str, slen, radix);
if (neg)
{
if (ret == str || ret == nullptr)
return nullptr;
*--ret = '-';
}
return ret;
}

View File

@ -1,9 +1,9 @@
/*
/*
stdlib_noniso.h - nonstandard (but usefull) conversion functions
Copyright (c) 2014 Ivan Grokhotkov. 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
@ -36,10 +36,14 @@ char* itoa (int val, char *s, int radix);
char* ltoa (long val, char *s, int radix);
char* lltoa (long long val, char* str, int slen, unsigned int radix);
char* utoa (unsigned int val, char *s, int radix);
char* ultoa (unsigned long val, char *s, int radix);
char* ulltoa (unsigned long long val, char* str, int slen, unsigned int radix);
char* dtostrf (double val, signed char width, unsigned char prec, char *s);
void reverse(char* begin, char* end);

View File

@ -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>

View File

@ -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;
}

View 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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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)

View File

@ -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;

View File

@ -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,

View 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_ */

View File

@ -207,6 +207,18 @@ ESP8285 (`datasheet <http://www.espressif.com/sites/default/files/0a-esp8285_dat
Note that since ESP8285 has SPI flash memory internally connected in DOUT mode, pins 9 and 10 may be used as GPIO / I2C / PWM pins.
Lifely Agrumino Lemon v4
------------------------
Procuct page https://www.lifely.cc
This Board "Lifely Agrumino Lemon" is based with WT8266-S1 core with WiFi 2,4Ghz and 2MB of Flash.
Power
Micro usb power cable, Lir2450 rechargeable battery (or not rechargeable)or with JST connector in the back board Max 6 Vin
Libraries and examples
Download libraries from: Official Arduino Ide, our website https://www.lifely.cc or https://github.com/lifely-cc/
Full pinout and PDF for setup here https://www.lifely.cc our libraries is OpenSource
ESPDuino (ESP-13 Module)
------------------------

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -32,7 +32,7 @@ For the SoftAP interface, when the interface is brought up, any servers should b
A detailed explanation of ``WiFiEventHandler`` can be found in the section with `examples :arrow\_right: <generic-examples.rst>`__ dedicated specifically to the Generic Class..
Alternatively, check the example sketch `WiFiEvents.ino <https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/WiFiEvents/WiFiEvents.ino>`__ available inside examples folder of the ESP8266WiFi library.
Alternatively, check the example sketch `WiFiEvents.ino <https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/WiFiEvents/WiFiEvents.ino>`__ available in the examples folder of the ESP8266WiFi library.
persistent
@ -42,30 +42,56 @@ persistent
WiFi.persistent(persistent)
ESP8266 is able to reconnect to the last used Wi-Fi network or establishes the same Access Point upon power up or reset.
ESP8266 is able to reconnect to the last used WiFi network or establishes the same Access Point upon power up or reset.
By default, these settings are written to specific sectors of flash memory every time they are changed in ``WiFi.begin(ssid, passphrase)`` or ``WiFi.softAP(ssid, passphrase, channel)``, and when ``WiFi.disconnect`` or ``WiFi.softAPdisconnect`` is invoked.
Frequently calling these functions could cause wear on the flash memory (see issue `#1054 <https://github.com/esp8266/Arduino/issues/1054>`__).
Once ``WiFi.persistent(false)`` is called, ``WiFi.begin``, ``WiFi.disconnect``, ``WiFi.softAP``, or ``WiFi.softAPdisconnect`` only changes the current in-memory Wi-Fi settings, and does not affect the Wi-Fi settings stored in flash memory.
Once ``WiFi.persistent(false)`` is called, ``WiFi.begin``, ``WiFi.disconnect``, ``WiFi.softAP``, or ``WiFi.softAPdisconnect`` only changes the current in-memory WiFi settings, and does not affect the WiFi settings stored in flash memory.
mode
~~~~
Regular WiFi modes
__________________
.. code:: cpp
WiFi.mode(m)
bool mode(WiFiMode_t m)
- ``WiFi.mode(m)``: set mode to ``WIFI_AP``, ``WIFI_STA``,
``WIFI_AP_STA`` or ``WIFI_OFF``
Switches to one of the regular WiFi modes, where ``m`` is one of:
- ``WIFI_OFF``: turn WiFi off.
- ``WIFI_STA``: switch to `Station (STA) <readme.rst#station>`__ mode.
- ``WIFI_AP``: switch to `Access Point (AP) <readme.rst#soft-access-point>`__ mode.
- ``WIFI_AP_STA``: enable both Station (STA) and Access Point (AP) mode.
Pseudo-modes
____________
.. code:: cpp
bool mode(WiFiMode_t m, WiFiState* state)
Used with the following pseudo-modes, where ``m`` is one of:
- ``WIFI_SHUTDOWN``: Fills in the provided ``WiFiState`` structure, switches to ``WIFI_OFF`` mode and puts WiFi into forced sleep, preserving energy.
- ``WIFI_RESUME``: Turns WiFi on and tries to re-establish the WiFi connection stored in the ``WiFiState`` structure.
These modes are used in low-power scenarios, e.g. where ESP.deepSleep is used between actions to preserve battery power.
It is the user's responsibility to preserve the WiFiState between ``WIFI_SHUTDOWN`` and ``WIFI_RESUME``, e.g. by storing it
in RTC user data and/or flash memory.
There is an example sketch `WiFiShutdown.ino <https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/WiFiShutdown/WiFiShutdown.ino>`__ available in the examples folder of the ESP8266WiFi library.
getMode
~~~~~~~
.. code:: cpp
WiFiMode_t WiFi.getMode()
WiFiMode_t getMode()
- ``WiFi.getMode()``: return current Wi-Fi mode (one out of four modes above)
Gets the current WiFi mode (one out of four regular modes above).
WiFi power management, DTIM
~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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.

View File

@ -13,10 +13,11 @@ 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>
Exception causes <exception_causes>
Debugging <Troubleshooting/debugging>
Stack Dump <Troubleshooting/stack_dump>

View File

@ -1,8 +1,8 @@
Libraries
=========
WiFi(ESP8266WiFi library)
-------------------------
WiFi (ESP8266WiFi library)
--------------------------
ESP8266WiFi library has been developed basing on ESP8266 SDK, using naming convention and overall functionality philosophy of the `Arduino WiFi Shield library <https://www.arduino.cc/en/Reference/WiFi>`__. Over time the wealth Wi-Fi features ported from ESP8266 SDK to this library outgrew the APIs of WiFi Shield library and it became apparent that we need to provide separate documentation on what is new and extra.

237
doc/mmu.rst Normal file
View 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);
::

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -109,6 +109,9 @@ PWM range may be changed by calling ``analogWriteRange(new_range)`` or
``analogWriteResolution(bits)``. ``new_range`` may be from 15...65535
or ``bits`` may be from 4...16.
The function ``analogWriteMode(pin, value, openDrain)`` allows to sets
the pin mode to ``OUTPUT_OPEN_DRAIN`` instead of ``OUTPUT``.
**NOTE:** The default ``analogWrite`` range was 1023 in releases before
3.0, but this lead to incompatibility with external libraries which
depended on the Arduino core default of 256. Existing applications which

View File

@ -13,6 +13,7 @@ analogWriteFreq KEYWORD2
analogWriteRange KEYWORD2
baudrate KEYWORD2
swap KEYWORD2
enablePhaseLockedWaveform KEYWORD2
######################################
# Constants (LITERAL1)

View File

@ -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() {

View File

@ -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();

View File

@ -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 {

@ -1 +1 @@
Subproject commit b240d2231a117bbd89b79902eb54cae948ee2f42
Subproject commit 9f94df41ccdb064ff4cb98d7b2737a369f6be1a0

View File

@ -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);
}
}

View File

@ -34,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 {
@ -254,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>
@ -606,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];
}
}

View File

@ -308,5 +308,4 @@ protected:
using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate<WiFiServer>;
using RequestHandler = esp8266webserver::RequestHandler<WiFiServer>;
#endif //ESP8266WEBSERVER_H
#endif //ESP8266WEBSERVER_H

View File

@ -467,8 +467,14 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const
for (int i = 0; i < boundaryPtr; i++) {
_uploadWriteByte( fastBoundary[ i ] );
}
_uploadWriteByte( in );
boundaryPtr = 0;
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

View File

@ -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
#endif //REQUESTHANDLERSIMPL_H

View File

@ -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();
}

View File

@ -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),

View File

@ -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: ");

View File

@ -7,6 +7,7 @@
- Registering multiple networks (at least 1)
- Connect to WiFi with strongest signal (RSSI)
- Fall back to connect to next WiFi when a connection failed or lost
- Fall back to connect to hidden SSID's which are not reported by WiFi scan
To enable debugging output, select in the Arduino iDE:
- Tools | Debug Port: Serial
@ -21,6 +22,9 @@ ESP8266WiFiMulti wifiMulti;
const uint32_t connectTimeoutMs = 5000;
void setup() {
// Don't save WiFi configuration in flash - optional
WiFi.persistent(false);
Serial.begin(115200);
Serial.println("\nESP8266 Multi WiFi example");

View File

@ -0,0 +1,85 @@
// Demonstrate the use of WiFi.mode(WIFI_SHUTDOWN)/WiFi.mode(WIFI_RESUME)
// Released to public domain
// Current on WEMOS D1 mini (including: LDO, usbserial chip):
// ~85mA during normal operations
// ~30mA during wifi shutdown
// ~5mA during deepsleep
#ifndef STASSID
#define STASSID "mynetwork"
#define STAPSK "mynetworkpasswd"
#endif
#ifndef RTC_USER_DATA_SLOT_WIFI_STATE
#define RTC_USER_DATA_SLOT_WIFI_STATE 33u
#endif
#include <ESP8266WiFi.h>
#include <include/WiFiState.h> // WiFiState structure details
WiFiState state;
const char* ssid = STASSID;
const char* password = STAPSK;
void preinit(void) {
// Make sure, wifi stays off after boot.
ESP8266WiFiClass::preinitWiFiOff();
}
void setup() {
Serial.begin(74880);
//Serial.setDebugOutput(true); // If you need debug output
Serial.println("Trying to resume WiFi connection...");
// May be necessary after deepSleep. Otherwise you may get "error: pll_cal exceeds 2ms!!!" when trying to connect
delay(1);
// ---
// Here you can do whatever you need to do that doesn't need a WiFi connection.
// ---
ESP.rtcUserMemoryRead(RTC_USER_DATA_SLOT_WIFI_STATE, reinterpret_cast<uint32_t *>(&state), sizeof(state));
unsigned long start = millis();
if (!WiFi.mode(WIFI_RESUME, &state)
|| (WiFi.waitForConnectResult(10000) != WL_CONNECTED)) {
Serial.println("Cannot resume WiFi connection, connecting via begin...");
WiFi.persistent(false);
if (!WiFi.mode(WIFI_STA)
|| !WiFi.begin(ssid, password)
|| (WiFi.waitForConnectResult(10000) != WL_CONNECTED)) {
WiFi.mode(WIFI_OFF);
Serial.println("Cannot connect!");
Serial.flush();
ESP.deepSleep(10e6, RF_DISABLED);
return;
}
}
unsigned long duration = millis() - start;
Serial.printf("Duration: %f", duration * 0.001);
Serial.println();
// ---
// Here you can do whatever you need to do that needs a WiFi connection.
// ---
WiFi.mode(WIFI_SHUTDOWN, &state);
ESP.rtcUserMemoryWrite(RTC_USER_DATA_SLOT_WIFI_STATE, reinterpret_cast<uint32_t *>(&state), sizeof(state));
// ---
// Here you can do whatever you need to do that doesn't need a WiFi connection anymore.
// ---
Serial.println("Done.");
Serial.flush();
ESP.deepSleep(10e6, RF_DISABLED);
}
void loop() {
// Nothing to do here.
}

View File

@ -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)
#######################################

View File

@ -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);
@ -872,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 );

View File

@ -133,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:
@ -170,7 +214,7 @@ class SigningVerifier : public UpdaterVerifyClass {
private:
PublicKey *_pubKey;
};
// Stack thunked versions of calls
extern "C" {
extern unsigned char *thunk_br_ssl_engine_recvapp_buf( const br_ssl_engine_context *cc, size_t *len);

View File

@ -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();

View File

@ -25,7 +25,7 @@
#include <stdint.h>
extern "C" {
#include "include/wl_definitions.h"
#include <wl_definitions.h>
}
#include "IPAddress.h"

View File

@ -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;
}
@ -228,6 +224,7 @@ bool ESP8266WiFiAPClass::softAPConfig(IPAddress local_ip, IPAddress gateway, IPA
}
struct dhcps_lease dhcp_lease;
dhcp_lease.enable = true;
IPAddress ip = local_ip;
ip[3] += 99;
dhcp_lease.start_ip.addr = ip.v4();
@ -237,19 +234,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;
}
@ -265,8 +265,7 @@ bool ESP8266WiFiAPClass::softAPConfig(IPAddress local_ip, IPAddress gateway, IPA
DEBUG_WIFI("[APConfig] IP config Invalid?!\n");
ret = false;
} else if(local_ip.v4() != info.ip.addr) {
ip = info.ip.addr;
DEBUG_WIFI("[APConfig] IP config not set correct?! new IP: %s\n", ip.toString().c_str());
DEBUG_WIFI("[APConfig] IP config not set correct?! new IP: %s\n", IPAddress(info.ip.addr).toString().c_str());
ret = false;
}
} else {

View File

@ -826,11 +826,10 @@ bool ESP8266WiFiGenericClass::resumeFromShutdown (WiFiState* state)
}
}
}
// state->state.fwconfig.bssid is not real bssid (it's what user may have provided when bssid_set==1)
auto beginResult = WiFi.begin((const char*)state->state.fwconfig.ssid,
(const char*)state->state.fwconfig.password,
state->state.channel,
nullptr/*(const uint8_t*)state->state.fwconfig.bssid*/, // <- try with gw's mac address?
state->state.fwconfig.bssid,
true);
if (beginResult == WL_CONNECT_FAILED)
{

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More