1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-21 10:26:06 +03:00

Merge branch 'master' into wifi_mesh_update_2.2

This commit is contained in:
Develo 2021-03-14 16:41:13 -03:00 committed by GitHub
commit 7fbf620ab6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
665 changed files with 28990 additions and 83668 deletions

271
.github/workflows/pull-request.yml vendored Normal file
View File

@ -0,0 +1,271 @@
# Run whenever a PR is generated or updated.
# Most jobs check out the code, ensure Python3 is installed, and for build
# tests the ESP8266 toolchain is cached when possible to speed up execution.
name: ESP8266 Arduino CI
on:
pull_request:
jobs:
# Run 8 parallel jobs for the default build of all examples.
build-linux:
name: Build ${{ matrix.chunk }}
runs-on: ubuntu-latest
strategy:
matrix:
chunk: [0, 1, 2, 3, 4, 5, 6, 7]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Cache Linux toolchain
id: cache-linux
uses: actions/cache@v2
with:
path: ./tools/dist
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
- name: Build Sketches
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
BUILD_PARITY: custom
mod: 8
rem: ${{ matrix.chunk }}
run: |
bash ./tests/build.sh
# Cover the debug and IPv6 cases by enabling both and running 8 parallel jobs
# over all example code.
build-debug-ipv6:
name: Debug IPv6 ${{ matrix.chunk }}
runs-on: ubuntu-latest
strategy:
matrix:
chunk: [0, 1, 2, 3, 4, 5, 6, 7]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Cache Linux toolchain
id: cache-linux
uses: actions/cache@v2
with:
path: ./tools/dist
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
- name: Build Sketches
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
BUILD_PARITY: custom
mod: 8
rem: ${{ matrix.chunk }}
run: |
bash ./tests/debug6.sh
# Single build under Windows to ensure the Win toolchain is good.
build-windows:
name: Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Cache Windows toolchain
id: cache-windows
uses: actions/cache@v2
with:
path: ./tools/dist
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
- name: Build Sketch
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
WINDOWS: 1
BUILD_PARITY: custom
mod: 500
rem: 1
run: |
# Windows has python3 already installed, but it's called "python".
# Copy python.exe to the proper name so scripts "just work".
try { Get-Command python3 } catch { copy (get-command python).source (get-command python).source.Replace("python.exe", "python3.exe") }
bash ./tests/build.sh
# Single build under macOS to ensure the Mac toolchain is good.
build-mac:
name: Mac
runs-on: macOS-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Cache Mac toolchain
id: cache-mac
uses: actions/cache@v2
with:
path: ./tools/dist
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
- name: Build Sketch
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
MACOSX: 1
BUILD_PARITY: custom
mod: 500
rem: 1
run: |
bash ./tests/build.sh
# Run a few Platform.IO jobs (not full suite) to check PIO integration.
build-pio:
name: Build Platform.IO
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Build subset on Platform.IO
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
BUILD_PARITY: custom
mod: 42 # Picked at random to give 4-5 builds and exit.
rem: 13
run: |
sudo apt update
sudo apt install python3-pip python3-setuptools
PATH=/home/runner/.local/bin:$PATH bash ./tests/platformio.sh
# Run host test suite under valgrind for runtime checking of code.
host-tests:
name: Host tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Run host tests
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
run: |
sudo apt update
sudo apt install valgrind lcov
bash ./tests/ci/host_test.sh
# Ensure Sphinx can build the documentation properly.
documentation:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Build documentation
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
run: |
sudo apt update
sudo apt install python3-pip python3-setuptools
# GitHub CI installs pip3 and setuptools outside the path.
# Update the path to include them and run.
PATH=/home/runner/.local/bin:$PATH pip3 install --user -r doc/requirements.txt
PATH=/home/runner/.local/bin:$PATH bash ./tests/ci/build_docs.sh
# Standard Arduino formatting in all the examples
style-check:
name: Style and formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Style check
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
run: |
sudo apt update
sudo apt install astyle
bash ./tests/ci/style_check.sh
# Quick test that the mocking builds succeed
mock-check:
name: Mock trivial test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Mock build
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
run: |
bash ./tests/buildm.sh
# Ensure no manual edits to boards.txt
boards-check:
name: Boards.txt check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Cache Linux toolchain
id: cache-linux
uses: actions/cache@v2
with:
path: ./tools/dist
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
- name: Boards.txt diff
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
run: |
bash ./tests/ci/build_boards.sh
bash ./tests/ci/eboot_test.sh
bash ./tests/ci/pkgrefs_test.sh

View File

@ -0,0 +1,56 @@
# Whenever a release is published from a draft, this will update the
# master Arduino JSON file to add its new entry.
# We keep the master JSON file in another repo, so we need to use a pre-set
# Deployment SSH key to be able to push a change to the repo.
#### Steps to follow when you need to make a new SSH key for upload (not
#### normally needed!)
# Generate a new SSH key private/public pair
# ssh-keygen -t rsa -b 4096 -C "your@email.com" -f ./deploy_rsa
# Upload deploy_rsa.pub to the *ESP8266.GITHUB.IO* repo as a deployment key
# Convert the private key to base64 (to remove line breaks and allow easier
# usage in the script as an environment variable)
# base64.exe -w 0 < deploy_rsa > deploy_rsa.b64
# Copy the contents of the .b64 file to the clipboard, make a new GitHub
# secret in the ESP8266/Arduino repo called "GHCI_DEPLOY_KEY" and paste
# the B64 code into the variable.
name: ESP8266 Arduino Release Publisher
on:
release:
types: [published]
jobs:
package:
name: Update master JSON file
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Set GIT tag name
run: |
echo "::set-env name=TRAVIS_TAG::$(git describe --exact-match --tags)"
- name: Deploy updated JSON
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
BUILD_TYPE: package
CI_GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
GHCI_DEPLOY_KEY: ${{ secrets.GHCI_DEPLOY_KEY }}
run: |
bash ./tests/ci/build_package.sh
# Only the regenerated JSON file will be used, but it's simpler
# than looking for it in a GH release.
bash ./package/deploy_package_index.sh

View File

@ -0,0 +1,40 @@
# Whenever a tag of the form #.xxxx is pushed against master, generate a
# draft release and upload the ZIP and JSON file to it. Maintainers then
# will manually add the changelist and publish it.
name: ESP8266 Arduino Draft Release
on:
push:
tags:
# Run for tags of the x.x.x* form (i.e. 3.0.0, 3.0.0-beta, etc.).
- '[0-9]+.[0-9]+.[0-9]+*'
jobs:
package:
name: Package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Set GIT tag name
run: |
# Sets an environment variable used in the next steps
echo "::set-env name=TRAVIS_TAG::$(git describe --exact-match --tags)"
- name: Build package JSON
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
BUILD_TYPE: package
CI_GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
run: |
bash ./tests/ci/build_package.sh
pip3 install PyGithub
# Create a draft release and upload the ZIP and JSON files.
# This draft is not visible to normal users and needs to be
# updated manually with release notes and published from the
# GitHub web interface.
python3 ./package/upload_release.py --user "$GITHUB_ACTOR" --repo "$GITHUB_REPOSITORY" --token "$CI_GITHUB_API_KEY" --tag "$TRAVIS_TAG" --name "Release $TRAVIS_TAG" --msg "Update the draft with release notes before publishing." package/versions/*/*.zip package/versions/*/package_esp8266com_index.json

5
.gitmodules vendored
View File

@ -19,6 +19,9 @@
[submodule "tools/esptool"]
path = tools/esptool
url = https://github.com/espressif/esptool.git
[submodule "libraries/Ethernet"]
path = libraries/Ethernet
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

@ -1,162 +0,0 @@
language: bash
os: linux
dist: bionic
git:
depth: 1
submodules: false
before_install:
- git submodule update --init # no recursive update
cache:
directories:
- $HOME/astyle
stages:
- build
- deploy
jobs:
include:
# Build stage. To save time, run all kinds of builds and tests in parallel.
- name: "Platformio (1)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/platformio.sh
install:
- sudo apt-get install python3-pip python3-setuptools
env:
- BUILD_PARITY=even
- name: "Platformio (2)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/platformio.sh
install:
- sudo apt-get install python3-pip python3-setuptools
env:
- BUILD_PARITY=odd
- name: "Build (1)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/build.sh
env:
- BUILD_PARITY=even
- name: "Build (2)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/build.sh
env:
- BUILD_PARITY=odd
- name: "Debug (1)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/debug.sh
env:
- BUILD_PARITY=even
- name: "Debug (2)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/debug.sh
env:
- BUILD_PARITY=odd
- name: "Build IPv6 (1)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/build6.sh
env:
- BUILD_PARITY=even
- name: "Build IPv6 (2)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/build6.sh
env:
- BUILD_PARITY=odd
- name: "Build lwIP-v1.4 (1)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/build1.sh
env:
- BUILD_PARITY=even
- name: "Build lwIP-v1.4 (2)"
stage: build
script: $TRAVIS_BUILD_DIR/tests/build1.sh
env:
- BUILD_PARITY=odd
- name: "Mac OSX can build sketches"
os: osx
stage: build
script: $TRAVIS_BUILD_DIR/tests/build.sh
env: MACOSX=1 BUILD_PARITY=custom mod=500 rem=1
- name: "Windows can build sketches"
os: windows
stage: build
script: $TRAVIS_BUILD_DIR/tests/build.sh
env: WINDOWS=1 BUILD_PARITY=custom mod=500 rem=1
- name: "Host tests"
stage: build
script: $TRAVIS_BUILD_DIR/tests/ci/host_test.sh
install:
- sudo apt-get install valgrind lcov
- name: "Docs"
stage: build
script: $TRAVIS_BUILD_DIR/tests/ci/build_docs.sh
install:
- sudo apt-get install python3-pip python3-setuptools
- pip3 install --user -r doc/requirements.txt;
- name: "Style check"
stage: build
script: $TRAVIS_BUILD_DIR/tests/ci/style_check.sh
install: tests/ci/install_astyle.sh
- name: "Mock trivial test"
stage: build
script: $TRAVIS_BUILD_DIR/tests/buildm.sh
- name: "Boards"
stage: build
script: $TRAVIS_BUILD_DIR/tests/ci/build_boards.sh
# Deploy stage.
# Here we build the package JSON (always) and do the deployments
- name: "Package / deploy"
stage: deploy
script: tests/ci/build_package.sh
env: BUILD_TYPE=package
before_deploy: git submodule update --init
deploy:
# Create Github release, upload artifacts
- provider: releases
draft: true
skip_cleanup: true
api_key:
secure: kYsxX/N21fwLSTLpbb0c96PnQHn1CIMqZstm02hfUhCX83FygWSh4vs3gzW28DMpjQMZ6vC4g+jtfosYU2tUhht/bynurDH4edpEyGeMyK+fzCI9pAr4JT0RbKQI84EC18ScpgP/UP0jTc1LJ+xl8UMwSiDE0mzHx7xJ4mMNQbA=
file_glob: true
tag_name: $TRAVIS_TAG
target_commitish: $TRAVIS_COMMIT
file:
- package/versions/$TRAVIS_TAG/esp8266-$TRAVIS_TAG.zip
- package/versions/$TRAVIS_TAG/package_esp8266com_index.json
on:
repo: esp8266/Arduino
tags: true
# Update the package index URL to point to the new version
- provider: script
skip_cleanup: true
script: bash package/deploy_package_index.sh
on:
repo: esp8266/Arduino
tags: true
notifications:
email:
on_success: change
on_failure: change
webhooks:
urls:
- secure: "dnSY+KA7NK+KD+Z71copmANDUsyVePrZ0iXvXxmqMEQv+lp3j2Z87G5pHn7j0WNcNZrejJqOdbElJ9Q4QESRaAYxTR7cA6ameJeEKHiFJrQtN/4abvoXb9E1CxpL8aNON/xgnqCk+fycOK3nbWWXlJBodzBm7KN64vrcHO7et+M="
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false

View File

@ -3,7 +3,7 @@ Arduino core for ESP8266 WiFi chip
# Quick links
- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.7.1/)
- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.7.4_a/)
- [Current "git version" documentation](https://arduino-esp8266.readthedocs.io/en/latest/)
- [Install git version](https://arduino-esp8266.readthedocs.io/en/latest/installing.html#using-git-version) ([sources](doc/installing.rst#using-git-version))
@ -30,13 +30,13 @@ 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/)
Boards manager link: `https://arduino.esp8266.com/stable/package_esp8266com_index.json`
Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.1/](https://arduino-esp8266.readthedocs.io/en/2.7.1/)
Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.4_a/](https://arduino-esp8266.readthedocs.io/en/2.7.4_a/)
### Using git version
[![Linux build status](https://travis-ci.org/esp8266/Arduino.svg)](https://travis-ci.org/esp8266/Arduino)
@ -108,7 +108,7 @@ ESP8266 core includes an xtensa gcc toolchain, which is also under GPL.
Esptool.py was initially created by Fredrik Ahlberg (@themadinventor, @kongo), and is currently maintained by Angus Gratton (@projectgus) under GPL 2.0 license.
Espressif SDK included in this build is under Espressif MIT License.
[Espressif's NONOS SDK](https://github.com/espressif/ESP8266_NONOS_SDK) included in this build is under Espressif MIT License.
ESP8266 core files are licensed under LGPL.
@ -118,8 +118,6 @@ ESP8266 core files are licensed under LGPL.
[SoftwareSerial](https://github.com/plerup/espsoftwareserial) library and examples written by Peter Lerup. Distributed under LGPL 2.1.
[axTLS](http://axtls.sourceforge.net/) library written by Cameron Rich, built from https://github.com/igrr/axtls-8266, is used in this project. It is distributed under [BSD license](https://github.com/igrr/axtls-8266/blob/master/LICENSE).
[BearSSL](https://bearssl.org) library written by Thomas Pornin, built from https://github.com/earlephilhower/bearssl-esp8266, is used in this project. It is distributed under the [MIT License](https://bearssl.org/#legal-details).
[LittleFS](https://github.com/ARMmbed/littlefs) library written by ARM Limited and released under the [BSD 3-clause license](https://github.com/ARMmbed/littlefs/blob/master/LICENSE.md).

12215
boards.txt

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 -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,7 +14,6 @@
#include "eboot_command.h"
#include <uzlib.h>
extern unsigned char _gzip_dict;
#define SWRST do { (*((volatile uint32_t*) 0x60000700)) |= 0x80000000; } while(0);
@ -27,15 +26,7 @@ int print_version(const uint32_t flash_addr)
if (SPIRead(flash_addr + APP_START_OFFSET + sizeof(image_header_t) + sizeof(section_header_t), &ver, sizeof(ver))) {
return 1;
}
char fmt[7];
fmt[0] = 'v';
fmt[1] = '%';
fmt[2] = '0';
fmt[3] = '8';
fmt[4] = 'x';
fmt[5] = '\n';
fmt[6] = 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;
}
@ -159,11 +152,6 @@ int copy_raw(const uint32_t src_addr,
gzip = true;
}
while (left > 0) {
if (!verify) {
if (SPIEraseSector(daddr/buffer_size)) {
return 2;
}
}
if (!gzip) {
if (SPIRead(saddr, buffer, buffer_size)) {
return 3;
@ -190,8 +178,25 @@ int copy_raw(const uint32_t src_addr,
return 9;
}
} else {
if (SPIWrite(daddr, buffer, buffer_size)) {
return 4;
// Special treatment for address 0 (bootloader). Only erase and
// rewrite if the data is different (i.e. very rarely).
bool skip = false;
if (daddr == 0) {
if (SPIRead(daddr, buffer2, buffer_size)) {
return 4;
}
if (!memcmp(buffer2, buffer, buffer_size)) {
ets_putc('B'); // Note we skipped the bootloader in output
skip = true; // And skip erase/write
}
}
if (!skip) {
if (SPIEraseSector(daddr/buffer_size)) {
return 2;
}
if (SPIWrite(daddr, buffer, buffer_size)) {
return 4;
}
}
}
saddr += buffer_size;
@ -208,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) {
@ -222,23 +237,27 @@ int main()
}
if (cmd.action == ACTION_COPY_RAW) {
ets_putc('c'); ets_putc('p'); ets_putc(':');
ets_printf("cp:");
ets_wdt_disable();
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], false);
ets_wdt_enable();
ets_putc('0'+res); ets_putc('\n');
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
ets_putc('c'); ets_putc('m'); ets_putc('p'); ets_putc(':');
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();
}
ets_putc('0'+res); ets_putc('\n');
ets_printf("%d\n", res);
#endif
if (res == 0) {
cmd.action = ACTION_LOAD_APP;
cmd.args[0] = cmd.args[1];
@ -250,10 +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
ets_putc('e'); ets_putc(':'); ets_putc('0'+res); ets_putc('\n');
// We will get to this only on load fail
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

@ -128,7 +128,7 @@ struct netifWrapper
const char* ifmac () const { return (const char*)_netif->hwaddr; }
int ifnumber () const { return _netif->num; }
bool ifUp () const { return !!(_netif->flags & NETIF_FLAG_UP); }
CONST netif* interface () const { return _netif; }
const netif* interface () const { return _netif; }
const ip_addr_t* ipFromNetifNum () const
{

View File

@ -37,14 +37,13 @@ extern "C" {
#include "binary.h"
#include "esp8266_peri.h"
#include "twi.h"
#include "core_esp8266_features.h"
#include "core_esp8266_version.h"
#define HIGH 0x1
#define LOW 0x0
#define PWMRANGE 1023
//GPIO FUNCTIONS
#define INPUT 0x00
#define INPUT_PULLUP 0x02
@ -127,21 +126,11 @@ void timer0_isr_init(void);
void timer0_attachInterrupt(timercallback userFunc);
void timer0_detachInterrupt(void);
// undefine stdlib's abs if encountered
#ifdef abs
#undef abs
#endif
#define abs(x) ((x)>0?(x):-(x))
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
#define round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))
#define radians(deg) ((deg)*DEG_TO_RAD)
#define degrees(rad) ((rad)*RAD_TO_DEG)
#define sq(x) ((x)*(x))
void ets_intr_lock();
void ets_intr_unlock();
#define interrupts() xt_rsil(0)
#define noInterrupts() xt_rsil(15)
@ -170,25 +159,23 @@ typedef uint16_t word;
typedef bool boolean;
typedef uint8_t byte;
void ets_intr_lock();
void ets_intr_unlock();
void init(void);
void initVariant(void);
int atexit(void (*func)()) __attribute__((weak));
void pinMode(uint8_t pin, uint8_t mode);
void digitalWrite(uint8_t pin, uint8_t val);
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);
unsigned long millis(void);
unsigned long micros(void);
uint64_t micros64(void);
void delay(unsigned long);
void delayMicroseconds(unsigned int us);
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout);
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout);
@ -224,34 +211,35 @@ void optimistic_yield(uint32_t interval_us);
} // extern "C"
#endif
// undefine stdlib's definitions when encountered, provide abs that supports floating point for C code
#ifndef __cplusplus
#undef abs
#define abs(x) ({ __typeof__(x) _x = (x); _x > 0 ? _x : -_x; })
#undef round
#define round(x) ({ __typeof__(x) _x = (x); _x >= 0 ? (long)(_x + 0.5) : (long)(_x - 0.5); })
#endif // ifndef __cplusplus
//for compatibility, below 4 lines to be removed in release 3.0.0
#ifdef __cplusplus
extern "C"
#endif
const int TIM_DIV265 __attribute__((deprecated, weak)) = TIM_DIV256;
// from this point onward, we need to configure the c++ environment
#ifdef __cplusplus
#include <algorithm>
#include <cstdlib>
#include <cmath>
#include <pgmspace.h>
#include "WCharacter.h"
#include "WString.h"
#include "HardwareSerial.h"
#include "Esp.h"
#include "Updater.h"
#include "debug.h"
#include "mmu_iram.h"
using std::min;
using std::max;
using std::round;
using std::isinf;
using std::isnan;
// Use float-compatible stl abs() and round(), we don't use Arduino macros to avoid issues with the C++ libraries
using std::abs;
using std::round;
#define _min(a,b) ({ decltype(a) _a = (a); decltype(b) _b = (b); _a < _b? _a : _b; })
#define _max(a,b) ({ decltype(a) _a = (a); decltype(b) _b = (b); _a > _b? _a : _b; })
@ -291,8 +279,19 @@ inline void configTzTime(const char* tz, const char* server1,
configTime(tz, server1, server2, server3);
}
// Everything we expect to be implicitly loaded for the sketch
#include <pgmspace.h>
#include "WCharacter.h"
#include "WString.h"
#include "HardwareSerial.h"
#include "Esp.h"
#include "Updater.h"
#endif // __cplusplus
#include "debug.h"
#include "pins_arduino.h"
#endif

View File

@ -42,11 +42,9 @@ class Client: public Stream {
uint8_t* rawIPAddress(IPAddress& addr) {
return addr.raw_address();
}
#if LWIP_VERSION_MAJOR != 1
const uint8_t* rawIPAddress(const IPAddress& addr) {
return addr.raw_address();
}
#endif
};
#endif

View File

@ -100,6 +100,7 @@ void *createBearsslHmac(const br_hash_class *hashType, const void *data, const s
String createBearsslHmac(const br_hash_class *hashType, const uint8_t hashTypeNaturalLength, const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
{
(void) hashTypeNaturalLength;
assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength);
uint8_t hmac[hmacLength];
@ -152,6 +153,7 @@ void *createBearsslHmacCT(const br_hash_class *hashType, const void *data, const
String createBearsslHmacCT(const br_hash_class *hashType, const uint8_t hashTypeNaturalLength, const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
{
(void) hashTypeNaturalLength;
assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength);
uint8_t hmac[hmacLength];

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

@ -21,7 +21,6 @@
#include <Arduino.h>
#include <user_interface.h>
#include <core_version.h>
#include <lwip/init.h> // LWIP_VERSION_*
#include <lwipopts.h> // LWIP_HASH_STR (lwip2)
#include <bearssl/bearssl_git.h> // BEARSSL_GIT short hash
@ -29,13 +28,11 @@
#define STR(x) STRHELPER(x) // stringifier
static const char arduino_esp8266_git_ver [] PROGMEM = "/Core:" STR(ARDUINO_ESP8266_GIT_DESC) "=";
#if LWIP_VERSION_MAJOR > 1
#if LWIP_IPV6
static const char lwip_version [] PROGMEM = "/lwIP:IPv6+" LWIP_HASH_STR;
#else
static const char lwip_version [] PROGMEM = "/lwIP:" LWIP_HASH_STR;
#endif
#endif
static const char bearssl_version [] PROGMEM = "/BearSSL:" STR(BEARSSL_GIT);
String EspClass::getFullVersion() {
@ -45,23 +42,7 @@ String EspClass::getFullVersion() {
s += system_get_sdk_version();
s += FPSTR(arduino_esp8266_git_ver);
s += String(esp8266::coreVersionNumeric());
#if LWIP_VERSION_MAJOR == 1
s += F("/lwIP:");
s += LWIP_VERSION_MAJOR;
s += '.';
s += LWIP_VERSION_MINOR;
s += '.';
s += LWIP_VERSION_REVISION;
#if LWIP_VERSION_IS_DEVELOPMENT
s += F("-dev");
#endif
#if LWIP_VERSION_IS_RC
s += F("rc");
s += String(LWIP_VERSION_RC);
#endif
#else // LWIP_VERSION_MAJOR != 1
s += FPSTR(lwip_version);
#endif // LWIP_VERSION_MAJOR != 1
s += FPSTR(bearssl_version);
return s;

View File

@ -26,7 +26,12 @@
#include "MD5Builder.h"
#include "umm_malloc/umm_malloc.h"
#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"
@ -40,11 +45,6 @@ extern struct rst_info resetInfo;
#ifndef PUYA_SUPPORT
#define PUYA_SUPPORT 1
#endif
#ifndef PUYA_BUFFER_SIZE
// Good alternative for buffer size is: SPI_FLASH_SEC_SIZE (= 4k)
// Always use a multiple of flash page size (256 bytes)
#define PUYA_BUFFER_SIZE 256
#endif
/**
* User-defined Literals
@ -204,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;
@ -264,13 +273,6 @@ uint8_t EspClass::getBootMode(void)
return system_get_boot_mode();
}
#ifndef F_CPU
uint8_t EspClass::getCpuFreqMHz(void)
{
return system_get_cpu_freq();
}
#endif
uint32_t EspClass::getFlashChipId(void)
{
static uint32_t flash_chip_id = 0;
@ -452,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;
}
@ -526,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;
}
@ -673,11 +677,14 @@ static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, si
if (data == nullptr) {
return SPI_FLASH_RESULT_ERR;
}
if (size % 4 != 0) {
return SPI_FLASH_RESULT_ERR;
}
// PUYA flash chips need to read existing data, update in memory and write modified data again.
static uint32_t *flash_write_puya_buf = nullptr;
if (flash_write_puya_buf == nullptr) {
flash_write_puya_buf = (uint32_t*) malloc(PUYA_BUFFER_SIZE);
flash_write_puya_buf = (uint32_t*) malloc(FLASH_PAGE_SIZE);
// No need to ever free this, since the flash chip will never change at runtime.
if (flash_write_puya_buf == nullptr) {
// Memory could not be allocated.
@ -691,9 +698,9 @@ static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, si
uint32_t pos = offset;
while (bytesLeft > 0 && rc == SPI_FLASH_RESULT_OK) {
size_t bytesNow = bytesLeft;
if (bytesNow > PUYA_BUFFER_SIZE) {
bytesNow = PUYA_BUFFER_SIZE;
bytesLeft -= PUYA_BUFFER_SIZE;
if (bytesNow > FLASH_PAGE_SIZE) {
bytesNow = FLASH_PAGE_SIZE;
bytesLeft -= FLASH_PAGE_SIZE;
} else {
bytesLeft = 0;
}
@ -712,23 +719,240 @@ static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, si
}
#endif
bool EspClass::flashWrite(uint32_t offset, uint32_t *data, size_t size) {
SpiFlashOpResult rc = SPI_FLASH_RESULT_OK;
bool EspClass::flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount) {
uint32_t alignedAddress = (address & ~3);
uint32_t alignmentOffset = address - alignedAddress;
if (alignedAddress != ((address + byteCount - 1) & ~3)) {
// Only one 4 byte block is supported
return false;
}
#if PUYA_SUPPORT
if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) {
rc = spi_flash_write_puya(offset, data, size);
uint8_t tempData[4] __attribute__((aligned(4)));
if (spi_flash_read(alignedAddress, (uint32_t *)tempData, 4) != SPI_FLASH_RESULT_OK) {
return false;
}
for (size_t i = 0; i < byteCount; i++) {
tempData[i + alignmentOffset] &= value[i];
}
if (spi_flash_write(alignedAddress, (uint32_t *)tempData, 4) != SPI_FLASH_RESULT_OK) {
return false;
}
}
else
#endif
#endif // PUYA_SUPPORT
{
rc = spi_flash_write(offset, data, size);
uint32_t tempData;
if (spi_flash_read(alignedAddress, &tempData, 4) != SPI_FLASH_RESULT_OK) {
return false;
}
memcpy((uint8_t *)&tempData + alignmentOffset, value, byteCount);
if (spi_flash_write(alignedAddress, &tempData, 4) != SPI_FLASH_RESULT_OK) {
return false;
}
}
return true;
}
size_t EspClass::flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size) {
size_t sizeLeft = (size & ~3);
size_t currentOffset = 0;
// Memory is unaligned, so we need to copy it to an aligned buffer
uint32_t alignedData[FLASH_PAGE_SIZE / sizeof(uint32_t)] __attribute__((aligned(4)));
// Handle page boundary
bool pageBreak = ((address % 4) != 0) && ((address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
if (pageBreak) {
size_t byteCount = 4 - (address % 4);
if (!flashReplaceBlock(address, data, byteCount)) {
return 0;
}
// We will now have aligned address, so we can cross page boundaries
currentOffset += byteCount;
// Realign size to 4
sizeLeft = (size - byteCount) & ~3;
}
while (sizeLeft) {
size_t willCopy = std::min(sizeLeft, sizeof(alignedData));
memcpy(alignedData, data + currentOffset, willCopy);
// We now have address, data and size aligned to 4 bytes, so we can use aligned write
if (!flashWrite(address + currentOffset, alignedData, willCopy))
{
return 0;
}
sizeLeft -= willCopy;
currentOffset += willCopy;
}
return currentOffset;
}
bool EspClass::flashWritePageBreak(uint32_t address, const uint8_t *data, size_t size) {
if (size > 4) {
return false;
}
size_t pageLeft = FLASH_PAGE_SIZE - (address % FLASH_PAGE_SIZE);
size_t offset = 0;
size_t sizeLeft = size;
if (pageLeft > 3) {
return false;
}
if (!flashReplaceBlock(address, data, pageLeft)) {
return false;
}
offset += pageLeft;
sizeLeft -= pageLeft;
// We replaced last 4-byte block of the page, now we write the remainder in next page
if (!flashReplaceBlock(address + offset, data + offset, sizeLeft)) {
return false;
}
return true;
}
bool EspClass::flashWrite(uint32_t address, const uint32_t *data, size_t size) {
SpiFlashOpResult rc = SPI_FLASH_RESULT_OK;
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + size - 1) / FLASH_PAGE_SIZE));
if ((uintptr_t)data % 4 != 0 || size % 4 != 0 || pageBreak) {
return false;
}
#if PUYA_SUPPORT
if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) {
rc = spi_flash_write_puya(address, const_cast<uint32_t *>(data), size);
}
else
#endif // PUYA_SUPPORT
{
rc = spi_flash_write(address, const_cast<uint32_t *>(data), size);
}
return rc == SPI_FLASH_RESULT_OK;
}
bool EspClass::flashRead(uint32_t offset, uint32_t *data, size_t size) {
auto rc = spi_flash_read(offset, (uint32_t*) data, size);
return rc == SPI_FLASH_RESULT_OK;
bool EspClass::flashWrite(uint32_t address, const uint8_t *data, size_t size) {
if (size == 0) {
return true;
}
size_t sizeLeft = size & ~3;
size_t currentOffset = 0;
if (sizeLeft) {
if ((uintptr_t)data % 4 != 0) {
size_t written = flashWriteUnalignedMemory(address, data, size);
if (!written) {
return false;
}
currentOffset += written;
sizeLeft -= written;
} else {
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
if (pageBreak) {
while (sizeLeft) {
// We cannot cross page boundary, but the write must be 4 byte aligned,
// so this is the maximum amount we can write
size_t pageBoundary = (FLASH_PAGE_SIZE - ((address + currentOffset) % FLASH_PAGE_SIZE)) & ~3;
if (sizeLeft > pageBoundary) {
// Aligned write up to page boundary
if (!flashWrite(address + currentOffset, (uint32_t *)(data + currentOffset), pageBoundary)) {
return false;
}
currentOffset += pageBoundary;
sizeLeft -= pageBoundary;
// Cross the page boundary
if (!flashWritePageBreak(address + currentOffset, data + currentOffset, 4)) {
return false;
}
currentOffset += 4;
sizeLeft -= 4;
} else {
// We do not cross page boundary
if (!flashWrite(address + currentOffset, (uint32_t *)(data + currentOffset), sizeLeft)) {
return false;
}
currentOffset += sizeLeft;
sizeLeft = 0;
}
}
} else {
// Pointer is properly aligned and write does not cross page boundary,
// so use aligned write
if (!flashWrite(address, (uint32_t *)data, sizeLeft)) {
return false;
}
currentOffset = sizeLeft;
sizeLeft = 0;
}
}
}
sizeLeft = size - currentOffset;
if (sizeLeft > 0) {
// Size was not aligned, so we have some bytes left to write, we also need to recheck for
// page boundary crossing
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
if (pageBreak) {
// Cross the page boundary
if (!flashWritePageBreak(address + currentOffset, data + currentOffset, sizeLeft)) {
return false;
}
} else {
// Just write partial block
flashReplaceBlock(address + currentOffset, data + currentOffset, sizeLeft);
}
}
return true;
}
bool EspClass::flashRead(uint32_t address, uint8_t *data, size_t size) {
size_t sizeAligned = size & ~3;
size_t currentOffset = 0;
if ((uintptr_t)data % 4 != 0) {
uint32_t alignedData[FLASH_PAGE_SIZE / sizeof(uint32_t)] __attribute__((aligned(4)));
size_t sizeLeft = sizeAligned;
while (sizeLeft) {
size_t willCopy = std::min(sizeLeft, sizeof(alignedData));
// We read to our aligned buffer and then copy to data
if (!flashRead(address + currentOffset, alignedData, willCopy))
{
return false;
}
memcpy(data + currentOffset, alignedData, willCopy);
sizeLeft -= willCopy;
currentOffset += willCopy;
}
} else {
// Pointer is properly aligned, so use aligned read
if (!flashRead(address, (uint32_t *)data, sizeAligned)) {
return false;
}
currentOffset = sizeAligned;
}
if (currentOffset < size) {
uint32_t tempData;
if (spi_flash_read(address + currentOffset, &tempData, 4) != SPI_FLASH_RESULT_OK) {
return false;
}
memcpy((uint8_t *)data + currentOffset, &tempData, size - currentOffset);
}
return true;
}
bool EspClass::flashRead(uint32_t address, uint32_t *data, size_t size) {
if ((uintptr_t)data % 4 != 0 || size % 4 != 0) {
return false;
}
return (spi_flash_read(address, data, size) == SPI_FLASH_RESULT_OK);
}
String EspClass::getSketchMD5()
@ -739,17 +963,17 @@ String EspClass::getSketchMD5()
}
uint32_t lengthLeft = getSketchSize();
const size_t bufSize = 512;
std::unique_ptr<uint8_t[]> buf(new uint8_t[bufSize]);
std::unique_ptr<uint8_t[]> buf(new (std::nothrow) uint8_t[bufSize]);
uint32_t offset = 0;
if(!buf.get()) {
return String();
return emptyString;
}
MD5Builder md5;
md5.begin();
while( lengthLeft > 0) {
size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
if (!flashRead(offset, reinterpret_cast<uint32_t*>(buf.get()), (readBytes + 3) & ~3)) {
return String();
return emptyString;
}
md5.add(buf.get(), readBytes);
lengthLeft -= readBytes;
@ -759,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

@ -22,6 +22,7 @@
#define ESP_H
#include <Arduino.h>
#include "core_esp8266_features.h"
#include "spi_vendors.h"
/**
@ -102,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();
@ -122,13 +129,12 @@ class EspClass {
uint8_t getBootMode();
#if defined(F_CPU) || defined(CORE_MOCK)
constexpr uint8_t getCpuFreqMHz() const
{
return clockCyclesPerMicrosecond();
}
#else
uint8_t getCpuFreqMHz();
constexpr
#endif
inline uint8_t getCpuFreqMHz() const __attribute__((always_inline))
{
return esp_get_cpu_freq_mhz();
}
uint32_t getFlashChipId();
uint8_t getFlashChipVendorId();
@ -150,8 +156,48 @@ class EspClass {
bool checkFlashCRC();
bool flashEraseSector(uint32_t sector);
bool flashWrite(uint32_t offset, uint32_t *data, size_t size);
bool flashRead(uint32_t offset, uint32_t *data, size_t size);
/**
* @brief Write @a size bytes from @a data to flash at @a address
* This overload requires @a data and @a size to be always 4 byte aligned and
* @a address to be 4 byte aligned if the write crossess page boundary,
* but guarantees no overhead (except on PUYA flashes)
* @param address address on flash where write should start, 4 byte alignment is conditional
* @param data input buffer, must be 4-byte aligned
* @param size amount of data, must be a multiple of 4
* @return bool result of operation
* @retval true success
* @retval false failure to write to flash or incorrect alignment of params
*/
bool flashWrite(uint32_t address, const uint32_t *data, size_t size);
/**
* @brief Write @a size bytes from @a data to flash at @a address
* This overload handles all misalignment cases
* @param address address on flash where write should start
* @param data input buffer, passing unaligned memory will cause significant stack usage
* @param size amount of data, passing not multiple of 4 will cause additional reads and writes
* @return bool result of operation
*/
bool flashWrite(uint32_t address, const uint8_t *data, size_t size);
/**
* @brief Read @a size bytes to @a data to flash at @a address
* This overload requires @a data and @a size to be 4 byte aligned
* @param address address on flash where read should start
* @param data input buffer, must be 4-byte aligned
* @param size amount of data, must be a multiple of 4
* @return bool result of operation
* @retval true success
* @retval false failure to read from flash or incorrect alignment of params
*/
bool flashRead(uint32_t address, uint32_t *data, size_t size);
/**
* @brief Read @a size bytes to @a data to flash at @a address
* This overload handles all misalignment cases
* @param address address on flash where read should start
* @param data input buffer, passing unaligned memory will cause significant stack usage
* @param size amount of data, passing not multiple of 4 will cause additional read
* @return bool result of operation
*/
bool flashRead(uint32_t address, uint8_t *data, size_t size);
uint32_t getSketchSize();
String getSketchMD5();
@ -167,21 +213,88 @@ class EspClass {
uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const;
uint32_t random() const;
#ifndef CORE_MOCK
inline uint32_t getCycleCount() __attribute__((always_inline));
#if !defined(CORE_MOCK)
inline uint32_t getCycleCount() __attribute__((always_inline))
{
return esp_get_cycle_count();
}
#else
uint32_t getCycleCount();
#endif
};
#ifndef CORE_MOCK
uint32_t EspClass::getCycleCount()
{
return esp_get_cycle_count();
}
#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
*
* @param address flash address
* @param value buffer with data
* @param byteCount number of bytes to replace
* @return bool result of operation
* @retval true success
* @retval false failed to read/write or invalid args
*/
bool flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount);
/**
* @brief Write up to @a size bytes from @a data to flash at @a address
* This function takes case of unaligned memory acces by copying @a data to a temporary buffer,
* it also takes care of page boundary crossing see @a flashWritePageBreak as to why it's done.
* Less than @a size bytes may be written, due to 4 byte alignment requirement of spi_flash_write
* @param address address on flash where write should start
* @param data input buffer
* @param size amount of data
* @return size_t amount of data written, 0 on failure
*/
size_t flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size);
/**
* @brief Splits up to 4 bytes into 4 byte blocks and writes them to flash
* We need this since spi_flash_write cannot handle writing over a page boundary with unaligned offset
* i.e. spi_flash_write(254, data, 4) will fail, also we cannot write less bytes as in
* spi_flash_write(254, data, 2) since it will be extended internally to 4 bytes and fail
* @param address start of write
* @param data data to be written
* @param size amount of data, must be < 4
* @return bool result of operation
*/
bool flashWritePageBreak(uint32_t address, const uint8_t *data, size_t size);
};
extern EspClass ESP;

View File

@ -46,6 +46,14 @@ int File::available() {
return _p->size() - _p->position();
}
int File::availableForWrite() {
if (!_p)
return false;
return _p->availableForWrite();
}
int File::read() {
if (!_p)
return -1;
@ -60,7 +68,7 @@ int File::read() {
size_t File::read(uint8_t* buf, size_t size) {
if (!_p)
return -1;
return 0;
return _p->read(buf, size);
}
@ -198,6 +206,7 @@ void File::setTimeCallback(time_t (*cb)(void)) {
if (!_p)
return;
_p->setTimeCallback(cb);
_timeCallback = cb;
}
File Dir::openFile(const char* mode) {
@ -213,7 +222,7 @@ File Dir::openFile(const char* mode) {
}
File f(_impl->openFile(om, am), _baseFS);
f.setTimeCallback(timeCallback);
f.setTimeCallback(_timeCallback);
return f;
}
@ -279,7 +288,7 @@ void Dir::setTimeCallback(time_t (*cb)(void)) {
if (!_impl)
return;
_impl->setTimeCallback(cb);
timeCallback = cb;
_timeCallback = cb;
}
@ -296,7 +305,7 @@ bool FS::begin() {
DEBUGV("#error: FS: no implementation");
return false;
}
_impl->setTimeCallback(timeCallback);
_impl->setTimeCallback(_timeCallback);
bool ret = _impl->begin();
DEBUGV("%s\n", ret? "": "#error: FS could not start");
return ret;
@ -359,7 +368,7 @@ File FS::open(const char* path, const char* mode) {
return File();
}
File f(_impl->open(path, om, am), this);
f.setTimeCallback(timeCallback);
f.setTimeCallback(_timeCallback);
return f;
}
@ -380,7 +389,7 @@ Dir FS::openDir(const char* path) {
}
DirImplPtr p = _impl->openDir(path);
Dir d(p, this);
d.setTimeCallback(timeCallback);
d.setTimeCallback(_timeCallback);
return d;
}
@ -432,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

@ -57,6 +57,7 @@ public:
// Print methods:
size_t write(uint8_t) override;
size_t write(const uint8_t *buf, size_t size) override;
int availableForWrite() override;
// Stream methods:
int available() override;
@ -117,6 +118,7 @@ public:
protected:
FileImplPtr _p;
time_t (*_timeCallback)(void) = nullptr;
// Arduino SD class emulation
std::shared_ptr<Dir> _fakeDir;
@ -144,7 +146,7 @@ public:
protected:
DirImplPtr _impl;
FS *_baseFS;
time_t (*timeCallback)(void) = nullptr;
time_t (*_timeCallback)(void) = nullptr;
};
// Backwards compatible, <4GB filesystem usage
@ -197,7 +199,7 @@ public:
class FS
{
public:
FS(FSImplPtr impl) : _impl(impl) { timeCallback = _defaultTimeCB; }
FS(FSImplPtr impl) : _impl(impl) { _timeCallback = _defaultTimeCB; }
bool setConfig(const FSConfig &cfg);
@ -233,13 +235,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

@ -35,6 +35,7 @@ public:
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
virtual size_t position() const = 0;
virtual size_t size() const = 0;
virtual int availableForWrite() { return 0; }
virtual bool truncate(uint32_t size) = 0;
virtual void close() = 0;
virtual const char* name() const = 0;
@ -44,8 +45,8 @@ public:
// Filesystems *may* support a timestamp per-file, so allow the user to override with
// their own callback for *this specific* file (as opposed to the FSImpl call of the
// same name. The default implementation simply returns time(&null)
virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
// same name. The default implementation simply returns time(null)
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
// Return the last written time for a file. Undefined when called on a writable file
// as the FS is allowed to return either the time of the last write() operation or the
@ -55,7 +56,7 @@ public:
virtual time_t getCreationTime() { return 0; } // Default is to not support timestamps
protected:
time_t (*timeCallback)(void) = nullptr;
time_t (*_timeCallback)(void) = nullptr;
};
enum OpenMode {
@ -89,11 +90,11 @@ public:
// Filesystems *may* support a timestamp per-file, so allow the user to override with
// their own callback for *this specific* file (as opposed to the FSImpl call of the
// same name. The default implementation simply returns time(&null)
virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
// same name. The default implementation simply returns time(null)
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
protected:
time_t (*timeCallback)(void) = nullptr;
time_t (*_timeCallback)(void) = nullptr;
};
class FSImpl {
@ -114,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

@ -32,6 +32,14 @@
#include "HardwareSerial.h"
#include "Esp.h"
// SerialEvent functions are weak, so when the user doesn't define them,
// the linker just sets their address to 0 (which is checked below).
// The Serialx_available is just a wrapper around Serialx.available(),
// but we can refer to it weakly so we don't pull in the entire
// HardwareSerial instance if the user doesn't also refer to it.
void serialEvent() __attribute__((weak));
HardwareSerial::HardwareSerial(int uart_nr)
: _uart_nr(uart_nr), _rx_size(256)
{}
@ -162,6 +170,14 @@ size_t HardwareSerial::readBytes(char* buffer, size_t size)
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
HardwareSerial Serial(UART0);
// Executed at end of loop() processing when > 0 bytes available in the Serial port
void serialEventRun(void)
{
if (serialEvent && Serial.available()) {
serialEvent();
}
}
#endif
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL1)
HardwareSerial Serial1(UART1);

View File

@ -150,7 +150,7 @@ public:
{
return readBytes((char*)buffer, size);
}
int availableForWrite(void)
int availableForWrite(void) override
{
return static_cast<int>(uart_tx_free(_uart));
}
@ -207,4 +207,6 @@ protected:
extern HardwareSerial Serial;
extern HardwareSerial Serial1;
extern void serialEventRun(void) __attribute__((weak));
#endif

View File

@ -32,7 +32,7 @@ IPAddress::IPAddress() {
}
bool IPAddress::isSet () const {
return !ip_addr_isany(&_ip);
return !ip_addr_isany(&_ip) && ((*this) != IPADDR_NONE);
}
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) {
@ -180,9 +180,13 @@ bool IPAddress::isValid(const char* arg) {
return IPAddress().fromString(arg);
}
CONST IPAddress INADDR_ANY; // generic "0.0.0.0" for IPv4 & IPv6
const IPAddress INADDR_ANY; // generic "0.0.0.0" for IPv4 & IPv6
const IPAddress INADDR_NONE(255,255,255,255);
void IPAddress::clear() {
(*this) = INADDR_ANY;
}
/**************************************/
#if LWIP_IPV6

View File

@ -26,28 +26,18 @@
#include <lwip/init.h>
#include <lwip/ip_addr.h>
#include <lwip/ip4_addr.h>
#if LWIP_VERSION_MAJOR == 1
// compatibility macros to make lwIP-v1 compiling lwIP-v2 API
#define LWIP_IPV6_NUM_ADDRESSES 0
#define ip_2_ip4(x) (x)
#define ipv4_addr ip_addr
#define ipv4_addr_t ip_addr_t
#define IP_IS_V4_VAL(x) (1)
#define IP_SET_TYPE_VAL(x,y) do { (void)0; } while (0)
#define IP_ANY_TYPE (&ip_addr_any)
#define IP4_ADDR_ANY IPADDR_ANY
#define IP4_ADDR_ANY4 IP_ADDR_ANY
#define IPADDR4_INIT(x) { x }
#define CONST /* nothing: lwIP-v1 does not use const */
#define ip4_addr_netcmp ip_addr_netcmp
#define netif_dhcp_data(netif) ((netif)->dhcp)
#else // lwIP-v2+
#define CONST const
#if !LWIP_IPV6
struct ip_addr: ipv4_addr { };
#endif // !LWIP_IPV6
#endif // lwIP-v2+
// to display a netif id with printf:
#define NETIFID_STR "%c%c%u"
#define NETIFID_VAL(netif) \
((netif)? (netif)->name[0]: '-'), \
((netif)? (netif)->name[1]: '-'), \
((netif)? netif_get_index(netif): 42)
// A class to make it easier to handle and pass around IP addresses
// IPv6 update:
@ -142,6 +132,8 @@ class IPAddress: public Printable {
virtual size_t printTo(Print& p) const;
String toString() const;
void clear();
/*
check if input string(arg) is a valid IPV4 address or not.
return true on valid.
@ -220,7 +212,7 @@ class IPAddress: public Printable {
};
extern CONST IPAddress INADDR_ANY;
extern const IPAddress INADDR_ANY;
extern const IPAddress INADDR_NONE;
#endif

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

49
cores/esp8266/LwipIntf.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef _LWIPINTF_H
#define _LWIPINTF_H
#include <lwip/netif.h>
#include <IPAddress.h>
#include <functional>
class LwipIntf
{
public:
using CBType = std::function <void(netif*)>;
static bool stateUpCB(LwipIntf::CBType&& cb);
// 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);
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);
};
#endif // _LWIPINTF_H

View File

@ -0,0 +1,44 @@
#include <LwipIntf.h>
#include <Schedule.h>
#include <debug.h>
#define NETIF_STATUS_CB_SIZE 3
static int netifStatusChangeListLength = 0;
LwipIntf::CBType netifStatusChangeList [NETIF_STATUS_CB_SIZE];
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)
{
if (netifStatusChangeListLength >= NETIF_STATUS_CB_SIZE)
{
#if defined(DEBUG_ESP_CORE)
DEBUGV("NETIF_STATUS_CB_SIZE is too low\n");
#endif
return false;
}
netifStatusChangeList[netifStatusChangeListLength++] = cb;
return true;
}
bool LwipIntf::stateUpCB(LwipIntf::CBType&& cb)
{
return stateChangeSysCB([cb](netif * nif)
{
if (netif_is_up(nif))
schedule_function([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

@ -4,7 +4,7 @@
/*
PolledTimeout.h - Encapsulation of a polled Timeout
Copyright (c) 2018 Daniel Salazar. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
@ -23,9 +23,10 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <limits>
#include <Arduino.h>
#include <c_types.h> // IRAM_ATTR
#include <limits> // std::numeric_limits
#include <type_traits> // std::is_unsigned
#include <core_esp8266_features.h>
namespace esp8266
{
@ -70,13 +71,13 @@ struct TimeSourceMillis
struct TimeSourceCycles
{
// time policy based on ESP.getCycleCount()
// time policy based on esp_get_cycle_count()
// this particular time measurement is intended to be called very often
// (every loop, every yield)
using timeType = decltype(ESP.getCycleCount());
static timeType time() {return ESP.getCycleCount();}
static constexpr timeType ticksPerSecond = ESP.getCpuFreqMHz() * 1000000UL; // 80'000'000 or 160'000'000 Hz
using timeType = decltype(esp_get_cycle_count());
static timeType time() {return esp_get_cycle_count();}
static constexpr timeType ticksPerSecond = esp_get_cpu_freq_mhz() * 1000000UL; // 80'000'000 or 160'000'000 Hz
static constexpr timeType ticksPerSecondMax = 160000000; // 160MHz
};
@ -161,13 +162,13 @@ public:
return expiredRetrigger();
return expiredOneShot();
}
IRAM_ATTR // fast
operator bool()
{
return expired();
return expired();
}
bool canExpire () const
{
return !_neverExpires;
@ -178,6 +179,7 @@ public:
return _timeout != alwaysExpired;
}
// Resets, will trigger after this new timeout.
IRAM_ATTR // called from ISR
void reset(const timeType newUserTimeout)
{
@ -186,12 +188,30 @@ public:
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
}
// Resets, will trigger after the timeout previously set.
IRAM_ATTR // called from ISR
void reset()
{
_start = TimePolicyT::time();
}
// Resets to just expired so that on next poll the check will immediately trigger for the user,
// also change timeout (after next immediate trigger).
IRAM_ATTR // called from ISR
void resetAndSetExpired (const timeType newUserTimeout)
{
reset(newUserTimeout);
_start -= _timeout;
}
// Resets to just expired so that on next poll the check will immediately trigger for the user.
IRAM_ATTR // called from ISR
void resetAndSetExpired ()
{
reset();
_start -= _timeout;
}
void resetToNeverExpires ()
{
_timeout = alwaysExpired + 1; // because canWait() has precedence
@ -202,7 +222,7 @@ public:
{
return TimePolicyT::toUserUnit(_timeout);
}
static constexpr timeType timeMax()
{
return TimePolicyT::timeMax;
@ -235,14 +255,14 @@ protected:
}
return false;
}
IRAM_ATTR // fast
bool expiredOneShot() const
{
// returns "always expired" or "has expired"
return !canWait() || checkExpired(TimePolicyT::time());
}
timeType _timeout;
timeType _start;
bool _neverExpires;
@ -259,14 +279,14 @@ using periodic = polledTimeout::timeoutTemplate<true> /*__attribute__((deprecate
using oneShotMs = polledTimeout::timeoutTemplate<false>;
using periodicMs = polledTimeout::timeoutTemplate<true>;
// Time policy based on ESP.getCycleCount(), and intended to be called very often:
// Time policy based on esp_get_cycle_count(), and intended to be called very often:
// "Fast" versions sacrifices time range for improved precision and reduced execution time (by 86%)
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (ESP.getCycleCount()))
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (esp_get_cycle_count()))
// timeMax() values:
// Ms: max is 26843 ms (26.8 s)
// Us: max is 26843545 us (26.8 s)
// Ns: max is 1073741823 ns ( 1.07 s)
// (time policy based on ESP.getCycleCount() is intended to be called very often)
// (time policy based on esp_get_cycle_count() is intended to be called very often)
using oneShotFastMs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
using periodicFastMs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;

View File

@ -63,7 +63,7 @@ size_t Print::printf(const char *format, ...) {
size_t len = vsnprintf(temp, sizeof(temp), format, arg);
va_end(arg);
if (len > sizeof(temp) - 1) {
buffer = new char[len + 1];
buffer = new (std::nothrow) char[len + 1];
if (!buffer) {
return 0;
}
@ -86,7 +86,7 @@ size_t Print::printf_P(PGM_P format, ...) {
size_t len = vsnprintf_P(temp, sizeof(temp), format, arg);
va_end(arg);
if (len > sizeof(temp) - 1) {
buffer = new char[len + 1];
buffer = new (std::nothrow) char[len + 1];
if (!buffer) {
return 0;
}
@ -146,35 +146,39 @@ size_t Print::print(unsigned int n, int base) {
}
size_t Print::print(long n, int base) {
if(base == 0) {
return write(n);
} else if(base == 10) {
if(n < 0) {
int t = print('-');
n = -n;
return printNumber(n, 10) + t;
}
return printNumber(n, 10);
} else {
return printNumber(n, base);
int t = 0;
if (base == 10 && n < 0) {
t = print('-');
n = -n;
}
return printNumber(static_cast<unsigned long>(n), base) + t;
}
size_t Print::print(unsigned long n, int base) {
if(base == 0)
if (base == 0) {
return write(n);
else
return printNumber(n, base);
}
return printNumber(n, base);
}
size_t Print::print(long long n, int base) {
int t = 0;
if (base == 10 && n < 0) {
t = print('-');
n = -n;
}
return printNumber(static_cast<unsigned long long>(n), base) + t;
}
size_t Print::print(unsigned long long n, int base) {
if (base == 0) {
return write(n);
}
return printNumber(n, base);
}
size_t Print::print(double n, int digits) {
return printFloat(n, digits);
}
size_t Print::println(const __FlashStringHelper *ifsh) {
size_t n = print(ifsh);
n += println();
return n;
return printNumber(n, digits);
}
size_t Print::print(const Printable& x) {
@ -185,89 +189,90 @@ size_t Print::println(void) {
return print("\r\n");
}
size_t Print::println(const __FlashStringHelper* ifsh) {
return _println<const __FlashStringHelper*>(ifsh);
}
size_t Print::println(const String &s) {
size_t n = print(s);
n += println();
return n;
return _println(s);
}
size_t Print::println(const char c[]) {
size_t n = print(c);
n += println();
return n;
return _println(c);
}
size_t Print::println(char c) {
size_t n = print(c);
n += println();
return n;
return _println(c);
}
size_t Print::println(unsigned char b, int base) {
size_t n = print(b, base);
n += println();
return n;
return _println(b, base);
}
size_t Print::println(int num, int base) {
size_t n = print(num, base);
n += println();
return n;
return _println(num, base);
}
size_t Print::println(unsigned int num, int base) {
size_t n = print(num, base);
n += println();
return n;
return _println(num, base);
}
size_t Print::println(long num, int base) {
size_t n = print(num, base);
n += println();
return n;
return _println(num, base);
}
size_t Print::println(unsigned long num, int base) {
size_t n = print(num, base);
n += println();
return n;
return _println(num, base);
}
size_t Print::println(long long num, int base) {
return _println(num, base);
}
size_t Print::println(unsigned long long num, int base) {
return _println(num, base);
}
size_t Print::println(double num, int digits) {
size_t n = print(num, digits);
n += println();
return n;
return _println(num, digits);
}
size_t Print::println(const Printable& x) {
size_t n = print(x);
n += println();
return n;
return _println<const Printable&>(x);
}
// Private Methods /////////////////////////////////////////////////////////////
size_t Print::printNumber(unsigned long n, uint8_t base) {
char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte.
char *str = &buf[sizeof(buf) - 1];
template<typename T, typename... P> inline size_t Print::_println(T v, P... args)
{
size_t n = print(v, args...);
n += println();
return n;
};
template<typename T> size_t Print::printNumber(T n, uint8_t base) {
char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
char* str = &buf[sizeof(buf) - 1];
*str = '\0';
// prevent crash if called with base == 1
if(base < 2)
if (base < 2) {
base = 10;
}
do {
unsigned long m = n;
auto m = n;
n /= base;
char c = m - base * n;
*--str = c < 10 ? c + '0' : c + 'A' - 10;
} while(n);
} while (n);
return write(str);
}
size_t Print::printFloat(double number, uint8_t digits) {
template<> size_t Print::printNumber(double number, uint8_t digits) {
char buf[40];
return write(dtostrf(number, 0, digits, buf));
}

View File

@ -35,17 +35,15 @@
class Print {
private:
int write_error;
size_t printNumber(unsigned long, uint8_t);
size_t printFloat(double, uint8_t);
int write_error = 0;
template<typename T> size_t printNumber(T n, uint8_t base);
template<typename T, typename... P> inline size_t _println(T v, P... args);
protected:
void setWriteError(int err = 1) {
write_error = err;
}
public:
Print() :
write_error(0) {
}
Print() {}
int getWriteError() {
return write_error;
@ -71,10 +69,16 @@ class Print {
inline size_t write(unsigned int t) { return write((uint8_t)t); }
inline size_t write(long t) { return write((uint8_t)t); }
inline size_t write(unsigned long t) { return write((uint8_t)t); }
inline size_t write(long long t) { return write((uint8_t)t); }
inline size_t write(unsigned long long t) { return write((uint8_t)t); }
// Enable write(char) to fall through to write(uint8_t)
inline size_t write(char c) { return write((uint8_t) c); }
inline size_t write(int8_t c) { return write((uint8_t) c); }
// default to zero, meaning "a single write may block"
// should be overriden by subclasses with buffering
virtual int availableForWrite() { return 0; }
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
size_t printf_P(PGM_P format, ...) __attribute__((format(printf, 2, 3)));
size_t print(const __FlashStringHelper *);
@ -86,6 +90,8 @@ class Print {
size_t print(unsigned int, int = DEC);
size_t print(long, int = DEC);
size_t print(unsigned long, int = DEC);
size_t print(long long, int = DEC);
size_t print(unsigned long long, int = DEC);
size_t print(double, int = 2);
size_t print(const Printable&);
@ -98,6 +104,8 @@ class Print {
size_t println(unsigned int, int = DEC);
size_t println(long, int = DEC);
size_t println(unsigned long, int = DEC);
size_t println(long long, int = DEC);
size_t println(unsigned long long, int = DEC);
size_t println(double, int = 2);
size_t println(const Printable&);
size_t println(void);
@ -105,4 +113,6 @@ class Print {
virtual void flush() { /* Empty implementation for backward compatibility */ }
};
template<> size_t Print::printNumber(double number, uint8_t digits);
#endif

View File

@ -1,3 +1,20 @@
/*
Schedule.cpp - Scheduled functions.
Copyright (c) 2020 esp8266/Arduino
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <assert.h>
@ -85,6 +102,7 @@ bool schedule_function(const std::function<void(void)>& fn)
return true;
}
IRAM_ATTR // (not only) called from ISR
bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
uint32_t repeat_us, const std::function<bool(void)>& alarm)
{

View File

@ -1,3 +1,21 @@
/*
Schedule.h - Header file for scheduled functions.
Copyright (c) 2020 esp8266/Arduino
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ESP_SCHEDULE_H
#define ESP_SCHEDULE_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

@ -51,7 +51,7 @@ extern uint32_t stack_thunk_refcnt;
// Thunking macro
#define make_stack_thunk(fcnToThunk) \
__asm("\n\
__asm__ ("\n\
.text\n\
.literal_position\n\
.literal .LC_STACK_VALUE"#fcnToThunk", 0xdeadbeef\n\

View File

@ -173,7 +173,7 @@ float Stream::parseFloat(char skipChar) {
boolean isFraction = false;
long value = 0;
int c;
float fraction = 1.0;
float fraction = 1.0f;
c = peekNextDigit();
// ignore non numeric leading characters
@ -190,7 +190,7 @@ float Stream::parseFloat(char skipChar) {
else if(c >= '0' && c <= '9') { // is c a digit?
value = value * 10 + c - '0';
if(isFraction)
fraction *= 0.1;
fraction *= 0.1f;
}
read(); // consume the character we got with peek
c = timedPeek();

View File

@ -37,7 +37,7 @@
class Stream: public Print {
protected:
unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read
unsigned long _timeout = 1000; // number of milliseconds to wait for the next char before aborting timed read
unsigned long _startMillis; // used for timeout measurement
int timedRead(); // private method to read stream with timeout
int timedPeek(); // private method to peek stream with timeout
@ -48,9 +48,7 @@ class Stream: public Print {
virtual int read() = 0;
virtual int peek() = 0;
Stream() {
_timeout = 1000;
}
Stream() {}
// parsing methods

View File

@ -1,10 +1,11 @@
// autogenerated from https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv
// by script <esp8266 arduino core>/tools/TZupdate.sh
// Thu May 7 19:02:21 UTC 2020
// Thu Nov 12 04:07:03 UTC 2020
//
// This database is autogenerated from IANA timezone database
// https://www.iana.org/time-zones
// https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv
// (using https://www.iana.org/time-zones)
// and can be updated on demand in this repository
// or by yourself using the above script
@ -105,7 +106,7 @@
#define TZ_America_Cuiaba PSTR("<-04>4")
#define TZ_America_Curacao PSTR("AST4")
#define TZ_America_Danmarkshavn PSTR("GMT0")
#define TZ_America_Dawson PSTR("PST8PDT,M3.2.0,M11.1.0")
#define TZ_America_Dawson PSTR("MST7")
#define TZ_America_Dawson_Creek PSTR("MST7")
#define TZ_America_Denver PSTR("MST7MDT,M3.2.0,M11.1.0")
#define TZ_America_Detroit PSTR("EST5EDT,M3.2.0,M11.1.0")
@ -207,14 +208,14 @@
#define TZ_America_Toronto PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Tortola PSTR("AST4")
#define TZ_America_Vancouver PSTR("PST8PDT,M3.2.0,M11.1.0")
#define TZ_America_Whitehorse PSTR("PST8PDT,M3.2.0,M11.1.0")
#define TZ_America_Whitehorse PSTR("MST7")
#define TZ_America_Winnipeg PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Yakutat PSTR("AKST9AKDT,M3.2.0,M11.1.0")
#define TZ_America_Yellowknife PSTR("MST7MDT,M3.2.0,M11.1.0")
#define TZ_Antarctica_Casey PSTR("<+08>-8")
#define TZ_Antarctica_Casey PSTR("<+11>-11")
#define TZ_Antarctica_Davis PSTR("<+07>-7")
#define TZ_Antarctica_DumontDUrville PSTR("<+10>-10")
#define TZ_Antarctica_Macquarie PSTR("<+11>-11")
#define TZ_Antarctica_Macquarie PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
#define TZ_Antarctica_Mawson PSTR("<+05>-5")
#define TZ_Antarctica_McMurdo PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
#define TZ_Antarctica_Palmer PSTR("<-03>3")
@ -248,8 +249,8 @@
#define TZ_Asia_Dubai PSTR("<+04>-4")
#define TZ_Asia_Dushanbe PSTR("<+05>-5")
#define TZ_Asia_Famagusta PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Asia_Gaza PSTR("EET-2EEST,M3.5.5/0,M10.5.6/1")
#define TZ_Asia_Hebron PSTR("EET-2EEST,M3.5.5/0,M10.5.6/1")
#define TZ_Asia_Gaza PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
#define TZ_Asia_Hebron PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
#define TZ_Asia_Ho_Chi_Minh PSTR("<+07>-7")
#define TZ_Asia_Hong_Kong PSTR("HKT-8")
#define TZ_Asia_Hovd PSTR("<+07>-7")

View File

@ -25,15 +25,16 @@
#include "core_esp8266_waveform.h"
#include "user_interface.h"
// Which pins have a tone running on them?
static uint32_t _toneMap = 0;
static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) {
if (_pin > 16) {
return;
}
// Stop any analogWrites (PWM) because they are a different generator
_stopPWM(_pin);
// If there's another Tone or startWaveform on this pin
// it will be changed on-the-fly (no need to stop it)
pinMode(_pin, OUTPUT);
high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency,
@ -42,9 +43,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat
duration = microsecondsToClockCycles(duration * 1000UL);
duration += high + low - 1;
duration -= duration % (high + low);
if (startWaveformClockCycles(_pin, high, low, duration)) {
_toneMap |= 1 << _pin;
}
startWaveformClockCycles(_pin, high, low, duration);
}
@ -86,6 +85,5 @@ void noTone(uint8_t _pin) {
return;
}
stopWaveform(_pin);
_toneMap &= ~(1 << _pin);
digitalWrite(_pin, 0);
}

View File

@ -83,14 +83,14 @@ class UDP: public Stream {
// Return the port of the host who sent the current incoming packet
virtual uint16_t remotePort() =0;
protected:
uint8_t* rawIPAddress(IPAddress& addr) {
return addr.raw_address();
}
#if LWIP_VERSION_MAJOR != 1
const uint8_t* rawIPAddress(const IPAddress& addr) {
return addr.raw_address();
}
#endif
};
#endif

View File

@ -27,17 +27,6 @@ extern "C" uint32_t _FS_start;
extern "C" uint32_t _FS_end;
UpdaterClass::UpdaterClass()
: _async(false)
, _error(0)
, _buffer(0)
, _bufferLen(0)
, _size(0)
, _startAddress(0)
, _currentAddress(0)
, _command(U_FLASH)
, _hash(nullptr)
, _verify(nullptr)
, _progress_callback(nullptr)
{
#if ARDUINO_SIGNING
installSignature(&esp8266::updaterSigningHash, &esp8266::updaterSigningVerifier);
@ -112,6 +101,8 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
_reset();
clearError(); // _error = 0
_target_md5 = emptyString;
_md5 = MD5Builder();
#ifndef HOST_MOCK
wifi_set_sleep_type(NONE_SLEEP_T);
@ -212,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);
@ -245,7 +241,7 @@ bool UpdaterClass::end(bool evenIfRemaining){
DEBUG_UPDATER.printf_P(PSTR("[Updater] Adjusted binsize: %d\n"), binSize);
#endif
// Calculate the MD5 and hash using proper size
uint8_t buff[128];
uint8_t buff[128] __attribute__((aligned(4)));
for(int i = 0; i < binSize; i += sizeof(buff)) {
ESP.flashRead(_startAddress + i, (uint32_t *)buff, sizeof(buff));
size_t read = std::min((int)sizeof(buff), binSize - i);
@ -264,7 +260,7 @@ bool UpdaterClass::end(bool evenIfRemaining){
_reset();
return false;
}
ESP.flashRead(_startAddress + binSize, (uint32_t *)sig, sigLen);
ESP.flashRead(_startAddress + binSize, sig, sigLen);
#ifdef DEBUG_UPDATER
DEBUG_UPDATER.printf_P(PSTR("[Updater] Received Signature:"));
for (size_t i=0; i<sigLen; i++) {
@ -279,6 +275,8 @@ bool UpdaterClass::end(bool evenIfRemaining){
return false;
}
free(sig);
_size = binSize; // Adjust size to remove signature, not part of bin payload
#ifdef DEBUG_UPDATER
DEBUG_UPDATER.printf_P(PSTR("[Updater] Signature matches\n"));
#endif
@ -364,7 +362,7 @@ bool UpdaterClass::_writeBuffer(){
if (eraseResult) {
if(!_async) yield();
writeResult = ESP.flashWrite(_currentAddress, (uint32_t*) _buffer, _bufferLen);
writeResult = ESP.flashWrite(_currentAddress, _buffer, _bufferLen);
} else { // if erase was unsuccessful
_currentAddress = (_startAddress + _size);
_setError(UPDATE_ERROR_ERASE);
@ -442,7 +440,7 @@ bool UpdaterClass::_verifyHeader(uint8_t data) {
bool UpdaterClass::_verifyEnd() {
if(_command == U_FLASH) {
uint8_t buf[4];
uint8_t buf[4] __attribute__((aligned(4)));
if(!ESP.flashRead(_startAddress, (uint32_t *) &buf[0], 4)) {
_currentAddress = (_startAddress);
_setError(UPDATE_ERROR_READ);
@ -558,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
@ -182,27 +183,27 @@ class UpdaterClass {
void _setError(int error);
bool _async;
uint8_t _error;
uint8_t *_buffer;
size_t _bufferLen; // amount of data written into _buffer
size_t _bufferSize; // total size of _buffer
size_t _size;
uint32_t _startAddress;
uint32_t _currentAddress;
uint32_t _command;
bool _async = false;
uint8_t _error = 0;
uint8_t *_buffer = nullptr;
size_t _bufferLen = 0; // amount of data written into _buffer
size_t _bufferSize = 0; // total size of _buffer
size_t _size = 0;
uint32_t _startAddress = 0;
uint32_t _currentAddress = 0;
uint32_t _command = U_FLASH;
String _target_md5;
MD5Builder _md5;
int _ledPin;
int _ledPin = -1;
uint8_t _ledOn;
// Optional signed binary verification
UpdaterHashClass *_hash;
UpdaterVerifyClass *_verify;
UpdaterHashClass *_hash = nullptr;
UpdaterVerifyClass *_verify = nullptr;
// Optional progress callback function
THandlerFunction_Progress _progress_callback;
THandlerFunction_Progress _progress_callback = nullptr;
};
extern UpdaterClass Update;

View File

@ -25,6 +25,12 @@
#include "WString.h"
#include "stdlib_noniso.h"
#define OOM_STRING_BORDER_DISPLAY 10
#define OOM_STRING_THRESHOLD_REALLOC_WARN 128
#define __STRHELPER(x) #x
#define STR(x) __STRHELPER(x) // stringifier
/*********************************************/
/* Constructors */
/*********************************************/
@ -32,7 +38,7 @@
String::String(const char *cstr) {
init();
if (cstr)
copy(cstr, strlen(cstr));
copy(cstr, strlen_P(cstr));
}
String::String(const String &value) {
@ -45,25 +51,15 @@ String::String(const __FlashStringHelper *pstr) {
*this = pstr; // see operator =
}
#ifdef __GXX_EXPERIMENTAL_CXX0X__
String::String(String &&rval) {
String::String(String &&rval) noexcept {
init();
move(rval);
}
String::String(StringSumHelper &&rval) {
String::String(StringSumHelper &&rval) noexcept {
init();
move(rval);
}
#endif
String::String(char c) {
init();
char buf[2];
buf[0] = c;
buf[1] = 0;
*this = buf;
}
String::String(unsigned char value, unsigned char base) {
init();
@ -93,7 +89,7 @@ String::String(unsigned int value, unsigned char base) {
String::String(long value, unsigned char base) {
init();
char buf[2 + 8 * sizeof(long)];
if (base==10) {
if (base == 10) {
sprintf(buf, "%ld", value);
} else {
ltoa(value, buf, base);
@ -108,6 +104,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];
@ -120,31 +142,21 @@ String::String(double value, unsigned char decimalPlaces) {
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
}
String::~String() {
invalidate();
}
// /*********************************************/
// /* Memory Management */
// /*********************************************/
inline void String::init(void) {
setSSO(true);
setLen(0);
wbuffer()[0] = 0;
}
/*********************************************/
/* Memory Management */
/*********************************************/
void String::invalidate(void) {
if(!isSSO() && wbuffer())
if (!isSSO() && wbuffer())
free(wbuffer());
init();
}
unsigned char String::reserve(unsigned int size) {
if(buffer() && capacity() >= size)
if (buffer() && capacity() >= size)
return 1;
if(changeBuffer(size)) {
if(len() == 0)
if (changeBuffer(size)) {
if (len() == 0)
wbuffer()[0] = 0;
return 1;
}
@ -159,35 +171,40 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
uint16_t oldLen = len();
setSSO(true);
setLen(oldLen);
return 1;
} else { // if bufptr && !isSSO()
// Using bufptr, need to shrink into sso.buff
char temp[sizeof(sso.buff)];
memcpy(temp, buffer(), maxStrLen);
free(wbuffer());
const char *temp = buffer();
uint16_t oldLen = len();
setSSO(true);
setLen(oldLen);
memcpy(wbuffer(), temp, maxStrLen);
return 1;
free((void *)temp);
}
return 1;
}
// Fallthrough to normal allocator
size_t newSize = (maxStrLen + 16) & (~0xf);
#ifdef DEBUG_ESP_OOM
if (!isSSO() && capacity() >= OOM_STRING_THRESHOLD_REALLOC_WARN && maxStrLen > capacity()) {
// warn when badly re-allocating
DEBUGV("[offending String op %d->%d ('%." STR(OOM_STRING_BORDER_DISPLAY) "s ... %." STR(OOM_STRING_BORDER_DISPLAY) "s')]\n",
len(), maxStrLen, c_str(),
len() > OOM_STRING_BORDER_DISPLAY? c_str() + std::max((int)len() - OOM_STRING_BORDER_DISPLAY, OOM_STRING_BORDER_DISPLAY): "");
}
#endif
// Make sure we can fit newsize in the buffer
if (newSize > CAPACITY_MAX) {
return false;
return 0;
}
uint16_t oldLen = len();
char *newbuffer = (char *) realloc(isSSO() ? nullptr : wbuffer(), newSize);
char *newbuffer = (char *)realloc(isSSO() ? nullptr : wbuffer(), newSize);
if (newbuffer) {
size_t oldSize = capacity() + 1; // include NULL.
if (isSSO()) {
// Copy the SSO buffer into allocated space
memmove_P(newbuffer, sso.buff, sizeof(sso.buff));
}
if (newSize > oldSize)
{
if (newSize > oldSize) {
memset(newbuffer + oldSize, 0, newSize - oldSize);
}
setSSO(false);
@ -199,11 +216,11 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
return 0;
}
// /*********************************************/
// /* Copy and Move */
// /*********************************************/
/*********************************************/
/* Copy and Move */
/*********************************************/
String & String::copy(const char *cstr, unsigned int length) {
String &String::copy(const char *cstr, unsigned int length) {
if (!reserve(length)) {
invalidate();
return *this;
@ -213,7 +230,7 @@ String & String::copy(const char *cstr, unsigned int length) {
return *this;
}
String & String::copy(const __FlashStringHelper *pstr, unsigned int length) {
String &String::copy(const __FlashStringHelper *pstr, unsigned int length) {
if (!reserve(length)) {
invalidate();
return *this;
@ -223,83 +240,47 @@ String & String::copy(const __FlashStringHelper *pstr, unsigned int length) {
return *this;
}
#ifdef __GXX_EXPERIMENTAL_CXX0X__
void String::move(String &rhs) {
if (buffer()) {
if (capacity() >= rhs.len()) {
memmove_P(wbuffer(), rhs.buffer(), rhs.length() + 1);
setLen(rhs.len());
rhs.invalidate();
return;
} else {
if (!isSSO()) {
free(wbuffer());
setBuffer(nullptr);
}
}
}
if (rhs.isSSO()) {
setSSO(true);
memmove_P(sso.buff, rhs.sso.buff, sizeof(sso.buff));
} else {
setSSO(false);
setBuffer(rhs.wbuffer());
}
setCapacity(rhs.capacity());
setLen(rhs.len());
rhs.setSSO(false);
rhs.setCapacity(0);
rhs.setLen(0);
rhs.setBuffer(nullptr);
void String::move(String &rhs) noexcept {
invalidate();
sso = rhs.sso;
rhs.init();
}
#endif
String & String::operator =(const String &rhs) {
String &String::operator =(const String &rhs) {
if (this == &rhs)
return *this;
if (rhs.buffer())
copy(rhs.buffer(), rhs.len());
else
invalidate();
return *this;
}
#ifdef __GXX_EXPERIMENTAL_CXX0X__
String & String::operator =(String &&rval) {
String &String::operator =(String &&rval) noexcept {
if (this != &rval)
move(rval);
return *this;
}
String & String::operator =(StringSumHelper &&rval) {
if (this != &rval)
move(rval);
return *this;
}
#endif
String & String::operator =(const char *cstr) {
String &String::operator =(const char *cstr) {
if (cstr)
copy(cstr, strlen(cstr));
else
invalidate();
return *this;
}
String & String::operator = (const __FlashStringHelper *pstr)
{
if (pstr) copy(pstr, strlen_P((PGM_P)pstr));
else invalidate();
String &String::operator =(const __FlashStringHelper *pstr) {
if (pstr)
copy(pstr, strlen_P((PGM_P)pstr));
else
invalidate();
return *this;
}
// /*********************************************/
// /* concat */
// /*********************************************/
/*********************************************/
/* concat */
/*********************************************/
unsigned char String::concat(const String &s) {
// Special case if we're concatting ourself (s += s;) since we may end up
@ -314,7 +295,7 @@ unsigned char String::concat(const String &s) {
return 0;
memmove_P(wbuffer() + len(), buffer(), len());
setLen(newlen);
wbuffer()[len()] = 0;
wbuffer()[newlen] = 0;
return 1;
} else {
return concat(s.buffer(), s.len());
@ -342,22 +323,17 @@ unsigned char String::concat(const char *cstr) {
}
unsigned char String::concat(char c) {
char buf[2];
buf[0] = c;
buf[1] = 0;
return concat(buf, 1);
return concat(&c, 1);
}
unsigned char String::concat(unsigned char num) {
char buf[1 + 3 * sizeof(unsigned char)];
sprintf(buf, "%d", num);
return concat(buf, strlen(buf));
return concat(buf, sprintf(buf, "%d", num));
}
unsigned char String::concat(int num) {
char buf[2 + 3 * sizeof(int)];
sprintf(buf, "%d", num);
return concat(buf, strlen(buf));
return concat(buf, sprintf(buf, "%d", num));
}
unsigned char String::concat(unsigned int num) {
@ -368,8 +344,7 @@ unsigned char String::concat(unsigned int num) {
unsigned char String::concat(long num) {
char buf[2 + 3 * sizeof(long)];
sprintf(buf, "%ld", num);
return concat(buf, strlen(buf));
return concat(buf, sprintf(buf, "%ld", num));
}
unsigned char String::concat(unsigned long num) {
@ -378,24 +353,37 @@ 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);
char *string = dtostrf(num, 4, 2, buf);
return concat(string, strlen(string));
}
unsigned char String::concat(double num) {
char buf[20];
char* string = dtostrf(num, 4, 2, buf);
char *string = dtostrf(num, 4, 2, buf);
return concat(string, strlen(string));
}
unsigned char String::concat(const __FlashStringHelper * str) {
if (!str) return 0;
unsigned char String::concat(const __FlashStringHelper *str) {
if (!str)
return 0;
int length = strlen_P((PGM_P)str);
if (length == 0) return 1;
if (length == 0)
return 1;
unsigned int newlen = len() + length;
if (!reserve(newlen)) return 0;
if (!reserve(newlen))
return 0;
memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1);
setLen(newlen);
return 1;
@ -405,94 +393,107 @@ unsigned char String::concat(const __FlashStringHelper * str) {
/* Concatenate */
/*********************************************/
StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs.buffer(), rhs.len()))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!cstr || !a.concat(cstr, strlen(cstr)))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, char c) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, char c) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(c))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, int num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, int num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, long num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, float num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, long long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator +(const StringSumHelper &lhs, double num) {
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs)
{
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
StringSumHelper &operator +(const StringSumHelper &lhs, float num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, double num) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(num))
a.invalidate();
return a;
}
StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs))
a.invalidate();
return a;
}
// /*********************************************/
// /* Comparison */
// /*********************************************/
/*********************************************/
/* Comparison */
/*********************************************/
int String::compareTo(const String &s) const {
if(!buffer() || !s.buffer()) {
if(s.buffer() && s.len() > 0)
return 0 - *(unsigned char *) s.buffer();
if(buffer() && len() > 0)
return *(unsigned char *) buffer();
if (!buffer() || !s.buffer()) {
if (s.buffer() && s.len() > 0)
return 0 - *(unsigned char *)s.buffer();
if (buffer() && len() > 0)
return *(unsigned char *)buffer();
return 0;
}
return strcmp(buffer(), s.buffer());
@ -550,7 +551,7 @@ unsigned char String::equalsConstantTime(const String &s2) const {
//at this point lengths are the same
if (len() == 0)
return 1;
//at this point lenghts are the same and non-zero
//at this point lengths are the same and non-zero
const char *p1 = buffer();
const char *p2 = s2.buffer();
unsigned int equalchars = 0;
@ -570,37 +571,33 @@ unsigned char String::equalsConstantTime(const String &s2) const {
}
unsigned char String::startsWith(const String &s2) const {
if(len() < s2.len())
if (len() < s2.len())
return 0;
return startsWith(s2, 0);
}
unsigned char String::startsWith(const String &s2, unsigned int offset) const {
if(offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer())
if (offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer())
return 0;
return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0;
}
unsigned char String::endsWith(const String &s2) const {
if(len() < s2.len() || !buffer() || !s2.buffer())
if (len() < s2.len() || !buffer() || !s2.buffer())
return 0;
return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0;
}
// /*********************************************/
// /* Character Access */
// /*********************************************/
char String::charAt(unsigned int loc) const {
return operator[](loc);
}
/*********************************************/
/* Character Access */
/*********************************************/
void String::setCharAt(unsigned int loc, char c) {
if (loc < len())
wbuffer()[loc] = c;
}
char & String::operator[](unsigned int index) {
char &String::operator[](unsigned int index) {
static char dummy_writable_char;
if (index >= len() || !buffer()) {
dummy_writable_char = 0;
@ -625,54 +622,51 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind
unsigned int n = bufsize - 1;
if (n > len() - index)
n = len() - index;
strncpy((char *) buf, buffer() + index, n);
strncpy((char *)buf, buffer() + index, n);
buf[n] = 0;
}
// /*********************************************/
// /* Search */
// /*********************************************/
int String::indexOf(char c) const {
return indexOf(c, 0);
}
/*********************************************/
/* Search */
/*********************************************/
int String::indexOf(char ch, unsigned int fromIndex) const {
if (fromIndex >= len())
return -1;
const char* temp = strchr(buffer() + fromIndex, ch);
const char *temp = strchr(buffer() + fromIndex, ch);
if (temp == NULL)
return -1;
return temp - buffer();
}
int String::indexOf(const String &s2) const {
return indexOf(s2, 0);
}
int String::indexOf(const String &s2, unsigned int fromIndex) const {
int String::indexOf(const char *s2, unsigned int fromIndex) const {
if (fromIndex >= len())
return -1;
const char *found = strstr(buffer() + fromIndex, s2.buffer());
const char *found = strstr_P(buffer() + fromIndex, s2);
if (found == NULL)
return -1;
return found - buffer();
}
int String::lastIndexOf(char theChar) const {
return lastIndexOf(theChar, len() - 1);
int String::indexOf(const String &s2, unsigned int fromIndex) const {
return indexOf(s2.c_str(), fromIndex);
}
int String::lastIndexOf(char ch) const {
return lastIndexOf(ch, len() - 1);
}
int String::lastIndexOf(char ch, unsigned int fromIndex) const {
if (fromIndex >= len())
return -1;
char tempchar = buffer()[fromIndex + 1];
wbuffer()[fromIndex + 1] = '\0';
char* temp = strrchr(wbuffer(), ch);
wbuffer()[fromIndex + 1] = tempchar;
char *writeTo = wbuffer();
char tempchar = writeTo[fromIndex + 1]; // save the replaced character
writeTo[fromIndex + 1] = '\0';
char *temp = strrchr(writeTo, ch);
writeTo[fromIndex + 1] = tempchar; // restore character
if (temp == NULL)
return -1;
return temp - buffer();
return temp - writeTo;
}
int String::lastIndexOf(const String &s2) const {
@ -685,11 +679,11 @@ int String::lastIndexOf(const String &s2, unsigned int fromIndex) const {
if (fromIndex >= len())
fromIndex = len() - 1;
int found = -1;
for (char *p = wbuffer(); p <= wbuffer() + fromIndex; p++) {
for (const char *p = buffer(); p <= buffer() + fromIndex; p++) {
p = strstr(p, s2.buffer());
if (!p)
break;
if ((unsigned int) (p - wbuffer()) <= fromIndex)
if ((unsigned int)(p - buffer()) <= fromIndex)
found = p - buffer();
}
return found;
@ -706,16 +700,17 @@ String String::substring(unsigned int left, unsigned int right) const {
return out;
if (right > len())
right = len();
char temp = buffer()[right]; // save the replaced character
wbuffer()[right] = '\0';
out = wbuffer() + left; // pointer arithmetic
wbuffer()[right] = temp; //restore character
char *writeTo = wbuffer();
char tempchar = writeTo[right]; // save the replaced character
writeTo[right] = '\0';
out = writeTo + left; // pointer arithmetic
writeTo[right] = tempchar; // restore character
return out;
}
// /*********************************************/
// /* Modification */
// /*********************************************/
/*********************************************/
/* Modification */
/*********************************************/
void String::replace(char find, char replace) {
if (!buffer())
@ -726,7 +721,7 @@ void String::replace(char find, char replace) {
}
}
void String::replace(const String& find, const String& replace) {
void String::replace(const String &find, const String &replace) {
if (len() == 0 || find.len() == 0)
return;
int diff = replace.len() - find.len();
@ -748,7 +743,7 @@ void String::replace(const String& find, const String& replace) {
readFrom = foundAt + find.len();
setLen(len() + diff);
}
memmove_P(writeTo, readFrom, strlen(readFrom)+1);
memmove_P(writeTo, readFrom, strlen(readFrom) + 1);
} else {
unsigned int size = len(); // compute size needed for result
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
@ -772,13 +767,6 @@ void String::replace(const String& find, const String& replace) {
}
}
void String::remove(unsigned int index) {
// Pass the biggest integer as the count. The remove method
// below will take care of truncating it at the end of the
// string.
remove(index, (unsigned int) -1);
}
void String::remove(unsigned int index, unsigned int count) {
if (index >= len()) {
return;
@ -828,9 +816,9 @@ void String::trim(void) {
wbuffer()[newlen] = 0;
}
// /*********************************************/
// /* Parsing / Conversion */
// /*********************************************/
/*********************************************/
/* Parsing / Conversion */
/*********************************************/
long String::toInt(void) const {
if (buffer())
@ -841,11 +829,10 @@ long String::toInt(void) const {
float String::toFloat(void) const {
if (buffer())
return atof(buffer());
return 0;
return 0.0F;
}
double String::toDouble(void) const
{
double String::toDouble(void) const {
if (buffer())
return atof(buffer());
return 0.0;

View File

@ -53,52 +53,60 @@ class String {
// if the initial value is null or invalid, or if memory allocation
// fails, the string will be marked as invalid (i.e. "if (s)" will
// be false).
String(const char *cstr = nullptr);
String() __attribute__((always_inline)) { // See init()
init();
}
String(const char *cstr);
String(const String &str);
String(const __FlashStringHelper *str);
#ifdef __GXX_EXPERIMENTAL_CXX0X__
String(String &&rval);
String(StringSumHelper &&rval);
#endif
explicit String(char c);
String(String &&rval) noexcept;
String(StringSumHelper &&rval) noexcept;
explicit String(char c) {
sso.buff[0] = c;
sso.buff[1] = 0;
sso.len = 1;
sso.isHeap = 0;
}
explicit String(unsigned char, unsigned char base = 10);
explicit String(int, unsigned char base = 10);
explicit String(unsigned int, unsigned char base = 10);
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(void);
~String() {
invalidate();
}
// memory management
// return true on success, false on failure (in which case, the string
// is left unchanged). reserve(0), if successful, will validate an
// invalid string (i.e., "if (s)" will be true afterwards)
unsigned char reserve(unsigned int size);
inline unsigned int length(void) const {
if(buffer()) {
return len();
} else {
return 0;
}
unsigned int length(void) const {
return buffer() ? len() : 0;
}
inline void clear(void) {
void clear(void) {
setLen(0);
}
inline bool isEmpty(void) const {
bool isEmpty(void) const {
return length() == 0;
}
// creates a copy of the assigned value. if the value is null or
// invalid, or if the memory allocation fails, the string will be
// marked as invalid ("if (s)" will be false).
String & operator =(const String &rhs);
String & operator =(const char *cstr);
String & operator = (const __FlashStringHelper *str);
#ifdef __GXX_EXPERIMENTAL_CXX0X__
String & operator =(String &&rval);
String & operator =(StringSumHelper &&rval);
#endif
String &operator =(const String &rhs);
String &operator =(const char *cstr);
String &operator =(const __FlashStringHelper *str);
String &operator =(String &&rval) noexcept;
String &operator =(StringSumHelper &&rval) noexcept {
return operator =((String &&)rval);
}
// concatenate (works w/ built-in types)
@ -113,69 +121,81 @@ 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);
unsigned char concat(const __FlashStringHelper *str);
unsigned char concat(const char *cstr, unsigned int length);
// if there's not enough memory for the concatenated value, the string
// will be left unchanged (but this isn't signalled in any way)
String & operator +=(const String &rhs) {
String &operator +=(const String &rhs) {
concat(rhs);
return (*this);
return *this;
}
String & operator +=(const char *cstr) {
String &operator +=(const char *cstr) {
concat(cstr);
return (*this);
return *this;
}
String & operator +=(char c) {
String &operator +=(char c) {
concat(c);
return (*this);
return *this;
}
String & operator +=(unsigned char num) {
String &operator +=(unsigned char num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(int num) {
String &operator +=(int num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(unsigned int num) {
String &operator +=(unsigned int num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(long num) {
String &operator +=(long num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(unsigned long num) {
String &operator +=(unsigned long num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(float num) {
String &operator +=(long long num) {
concat(num);
return (*this);
return *this;
}
String & operator +=(double num) {
String &operator +=(unsigned long long num) {
concat(num);
return (*this);
return *this;
}
String & operator += (const __FlashStringHelper *str){
String &operator +=(float num) {
concat(num);
return *this;
}
String &operator +=(double num) {
concat(num);
return *this;
}
String &operator +=(const __FlashStringHelper *str) {
concat(str);
return (*this);
return *this;
}
friend StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs);
friend StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr);
friend StringSumHelper & operator +(const StringSumHelper &lhs, char c);
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, int num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, long num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, float num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, double num);
friend StringSumHelper & operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs);
friend StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr);
friend StringSumHelper &operator +(const StringSumHelper &lhs, char c);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, int num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num);
friend StringSumHelper &operator +(const StringSumHelper &lhs, 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);
// comparison (only works w/ Strings and "strings")
operator StringIfHelperType() const {
@ -203,41 +223,45 @@ class String {
unsigned char equalsIgnoreCase(const String &s) const;
unsigned char equalsConstantTime(const String &s) const;
unsigned char startsWith(const String &prefix) const;
unsigned char startsWith(const char * prefix) const {
unsigned char startsWith(const char *prefix) const {
return this->startsWith(String(prefix));
}
unsigned char startsWith(const __FlashStringHelper * prefix) const {
unsigned char startsWith(const __FlashStringHelper *prefix) const {
return this->startsWith(String(prefix));
}
unsigned char startsWith(const String &prefix, unsigned int offset) const;
unsigned char endsWith(const String &suffix) const;
unsigned char endsWith(const char * suffix) const {
unsigned char endsWith(const char *suffix) const {
return this->endsWith(String(suffix));
}
unsigned char endsWith(const __FlashStringHelper * suffix) const {
unsigned char endsWith(const __FlashStringHelper *suffix) const {
return this->endsWith(String(suffix));
}
// character access
char charAt(unsigned int index) const;
char charAt(unsigned int index) const {
return operator [](index);
}
void setCharAt(unsigned int index, char c);
char operator [](unsigned int index) const;
char& operator [](unsigned int index);
char &operator [](unsigned int index);
void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const;
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
getBytes((unsigned char *) buf, bufsize, index);
}
const char* c_str() const { return buffer(); }
char* begin() { return wbuffer(); }
char* end() { return wbuffer() + length(); }
const char* begin() const { return c_str(); }
const char* end() const { return c_str() + length(); }
const char *c_str() const { return buffer(); }
char *begin() { return wbuffer(); }
char *end() { return wbuffer() + length(); }
const char *begin() const { return c_str(); }
const char *end() const { return c_str() + length(); }
// search
int indexOf(char ch) const;
int indexOf(char ch, unsigned int fromIndex) const;
int indexOf(const String &str) const;
int indexOf(const String &str, unsigned int fromIndex) const;
int indexOf(char ch, unsigned int fromIndex = 0) const;
int indexOf(const char *str, unsigned int fromIndex = 0) const;
int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) const {
return indexOf((const char*)str, fromIndex);
}
int indexOf(const String &str, unsigned int fromIndex = 0) const;
int lastIndexOf(char ch) const;
int lastIndexOf(char ch, unsigned int fromIndex) const;
int lastIndexOf(const String &str) const;
@ -245,29 +269,29 @@ class String {
String substring(unsigned int beginIndex) const {
return substring(beginIndex, len());
}
;
String substring(unsigned int beginIndex, unsigned int endIndex) const;
// modification
void replace(char find, char replace);
void replace(const String& find, const String& replace);
void replace(const char * find, const String& replace) {
void replace(const String &find, const String &replace);
void replace(const char *find, const String &replace) {
this->replace(String(find), replace);
}
void replace(const __FlashStringHelper * find, const String& replace) {
void replace(const __FlashStringHelper *find, const String &replace) {
this->replace(String(find), replace);
}
void replace(const char * find, const char * replace) {
void replace(const char *find, const char *replace) {
this->replace(String(find), String(replace));
}
void replace(const __FlashStringHelper * find, const char * replace) {
void replace(const __FlashStringHelper *find, const char *replace) {
this->replace(String(find), String(replace));
}
void replace(const __FlashStringHelper * find, const __FlashStringHelper * replace) {
void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) {
this->replace(String(find), String(replace));
}
void remove(unsigned int index);
void remove(unsigned int index, unsigned int count);
// Pass the biggest integer if the count is not specified.
// The remove method below will take care of truncating it at the end of the string.
void remove(unsigned int index, unsigned int count = (unsigned int)-1);
void toLowerCase(void);
void toUpperCase(void);
void trim(void);
@ -275,11 +299,11 @@ class String {
// parsing/conversion
long toInt(void) const;
float toFloat(void) const;
double toDouble(void) const;
double toDouble(void) const;
protected:
// Contains the string info when we're not in SSO mode
struct _ptr {
struct _ptr {
char * buff;
uint16_t cap;
uint16_t len;
@ -288,8 +312,8 @@ class String {
enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more
struct _sso {
char buff[SSOSIZE];
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
unsigned char isSSO : 1;
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
unsigned char isHeap : 1;
} __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues
enum { CAPACITY_MAX = 65535 }; // If typeof(cap) changed from uint16_t, be sure to update this enum to the max value storable in the type
union {
@ -297,28 +321,48 @@ class String {
struct _sso sso;
};
// Accessor functions
inline bool isSSO() const { return sso.isSSO; }
inline unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
inline unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
inline void setSSO(bool set) { sso.isSSO = set; }
inline void setLen(int len) { if (isSSO()) sso.len = len; else ptr.len = len; }
inline void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
inline void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
bool isSSO() const { return !sso.isHeap; }
unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
void setSSO(bool set) { sso.isHeap = !set; }
void setLen(int len) {
if (isSSO()) {
setSSO(true); // Avoid emitting of bitwise EXTRACT-AND-OR ops (store-merging optimization)
sso.len = len;
} else
ptr.len = len;
}
void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
// Buffer accessor functions
inline const char *buffer() const { return (const char *)(isSSO() ? sso.buff : ptr.buff); }
inline char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
const char *buffer() const { return wbuffer(); }
char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
protected:
void init(void);
void init(void) __attribute__((always_inline)) {
sso.buff[0] = 0;
sso.len = 0;
sso.isHeap = 0;
// Without the 6 statements shown below, GCC simply emits such as: "MOVI.N aX,0", "S8I aX,a2,0" and "S8I aX,a2,11" (8 bytes in total)
sso.buff[1] = 0;
sso.buff[2] = 0;
sso.buff[3] = 0;
sso.buff[8] = 0;
sso.buff[9] = 0;
sso.buff[10] = 0;
// With the above, thanks to store-merging, GCC can use the narrow form of 32-bit store insn ("S32I.N") and emits:
// "MOVI.N aX,0", "S32I.N aX,a2,0" and "S32I.N aX,a2,8" (6 bytes in total)
// (Literature: Xtensa(R) Instruction Set Reference Manual, "S8I - Store 8-bit" [p.504] and "S32I.N - Narrow Store 32-bit" [p.512])
// Unfortunately, GCC seems not to re-evaluate the cost of inlining after the store-merging optimizer stage,
// `always_inline` attribute is necessary in order to keep inlining.
}
void invalidate(void);
unsigned char changeBuffer(unsigned int maxStrLen);
// copy and move
String & copy(const char *cstr, unsigned int length);
String & copy(const __FlashStringHelper *pstr, unsigned int length);
#ifdef __GXX_EXPERIMENTAL_CXX0X__
void move(String &rhs);
#endif
String &copy(const char *cstr, unsigned int length);
String &copy(const __FlashStringHelper *pstr, unsigned int length);
void move(String &rhs) noexcept;
};
class StringSumHelper: public String {
@ -347,12 +391,21 @@ 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) {
}
StringSumHelper(double num) :
String(num) {
}
StringSumHelper(const __FlashStringHelper *s) :
String(s) {
}
};
extern const String emptyString;

View File

@ -18,7 +18,6 @@
#include <stdlib.h>
#include <assert.h>
#include <debug.h>
#include <Arduino.h>
#include <cxxabi.h>
@ -32,18 +31,33 @@ extern "C" void __cxa_pure_virtual(void) __attribute__ ((__noreturn__));
extern "C" void __cxa_deleted_virtual(void) __attribute__ ((__noreturn__));
#if !defined(__cpp_exceptions) && !defined(NEW_OOM_ABORT)
void *operator new(size_t size)
#if !defined(__cpp_exceptions)
// overwrite weak operators new/new[] definitions
void* operator new(size_t size)
{
void *ret = malloc(size);
if (0 != size && 0 == ret) {
umm_last_fail_alloc_addr = __builtin_return_address(0);
umm_last_fail_alloc_size = size;
__unhandled_exception(PSTR("OOM"));
}
return ret;
return ret;
}
void *operator new[](size_t size)
void* operator new[](size_t size)
{
void *ret = malloc(size);
if (0 != size && 0 == ret) {
umm_last_fail_alloc_addr = __builtin_return_address(0);
umm_last_fail_alloc_size = size;
__unhandled_exception(PSTR("OOM"));
}
return ret;
}
void* operator new (size_t size, const std::nothrow_t&)
{
void *ret = malloc(size);
if (0 != size && 0 == ret) {
@ -52,7 +66,18 @@ void *operator new[](size_t size)
}
return ret;
}
#endif // arduino's std::new legacy
void* operator new[] (size_t size, const std::nothrow_t&)
{
void *ret = malloc(size);
if (0 != size && 0 == ret) {
umm_last_fail_alloc_addr = __builtin_return_address(0);
umm_last_fail_alloc_size = size;
}
return ret;
}
#endif // !defined(__cpp_exceptions)
void __cxa_pure_virtual(void)
{

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

@ -25,6 +25,8 @@
#ifndef CORE_BASE64_H_
#define CORE_BASE64_H_
#include <WString.h>
class base64
{
public:

View File

@ -18,6 +18,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <new> // std::nothrow
#include "cbuf.h"
#include "c_types.h"
@ -43,7 +44,7 @@ size_t cbuf::resize(size_t newSize) {
return _size;
}
char *newbuf = new char[newSize];
char *newbuf = new (std::nothrow) char[newSize];
char *oldbuf = _buf;
if(!newbuf) {

View File

@ -37,8 +37,9 @@ void precache(void *f, uint32_t bytes) {
// page (ie 1 word in 8) for this to work.
#define CACHE_PAGE_SIZE 32
register uint32_t a0 asm("a0");
register uint32_t lines = (bytes/CACHE_PAGE_SIZE)+2;
uint32_t a0;
__asm__("mov.n %0, a0" : "=r"(a0));
uint32_t lines = (bytes/CACHE_PAGE_SIZE)+2;
volatile uint32_t *p = (uint32_t*)((f ? (uint32_t)f : a0) & ~0x03);
uint32_t x;
for (uint32_t i=0; i<lines; i++, p+=CACHE_PAGE_SIZE/sizeof(uint32_t)) x=*p;

View File

@ -36,35 +36,6 @@
#include <stddef.h> // size_t
#include <stdint.h>
#ifdef __cplusplus
namespace arduino
{
extern "C++"
template <typename T, typename ...TConstructorArgs>
T* new0 (size_t n, TConstructorArgs... TconstructorArgs)
{
// n==0: single allocation, otherwise it is an array
size_t offset = n? sizeof(size_t): 0;
size_t arraysize = n? n: 1;
T* ptr = (T*)malloc(offset + (arraysize * sizeof(T)));
if (ptr)
{
if (n)
*(size_t*)(ptr) = n;
for (size_t i = 0; i < arraysize; i++)
new (ptr + offset + i * sizeof(T)) T(TconstructorArgs...);
return ptr + offset;
}
return nullptr;
}
}
#define arduino_new(Type, ...) arduino::new0<Type>(0, ##__VA_ARGS__)
#define arduino_newarray(Type, n, ...) arduino::new0<Type>(n, ##__VA_ARGS__)
#endif // __cplusplus
#ifndef __STRINGIFY
#define __STRINGIFY(a) #a
#endif
@ -83,6 +54,7 @@ namespace arduino
// level 0 will enable ALL interrupts,
//
#ifndef CORE_MOCK
#define xt_rsil(level) (__extension__({uint32_t state; __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state) :: "memory"); state;}))
#define xt_wsr_ps(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory")
@ -92,7 +64,22 @@ inline uint32_t esp_get_cycle_count() {
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
return ccount;
}
#endif // not CORE_MOCK
inline uint32_t esp_get_program_counter() __attribute__((always_inline));
inline uint32_t esp_get_program_counter() {
uint32_t pc;
__asm__ __volatile__("movi %0, ." : "=r" (pc) : : ); // ©earlephilhower
return pc;
}
#else // CORE_MOCK
#define xt_rsil(level) (level)
#define xt_wsr_ps(state) do { (void)(state); } while (0)
inline uint32_t esp_get_program_counter() { return 0; }
#endif // CORE_MOCK
// Tools for preloading code into the flash cache
@ -111,6 +98,29 @@ extern "C" {
#endif
void precache(void *f, uint32_t bytes);
unsigned long millis(void);
unsigned long micros(void);
uint64_t micros64(void);
void delay(unsigned long);
void delayMicroseconds(unsigned int us);
#if defined(F_CPU) || defined(CORE_MOCK)
#ifdef __cplusplus
constexpr
#else
inline
#endif
int esp_get_cpu_freq_mhz()
{
return F_CPU / 1000000L;
}
#else
inline int esp_get_cpu_freq_mhz()
{
return system_get_cpu_freq();
}
#endif
#ifdef __cplusplus
}

View File

@ -24,7 +24,7 @@
#include "osapi.h"
#include "ets_sys.h"
#include "i2s_reg.h"
#include "i2s.h"
#include "core_esp8266_i2s.h"
extern "C" {
@ -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;
@ -184,11 +194,11 @@ static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
}
void i2s_set_callback(void (*callback) (void)) {
tx->callback = callback;
if (tx) tx->callback = callback;
}
void i2s_rx_set_callback(void (*callback) (void)) {
rx->callback = callback;
if (rx) rx->callback = callback;
}
static bool _alloc_channel(i2s_state_t *ch) {
@ -333,7 +343,7 @@ bool i2s_write_lr(int16_t left, int16_t right){
// writes a buffer of frames into the DMA memory, returns the amount of frames written
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mono, bool nb) {
static uint16_t _i2s_write_buffer(const int16_t *frames, uint16_t frame_count, bool mono, bool nb) {
uint16_t frames_written=0;
while(frame_count>0) {
@ -391,13 +401,13 @@ static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mo
return frames_written;
}
uint16_t i2s_write_buffer_mono_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); }
uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); }
uint16_t i2s_write_buffer_mono(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); }
uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); }
uint16_t i2s_write_buffer_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); }
uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); }
uint16_t i2s_write_buffer(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); }
uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); }
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) {
if (!rx) {
@ -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

@ -0,0 +1,81 @@
/*
i2s.h - Software I2S library for esp8266
Copyright (c) 2015 Hristo Gochkov. 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
*/
#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.
The 32bit samples basically are 2 16-bit signed values (the analog values for
the left and right channel) concatenated as (Rout<<16)+Lout
i2s_write_sample will block when you're sending data too quickly, so you can just
generate and push data as fast as you can and i2s_write_sample will regulate the
speed.
*/
#ifdef __cplusplus
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
float i2s_get_real_rate();//The actual Sample Rate on output
bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full)
bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead
bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs.
bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow)
bool i2s_is_empty();//returns true if DMA is empty (underflow)
bool i2s_rx_is_full();
bool i2s_rx_is_empty();
uint16_t i2s_available();// returns the number of samples than can be written before blocking
uint16_t i2s_rx_available();// returns the number of samples than can be written before blocking
void i2s_set_callback(void (*callback) (void));
void i2s_rx_set_callback(void (*callback) (void));
// writes a buffer of frames into the DMA memory, returns the amount of frames written
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count);
#ifdef __cplusplus
}
#endif
#endif

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
@ -172,7 +175,7 @@ extern "C" bool ets_post_rom(uint8 prio, ETSSignal sig, ETSParam par);
extern "C" bool IRAM_ATTR ets_post(uint8 prio, ETSSignal sig, ETSParam par) {
uint32_t saved;
asm volatile ("rsr %0,ps":"=a" (saved));
__asm__ __volatile__ ("rsr %0,ps":"=a" (saved));
bool rc=ets_post_rom(prio, sig, par);
xt_wsr_ps(saved);
return rc;
@ -195,13 +198,18 @@ static void loop_wrapper() {
}
loop();
loop_end();
if (serialEventRun) {
serialEventRun();
}
esp_schedule();
}
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();
}
@ -253,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.
@ -311,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();
}
@ -339,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

@ -117,4 +117,36 @@ char * dtostrf(double number, signed char width, unsigned char prec, char *s) {
return s;
}
/*
strrstr (static)
Backwards search for p_pcPattern in p_pcString
Based on: https://stackoverflow.com/a/1634398/2778898
*/
const char* strrstr(const char*__restrict p_pcString,
const char*__restrict p_pcPattern)
{
const char* pcResult = 0;
size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0);
size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0);
if ((stStringLength) &&
(stPatternLength) &&
(stPatternLength <= stStringLength))
{
// Pattern is shorter or has the same length than the string
for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s)
{
if (0 == strncmp(s, p_pcPattern, stPatternLength))
{
pcResult = s;
break;
}
}
}
return pcResult;
}
};

View File

@ -45,6 +45,8 @@ static const char* s_panic_what = 0;
static bool s_abort_called = false;
static const char* s_unhandled_exception = NULL;
static uint32_t s_stacksmash_addr = 0;
void abort() __attribute__((noreturn));
static void uart_write_char_d(char c);
static void uart0_write_char_d(char c);
@ -54,6 +56,7 @@ static void print_stack(uint32_t start, uint32_t end);
// using numbers different from "REASON_" in user_interface.h (=0..6)
enum rst_reason_sw
{
REASON_USER_STACK_SMASH = 253,
REASON_USER_SWEXCEPTION_RST = 254
};
static int s_user_reset_reason = REASON_DEFAULT_RST;
@ -141,12 +144,20 @@ void __wrap_system_restart_local() {
ets_printf_P(PSTR("\nAbort called\n"));
}
else if (rst_info.reason == REASON_EXCEPTION_RST) {
// The GCC divide routine in ROM jumps to the address below and executes ILL (00 00 00) on div-by-zero
// In that case, print the exception as (6) which is IntegerDivZero
bool div_zero = (rst_info.exccause == 0) && (rst_info.epc1 == 0x4000dce5);
ets_printf_P(PSTR("\nException (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"),
rst_info.exccause, rst_info.epc1, rst_info.epc2, rst_info.epc3, rst_info.excvaddr, rst_info.depc);
div_zero ? 6 : rst_info.exccause, rst_info.epc1, rst_info.epc2, rst_info.epc3, rst_info.excvaddr, rst_info.depc);
}
else if (rst_info.reason == REASON_SOFT_WDT_RST) {
ets_printf_P(PSTR("\nSoft WDT reset\n"));
}
else if (rst_info.reason == REASON_USER_STACK_SMASH) {
ets_printf_P(PSTR("\nStack overflow detected.\n"));
ets_printf_P(PSTR("\nException (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"),
5 /* Alloca exception, closest thing to stack fault*/, s_stacksmash_addr, 0, 0, 0, 0);
}
else {
ets_printf_P(PSTR("\nGeneric Reset\n"));
}
@ -209,6 +220,12 @@ void __wrap_system_restart_local() {
cut_here();
if (s_unhandled_exception && umm_last_fail_alloc_addr) {
// now outside from the "cut-here" zone, print correctly the `new` caller address,
// idf-monitor.py will be able to decode this one and show exact location in sources
ets_printf_P(PSTR("\nlast failed alloc caller: 0x%08x\n"), (uint32_t)umm_last_fail_alloc_addr);
}
custom_crash_callback( &rst_info, sp_dump + offset, stack_end );
ets_delay_us(10000);
@ -290,4 +307,20 @@ void __panic_func(const char* file, int line, const char* func) {
raise_exception();
}
uintptr_t __stack_chk_guard = 0x08675309 ^ RANDOM_REG32;
void __stack_chk_fail(void) {
s_user_reset_reason = REASON_USER_STACK_SMASH;
ets_printf_P(PSTR("\nPANIC: Stack overrun"));
s_stacksmash_addr = (uint32_t)__builtin_return_address(0);
if (gdb_present())
__asm__ __volatile__ ("syscall"); // triggers GDB when enabled
__wrap_system_restart_local();
while (1); // never reached, needed to satisfy "noreturn" attribute
}
};

View File

@ -234,7 +234,7 @@ void ICACHE_RAM_ATTR Twi::busywait(unsigned int v)
unsigned int i;
for (i = 0; i < v; i++) // loop time is 5 machine cycles: 31.25ns @ 160MHz, 62.5ns @ 80MHz
{
asm("nop"); // minimum element to keep GCC from optimizing this function out.
__asm__ __volatile__("nop"); // minimum element to keep GCC from optimizing this function out.
}
}

View File

@ -31,8 +31,9 @@ extern "C" {
static volatile timercallback timer1_user_cb = NULL;
void ICACHE_RAM_ATTR timer1_isr_handler(void *para){
void ICACHE_RAM_ATTR timer1_isr_handler(void *para, void *frame) {
(void) para;
(void) frame;
if ((T1C & ((1 << TCAR) | (1 << TCIT))) == 0) TEIE &= ~TEIE1;//edge int disable
T1I = 0;
if (timer1_user_cb) {
@ -79,8 +80,9 @@ void ICACHE_RAM_ATTR timer1_disable(){
static volatile timercallback timer0_user_cb = NULL;
void ICACHE_RAM_ATTR timer0_isr_handler(void* para){
void ICACHE_RAM_ATTR timer0_isr_handler(void *para, void *frame) {
(void) para;
(void) frame;
if (timer0_user_cb) {
// to make ISR compatible to Arduino AVR model where interrupts are disabled
// we disable them before we call the client ISR

View File

@ -1,312 +0,0 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "ets_sys.h"
#include "core_esp8266_waveform.h"
extern "C" {
// Maximum delay between IRQs
#define MAXIRQUS (10000)
// Set/clear GPIO 0-15 by bitmask
#define SetGPIO(a) do { GPOS = a; } while (0)
#define ClearGPIO(a) do { GPOC = a; } while (0)
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
uint32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform
uint32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform
} Waveform;
static Waveform waveform[17]; // State of all possible pins
static volatile uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
static uint32_t (*timer1CB)() = NULL;
// Non-speed critical bits
#pragma GCC optimize ("Os")
static inline ICACHE_RAM_ATTR uint32_t GetCycleCount() {
uint32_t ccount;
__asm__ __volatile__("esync; rsr %0,ccount":"=a"(ccount));
return ccount;
}
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
static bool timerRunning = false;
static void initTimer() {
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timerRunning = true;
}
static void ICACHE_RAM_ATTR deinitTimer() {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
timerRunning = false;
}
// Set a callback. Pass in NULL to stop it
void setTimer1Callback(uint32_t (*fn)()) {
timer1CB = fn;
if (!timerRunning && fn) {
initTimer();
timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste
} else if (timerRunning && !fn && !waveformEnabled) {
deinitTimer();
}
}
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) {
return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS));
}
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) {
if ((pin > 16) || isFlashInterfacePin(pin)) {
return false;
}
Waveform *wave = &waveform[pin];
// Adjust to shave off some of the IRQ time, approximately
wave->nextTimeHighCycles = timeHighCycles;
wave->nextTimeLowCycles = timeLowCycles;
wave->expiryCycle = runTimeCycles ? GetCycleCount() + runTimeCycles : 0;
if (runTimeCycles && !wave->expiryCycle) {
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
}
uint32_t mask = 1<<pin;
if (!(waveformEnabled & mask)) {
// Actually set the pin high or low in the IRQ service to guarantee times
wave->nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(1);
waveformToEnable |= mask;
if (!timerRunning) {
initTimer();
timer1_write(microsecondsToClockCycles(10));
} else {
// Ensure timely service....
if (T1L > microsecondsToClockCycles(10)) {
timer1_write(microsecondsToClockCycles(10));
}
}
while (waveformToEnable) {
delay(0); // Wait for waveform to update
}
}
return true;
}
// Speed critical bits
#pragma GCC optimize ("O2")
// Normally would not want two copies like this, but due to different
// optimization levels the inline attribute gets lost if we try the
// other version.
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
return ccount;
}
static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) {
if (a < b) {
return a;
}
return b;
}
// Stops a waveform on a pin
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!timerRunning) {
return false;
}
// If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false
if (waveformEnabled & (1UL << pin)) {
waveformToDisable = 1UL << pin;
// Must not interfere if Timer is due shortly
if (T1L > microsecondsToClockCycles(10)) {
timer1_write(microsecondsToClockCycles(10));
}
while (waveformToDisable) {
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
}
}
if (!waveformEnabled && !timer1CB) {
deinitTimer();
}
return true;
}
// The SDK and hardware take some time to actually get to our NMI code, so
// decrement the next IRQ's timer value by a bit so we can actually catch the
// real CPU cycle counter we want for the waveforms.
#if F_CPU == 80000000
#define DELTAIRQ (microsecondsToClockCycles(3))
#else
#define DELTAIRQ (microsecondsToClockCycles(2))
#endif
static ICACHE_RAM_ATTR void timer1Interrupt() {
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
// are generating. In the common case (1 PWM) these may be the same pin and
// we can avoid looking at the other pins.
static int startPin = 0;
static int endPin = 0;
uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
if (waveformToEnable || waveformToDisable) {
// Handle enable/disable requests from main app.
waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off
waveformState &= ~waveformToEnable; // And clear the state of any just started
waveformToEnable = 0;
waveformToDisable = 0;
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
startPin = __builtin_ffs(waveformEnabled) - 1;
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
endPin = 32 - __builtin_clz(waveformEnabled);
}
bool done = false;
if (waveformEnabled) {
do {
nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
for (int i = startPin; i <= endPin; i++) {
uint32_t mask = 1<<i;
// If it's not on, ignore!
if (!(waveformEnabled & mask)) {
continue;
}
Waveform *wave = &waveform[i];
uint32_t now = GetCycleCountIRQ();
// Disable any waveforms that are done
if (wave->expiryCycle) {
int32_t expiryToGo = wave->expiryCycle - now;
if (expiryToGo < 0) {
// Done, remove!
waveformEnabled &= ~mask;
if (i == 16) {
GP16O &= ~1;
} else {
ClearGPIO(mask);
}
continue;
}
}
// Check for toggles
int32_t cyclesToGo = wave->nextServiceCycle - now;
if (cyclesToGo < 0) {
waveformState ^= mask;
if (waveformState & mask) {
if (i == 16) {
GP16O |= 1; // GPIO16 write slow as it's RMW
} else {
SetGPIO(mask);
}
wave->nextServiceCycle = now + wave->nextTimeHighCycles;
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles);
} else {
if (i == 16) {
GP16O &= ~1; // GPIO16 write slow as it's RMW
} else {
ClearGPIO(mask);
}
wave->nextServiceCycle = now + wave->nextTimeLowCycles;
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles);
}
} else {
uint32_t deltaCycles = wave->nextServiceCycle - now;
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
}
}
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
uint32_t now = GetCycleCountIRQ();
int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles);
int32_t cyclesLeftTimeout = timeoutCycle - now;
done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0);
} while (!done);
} // if (waveformEnabled)
if (timer1CB) {
nextEventCycles = min_u32(nextEventCycles, timer1CB());
}
if (nextEventCycles < microsecondsToClockCycles(10)) {
nextEventCycles = microsecondsToClockCycles(10);
}
nextEventCycles -= DELTAIRQ;
// Do it here instead of global function to save time and because we know it's edge-IRQ
#if F_CPU == 160000000
T1L = nextEventCycles >> 1; // Already know we're in range by MAXIRQUS
#else
T1L = nextEventCycles; // Already know we're in range by MAXIRQUS
#endif
TEIE |= TEIE1; // Edge int enable
}
};

View File

@ -2,6 +2,7 @@
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
-- Default, PWM locked version --
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
The core idea is to have a programmable waveform generator with a unique
@ -22,6 +23,30 @@
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
----------
-- Phase locked version --
Copyright (c) 2020 Dirk O. Kaar.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
----------
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@ -47,20 +72,41 @@
extern "C" {
#endif
// Call this function in your setup() to cause the phase locked version of the generator to
// be linked in automatically. Otherwise, the default PWM locked version will be used.
void enablePhaseLockedWaveform(void);
// Start or change a waveform of the specified high and low times on specific pin.
// If runtimeUS > 0 then automatically stop it after that many usecs.
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
// full period.
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
// under load, for applications where frequency or duty cycle must not change, leave false.
// Returns true or false on success or failure.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS = 0,
// Following parameters are ignored unless in PhaseLocked mode
int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles.
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next
// full period.
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that.
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
// under load, for applications where frequency or duty cycle must not change, leave false.
// Returns true or false on success or failure.
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys = 0,
// Following parameters are ignored unless in PhaseLocked mode
int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
// Stop a waveform, if any, on the specified pin.
// Returns true or false on success or failure.
int stopWaveform(uint8_t pin);
// Add a callback function to be called on *EVERY* timer1 trigger. The
// callback returns the number of microseconds until the next desired call.
// callback must return the number of CPU clock cycles until the next desired call.
// However, since it is called every timer1 interrupt, it may be called
// again before this period. It should therefore use the ESP Cycle Counter
// to determine whether or not to perform an operation.
@ -69,6 +115,12 @@ int stopWaveform(uint8_t pin);
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
void setTimer1Callback(uint32_t (*fn)());
// Internal-only calls, not for applications
extern void _setPWMFreq(uint32_t freq);
extern bool _stopPWM(uint8_t pin);
extern bool _setPWM(int pin, uint32_t val, uint32_t range);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,444 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
Copyright (c) 2020 Dirk O. Kaar.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
clock cycle time, or an interval measured in clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "core_esp8266_waveform.h"
#include <Arduino.h>
#include "debug.h"
#include "ets_sys.h"
#include <atomic>
extern "C" void enablePhaseLockedWaveform (void)
{
// Does nothing, added to app to enable linking these versions
// of the waveform functions instead of the default.
DEBUGV("Enabling phase locked waveform generator\n");
}
// No-op calls to override the PWM implementation
extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; }
extern "C" bool _stopPWM_weak(int pin) { (void) pin; return false; }
extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; }
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
// Maximum servicing time for any single IRQ
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
// The latency between in-ISR rearming of the timer and the earliest firing
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
// The SDK and hardware take some time to actually get to our NMI code
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3};
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
int32_t adjDutyCcys; // Temporary correction for next period
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
WaveformMode mode;
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
} Waveform;
namespace {
static struct {
Waveform pins[17]; // State of all possible pins
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
uint32_t(*timer1CB)() = nullptr;
bool timer1Running = false;
uint32_t nextEventCcy;
} waveform;
}
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
// Non-speed critical bits
#pragma GCC optimize ("Os")
static void initTimer() {
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
waveform.timer1Running = true;
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
}
static void ICACHE_RAM_ATTR deinitTimer() {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
waveform.timer1Running = false;
}
extern "C" {
// Set a callback. Pass in NULL to stop it
void setTimer1Callback_weak(uint32_t (*fn)()) {
waveform.timer1CB = fn;
std::atomic_thread_fence(std::memory_order_acq_rel);
if (!waveform.timer1Running && fn) {
initTimer();
} else if (waveform.timer1Running && !fn && !waveform.enabled) {
deinitTimer();
}
}
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
uint32_t periodCcys = highCcys + lowCcys;
if (periodCcys < MAXIRQTICKSCCYS) {
if (!highCcys) {
periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
}
else if (!lowCcys) {
highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
}
}
// sanity checks, including mixed signed/unsigned arithmetic safety
if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
static_cast<int32_t>(periodCcys) <= 0 ||
static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) {
return false;
}
Waveform& wave = waveform.pins[pin];
wave.dutyCcys = highCcys;
wave.adjDutyCcys = 0;
wave.periodCcys = periodCcys;
wave.autoPwm = autoPwm;
std::atomic_thread_fence(std::memory_order_acquire);
const uint32_t pinBit = 1UL << pin;
if (!(waveform.enabled & pinBit)) {
// wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
wave.nextPeriodCcy = phaseOffsetCcys;
wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
wave.mode = WaveformMode::INIT;
wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
if (!wave.dutyCcys) {
// If initially at zero duty cycle, force GPIO off
if (pin == 16) {
GP16O = 0;
}
else {
GPOC = pinBit;
}
}
std::atomic_thread_fence(std::memory_order_release);
waveform.toSetBits = 1UL << pin;
std::atomic_thread_fence(std::memory_order_release);
if (!waveform.timer1Running) {
initTimer();
}
else if (T1V > IRQLATENCYCCYS) {
// Must not interfere if Timer is due shortly
timer1_write(IRQLATENCYCCYS);
}
}
else {
wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
std::atomic_thread_fence(std::memory_order_release);
wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
if (runTimeCcys) {
wave.mode = WaveformMode::UPDATEEXPIRY;
std::atomic_thread_fence(std::memory_order_release);
waveform.toSetBits = 1UL << pin;
}
}
std::atomic_thread_fence(std::memory_order_acq_rel);
while (waveform.toSetBits) {
delay(0); // Wait for waveform to update
std::atomic_thread_fence(std::memory_order_acquire);
}
return true;
}
// Stops a waveform on a pin
ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!waveform.timer1Running) {
return false;
}
// If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false
std::atomic_thread_fence(std::memory_order_acquire);
const uint32_t pinBit = 1UL << pin;
if (waveform.enabled & pinBit) {
waveform.toDisableBits = 1UL << pin;
std::atomic_thread_fence(std::memory_order_release);
// Must not interfere if Timer is due shortly
if (T1V > IRQLATENCYCCYS) {
timer1_write(IRQLATENCYCCYS);
}
while (waveform.toDisableBits) {
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
std::atomic_thread_fence(std::memory_order_acquire);
}
}
if (!waveform.enabled && !waveform.timer1CB) {
deinitTimer();
}
return true;
}
};
// Speed critical bits
#pragma GCC optimize ("O2")
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
if (ISCPUFREQ160MHZ) {
return isCPU2X ? ccys : (ccys >> 1);
}
else {
return isCPU2X ? (ccys << 1) : ccys;
}
}
static ICACHE_RAM_ATTR void timer1Interrupt() {
const uint32_t isrStartCcy = ESP.getCycleCount();
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
const bool isCPU2X = CPU2X & 1;
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
// Handle enable/disable requests from main app.
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
waveform.toDisableBits = 0;
}
if (waveform.toSetBits) {
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
Waveform& wave = waveform.pins[toSetPin];
switch (wave.mode) {
case WaveformMode::INIT:
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) {
wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy;
}
else {
wave.nextPeriodCcy = waveform.nextEventCcy;
}
if (!wave.expiryCcy) {
wave.mode = WaveformMode::INFINITE;
break;
}
// fall through
case WaveformMode::UPDATEEXPIRY:
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
wave.mode = WaveformMode::EXPIRES;
break;
default:
break;
}
waveform.toSetBits = 0;
}
// Exit the loop if the next event, if any, is sufficiently distant.
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
uint32_t busyPins = waveform.enabled;
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
uint32_t now = ESP.getCycleCount();
uint32_t isrNextEventCcy = now;
while (busyPins) {
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
waveform.nextEventCcy = isrNextEventCcy;
break;
}
isrNextEventCcy = waveform.nextEventCcy;
uint32_t loopPins = busyPins;
while (loopPins) {
const int pin = __builtin_ffsl(loopPins) - 1;
const uint32_t pinBit = 1UL << pin;
loopPins ^= pinBit;
Waveform& wave = waveform.pins[pin];
if (clockDrift) {
wave.endDutyCcy += clockDrift;
wave.nextPeriodCcy += clockDrift;
wave.expiryCcy += clockDrift;
}
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
if (WaveformMode::EXPIRES == wave.mode &&
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
// Disable any waveforms that are done
waveform.enabled ^= pinBit;
busyPins ^= pinBit;
}
else {
const int32_t overshootCcys = now - waveNextEventCcy;
if (overshootCcys >= 0) {
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
if (waveform.states & pinBit) {
// active configuration and forward are 100% duty
if (wave.periodCcys == wave.dutyCcys) {
wave.nextPeriodCcy += periodCcys;
wave.endDutyCcy = wave.nextPeriodCcy;
}
else {
if (wave.autoPwm) {
wave.adjDutyCcys += overshootCcys;
}
waveform.states ^= pinBit;
if (16 == pin) {
GP16O = 0;
}
else {
GPOC = pinBit;
}
}
waveNextEventCcy = wave.nextPeriodCcy;
}
else {
wave.nextPeriodCcy += periodCcys;
if (!wave.dutyCcys) {
wave.endDutyCcy = wave.nextPeriodCcy;
}
else {
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
if (dutyCcys <= wave.adjDutyCcys) {
dutyCcys >>= 1;
wave.adjDutyCcys -= dutyCcys;
}
else if (wave.adjDutyCcys) {
dutyCcys -= wave.adjDutyCcys;
wave.adjDutyCcys = 0;
}
wave.endDutyCcy = now + dutyCcys;
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
wave.endDutyCcy = wave.nextPeriodCcy;
}
waveform.states |= pinBit;
if (16 == pin) {
GP16O = 1;
}
else {
GPOS = pinBit;
}
}
waveNextEventCcy = wave.endDutyCcy;
}
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
waveNextEventCcy = wave.expiryCcy;
}
}
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
busyPins ^= pinBit;
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
waveform.nextEventCcy = waveNextEventCcy;
}
}
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
isrNextEventCcy = waveNextEventCcy;
}
}
now = ESP.getCycleCount();
}
clockDrift = 0;
}
int32_t callbackCcys = 0;
if (waveform.timer1CB) {
callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X);
}
now = ESP.getCycleCount();
int32_t nextEventCcys = waveform.nextEventCcy - now;
// Account for unknown duration of timer1CB().
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
waveform.nextEventCcy = now + callbackCcys;
nextEventCcys = callbackCcys;
}
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
int32_t deltaIrqCcys = DELTAIRQCCYS;
int32_t irqLatencyCcys = IRQLATENCYCCYS;
if (isCPU2X) {
nextEventCcys >>= 1;
deltaIrqCcys >>= 1;
irqLatencyCcys >>= 1;
}
// Firing timer too soon, the NMI occurs before ISR has returned.
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
nextEventCcys = irqLatencyCcys;
}
else {
nextEventCcys -= deltaIrqCcys;
}
// Register access is fast and edge IRQ was configured before.
T1L = nextEventCcys;
}

View File

@ -0,0 +1,666 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1
is set to 1-shot mode and is always loaded with the time until the next
edge of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not
the timer. This allows for removing interrupt jitter and delay as the
counter always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not
TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "ets_sys.h"
#include "core_esp8266_waveform.h"
#include "user_interface.h"
extern "C" {
// Maximum delay between IRQs
#define MAXIRQUS (10000)
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
uint32_t timeLowCycles; //
uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
uint32_t desiredLowCycles; //
uint32_t lastEdge; // Cycle when this generator last changed
} Waveform;
class WVFState {
public:
Waveform waveform[17]; // State of all possible pins
uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI
uint32_t waveformNewHigh = 0;
uint32_t waveformNewLow = 0;
uint32_t (*timer1CB)() = NULL;
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
// are generating. In the common case (1 PWM) these may be the same pin and
// we can avoid looking at the other pins.
uint16_t startPin = 0;
uint16_t endPin = 0;
};
static WVFState wvfState;
// Ensure everything is read/written to RAM
#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
// Non-speed critical bits
#pragma GCC optimize ("Os")
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
static bool timerRunning = false;
static __attribute__((noinline)) void initTimer() {
if (!timerRunning) {
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timerRunning = true;
timer1_write(microsecondsToClockCycles(10));
}
}
static ICACHE_RAM_ATTR void forceTimerInterrupt() {
if (T1L > microsecondsToClockCycles(10)) {
T1L = microsecondsToClockCycles(10);
}
}
// PWM implementation using special purpose state machine
//
// Keep an ordered list of pins with the delta in cycles between each
// element, with a terminal entry making up the remainder of the PWM
// period. With this method sum(all deltas) == PWM period clock cycles.
//
// At t=0 set all pins high and set the timeout for the 1st edge.
// On interrupt, if we're at the last element reset to t=0 state
// Otherwise, clear that pin down and set delay for next element
// and so forth.
constexpr int maxPWMs = 8;
// PWM machine state
typedef struct PWMState {
uint32_t mask; // Bitmask of active pins
uint32_t cnt; // How many entries
uint32_t idx; // Where the state machine is along the list
uint8_t pin[maxPWMs + 1];
uint32_t delta[maxPWMs + 1];
uint32_t nextServiceCycle; // Clock cycle for next step
struct PWMState *pwmUpdate; // Set by main code, cleared by ISR
} PWMState;
static PWMState pwmState;
static uint32_t _pwmFreq = 1000;
static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq;
// If there are no more scheduled activities, shut down Timer 1.
// Otherwise, do nothing.
static ICACHE_RAM_ATTR void disableIdleTimer() {
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
timerRunning = false;
}
}
// Notify the NMI that a new PWM state is available through the mailbox.
// Wait for mailbox to be emptied (either busy or delay() as needed)
static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
p->pwmUpdate = nullptr;
pwmState.pwmUpdate = p;
MEMBARRIER();
forceTimerInterrupt();
while (pwmState.pwmUpdate) {
if (idle) {
delay(0);
}
MEMBARRIER();
}
}
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range);
// Called when analogWriteFreq() changed to update the PWM total period
extern void _setPWMFreq_weak(uint32_t freq) __attribute__((weak));
void _setPWMFreq_weak(uint32_t freq) {
_pwmFreq = freq;
// Convert frequency into clock cycles
uint32_t cc = microsecondsToClockCycles(1000000UL) / freq;
// Simple static adjustment to bring period closer to requested due to overhead
// Empirically determined as a constant PWM delay and a function of the number of PWMs
#if F_CPU == 80000000
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110;
#else
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75;
#endif
if (cc == _pwmPeriod) {
return; // No change
}
_pwmPeriod = cc;
if (pwmState.cnt) {
PWMState p; // The working copy since we can't edit the one in use
p.mask = 0;
p.cnt = 0;
for (uint32_t i = 0; i < pwmState.cnt; i++) {
auto pin = pwmState.pin[i];
_addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles);
}
// Update and wait for mailbox to be emptied
initTimer();
_notifyPWM(&p, true);
disableIdleTimer();
}
}
static void _setPWMFreq_bound(uint32_t freq) __attribute__((weakref("_setPWMFreq_weak")));
void _setPWMFreq(uint32_t freq) {
_setPWMFreq_bound(freq);
}
// Helper routine to remove an entry from the state machine
// and clean up any marked-off entries
static void _cleanAndRemovePWM(PWMState *p, int pin) {
uint32_t leftover = 0;
uint32_t in, out;
for (in = 0, out = 0; in < p->cnt; in++) {
if ((p->pin[in] != pin) && (p->mask & (1<<p->pin[in]))) {
p->pin[out] = p->pin[in];
p->delta[out] = p->delta[in] + leftover;
leftover = 0;
out++;
} else {
leftover += p->delta[in];
p->mask &= ~(1<<p->pin[in]);
}
}
p->cnt = out;
// Final pin is never used: p->pin[out] = 0xff;
p->delta[out] = p->delta[in] + leftover;
}
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak));
ICACHE_RAM_ATTR bool _stopPWM_weak(uint8_t pin) {
if (!((1<<pin) & pwmState.mask)) {
return false; // Pin not actually active
}
PWMState p; // The working copy since we can't edit the one in use
p = pwmState;
// In _stopPWM we just clear the mask but keep everything else
// untouched to save IRAM. The main startPWM will handle cleanup.
p.mask &= ~(1<<pin);
if (!p.mask) {
// If all have been stopped, then turn PWM off completely
p.cnt = 0;
}
// Update and wait for mailbox to be emptied, no delay (could be in ISR)
_notifyPWM(&p, false);
// Possibly shut down the timer completely if we're done
disableIdleTimer();
return true;
}
static bool _stopPWM_bound(uint8_t pin) __attribute__((weakref("_stopPWM_weak")));
bool _stopPWM(uint8_t pin) {
return _stopPWM_bound(pin);
}
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) {
// Stash the val and range so we can re-evaluate the fraction
// should the user change PWM frequency. This allows us to
// give as great a precision as possible. We know by construction
// that the waveform for this pin will be inactive so we can borrow
// memory from that structure.
wvfState.waveform[pin].desiredHighCycles = val; // Numerator == high
wvfState.waveform[pin].desiredLowCycles = range; // Denominator == low
uint32_t cc = (_pwmPeriod * val) / range;
// Clip to sane values in the case we go from OK to not-OK when adjusting frequencies
if (cc == 0) {
cc = 1;
} else if (cc >= _pwmPeriod) {
cc = _pwmPeriod - 1;
}
if (p.cnt == 0) {
// Starting up from scratch, special case 1st element and PWM period
p.pin[0] = pin;
p.delta[0] = cc;
// Final pin is never used: p.pin[1] = 0xff;
p.delta[1] = _pwmPeriod - cc;
} else {
uint32_t ttl = 0;
uint32_t i;
// Skip along until we're at the spot to insert
for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
ttl += p.delta[i];
}
// Shift everything out by one to make space for new edge
for (int32_t j = p.cnt; j >= (int)i; j--) {
p.pin[j + 1] = p.pin[j];
p.delta[j + 1] = p.delta[j];
}
int off = cc - ttl; // The delta from the last edge to the one we're inserting
p.pin[i] = pin;
p.delta[i] = off; // Add the delta to this new pin
p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant
}
p.cnt++;
p.mask |= 1<<pin;
}
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
extern bool _setPWM_weak(int pin, uint32_t val, uint32_t range) __attribute__((weak));
bool _setPWM_weak(int pin, uint32_t val, uint32_t range) {
stopWaveform(pin);
PWMState p; // Working copy
p = pwmState;
// Get rid of any entries for this pin
_cleanAndRemovePWM(&p, pin);
// And add it to the list, in order
if (p.cnt >= maxPWMs) {
return false; // No space left
}
// Sanity check for all-on/off
uint32_t cc = (_pwmPeriod * val) / range;
if ((cc == 0) || (cc >= _pwmPeriod)) {
digitalWrite(pin, cc ? HIGH : LOW);
return true;
}
_addPWMtoList(p, pin, val, range);
// Set mailbox and wait for ISR to copy it over
initTimer();
_notifyPWM(&p, true);
disableIdleTimer();
// Potentially recalculate the PWM period if we've added another pin
_setPWMFreq(_pwmFreq);
return true;
}
static bool _setPWM_bound(int pin, uint32_t val, uint32_t range) __attribute__((weakref("_setPWM_weak")));
bool _setPWM(int pin, uint32_t val, uint32_t range) {
return _setPWM_bound(pin, val, range);
}
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
extern int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weak));
int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles,
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
(void) alignPhase;
(void) phaseOffsetUS;
(void) autoPwm;
if ((pin > 16) || isFlashInterfacePin(pin)) {
return false;
}
Waveform *wave = &wvfState.waveform[pin];
wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0;
if (runTimeCycles && !wave->expiryCycle) {
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
}
_stopPWM(pin); // Make sure there's no PWM live here
uint32_t mask = 1<<pin;
MEMBARRIER();
if (wvfState.waveformEnabled & mask) {
// Make sure no waveform changes are waiting to be applied
while (wvfState.waveformToChange) {
delay(0); // Wait for waveform to update
// No mem barrier here, the call to a global function implies global state updated
}
wvfState.waveformNewHigh = timeHighCycles;
wvfState.waveformNewLow = timeLowCycles;
MEMBARRIER();
wvfState.waveformToChange = mask;
// The waveform will be updated some time in the future on the next period for the signal
} else { // if (!(wvfState.waveformEnabled & mask)) {
wave->timeHighCycles = timeHighCycles;
wave->desiredHighCycles = timeHighCycles;
wave->timeLowCycles = timeLowCycles;
wave->desiredLowCycles = timeLowCycles;
wave->lastEdge = 0;
wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
wvfState.waveformToEnable |= mask;
MEMBARRIER();
initTimer();
forceTimerInterrupt();
while (wvfState.waveformToEnable) {
delay(0); // Wait for waveform to update
// No mem barrier here, the call to a global function implies global state updated
}
}
return true;
}
static int startWaveformClockCycles_bound(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weakref("startWaveformClockCycles_weak")));
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
return startWaveformClockCycles_bound(pin, timeHighCycles, timeLowCycles, runTimeCycles, alignPhase, phaseOffsetUS, autoPwm);
}
// This version falls-thru to the proper startWaveformClockCycles call and is invariant across waveform generators
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS,
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
return startWaveformClockCycles_bound(pin,
microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS),
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
}
// Set a callback. Pass in NULL to stop it
extern void setTimer1Callback_weak(uint32_t (*fn)()) __attribute__((weak));
void setTimer1Callback_weak(uint32_t (*fn)()) {
wvfState.timer1CB = fn;
if (fn) {
initTimer();
forceTimerInterrupt();
}
disableIdleTimer();
}
static void setTimer1Callback_bound(uint32_t (*fn)()) __attribute__((weakref("setTimer1Callback_weak")));
void setTimer1Callback(uint32_t (*fn)()) {
setTimer1Callback_bound(fn);
}
// Stops a waveform on a pin
extern int stopWaveform_weak(uint8_t pin) __attribute__((weak));
ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!timerRunning) {
return false;
}
// If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false
uint32_t mask = 1<<pin;
if (wvfState.waveformEnabled & mask) {
wvfState.waveformToDisable = mask;
// Cancel any pending updates for this waveform, too.
if (wvfState.waveformToChange & mask) {
wvfState.waveformToChange = 0;
}
forceTimerInterrupt();
while (wvfState.waveformToDisable) {
MEMBARRIER(); // If it wasn't written yet, it has to be by now
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
}
}
disableIdleTimer();
return true;
}
static int stopWaveform_bound(uint8_t pin) __attribute__((weakref("stopWaveform_weak")));
ICACHE_RAM_ATTR int stopWaveform(uint8_t pin) {
return stopWaveform_bound(pin);
}
// Speed critical bits
#pragma GCC optimize ("O2")
// Normally would not want two copies like this, but due to different
// optimization levels the inline attribute gets lost if we try the
// other version.
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
return ccount;
}
// Find the earliest cycle as compared to right now
static inline ICACHE_RAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
uint32_t now = GetCycleCountIRQ();
int32_t da = a - now;
int32_t db = b - now;
return (da < db) ? a : b;
}
// The SDK and hardware take some time to actually get to our NMI code, so
// decrement the next IRQ's timer value by a bit so we can actually catch the
// real CPU cycle counter we want for the waveforms.
// The SDK also sometimes is running at a different speed the the Arduino core
// so the ESP cycle counter is actually running at a variable speed.
// adjust(x) takes care of adjusting a delta clock cycle amount accordingly.
#if F_CPU == 80000000
#define DELTAIRQ (microsecondsToClockCycles(9)/4)
#define adjust(x) ((x) << (turbo ? 1 : 0))
#else
#define DELTAIRQ (microsecondsToClockCycles(9)/8)
#define adjust(x) ((x) >> 0)
#endif
// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
#define MINIRQTIME microsecondsToClockCycles(4)
static ICACHE_RAM_ATTR void timer1Interrupt() {
// Flag if the core is at 160 MHz, for use by adjust()
bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
if (wvfState.waveformToEnable || wvfState.waveformToDisable) {
// Handle enable/disable requests from main app
wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off
wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started
wvfState.waveformToEnable = 0;
wvfState.waveformToDisable = 0;
// No mem barrier. Globals must be written to RAM on ISR exit.
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
} else if (!pwmState.cnt && pwmState.pwmUpdate) {
// Start up the PWM generator by copying from the mailbox
pwmState.cnt = 1;
pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0
pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
// No need for mem barrier here. Global must be written by IRQ exit
}
bool done = false;
if (wvfState.waveformEnabled || pwmState.cnt) {
do {
nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
// PWM state machine implementation
if (pwmState.cnt) {
int32_t cyclesToGo;
do {
cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ();
if (cyclesToGo < 0) {
if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
if (pwmState.pwmUpdate) {
// Do the memory copy from temp to global and clear mailbox
pwmState = *(PWMState*)pwmState.pwmUpdate;
}
GPOS = pwmState.mask; // Set all active pins high
if (pwmState.mask & (1<<16)) {
GP16O = 1;
}
pwmState.idx = 0;
} else {
do {
// Drop the pin at this edge
if (pwmState.mask & (1<<pwmState.pin[pwmState.idx])) {
GPOC = 1<<pwmState.pin[pwmState.idx];
if (pwmState.pin[pwmState.idx] == 16) {
GP16O = 0;
}
}
pwmState.idx++;
// Any other pins at this same PWM value will have delta==0, drop them too.
} while (pwmState.delta[pwmState.idx] == 0);
}
// Preserve duty cycle over PWM period by using now+xxx instead of += delta
cyclesToGo = adjust(pwmState.delta[pwmState.idx]);
pwmState.nextServiceCycle = GetCycleCountIRQ() + cyclesToGo;
}
nextEventCycle = earliest(nextEventCycle, pwmState.nextServiceCycle);
} while (pwmState.cnt && (cyclesToGo < 100));
}
for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) {
uint32_t mask = 1<<i;
// If it's not on, ignore!
if (!(wvfState.waveformEnabled & mask)) {
continue;
}
Waveform *wave = &wvfState.waveform[i];
uint32_t now = GetCycleCountIRQ();
// Disable any waveforms that are done
if (wave->expiryCycle) {
int32_t expiryToGo = wave->expiryCycle - now;
if (expiryToGo < 0) {
// Done, remove!
if (i == 16) {
GP16O = 0;
}
GPOC = mask;
wvfState.waveformEnabled &= ~mask;
continue;
}
}
// Check for toggles
int32_t cyclesToGo = wave->nextServiceCycle - now;
if (cyclesToGo < 0) {
uint32_t nextEdgeCycles;
uint32_t desired = 0;
uint32_t *timeToUpdate;
wvfState.waveformState ^= mask;
if (wvfState.waveformState & mask) {
if (i == 16) {
GP16O = 1;
}
GPOS = mask;
if (wvfState.waveformToChange & mask) {
// Copy over next full-cycle timings
wave->timeHighCycles = wvfState.waveformNewHigh;
wave->desiredHighCycles = wvfState.waveformNewHigh;
wave->timeLowCycles = wvfState.waveformNewLow;
wave->desiredLowCycles = wvfState.waveformNewLow;
wave->lastEdge = 0;
wvfState.waveformToChange = 0;
}
if (wave->lastEdge) {
desired = wave->desiredLowCycles;
timeToUpdate = &wave->timeLowCycles;
}
nextEdgeCycles = wave->timeHighCycles;
} else {
if (i == 16) {
GP16O = 0;
}
GPOC = mask;
desired = wave->desiredHighCycles;
timeToUpdate = &wave->timeHighCycles;
nextEdgeCycles = wave->timeLowCycles;
}
if (desired) {
desired = adjust(desired);
int32_t err = desired - (now - wave->lastEdge);
if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal
err /= 2;
*timeToUpdate += err;
}
}
nextEdgeCycles = adjust(nextEdgeCycles);
wave->nextServiceCycle = now + nextEdgeCycles;
wave->lastEdge = now;
}
nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle);
}
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
uint32_t now = GetCycleCountIRQ();
int32_t cycleDeltaNextEvent = nextEventCycle - now;
int32_t cyclesLeftTimeout = timeoutCycle - now;
done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0);
} while (!done);
} // if (wvfState.waveformEnabled)
if (wvfState.timer1CB) {
nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB());
}
int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ();
if (nextEventCycles < MINIRQTIME) {
nextEventCycles = MINIRQTIME;
}
nextEventCycles -= DELTAIRQ;
// Do it here instead of global function to save time and because we know it's edge-IRQ
T1L = nextEventCycles >> (turbo ? 1 : 0);
}
};

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,7 +82,8 @@ extern void __pinMode(uint8_t pin, uint8_t mode) {
}
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
stopWaveform(pin);
stopWaveform(pin); // Disable any Tone or startWaveform on this pin
_stopPWM(pin); // and any analogWrites (PWM)
if(pin < 16){
if(val) GPOS = (1 << pin);
else GPOC = (1 << pin);
@ -130,8 +131,10 @@ typedef struct {
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
static uint32_t interrupt_reg = 0;
void ICACHE_RAM_ATTR interrupt_handler(void*)
void ICACHE_RAM_ATTR interrupt_handler(void *arg, void *frame)
{
(void) arg;
(void) frame;
uint32_t status = GPIE;
GPIEC = status;//clear them interrupts
uint32_t levels = GPI;

View File

@ -26,27 +26,28 @@
extern "C" {
static uint32_t analogMap = 0;
static int32_t analogScale = PWMRANGE;
static uint16_t analogFreq = 1000;
static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x
extern void __analogWriteRange(uint32_t range) {
if (range > 0) {
analogScale = range;
}
}
static uint32_t analogMap = 0;
static uint16_t analogFreq = 1000;
extern void __analogWriteFreq(uint32_t freq) {
if (freq < 100) {
analogFreq = 100;
} else if (freq > 40000) {
analogFreq = 40000;
} else if (freq > 60000) {
analogFreq = 60000;
} else {
analogFreq = freq;
}
_setPWMFreq(freq);
}
extern void __analogWrite(uint8_t pin, int val) {
analogWriteMode(pin, val, false);
}
extern void __analogWriteMode(uint8_t pin, int val, bool openDrain) {
if (pin > 16) {
return;
}
@ -57,23 +58,47 @@ extern void __analogWrite(uint8_t pin, int val) {
val = analogScale;
}
analogMap &= ~(1 << pin);
if (analogMap & 1UL << pin) {
// Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
// val: the duty cycle: between 0 (always off) and 255 (always on).
// So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH)
analogMap &= ~(1 << pin);
}
else {
if(openDrain) {
pinMode(pin, OUTPUT_OPEN_DRAIN);
} else {
pinMode(pin, OUTPUT);
}
}
uint32_t high = (analogPeriod * val) / analogScale;
uint32_t low = analogPeriod - high;
pinMode(pin, OUTPUT);
if (low == 0) {
digitalWrite(pin, HIGH);
} else if (high == 0) {
digitalWrite(pin, LOW);
} else {
if (startWaveformClockCycles(pin, high, low, 0)) {
analogMap |= (1 << pin);
}
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
int phaseReference = __builtin_ffs(analogMap) - 1;
if (_setPWM(pin, val, analogScale)) {
analogMap |= (1 << pin);
} else if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
analogMap |= (1 << pin);
}
}
extern void __analogWriteRange(uint32_t range) {
if ((range >= 15) && (range <= 65535)) {
analogScale = range;
}
}
extern void __analogWriteResolution(int res) {
if ((res >= 4) && (res <= 16)) {
analogScale = (1 << res) - 1;
}
}
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

@ -12,9 +12,6 @@ extern "C" {
#include <stdint.h>
#include <cont.h> // g_pcont declaration
extern bool timeshift64_is_set;
extern uint32_t sntp_real_timestamp;
bool can_yield();
void esp_yield();
void esp_schedule();
@ -31,9 +28,10 @@ uint32_t crc32 (const void* data, size_t length, uint32_t crc = 0xffffffff);
#include <functional>
using BoolCB = std::function<void(bool)>;
using TrivialCB = std::function<void()>;
void settimeofday_cb (TrivialCB&& cb);
void settimeofday_cb (const BoolCB& cb);
void settimeofday_cb (const TrivialCB& cb);
#endif

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
@ -22,6 +22,7 @@ void hexdump(const void *mem, uint32_t len, uint8_t cols);
extern "C" {
#endif
void __unhandled_exception(const char *str) __attribute__((noreturn));
void __panic_func(const char* file, int line, const char* func) __attribute__((noreturn));
#define panic() __panic_func(PSTR(__FILE__), __LINE__, __func__)

View File

@ -21,6 +21,9 @@
#ifndef ESP8266_PERI_H_INCLUDED
#define ESP8266_PERI_H_INCLUDED
// we expect mocking framework to provide these
#ifndef CORE_MOCK
#include "c_types.h"
#include "esp8266_undocumented.h"
@ -847,4 +850,6 @@ extern volatile uint32_t* const esp8266_gpioToFn[16];
**/
#define RANDOM_REG32 ESP8266_DREG(0x20E44)
#endif // ifndef CORE_MOCK
#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

@ -28,141 +28,27 @@ extern "C" {
#include "c_types.h"
#include "spi_flash.h"
}
/*
spi_flash_read function requires flash address to be aligned on word boundary.
We take care of this by reading first and last words separately and memcpy
relevant bytes into result buffer.
alignment: 012301230123012301230123
bytes requested: -------***********------
read directly: --------xxxxxxxx--------
read pre: ----aaaa----------------
read post: ----------------bbbb----
alignedBegin: ^
alignedEnd: ^
*/
int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
optimistic_yield(10000);
uint32_t result = FLASH_HAL_OK;
uint32_t alignedBegin = (addr + 3) & (~3);
uint32_t alignedEnd = (addr + size) & (~3);
if (alignedEnd < alignedBegin) {
alignedEnd = alignedBegin;
// We use flashRead overload that handles proper alignment
if (ESP.flashRead(addr, dst, size)) {
return FLASH_HAL_OK;
} else {
return FLASH_HAL_READ_ERROR;
}
if (addr < alignedBegin) {
uint32_t nb = alignedBegin - addr;
uint32_t tmp;
if (!ESP.flashRead(alignedBegin - 4, &tmp, 4)) {
DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
__LINE__, addr, size, alignedBegin, alignedEnd);
return FLASH_HAL_READ_ERROR;
}
memcpy(dst, ((uint8_t*) &tmp) + 4 - nb, nb);
}
if (alignedEnd != alignedBegin) {
if (!ESP.flashRead(alignedBegin, (uint32_t*) (dst + alignedBegin - addr),
alignedEnd - alignedBegin)) {
DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
__LINE__, addr, size, alignedBegin, alignedEnd);
return FLASH_HAL_READ_ERROR;
}
}
if (addr + size > alignedEnd) {
uint32_t nb = addr + size - alignedEnd;
uint32_t tmp;
if (!ESP.flashRead(alignedEnd, &tmp, 4)) {
DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
__LINE__, addr, size, alignedBegin, alignedEnd);
return FLASH_HAL_READ_ERROR;
}
memcpy(dst + size - nb, &tmp, nb);
}
return result;
}
/*
Like spi_flash_read, spi_flash_write has a requirement for flash address to be
aligned. However it also requires RAM address to be aligned as it reads data
in 32-bit words. Flash address (mis-)alignment is handled much the same way
as for reads, but for RAM alignment we have to copy data into a temporary
buffer. The size of this buffer is a tradeoff between number of writes required
and amount of stack required. This is chosen to be 512 bytes here, but might
be adjusted in the future if there are good reasons to do so.
*/
static const int UNALIGNED_WRITE_BUFFER_SIZE = 512;
int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) {
optimistic_yield(10000);
uint32_t alignedBegin = (addr + 3) & (~3);
uint32_t alignedEnd = (addr + size) & (~3);
if (alignedEnd < alignedBegin) {
alignedEnd = alignedBegin;
// We use flashWrite overload that handles proper alignment
if (ESP.flashWrite(addr, src, size)) {
return FLASH_HAL_OK;
} else {
return FLASH_HAL_WRITE_ERROR;
}
if (addr < alignedBegin) {
uint32_t ofs = alignedBegin - addr;
uint32_t nb = (size < ofs) ? size : ofs;
uint8_t tmp[4] __attribute__((aligned(4))) = {0xff, 0xff, 0xff, 0xff};
memcpy(tmp + 4 - ofs, src, nb);
if (!ESP.flashWrite(alignedBegin - 4, (uint32_t*) tmp, 4)) {
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
__LINE__, addr, size, alignedBegin, alignedEnd);
return FLASH_HAL_WRITE_ERROR;
}
}
if (alignedEnd != alignedBegin) {
uint32_t* srcLeftover = (uint32_t*) (src + alignedBegin - addr);
uint32_t srcAlign = ((uint32_t) srcLeftover) & 3;
if (!srcAlign) {
if (!ESP.flashWrite(alignedBegin, (uint32_t*) srcLeftover,
alignedEnd - alignedBegin)) {
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
__LINE__, addr, size, alignedBegin, alignedEnd);
return FLASH_HAL_WRITE_ERROR;
}
}
else {
uint8_t buf[UNALIGNED_WRITE_BUFFER_SIZE];
for (uint32_t sizeLeft = alignedEnd - alignedBegin; sizeLeft; ) {
size_t willCopy = std::min(sizeLeft, sizeof(buf));
memcpy(buf, srcLeftover, willCopy);
if (!ESP.flashWrite(alignedBegin, (uint32_t*) buf, willCopy)) {
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
__LINE__, addr, size, alignedBegin, alignedEnd);
return FLASH_HAL_WRITE_ERROR;
}
sizeLeft -= willCopy;
srcLeftover += willCopy;
alignedBegin += willCopy;
}
}
}
if (addr + size > alignedEnd) {
uint32_t nb = addr + size - alignedEnd;
uint32_t tmp = 0xffffffff;
memcpy(&tmp, src + size - nb, nb);
if (!ESP.flashWrite(alignedEnd, &tmp, 4)) {
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
__LINE__, addr, size, alignedBegin, alignedEnd);
return FLASH_HAL_WRITE_ERROR;
}
}
return FLASH_HAL_OK;
}
int32_t flash_hal_erase(uint32_t addr, uint32_t size) {

View File

@ -31,6 +31,11 @@ static bool ICACHE_RAM_ATTR __gdb_no_op()
return false;
}
// To save space, don't create a dummy no-op for each GCC, just point to the no-op
// Need to turn off GCC's checking of parameter types or we'll get many warnings
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattribute-alias"
#pragma GCC diagnostic ignored "-Wmissing-attributes"
void gdb_init(void) __attribute__ ((weak, alias("__gdb_no_op")));
void gdb_do_break(void) __attribute__ ((weak, alias("__gdb_no_op")));
bool gdb_present(void) __attribute__ ((weak, alias("__gdb_no_op")));
@ -40,5 +45,6 @@ bool gdbstub_has_uart_isr_control(void) __attribute__ ((weak, alias("__gdb_no_op
void gdbstub_set_uart_isr_callback(void (*func)(void*, uint8_t), void* arg) __attribute__ ((weak, alias("__gdb_no_op")));
void gdbstub_write_char(char c) __attribute__ ((weak, alias("__gdb_no_op")));
void gdbstub_write(const char* buf, size_t size) __attribute__ ((weak, alias("__gdb_no_op")));
#pragma GCC diagnostic pop
};

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,15 +21,17 @@ 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
#elif defined(DEBUG_ESP_OOM)
#elif defined(DEBUG_ESP_OOM) || defined(UMM_INTEGRITY_CHECK)
#define UMM_MALLOC(s) umm_malloc(s)
#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
@ -164,7 +175,7 @@ void ICACHE_RAM_ATTR print_loc(size_t size, const char* file, int line)
if (inISR && (uint32_t)file >= 0x40200000) {
DEBUG_HEAP_PRINTF("File: %p", file);
} else if (!inISR && (uint32_t)file >= 0x40200000) {
char buf[ets_strlen(file)] __attribute__ ((aligned(4)));
char buf[ets_strlen(file) + 1] __attribute__((aligned(4)));
ets_strcpy(buf, file);
DEBUG_HEAP_PRINTF(buf);
} else {
@ -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

@ -1,73 +1,12 @@
/*
i2s.h - Software I2S library for esp8266
// This include file is a hack to ensure backward compatibility with
// pre 3.0.0 versions of the core. There was a *lowercase* "i2s.h"
// header which was in this directory, now renamed to "core_esp82i66s.h"
// But, the I2S class has a header, "I2S.h" in uppercase. On Linux
// the two names are different, but on Windows it's case-insensitive
// so the names conflict.
//
// Avoid the issue by preserving the old i2s.h file and have it redirect
// to I2S.h which will give the ESP8266-specific functions as well as
// the generic I2S class.
Copyright (c) 2015 Hristo Gochkov. 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
*/
#ifndef I2S_h
#define I2S_h
/*
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_begin()
- Call i2s_set_rate() with the sample rate you want.
- Generate sound and call i2s_write_sample() with 32-bit samples.
The 32bit samples basically are 2 16-bit signed values (the analog values for
the left and right channel) concatenated as (Rout<<16)+Lout
i2s_write_sample will block when you're sending data too quickly, so you can just
generate and push data as fast as you can and i2s_write_sample will regulate the
speed.
*/
#ifdef __cplusplus
extern "C" {
#endif
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
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
float i2s_get_real_rate();//The actual Sample Rate on output
bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full)
bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead
bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs.
bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow)
bool i2s_is_empty();//returns true if DMA is empty (underflow)
bool i2s_rx_is_full();
bool i2s_rx_is_empty();
uint16_t i2s_available();// returns the number of samples than can be written before blocking
uint16_t i2s_rx_available();// returns the number of samples than can be written before blocking
void i2s_set_callback(void (*callback) (void));
void i2s_rx_set_callback(void (*callback) (void));
// writes a buffer of frames into the DMA memory, returns the amount of frames written
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
uint16_t i2s_write_buffer_mono(int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer_mono_nb(int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer(int16_t *frames, uint16_t frame_count);
uint16_t i2s_write_buffer_nb(int16_t *frames, uint16_t frame_count);
#ifdef __cplusplus
}
#endif
#endif
#include "../../libraries/I2S/src/I2S.h"

View File

@ -127,6 +127,7 @@ void _exit(int status) {
abort();
}
int atexit(void (*func)()) __attribute__((weak));
int atexit(void (*func)()) {
(void) func;
return 0;

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

@ -1,133 +0,0 @@
/*
* sntp-lwip2.c - ESP8266-specific functions for SNTP and lwIP-v2
* Copyright (c) 2015 Espressif (license is tools/sdk/lwip/src/core/sntp.c's)
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
*
* History:
* This code is extracted from lwip1.4-espressif's sntp.c
* which is a patched version of the original lwip1's sntp.
* (check the mix-up in tools/sdk/lwip/src/core/sntp.c)
* It is moved here as-is and cleaned for maintainability and
* because it does not belong to lwip.
*
* TODOs:
* settimeofday(): handle tv->tv_usec
* sntp_mktm_r(): review, fix DST handling (this one is currently untouched from lwip-1.4)
* implement adjtime()
*/
#include <lwip/init.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <osapi.h>
#include <os_type.h>
#include "coredecls.h"
#include "Schedule.h"
static TrivialCB _settimeofday_cb;
void settimeofday_cb (TrivialCB&& cb)
{
_settimeofday_cb = std::move(cb);
}
void settimeofday_cb (const TrivialCB& cb)
{
_settimeofday_cb = cb;
}
extern "C" {
#if LWIP_VERSION_MAJOR == 1
#include <pgmspace.h>
static const char stod14[] PROGMEM = "settimeofday() can't set time!\n";
bool sntp_set_timezone(sint8 timezone);
bool sntp_set_timezone_in_seconds(int32_t timezone)
{
return sntp_set_timezone((sint8)(timezone/(60*60))); //TODO: move this to the same file as sntp_set_timezone() in lwip1.4, and implement correctly over there.
}
void sntp_set_daylight(int daylight);
int settimeofday(const struct timeval* tv, const struct timezone* tz)
{
if (tz) /*before*/
{
sntp_set_timezone_in_seconds(tz->tz_minuteswest * 60);
// apparently tz->tz_dsttime is a bitfield and should not be further used (cf man)
sntp_set_daylight(0);
}
if (tv) /* after*/
{
// can't call lwip1.4's static sntp_set_system_time()
os_printf(stod14);
// reset time subsystem
timeshift64_is_set = false;
return -1;
}
return 0;
}
#endif // lwip 1.4 only
#if LWIP_VERSION_MAJOR == 2
#include <lwip/apps/sntp.h>
uint32_t sntp_real_timestamp = 0;
LOCAL os_timer_t sntp_timer;
void ICACHE_RAM_ATTR sntp_time_inc (void)
{
sntp_real_timestamp++;
}
int settimeofday(const struct timeval* tv, const struct timezone* tz)
{
if (tz || !tv)
// tz is obsolete (cf. man settimeofday)
return EINVAL;
// reset time subsystem
tune_timeshift64(tv->tv_sec * 1000000ULL + tv->tv_usec);
sntp_real_timestamp = tv->tv_sec;
os_timer_disarm(&sntp_timer);
os_timer_setfn(&sntp_timer, (os_timer_func_t *)sntp_time_inc, NULL);
os_timer_arm(&sntp_timer, 1000, 1);
if (_settimeofday_cb)
schedule_recurrent_function_us([](){ _settimeofday_cb(); return false; }, 0);
return 0;
}
#endif // lwip2 only
};

View File

@ -1,6 +0,0 @@
#ifndef __sntp_lwip2_h__
#define __sntp_lwip2_h__
#include <coredecls.h>
#endif

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,14 +36,21 @@ 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);
const char* strrstr(const char*__restrict p_pcString,
const char*__restrict p_pcPattern);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -14,19 +14,27 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* reworked for newlib and lwIP-v2:
* time source is SNTP/settimeofday()
* system time is micros64() / NONOS-SDK's system_get_time()
* synchronisation of the two through timeshift64
*/
#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 "sntp.h"
#include "coredecls.h"
#include <errno.h>
#include <sntp.h> // nonos-sdk
#include <coredecls.h>
#include <Schedule.h>
#include <Arduino.h> // configTime()
#include "sntp-lwip2.h"
extern "C" {
#ifndef _TIMEVAL_DEFINED
@ -42,16 +50,11 @@ extern struct tm* sntp_localtime(const time_t *clock);
extern uint64_t micros64();
extern void sntp_set_daylight(int daylight);
// time gap in seconds from 01.01.1900 (NTP time) to 01.01.1970 (UNIX time)
#define DIFF1900TO1970 2208988800UL
bool timeshift64_is_set = false;
static uint64_t timeshift64 = 0;
void tune_timeshift64 (uint64_t now_us)
{
timeshift64 = now_us - micros64();
timeshift64_is_set = true;
}
static void setServer(int id, const char* name_or_ip)
@ -73,14 +76,8 @@ int clock_gettime(clockid_t unused, struct timespec *tp)
return 0;
}
#if LWIP_VERSION_MAJOR == 1
// hack for espressif time management included in patched lwIP-1.4
#define sntp_real_timestamp sntp_get_current_timestamp()
#endif
#if LWIP_VERSION_MAJOR != 1
// backport Espressif api
///////////////////////////////////////////
// backport legacy nonos-sdk Espressif api
bool sntp_set_timezone_in_seconds (int32_t timezone_sec)
{
@ -100,18 +97,20 @@ char* sntp_get_real_time(time_t t)
uint32 sntp_get_current_timestamp()
{
return sntp_real_timestamp;
return time(nullptr);
}
#endif
// backport legacy nonos-sdk Espressif api
///////////////////////////////////////////
time_t time(time_t * t)
{
time_t currentTime_s = (micros64() + timeshift64) / 1000000ULL;
if (t)
{
*t = sntp_real_timestamp;
*t = currentTime_s;
}
return sntp_real_timestamp;
return currentTime_s;
}
int _gettimeofday_r(struct _reent* unused, struct timeval *tp, void *tzp)
@ -120,8 +119,6 @@ int _gettimeofday_r(struct _reent* unused, struct timeval *tp, void *tzp)
(void) tzp;
if (tp)
{
if (!timeshift64_is_set)
tune_timeshift64(sntp_real_timestamp * 1000000ULL);
uint64_t currentTime_us = timeshift64 + micros64();
tp->tv_sec = currentTime_us / 1000000ULL;
tp->tv_usec = currentTime_us % 1000000ULL;
@ -133,6 +130,8 @@ int _gettimeofday_r(struct _reent* unused, struct timeval *tp, void *tzp)
void configTime(int timezone_sec, int daylightOffset_sec, const char* server1, const char* server2, const char* server3)
{
sntp_stop();
// There is no way to tell when DST starts or stop with this API
// So DST is always integrated in TZ
// The other API should be preferred
@ -155,7 +154,7 @@ void configTime(int timezone_sec, int daylightOffset_sec, const char* server1, c
newlib inspection and internal structure hacking
(no sprintf, no sscanf, -7584 flash bytes):
***/
*** hack starts here: ***/
static char gmt[] = "GMT";
@ -178,12 +177,14 @@ void configTime(int timezone_sec, int daylightOffset_sec, const char* server1, c
tzr->offset = -_timezone;
}
/*** end of hack ***/
// sntp servers
setServer(0, server1);
setServer(1, server2);
setServer(2, server3);
/*** end of posix replacement ***/
sntp_init();
}
void setTZ(const char* tz){
@ -206,3 +207,49 @@ void configTime(const char* tz, const char* server1, const char* server2, const
sntp_init();
}
static BoolCB _settimeofday_cb;
void settimeofday_cb (const TrivialCB& cb)
{
_settimeofday_cb = [cb](bool sntp) { (void)sntp; cb(); };
}
void settimeofday_cb (const BoolCB& cb)
{
_settimeofday_cb = cb;
}
extern "C" {
#include <lwip/apps/sntp.h>
int settimeofday(const struct timeval* tv, const struct timezone* tz)
{
bool from_sntp;
if (tz == (struct timezone*)0xFeedC0de)
{
// This special constant is used by lwip2/SNTP calling
// settimeofday(sntp-time, 0xfeedc0de), secretly using the
// obsolete-but-yet-still-there `tz` field.
// It allows to avoid duplicating this function and inform user
// about the source time change.
tz = nullptr;
from_sntp = true;
}
else
from_sntp = false;
if (tz || !tv)
// tz is obsolete (cf. man settimeofday)
return EINVAL;
// reset time subsystem
tune_timeshift64(tv->tv_sec * 1000000ULL + tv->tv_usec);
if (_settimeofday_cb)
schedule_recurrent_function_us([from_sntp](){ _settimeofday_cb(from_sntp); return false; }, 0);
return 0;
}
};

View File

@ -371,8 +371,9 @@ uart_get_rx_buffer_size(uart_t* uart)
// The default ISR handler called when GDB is not enabled
void ICACHE_RAM_ATTR
uart_isr(void * arg)
uart_isr(void * arg, void * frame)
{
(void) frame;
uart_t* uart = (uart_t*)arg;
uint32_t usis = USIS(uart->uart_nr);
@ -505,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

@ -15,7 +15,7 @@ UMM_TIME_STATS time_stats = {
#ifdef UMM_INFO
{0xFFFFFFFF, 0U, 0U, 0U},
#endif
#ifdef UMM_POISON_CHECK
#if defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE)
{0xFFFFFFFF, 0U, 0U, 0U},
#endif
#ifdef UMM_INTEGRITY_CHECK
@ -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" );
@ -206,7 +209,7 @@ int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) {
the PROGMEM address must be word (4 bytes) aligned. The destination
address for ets_memcpy must also be word-aligned.
*/
char ram_buf[ets_strlen(fmt)] __attribute__ ((aligned(4)));
char ram_buf[ets_strlen(fmt) + 1] __attribute__((aligned(4)));
ets_strcpy(ram_buf, fmt);
va_list argPtr;
va_start(argPtr, fmt);
@ -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 :-) */
@ -647,9 +921,21 @@ void *umm_realloc( void *ptr, size_t size ) {
DBGLOG_DEBUG( "realloc blocks %d blockSize %d nextBlockSize %d prevBlockSize %d\n", blocks, blockSize, nextBlockSize, prevBlockSize );
//C This has changed need to review and see if UMM_REALLOC_MINIMIZE_COPY really
//C is that any more. or is it equivalent or close enough to my defrag
//C - mjh
//C With each upstream update this section should be reevaluated.
/*C
*
* The `#if defined(UMM_REALLOC_MINIMIZE_COPY)` section tracks the content of
* the upstream with some local macros added. Back when I made my 1st update to
* umm_malloc PR, I found the upstream had been refactored and removed the
* defragmenting properties that were originally present. It took some looking
* to see the logic, it didn't have any comments to make it stand out.
*
* I added the `#elif defined(UMM_REALLOC_DEFRAG)` to recreate and preserve the
* defragmenting functionality that was lost. This is the default build option
* we have set in `umm_malloc_cfg.h`. I have not done any structured testing to
* confirm; however, I think this to be the best option when considering the
* amount of reallocates that can occur with the Strings library.
*/
#if defined(UMM_REALLOC_MINIMIZE_COPY)
/*
* Ok, now that we're here we know how many blocks we want and the current
@ -701,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;
@ -720,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;
@ -743,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.
@ -786,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) {
@ -795,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;
@ -816,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.
@ -847,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.
@ -870,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();

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