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:
commit
7fbf620ab6
271
.github/workflows/pull-request.yml
vendored
Normal file
271
.github/workflows/pull-request.yml
vendored
Normal 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
|
56
.github/workflows/release-to-publish.yml
vendored
Normal file
56
.github/workflows/release-to-publish.yml
vendored
Normal 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
|
||||
|
40
.github/workflows/tag-to-draft-release.yml
vendored
Normal file
40
.github/workflows/tag-to-draft-release.yml
vendored
Normal 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
5
.gitmodules
vendored
@ -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
|
||||
|
162
.travis.yml
162
.travis.yml
@ -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
|
10
README.md
10
README.md
@ -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 [](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
|
||||
[](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
12215
boards.txt
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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,10 +178,27 @@ int copy_raw(const uint32_t src_addr,
|
||||
return 9;
|
||||
}
|
||||
} else {
|
||||
// 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;
|
||||
daddr += buffer_size;
|
||||
left -= 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.
@ -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)
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
@ -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));
|
||||
#else
|
||||
uint32_t getCycleCount();
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef CORE_MOCK
|
||||
|
||||
uint32_t EspClass::getCycleCount()
|
||||
#if !defined(CORE_MOCK)
|
||||
inline uint32_t getCycleCount() __attribute__((always_inline))
|
||||
{
|
||||
return esp_get_cycle_count();
|
||||
}
|
||||
|
||||
#else
|
||||
uint32_t getCycleCount();
|
||||
#endif // !defined(CORE_MOCK)
|
||||
/**
|
||||
* @brief Installs VM exception handler to support External memory (Experimental)
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void enableVM();
|
||||
/**
|
||||
* @brief Push current Heap selection and set Heap selection to DRAM.
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void setDramHeap();
|
||||
/**
|
||||
* @brief Push current Heap selection and set Heap selection to IRAM.
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void setIramHeap();
|
||||
/**
|
||||
* @brief Push current Heap selection and set Heap selection to External. (Experimental)
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void setExternalHeap();
|
||||
/**
|
||||
* @brief Restores Heap selection back to value present when
|
||||
* setDramHeap, setIramHeap, or setExternalHeap was called.
|
||||
*
|
||||
* @param none
|
||||
* @return none
|
||||
*/
|
||||
void resetHeap();
|
||||
private:
|
||||
#ifdef UMM_HEAP_EXTERNAL
|
||||
bool vmEnabled = false;
|
||||
#endif
|
||||
/**
|
||||
* @brief Replaces @a byteCount bytes of a 4 byte block on flash
|
||||
*
|
||||
* @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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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); }
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
64
cores/esp8266/LwipDhcpServer-NonOS.cpp
Normal file
64
cores/esp8266/LwipDhcpServer-NonOS.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
lwIPDhcpServer-NonOS.cpp - DHCP server wrapper
|
||||
|
||||
Copyright (c) 2020 esp8266 arduino. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
// STARTS/STOPS DHCP SERVER ON WIFI AP INTERFACE
|
||||
// these functions must exists as-is with "C" interface,
|
||||
// nonos-sdk calls them at boot time and later
|
||||
|
||||
#include <lwip/init.h> // LWIP_VERSION
|
||||
|
||||
#include <lwip/netif.h>
|
||||
#include "LwipDhcpServer.h"
|
||||
|
||||
extern netif netif_git[2];
|
||||
|
||||
// global DHCP instance for softAP interface
|
||||
DhcpServer dhcpSoftAP(&netif_git[SOFTAP_IF]);
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
void dhcps_start(struct ip_info *info, netif* apnetif)
|
||||
{
|
||||
// apnetif is esp interface, replaced by lwip2's
|
||||
// netif_git[SOFTAP_IF] interface in constructor
|
||||
(void)apnetif;
|
||||
|
||||
#if 0
|
||||
// can't use C++ now, global ctors are not initialized yet
|
||||
dhcpSoftAP.begin(info);
|
||||
#else
|
||||
(void)info;
|
||||
// initial version: emulate nonos-sdk in DhcpServer class before
|
||||
// trying to change legacy behavor
|
||||
// `fw_has_started_softap_dhcps` will be read in DhcpServer::DhcpServer
|
||||
// which is called when c++ ctors are initialized, specifically
|
||||
// dhcpSoftAP intialized with AP interface number above.
|
||||
fw_has_started_softap_dhcps = 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void dhcps_stop()
|
||||
{
|
||||
dhcpSoftAP.end();
|
||||
}
|
||||
|
||||
} // extern "C"
|
1606
cores/esp8266/LwipDhcpServer.cpp
Normal file
1606
cores/esp8266/LwipDhcpServer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
124
cores/esp8266/LwipDhcpServer.h
Normal file
124
cores/esp8266/LwipDhcpServer.h
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
lwIPDhcpServer.h - DHCP server
|
||||
|
||||
Copyright (c) 2016 Espressif. All rights reserved.
|
||||
Copyright (c) 2020 esp8266 arduino. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
// original sources (no license provided)
|
||||
// ESP8266_NONOS_SDK/third_party/lwip/app/dhcpserver.c
|
||||
// ESP8266_NONOS_SDK/third_party/include/lwip/app/dhcpserver.h
|
||||
*/
|
||||
|
||||
// lwIPDhcpServer.{cc,h} encapsulate original nonos-sdk dhcp server
|
||||
// nearly as-is. This is an initial version to guaranty legacy behavior
|
||||
// with same default values.
|
||||
|
||||
#ifndef __DHCPS_H__
|
||||
#define __DHCPS_H__
|
||||
|
||||
#include <lwip/init.h> // LWIP_VERSION
|
||||
|
||||
class DhcpServer
|
||||
{
|
||||
public:
|
||||
|
||||
DhcpServer(netif* netif);
|
||||
~DhcpServer();
|
||||
|
||||
void setDns(int num, const ipv4_addr_t* dns);
|
||||
|
||||
bool begin(ip_info* info);
|
||||
void end();
|
||||
bool isRunning();
|
||||
|
||||
// this is the C interface encapsulated in a class
|
||||
// (originally dhcpserver.c in lwIP-v1.4 in NonOS-SDK)
|
||||
// (not changing everything at once)
|
||||
// the API below is subject to change
|
||||
|
||||
// legacy public C structure and API to eventually turn into C++
|
||||
|
||||
void init_dhcps_lease(uint32 ip);
|
||||
bool set_dhcps_lease(struct dhcps_lease *please);
|
||||
bool get_dhcps_lease(struct dhcps_lease *please);
|
||||
bool set_dhcps_offer_option(uint8 level, void* optarg);
|
||||
bool set_dhcps_lease_time(uint32 minute);
|
||||
bool reset_dhcps_lease_time(void);
|
||||
uint32 get_dhcps_lease_time(void);
|
||||
bool add_dhcps_lease(uint8 *macaddr);
|
||||
|
||||
void dhcps_set_dns(int num, const ipv4_addr_t* dns);
|
||||
|
||||
protected:
|
||||
|
||||
// legacy C structure and API to eventually turn into C++
|
||||
|
||||
typedef struct _list_node
|
||||
{
|
||||
void *pnode;
|
||||
struct _list_node *pnext;
|
||||
} list_node;
|
||||
|
||||
void node_insert_to_list(list_node **phead, list_node* pinsert);
|
||||
void node_remove_from_list(list_node **phead, list_node* pdelete);
|
||||
uint8_t* add_msg_type(uint8_t *optptr, uint8_t type);
|
||||
uint8_t* add_offer_options(uint8_t *optptr);
|
||||
uint8_t* add_end(uint8_t *optptr);
|
||||
void create_msg(struct dhcps_msg *m);
|
||||
void send_offer(struct dhcps_msg *m);
|
||||
void send_nak(struct dhcps_msg *m);
|
||||
void send_ack(struct dhcps_msg *m);
|
||||
uint8_t parse_options(uint8_t *optptr, sint16_t len);
|
||||
sint16_t parse_msg(struct dhcps_msg *m, u16_t len);
|
||||
static void S_handle_dhcp(void *arg,
|
||||
struct udp_pcb *pcb,
|
||||
struct pbuf *p,
|
||||
const ip_addr_t *addr,
|
||||
uint16_t port);
|
||||
void handle_dhcp(
|
||||
struct udp_pcb *pcb,
|
||||
struct pbuf *p,
|
||||
const ip_addr_t *addr,
|
||||
uint16_t port);
|
||||
void kill_oldest_dhcps_pool(void);
|
||||
void dhcps_coarse_tmr(void); // CURRENTLY NOT CALLED
|
||||
void dhcps_client_leave(u8 *bssid, struct ipv4_addr *ip, bool force);
|
||||
uint32 dhcps_client_update(u8 *bssid, struct ipv4_addr *ip);
|
||||
|
||||
netif* _netif;
|
||||
|
||||
struct udp_pcb *pcb_dhcps;
|
||||
ip_addr_t broadcast_dhcps;
|
||||
struct ipv4_addr server_address;
|
||||
struct ipv4_addr client_address;
|
||||
struct ipv4_addr dns_address;
|
||||
uint32 dhcps_lease_time;
|
||||
|
||||
struct dhcps_lease dhcps_lease;
|
||||
list_node *plist;
|
||||
uint8 offer;
|
||||
bool renew;
|
||||
|
||||
static const uint32 magic_cookie;
|
||||
};
|
||||
|
||||
// SoftAP DHCP server always exists and is started on boot
|
||||
extern DhcpServer dhcpSoftAP;
|
||||
extern "C" int fw_has_started_softap_dhcps;
|
||||
|
||||
#endif // __DHCPS_H__
|
165
cores/esp8266/LwipIntf.cpp
Normal file
165
cores/esp8266/LwipIntf.cpp
Normal 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
49
cores/esp8266/LwipIntf.h
Normal 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
|
44
cores/esp8266/LwipIntfCB.cpp
Normal file
44
cores/esp8266/LwipIntfCB.cpp
Normal 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
386
cores/esp8266/LwipIntfDev.h
Normal file
@ -0,0 +1,386 @@
|
||||
|
||||
#ifndef _LWIPINTFDEV_H
|
||||
#define _LWIPINTFDEV_H
|
||||
|
||||
// TODO:
|
||||
// remove all Serial.print
|
||||
// unchain pbufs
|
||||
|
||||
#include <netif/ethernet.h>
|
||||
#include <lwip/init.h>
|
||||
#include <lwip/netif.h>
|
||||
#include <lwip/etharp.h>
|
||||
#include <lwip/dhcp.h>
|
||||
#include <lwip/apps/sntp.h>
|
||||
|
||||
#include <user_interface.h> // wifi_get_macaddr()
|
||||
|
||||
#include "SPI.h"
|
||||
#include "Schedule.h"
|
||||
#include "LwipIntf.h"
|
||||
#include "wl_definitions.h"
|
||||
|
||||
#ifndef DEFAULT_MTU
|
||||
#define DEFAULT_MTU 1500
|
||||
#endif
|
||||
|
||||
template <class RawDev>
|
||||
class LwipIntfDev: public LwipIntf, public RawDev
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
LwipIntfDev(int8_t cs = SS, SPIClass& spi = SPI, int8_t intr = -1):
|
||||
RawDev(cs, spi, intr),
|
||||
_mtu(DEFAULT_MTU),
|
||||
_intrPin(intr),
|
||||
_started(false),
|
||||
_default(false)
|
||||
{
|
||||
memset(&_netif, 0, sizeof(_netif));
|
||||
}
|
||||
|
||||
boolean config(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3, const IPAddress& dns2);
|
||||
|
||||
// default mac-address is inferred from esp8266's STA interface
|
||||
boolean begin(const uint8_t *macAddress = nullptr, const uint16_t mtu = DEFAULT_MTU);
|
||||
|
||||
const netif* getNetIf() const
|
||||
{
|
||||
return &_netif;
|
||||
}
|
||||
|
||||
IPAddress localIP() const
|
||||
{
|
||||
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr)));
|
||||
}
|
||||
IPAddress subnetMask() const
|
||||
{
|
||||
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.netmask)));
|
||||
}
|
||||
IPAddress gatewayIP() const
|
||||
{
|
||||
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.gw)));
|
||||
}
|
||||
|
||||
void setDefault();
|
||||
|
||||
bool connected()
|
||||
{
|
||||
return !!ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr));
|
||||
}
|
||||
|
||||
// ESP8266WiFi API compatibility
|
||||
|
||||
wl_status_t status();
|
||||
|
||||
protected:
|
||||
|
||||
err_t netif_init();
|
||||
void netif_status_callback();
|
||||
|
||||
static err_t netif_init_s(netif* netif);
|
||||
static err_t linkoutput_s(netif *netif, struct pbuf *p);
|
||||
static void netif_status_callback_s(netif* netif);
|
||||
|
||||
// called on a regular basis or on interrupt
|
||||
err_t handlePackets();
|
||||
|
||||
// members
|
||||
|
||||
netif _netif;
|
||||
|
||||
uint16_t _mtu;
|
||||
int8_t _intrPin;
|
||||
uint8_t _macAddress[6];
|
||||
bool _started;
|
||||
bool _default;
|
||||
|
||||
};
|
||||
|
||||
template <class RawDev>
|
||||
boolean LwipIntfDev<RawDev>::config(const IPAddress& localIP, const IPAddress& gateway, const IPAddress& netmask, const IPAddress& dns1, const IPAddress& dns2)
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
DEBUGV("LwipIntfDev: use config() then begin()\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
IPAddress realGateway, realNetmask, realDns1;
|
||||
if (!ipAddressReorder(localIP, gateway, netmask, dns1, realGateway, realNetmask, realDns1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ip4_addr_set_u32(ip_2_ip4(&_netif.ip_addr), localIP.v4());
|
||||
ip4_addr_set_u32(ip_2_ip4(&_netif.gw), realGateway.v4());
|
||||
ip4_addr_set_u32(ip_2_ip4(&_netif.netmask), realNetmask.v4());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
boolean LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu)
|
||||
{
|
||||
if (mtu)
|
||||
{
|
||||
_mtu = mtu;
|
||||
}
|
||||
|
||||
if (macAddress)
|
||||
{
|
||||
memcpy(_macAddress, macAddress, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
_netif.num = 2;
|
||||
for (auto n = netif_list; n; n = n->next)
|
||||
if (n->num >= _netif.num)
|
||||
{
|
||||
_netif.num = n->num + 1;
|
||||
}
|
||||
|
||||
#if 1
|
||||
// forge a new mac-address from the esp's wifi sta one
|
||||
// I understand this is cheating with an official mac-address
|
||||
wifi_get_macaddr(STATION_IF, (uint8*)_macAddress);
|
||||
#else
|
||||
// https://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
|
||||
memset(_macAddress, 0, 6);
|
||||
_macAddress[0] = 0xEE;
|
||||
#endif
|
||||
_macAddress[3] += _netif.num; // alter base mac address
|
||||
_macAddress[0] &= 0xfe; // set as locally administered, unicast, per
|
||||
_macAddress[0] |= 0x02; // https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local
|
||||
}
|
||||
|
||||
if (!RawDev::begin(_macAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// setup lwIP netif
|
||||
|
||||
_netif.hwaddr_len = sizeof _macAddress;
|
||||
memcpy(_netif.hwaddr, _macAddress, sizeof _macAddress);
|
||||
|
||||
// due to netif_add() api: ...
|
||||
ip_addr_t ip_addr, netmask, gw;
|
||||
ip_addr_copy(ip_addr, _netif.ip_addr);
|
||||
ip_addr_copy(netmask, _netif.netmask);
|
||||
ip_addr_copy(gw, _netif.gw);
|
||||
|
||||
if (!netif_add(&_netif, ip_2_ip4(&ip_addr), ip_2_ip4(&netmask), ip_2_ip4(&gw), this, netif_init_s, ethernet_input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_netif.flags |= NETIF_FLAG_UP;
|
||||
|
||||
if (localIP().v4() == 0)
|
||||
{
|
||||
switch (dhcp_start(&_netif))
|
||||
{
|
||||
case ERR_OK:
|
||||
break;
|
||||
|
||||
case ERR_IF:
|
||||
return false;
|
||||
|
||||
default:
|
||||
netif_remove(&_netif);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_started = true;
|
||||
|
||||
if (_intrPin >= 0)
|
||||
{
|
||||
if (RawDev::interruptIsPossible())
|
||||
{
|
||||
//attachInterrupt(_intrPin, [&]() { this->handlePackets(); }, FALLING);
|
||||
}
|
||||
else
|
||||
{
|
||||
::printf((PGM_P)F("lwIP_Intf: Interrupt not implemented yet, enabling transparent polling\r\n"));
|
||||
_intrPin = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (_intrPin < 0 && !schedule_recurrent_function_us([&]()
|
||||
{
|
||||
this->handlePackets();
|
||||
return true;
|
||||
}, 100))
|
||||
{
|
||||
netif_remove(&_netif);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
wl_status_t LwipIntfDev<RawDev>::status()
|
||||
{
|
||||
return _started ? (connected() ? WL_CONNECTED : WL_DISCONNECTED) : WL_NO_SHIELD;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
err_t LwipIntfDev<RawDev>::linkoutput_s(netif *netif, struct pbuf *pbuf)
|
||||
{
|
||||
LwipIntfDev* ths = (LwipIntfDev*)netif->state;
|
||||
|
||||
if (pbuf->len != pbuf->tot_len || pbuf->next)
|
||||
{
|
||||
Serial.println("ERRTOT\r\n");
|
||||
}
|
||||
|
||||
uint16_t len = ths->sendFrame((const uint8_t*)pbuf->payload, pbuf->len);
|
||||
|
||||
#if PHY_HAS_CAPTURE
|
||||
if (phy_capture)
|
||||
{
|
||||
phy_capture(ths->_netif.num, (const char*)pbuf->payload, pbuf->len, /*out*/1, /*success*/len == pbuf->len);
|
||||
}
|
||||
#endif
|
||||
|
||||
return len == pbuf->len ? ERR_OK : ERR_MEM;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
err_t LwipIntfDev<RawDev>::netif_init_s(struct netif* netif)
|
||||
{
|
||||
return ((LwipIntfDev*)netif->state)->netif_init();
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
void LwipIntfDev<RawDev>::netif_status_callback_s(struct netif* netif)
|
||||
{
|
||||
((LwipIntfDev*)netif->state)->netif_status_callback();
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
err_t LwipIntfDev<RawDev>::netif_init()
|
||||
{
|
||||
_netif.name[0] = 'e';
|
||||
_netif.name[1] = '0' + _netif.num;
|
||||
_netif.mtu = _mtu;
|
||||
_netif.chksum_flags = NETIF_CHECKSUM_ENABLE_ALL;
|
||||
_netif.flags =
|
||||
NETIF_FLAG_ETHARP
|
||||
| NETIF_FLAG_IGMP
|
||||
| NETIF_FLAG_BROADCAST
|
||||
| NETIF_FLAG_LINK_UP;
|
||||
|
||||
// lwIP's doc: This function typically first resolves the hardware
|
||||
// address, then sends the packet. For ethernet physical layer, this is
|
||||
// usually lwIP's etharp_output()
|
||||
_netif.output = etharp_output;
|
||||
|
||||
// lwIP's doc: This function outputs the pbuf as-is on the link medium
|
||||
// (this must points to the raw ethernet driver, meaning: us)
|
||||
_netif.linkoutput = linkoutput_s;
|
||||
|
||||
_netif.status_callback = netif_status_callback_s;
|
||||
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
void LwipIntfDev<RawDev>::netif_status_callback()
|
||||
{
|
||||
if (connected())
|
||||
{
|
||||
if (_default)
|
||||
{
|
||||
netif_set_default(&_netif);
|
||||
}
|
||||
sntp_stop();
|
||||
sntp_init();
|
||||
}
|
||||
else if (netif_default == &_netif)
|
||||
{
|
||||
netif_set_default(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
err_t LwipIntfDev<RawDev>::handlePackets()
|
||||
{
|
||||
int pkt = 0;
|
||||
while (1)
|
||||
{
|
||||
if (++pkt == 10)
|
||||
// prevent starvation
|
||||
{
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
uint16_t tot_len = RawDev::readFrameSize();
|
||||
if (!tot_len)
|
||||
{
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
// from doc: use PBUF_RAM for TX, PBUF_POOL from RX
|
||||
// however:
|
||||
// PBUF_POOL can return chained pbuf (not in one piece)
|
||||
// and WiznetDriver does not have the proper API to deal with that
|
||||
// so in the meantime, we use PBUF_RAM instead which is currently
|
||||
// guarantying to deliver a continuous chunk of memory.
|
||||
// TODO: tweak the wiznet driver to allow copying partial chunk
|
||||
// of received data and use PBUF_POOL.
|
||||
pbuf* pbuf = pbuf_alloc(PBUF_RAW, tot_len, PBUF_RAM);
|
||||
if (!pbuf || pbuf->len < tot_len)
|
||||
{
|
||||
if (pbuf)
|
||||
{
|
||||
pbuf_free(pbuf);
|
||||
}
|
||||
RawDev::discardFrame(tot_len);
|
||||
return ERR_BUF;
|
||||
}
|
||||
|
||||
uint16_t len = RawDev::readFrameData((uint8_t*)pbuf->payload, tot_len);
|
||||
if (len != tot_len)
|
||||
{
|
||||
// tot_len is given by readFrameSize()
|
||||
// and is supposed to be honoured by readFrameData()
|
||||
// todo: ensure this test is unneeded, remove the print
|
||||
Serial.println("read error?\r\n");
|
||||
pbuf_free(pbuf);
|
||||
return ERR_BUF;
|
||||
}
|
||||
|
||||
err_t err = _netif.input(pbuf, &_netif);
|
||||
|
||||
#if PHY_HAS_CAPTURE
|
||||
if (phy_capture)
|
||||
{
|
||||
phy_capture(_netif.num, (const char*)pbuf->payload, tot_len, /*out*/0, /*success*/err == ERR_OK);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (err != ERR_OK)
|
||||
{
|
||||
pbuf_free(pbuf);
|
||||
return err;
|
||||
}
|
||||
// (else) allocated pbuf is now lwIP's responsibility
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
template <class RawDev>
|
||||
void LwipIntfDev<RawDev>::setDefault()
|
||||
{
|
||||
_default = true;
|
||||
if (connected())
|
||||
{
|
||||
netif_set_default(&_netif);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _LWIPINTFDEV_H
|
@ -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
|
||||
};
|
||||
|
||||
@ -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
|
||||
@ -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>;
|
||||
|
@ -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('-');
|
||||
int t = 0;
|
||||
if (base == 10 && n < 0) {
|
||||
t = print('-');
|
||||
n = -n;
|
||||
return printNumber(n, 10) + t;
|
||||
}
|
||||
return printNumber(n, 10);
|
||||
} else {
|
||||
return printNumber(n, base);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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.
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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\
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
@ -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,19 +142,9 @@ 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())
|
||||
@ -159,24 +171,30 @@ 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);
|
||||
@ -186,8 +204,7 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
||||
// 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,9 +216,9 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// /*********************************************/
|
||||
// /* Copy and Move */
|
||||
// /*********************************************/
|
||||
/*********************************************/
|
||||
/* Copy and Move */
|
||||
/*********************************************/
|
||||
|
||||
String &String::copy(const char *cstr, unsigned int length) {
|
||||
if (!reserve(length)) {
|
||||
@ -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);
|
||||
void String::move(String &rhs) noexcept {
|
||||
invalidate();
|
||||
sso = rhs.sso;
|
||||
rhs.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
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) {
|
||||
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,6 +353,16 @@ unsigned char String::concat(unsigned long num) {
|
||||
return concat(buf, strlen(buf));
|
||||
}
|
||||
|
||||
unsigned char String::concat(long long num) {
|
||||
char buf[2 + 3 * sizeof(long long)];
|
||||
return concat(buf, sprintf(buf, "%lld", num));
|
||||
}
|
||||
|
||||
unsigned char String::concat(unsigned long long num) {
|
||||
char buf[1 + 3 * sizeof(unsigned long long)];
|
||||
return concat(buf, sprintf(buf, "%llu", num));
|
||||
}
|
||||
|
||||
unsigned char String::concat(float num) {
|
||||
char buf[20];
|
||||
char *string = dtostrf(num, 4, 2, buf);
|
||||
@ -391,11 +376,14 @@ unsigned char String::concat(double num) {
|
||||
}
|
||||
|
||||
unsigned char String::concat(const __FlashStringHelper *str) {
|
||||
if (!str) return 0;
|
||||
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;
|
||||
@ -461,6 +449,20 @@ StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num) {
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, long long num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
a.invalidate();
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper &operator +(const StringSumHelper &lhs, float num) {
|
||||
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||
if (!a.concat(num))
|
||||
@ -475,17 +477,16 @@ StringSumHelper & operator +(const StringSumHelper &lhs, double num) {
|
||||
return a;
|
||||
}
|
||||
|
||||
StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs)
|
||||
{
|
||||
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()) {
|
||||
@ -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;
|
||||
@ -587,13 +588,9 @@ unsigned char String::endsWith(const String &s2) const {
|
||||
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())
|
||||
@ -629,13 +626,9 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind
|
||||
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())
|
||||
@ -646,33 +639,34 @@ int String::indexOf(char ch, unsigned int fromIndex) const {
|
||||
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())
|
||||
@ -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;
|
||||
|
@ -53,39 +53,47 @@ 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;
|
||||
}
|
||||
|
||||
@ -95,10 +103,10 @@ class String {
|
||||
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 =(String &&rval) noexcept;
|
||||
String &operator =(StringSumHelper &&rval) noexcept {
|
||||
return operator =((String &&)rval);
|
||||
}
|
||||
|
||||
// concatenate (works w/ built-in types)
|
||||
|
||||
@ -113,6 +121,8 @@ class String {
|
||||
unsigned char concat(unsigned int num);
|
||||
unsigned char concat(long num);
|
||||
unsigned char concat(unsigned long num);
|
||||
unsigned char concat(long long num);
|
||||
unsigned char concat(unsigned long long num);
|
||||
unsigned char concat(float num);
|
||||
unsigned char concat(double num);
|
||||
unsigned char concat(const __FlashStringHelper *str);
|
||||
@ -122,47 +132,55 @@ class String {
|
||||
// will be left unchanged (but this isn't signalled in any way)
|
||||
String &operator +=(const String &rhs) {
|
||||
concat(rhs);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(const char *cstr) {
|
||||
concat(cstr);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(char c) {
|
||||
concat(c);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(unsigned char num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(int num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(unsigned int num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(long num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(unsigned long num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(long long num) {
|
||||
concat(num);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(unsigned long long num) {
|
||||
concat(num);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(float num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(double num) {
|
||||
concat(num);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
String &operator +=(const __FlashStringHelper *str) {
|
||||
concat(str);
|
||||
return (*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs);
|
||||
@ -173,6 +191,8 @@ class String {
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, long num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, long long num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, float num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, double num);
|
||||
friend StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
|
||||
@ -219,7 +239,9 @@ class String {
|
||||
}
|
||||
|
||||
// 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);
|
||||
@ -234,10 +256,12 @@ class String {
|
||||
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,7 +269,6 @@ class String {
|
||||
String substring(unsigned int beginIndex) const {
|
||||
return substring(beginIndex, len());
|
||||
}
|
||||
;
|
||||
String substring(unsigned int beginIndex, unsigned int endIndex) const;
|
||||
|
||||
// modification
|
||||
@ -266,8 +289,9 @@ class String {
|
||||
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);
|
||||
@ -289,7 +313,7 @@ class String {
|
||||
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 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 ©(const char *cstr, unsigned int length);
|
||||
String ©(const __FlashStringHelper *pstr, unsigned int length);
|
||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
||||
void move(String &rhs);
|
||||
#endif
|
||||
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;
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <debug.h>
|
||||
#include <Arduino.h>
|
||||
#include <cxxabi.h>
|
||||
|
||||
@ -32,13 +31,17 @@ 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)
|
||||
#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;
|
||||
}
|
||||
@ -49,10 +52,32 @@ void *operator new[](size_t 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;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
163
cores/esp8266/aes_unwrap.cpp
Normal file
163
cores/esp8266/aes_unwrap.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Replacement for the ROM aes_unwrap() function. It uses the heap instead of
|
||||
* the static DRAM address at 0x3FFFEA80, which may step on the SYS stack in
|
||||
* special circumstances such as HWDT Stack Dump.
|
||||
*
|
||||
* When not using WPS, the address space 0x3FFFE000 up to 0x40000000 is mostly
|
||||
* available for the stacks. The one known exception is the ROM AES APIs. When
|
||||
* `aes_decrypt_init` is called, it uses memory at 0x3FFFEA80 up to 0x3FFFEB30
|
||||
* for a buffer. At the finish, `aes_decrypt_deinit` zeros out the buffer.
|
||||
*
|
||||
* The NONOS SDK appears to have replacements for most of the ROM's AES APIs.
|
||||
* However, the SDK still calls on the ROM's aes_unwrap function, which uses
|
||||
* the ROM's AES APIs to operate. These calls can overwrite some of the stack
|
||||
* space. To resolve the problem, this module replaces `aes_unwrap`.
|
||||
*
|
||||
* Final note, so far, I have not seen a problem when using the extra 4K heap
|
||||
* option without the "debug HWDT". It is when combined with the HWDT Stack
|
||||
* Dump that a problem shows. This combination adds a Boot ROM stack, which
|
||||
* pushes up the SYS and CONT stacks into the AES Buffer space. Then the
|
||||
* problem shows.
|
||||
*
|
||||
* While debugging with painted stack space, during WiFi Connect, Reconnect,
|
||||
* and about every hour, a block of memory 0x3FFFEA80 - 0x3FFFEB30 (176 bytes)
|
||||
* was zeroed by the Boot ROM function aes_decrypt_init. All other painted
|
||||
* memory in the area was untouched after starting WiFi.
|
||||
*/
|
||||
|
||||
#if defined(KEEP_ROM_AES_UNWRAP)
|
||||
// Using the ROM version of aes_unwrap should be fine for the no extra 4K case
|
||||
// which is usually used in conjunction with WPS.
|
||||
|
||||
#else
|
||||
// This is required for DEBUG_ESP_HWDT.
|
||||
// The need is unconfirmed for the extra 4K heap case.
|
||||
#include "umm_malloc/umm_malloc.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Uses this function from the Boot ROM
|
||||
void rijndaelKeySetupDec(u32 rk[], const u8 cipherKey[]);
|
||||
|
||||
// This replaces the Boot ROM version just for this module
|
||||
// Uses a malloc-ed buffer instead of the static buffer in stack address space.
|
||||
static void *aes_decrypt_init(const u8 *key, size_t len) {
|
||||
if (16u != len) {
|
||||
return 0;
|
||||
}
|
||||
u32 *rk = (u32 *)malloc(16*11);
|
||||
// u32 *rk = (u32 *)0x3FFFEA80u; // This is what the ROM would have used.
|
||||
if (rk) {
|
||||
rijndaelKeySetupDec(rk, key);
|
||||
}
|
||||
return (void *)rk;
|
||||
}
|
||||
|
||||
// This replaces the Boot ROM version just for this module
|
||||
static void aes_decrypt_deinit(void *ctx) {
|
||||
if (ctx) {
|
||||
ets_memset(ctx, 0, 16*11);
|
||||
free(ctx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The NONOS SDK has an override on this function. To replace the aes_unwrap
|
||||
* without changing its behavior too much. We need access to the ROM version of
|
||||
* the AES APIs to make our aes_unwrap functionally equal to the current
|
||||
* environment except for the AES Buffer.
|
||||
*/
|
||||
#ifndef ROM_aes_decrypt
|
||||
#define ROM_aes_decrypt 0x400092d4
|
||||
#endif
|
||||
|
||||
typedef void (*fp_aes_decrypt_t)(void *ctx, const u8 *crypt, u8 *plain);
|
||||
#define AES_DECRYPT (reinterpret_cast<fp_aes_decrypt_t>(ROM_aes_decrypt))
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* This aes_unwrap() function overrides/replaces the Boot ROM version.
|
||||
*
|
||||
* It was adapted from aes_unwrap() found in the ESP8266 RTOS SDK
|
||||
* .../components/wap_supplicant/src/crypto/aes-unwrap.c
|
||||
*
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* AES key unwrap (128-bit KEK, RFC3394)
|
||||
*
|
||||
* Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Alternatively, this software may be distributed under the terms of BSD
|
||||
* license.
|
||||
*
|
||||
* See README and COPYING for more details.
|
||||
*/
|
||||
|
||||
/** based on RTOS SDK
|
||||
* aes_unwrap - Unwrap key with AES Key Wrap Algorithm (128-bit KEK) (RFC3394)
|
||||
* @kek: Key encryption key (KEK)
|
||||
* @n: Length of the plaintext key in 64-bit units; e.g., 2 = 128-bit = 16
|
||||
* bytes
|
||||
* @cipher: Wrapped key to be unwrapped, (n + 1) * 64 bits
|
||||
* @plain: Plaintext key, n * 64 bits
|
||||
* Returns: 0 on success, -1 on failure (e.g., integrity verification failed)
|
||||
*/
|
||||
int aes_unwrap(const u8 *kek, int n, const u8 *cipher, u8 *plain)
|
||||
{
|
||||
u8 a[8], *r, b[16];
|
||||
int i, j;
|
||||
void *ctx;
|
||||
|
||||
/* 1) Initialize variables. */
|
||||
ets_memcpy(a, cipher, 8);
|
||||
r = plain;
|
||||
ets_memcpy(r, cipher + 8, 8 * n);
|
||||
|
||||
ctx = aes_decrypt_init(kek, 16);
|
||||
if (ctx == NULL)
|
||||
return -1;
|
||||
|
||||
/* 2) Compute intermediate values.
|
||||
* For j = 5 to 0
|
||||
* For i = n to 1
|
||||
* B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i
|
||||
* A = MSB(64, B)
|
||||
* R[i] = LSB(64, B)
|
||||
*/
|
||||
for (j = 5; j >= 0; j--) {
|
||||
r = plain + (n - 1) * 8;
|
||||
for (i = n; i >= 1; i--) {
|
||||
ets_memcpy(b, a, 8);
|
||||
b[7] ^= n * j + i;
|
||||
|
||||
ets_memcpy(b + 8, r, 8);
|
||||
AES_DECRYPT(ctx, b, b);
|
||||
ets_memcpy(a, b, 8);
|
||||
ets_memcpy(r, b + 8, 8);
|
||||
r -= 8;
|
||||
}
|
||||
}
|
||||
aes_decrypt_deinit(ctx);
|
||||
|
||||
/* 3) Output results.
|
||||
*
|
||||
* These are already in @plain due to the location of temporary
|
||||
* variables. Just verify that the IV matches with the expected value.
|
||||
*/
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (a[i] != 0xa6)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
#endif
|
@ -25,6 +25,8 @@
|
||||
#ifndef CORE_BASE64_H_
|
||||
#define CORE_BASE64_H_
|
||||
|
||||
#include <WString.h>
|
||||
|
||||
class base64
|
||||
{
|
||||
public:
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,23 +520,29 @@ 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);
|
||||
if (driveTxClocks) {
|
||||
pinMode(I2SO_WS, FUNCTION_1);
|
||||
pinMode(I2SO_BCK, FUNCTION_1);
|
||||
}
|
||||
}
|
||||
if (enableRx) {
|
||||
rx = (i2s_state_t*)calloc(1, sizeof(*rx));
|
||||
if (!rx) {
|
||||
i2s_end(); // Clean up any TX or pin changes
|
||||
return false; // OOM error!
|
||||
}
|
||||
rx->driveClocks = driveRxClocks;
|
||||
pinMode(I2SI_DATA, INPUT);
|
||||
if (driveRxClocks) {
|
||||
pinMode(I2SI_WS, OUTPUT);
|
||||
pinMode(I2SI_BCK, OUTPUT);
|
||||
pinMode(I2SI_DATA, INPUT);
|
||||
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);
|
||||
}
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_I2SI_DATA);
|
||||
}
|
||||
|
||||
if (!i2s_slc_begin()) {
|
||||
// OOM in SLC memory allocations, tear it all down and abort!
|
||||
@ -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);
|
||||
if (tx->driveClocks) {
|
||||
pinMode(I2SO_BCK, INPUT);
|
||||
pinMode(I2SO_WS, INPUT);
|
||||
}
|
||||
free(tx);
|
||||
tx = NULL;
|
||||
}
|
||||
if (rx) {
|
||||
pinMode(I2SI_DATA, INPUT);
|
||||
if (rx->driveClocks) {
|
||||
pinMode(I2SI_BCK, INPUT);
|
||||
pinMode(I2SI_WS, INPUT);
|
||||
}
|
||||
free(rx);
|
||||
rx = NULL;
|
||||
}
|
||||
|
81
cores/esp8266/core_esp8266_i2s.h
Normal file
81
cores/esp8266/core_esp8266_i2s.h
Normal 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
|
@ -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,
|
||||
|
219
cores/esp8266/core_esp8266_non32xfer.cpp
Normal file
219
cores/esp8266/core_esp8266_non32xfer.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
/* 020819
|
||||
Based on PR https://github.com/esp8266/Arduino/pull/6978
|
||||
Enhanced to also handle store operations to iRAM and optional range
|
||||
validation. Also improved failed path to generate crash report.
|
||||
And, partially refactored.
|
||||
|
||||
Apologies if this is being pedantic, I was getting confused over these so
|
||||
I tried to understand what makes them different.
|
||||
|
||||
EXCCAUSE_LOAD_STORE_ERROR 3 is a non-32-bit load or store to an address that
|
||||
only supports a full 32-bit aligned transfer like IRAM or ICACHE. i.e., No
|
||||
8-bit char or 16-bit short transfers allowed.
|
||||
|
||||
EXCCAUSE_UNALIGNED 9 is an exception cause when load or store is not on an
|
||||
aligned boundary that matches the element's width.
|
||||
eg. *(short *)0x3FFF8001 = 1; or *(long *)0x3FFF8002 = 1;
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* This exception handler handles EXCCAUSE_LOAD_STORE_ERROR. It allows for a
|
||||
* byte or short access to iRAM or PROGMEM to succeed without causing a crash.
|
||||
* When reading, it is still preferred to use the xxx_P macros when possible
|
||||
* since they are probably 30x faster than this exception handler method.
|
||||
*
|
||||
* Code taken directly from @pvvx's public domain code in
|
||||
* https://github.com/pvvx/esp8266web/blob/master/app/sdklib/system/app_main.c
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#define VERIFY_C_ASM_EXCEPTION_FRAME_STRUCTURE
|
||||
#include <esp8266_undocumented.h>
|
||||
#include <core_esp8266_non32xfer.h>
|
||||
#include <mmu_iram.h>
|
||||
#include <Schedule.h>
|
||||
#include <debug.h>
|
||||
|
||||
// All of these optimization were tried and now work
|
||||
// These results were from irammem.ino using GCC 10.2
|
||||
// DRAM reference uint16 9 AVG cycles/transfer
|
||||
// #pragma GCC optimize("O0") // uint16, 289 AVG cycles/transfer, IRAM: +180
|
||||
// #pragma GCC optimize("O1") // uint16, 241 AVG cycles/transfer, IRAM: +16
|
||||
#pragma GCC optimize("O2") // uint16, 230 AVG cycles/transfer, IRAM: +4
|
||||
// #pragma GCC optimize("O3") // uint16, 230 AVG cycles/transfer, IRAM: +4
|
||||
// #pragma GCC optimize("Ofast") // uint16, 230 AVG cycles/transfer, IRAM: +4
|
||||
// #pragma GCC optimize("Os") // uint16, 233 AVG cycles/transfer, IRAM: 27556 +0
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define LOAD_MASK 0x00f00fu
|
||||
#define L8UI_MATCH 0x000002u
|
||||
#define L16UI_MATCH 0x001002u
|
||||
#define L16SI_MATCH 0x009002u
|
||||
#define S8I_MATCH 0x004002u
|
||||
#define S16I_MATCH 0x005002u
|
||||
|
||||
#define EXCCAUSE_LOAD_STORE_ERROR 3 /* Non 32-bit read/write error */
|
||||
|
||||
static fn_c_exception_handler_t old_c_handler = NULL;
|
||||
|
||||
static
|
||||
IRAM_ATTR void non32xfer_exception_handler(struct __exception_frame *ef, int cause)
|
||||
{
|
||||
do {
|
||||
/*
|
||||
In adapting the public domain version, a crash would come or go away with
|
||||
the slightest unrelated changes elsewhere in the function. Observed that
|
||||
register a15 was used for epc1, then clobbered by `rsr.` I now believe a
|
||||
"&" on the output register would have resolved the problem.
|
||||
|
||||
However, I have refactored the Extended ASM to reduce and consolidate
|
||||
register usage and corrected the issue.
|
||||
|
||||
The positioning of the Extended ASM block (as early as possible in the
|
||||
compiled function) is in part controlled by the immediate need for
|
||||
output variable `insn`. This placement aids in getting excvaddr read as
|
||||
early as possible.
|
||||
*/
|
||||
uint32_t insn, excvaddr;
|
||||
#if 1
|
||||
{
|
||||
uint32_t tmp;
|
||||
__asm__ (
|
||||
"rsr.excvaddr %[vaddr]\n\t" /* Read faulting address as early as possible */
|
||||
"movi.n %[tmp], ~3\n\t" /* prepare a mask for the EPC */
|
||||
"and %[tmp], %[tmp], %[epc]\n\t" /* apply mask for 32-bit aligned base */
|
||||
"ssa8l %[epc]\n\t" /* set up shift register for src op */
|
||||
"l32i %[insn], %[tmp], 0\n\t" /* load part 1 */
|
||||
"l32i %[tmp], %[tmp], 4\n\t" /* load part 2 */
|
||||
"src %[insn], %[tmp], %[insn]\n\t" /* right shift to get faulting instruction */
|
||||
: [vaddr]"=&r"(excvaddr), [insn]"=&r"(insn), [tmp]"=&r"(tmp)
|
||||
: [epc]"r"(ef->epc) :);
|
||||
}
|
||||
|
||||
#else
|
||||
{
|
||||
__asm__ __volatile__ ("rsr.excvaddr %0;" : "=r"(excvaddr):: "memory");
|
||||
/*
|
||||
"C" reference code for the ASM to document intent.
|
||||
May also prove useful when issolating possible issues with Extended ASM,
|
||||
optimizations, new compilers, etc.
|
||||
*/
|
||||
uint32_t epc = ef->epc;
|
||||
uint32_t *pWord = (uint32_t *)(epc & ~3);
|
||||
uint64_t big_word = ((uint64_t)pWord[1] << 32) | pWord[0];
|
||||
uint32_t pos = (epc & 3) * 8;
|
||||
insn = (uint32_t)(big_word >>= pos);
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t what = insn & LOAD_MASK;
|
||||
uint32_t valmask = 0;
|
||||
|
||||
uint32_t is_read = 1;
|
||||
if (L8UI_MATCH == what || S8I_MATCH == what) {
|
||||
valmask = 0xffu;
|
||||
if (S8I_MATCH == what) {
|
||||
is_read = 0;
|
||||
}
|
||||
} else if (L16UI_MATCH == what || L16SI_MATCH == what || S16I_MATCH == what) {
|
||||
valmask = 0xffffu;
|
||||
if (S16I_MATCH == what) {
|
||||
is_read = 0;
|
||||
}
|
||||
} else {
|
||||
continue; /* fail */
|
||||
}
|
||||
|
||||
int regno = (insn & 0x0000f0u) >> 4;
|
||||
if (regno == 1) {
|
||||
continue; /* we can't support storing into a1, just die */
|
||||
} else if (regno != 0) {
|
||||
--regno; /* account for skipped a1 in exception_frame */
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ESP_MMU
|
||||
/* debug option to validate address so we don't hide memory access bugs in APP */
|
||||
if (mmu_is_iram((void *)excvaddr) || (is_read && mmu_is_icache((void *)excvaddr))) {
|
||||
/* all is good */
|
||||
} else {
|
||||
continue; /* fail */
|
||||
}
|
||||
#endif
|
||||
{
|
||||
uint32_t *pWord = (uint32_t *)(excvaddr & ~0x3);
|
||||
uint32_t pos = (excvaddr & 0x3) * 8;
|
||||
uint32_t mem_val = *pWord;
|
||||
|
||||
if (is_read) {
|
||||
/* shift and mask down to correct size */
|
||||
mem_val >>= pos;
|
||||
mem_val &= valmask;
|
||||
|
||||
/* Sign-extend for L16SI, if applicable */
|
||||
if (what == L16SI_MATCH && (mem_val & 0x8000)) {
|
||||
mem_val |= 0xffff0000;
|
||||
}
|
||||
|
||||
ef->a_reg[regno] = mem_val; /* carry out the load */
|
||||
|
||||
} else { /* is write */
|
||||
uint32_t val = ef->a_reg[regno]; /* get value to store from register */
|
||||
val <<= pos;
|
||||
valmask <<= pos;
|
||||
val &= valmask;
|
||||
|
||||
/* mask out field, and merge */
|
||||
mem_val &= (~valmask);
|
||||
mem_val |= val;
|
||||
*pWord = mem_val; /* carry out the store */
|
||||
}
|
||||
}
|
||||
|
||||
ef->epc += 3; /* resume at following instruction */
|
||||
return;
|
||||
|
||||
} while(false);
|
||||
|
||||
/* Fail request, die */
|
||||
/*
|
||||
The old handler points to the SDK. Be alert for HWDT when Calling with
|
||||
INTLEVEL != 0. I cannot create it any more. I thought I saw this as a
|
||||
problem; however, my test case shows no problem ?? Maybe I was confused.
|
||||
*/
|
||||
if (old_c_handler) { // if (0 == (ef->ps & 0x0F)) {
|
||||
DBG_MMU_PRINTF("\ncalling previous load/store handler(%p)\n", old_c_handler);
|
||||
old_c_handler(ef, cause);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Calling _xtos_unhandled_exception(ef, cause) in the Boot ROM, gets us a
|
||||
hardware wdt.
|
||||
|
||||
Use panic instead as a fall back. It will produce a stack trace.
|
||||
*/
|
||||
panic();
|
||||
}
|
||||
|
||||
/*
|
||||
To operate reliably, this module requires the new
|
||||
`_xtos_set_exception_handler` from `exc-sethandler.cpp` and
|
||||
`_xtos_c_wrapper_handler` from `exc-c-wrapper-handler.S`. See comment block in
|
||||
`exc-sethandler.cpp` for details on issues with interrupts being enabled by
|
||||
"C" wrapper.
|
||||
*/
|
||||
void install_non32xfer_exception_handler(void) __attribute__((weak));
|
||||
void install_non32xfer_exception_handler(void) {
|
||||
if (NULL == old_c_handler) {
|
||||
// Set the "C" exception handler the wrapper will call
|
||||
old_c_handler =
|
||||
_xtos_set_exception_handler(EXCCAUSE_LOAD_STORE_ERROR,
|
||||
non32xfer_exception_handler);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
14
cores/esp8266/core_esp8266_non32xfer.h
Normal file
14
cores/esp8266/core_esp8266_non32xfer.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef __CORE_ESP8266_NON32XFER_H
|
||||
#define __CORE_ESP8266_NON32XFER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern void install_non32xfer_exception_handler();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -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,11 +144,19 @@ 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
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,312 +0,0 @@
|
||||
/*
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||
set to 1-shot mode and is always loaded with the time until the next edge
|
||||
of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||
timer. This allows for removing interrupt jitter and delay as the counter
|
||||
always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ets_sys.h"
|
||||
#include "core_esp8266_waveform.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Maximum delay between IRQs
|
||||
#define MAXIRQUS (10000)
|
||||
|
||||
// Set/clear GPIO 0-15 by bitmask
|
||||
#define SetGPIO(a) do { GPOS = a; } while (0)
|
||||
#define ClearGPIO(a) do { GPOC = a; } while (0)
|
||||
|
||||
// Waveform generator can create tones, PWM, and servos
|
||||
typedef struct {
|
||||
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
||||
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
|
||||
uint32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform
|
||||
uint32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform
|
||||
} Waveform;
|
||||
|
||||
static Waveform waveform[17]; // State of all possible pins
|
||||
static volatile uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||
static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||
|
||||
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
|
||||
static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
|
||||
static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
|
||||
|
||||
static uint32_t (*timer1CB)() = NULL;
|
||||
|
||||
|
||||
// Non-speed critical bits
|
||||
#pragma GCC optimize ("Os")
|
||||
|
||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCount() {
|
||||
uint32_t ccount;
|
||||
__asm__ __volatile__("esync; rsr %0,ccount":"=a"(ccount));
|
||||
return ccount;
|
||||
}
|
||||
|
||||
// Interrupt on/off control
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
||||
static bool timerRunning = false;
|
||||
|
||||
static void initTimer() {
|
||||
timer1_disable();
|
||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||
timerRunning = true;
|
||||
}
|
||||
|
||||
static void ICACHE_RAM_ATTR deinitTimer() {
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
timerRunning = false;
|
||||
}
|
||||
|
||||
// Set a callback. Pass in NULL to stop it
|
||||
void setTimer1Callback(uint32_t (*fn)()) {
|
||||
timer1CB = fn;
|
||||
if (!timerRunning && fn) {
|
||||
initTimer();
|
||||
timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste
|
||||
} else if (timerRunning && !fn && !waveformEnabled) {
|
||||
deinitTimer();
|
||||
}
|
||||
}
|
||||
|
||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||
// first, then it will immediately begin.
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) {
|
||||
return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS));
|
||||
}
|
||||
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) {
|
||||
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
||||
return false;
|
||||
}
|
||||
Waveform *wave = &waveform[pin];
|
||||
// Adjust to shave off some of the IRQ time, approximately
|
||||
wave->nextTimeHighCycles = timeHighCycles;
|
||||
wave->nextTimeLowCycles = timeLowCycles;
|
||||
wave->expiryCycle = runTimeCycles ? GetCycleCount() + runTimeCycles : 0;
|
||||
if (runTimeCycles && !wave->expiryCycle) {
|
||||
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
|
||||
}
|
||||
|
||||
uint32_t mask = 1<<pin;
|
||||
if (!(waveformEnabled & mask)) {
|
||||
// Actually set the pin high or low in the IRQ service to guarantee times
|
||||
wave->nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(1);
|
||||
waveformToEnable |= mask;
|
||||
if (!timerRunning) {
|
||||
initTimer();
|
||||
timer1_write(microsecondsToClockCycles(10));
|
||||
} else {
|
||||
// Ensure timely service....
|
||||
if (T1L > microsecondsToClockCycles(10)) {
|
||||
timer1_write(microsecondsToClockCycles(10));
|
||||
}
|
||||
}
|
||||
while (waveformToEnable) {
|
||||
delay(0); // Wait for waveform to update
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Speed critical bits
|
||||
#pragma GCC optimize ("O2")
|
||||
// Normally would not want two copies like this, but due to different
|
||||
// optimization levels the inline attribute gets lost if we try the
|
||||
// other version.
|
||||
|
||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
|
||||
uint32_t ccount;
|
||||
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
||||
return ccount;
|
||||
}
|
||||
|
||||
static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) {
|
||||
if (a < b) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// Stops a waveform on a pin
|
||||
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
|
||||
// Can't possibly need to stop anything if there is no timer active
|
||||
if (!timerRunning) {
|
||||
return false;
|
||||
}
|
||||
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
||||
// If they send >=32, then the shift will result in 0 and it will also return false
|
||||
if (waveformEnabled & (1UL << pin)) {
|
||||
waveformToDisable = 1UL << pin;
|
||||
// Must not interfere if Timer is due shortly
|
||||
if (T1L > microsecondsToClockCycles(10)) {
|
||||
timer1_write(microsecondsToClockCycles(10));
|
||||
}
|
||||
while (waveformToDisable) {
|
||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||
}
|
||||
}
|
||||
if (!waveformEnabled && !timer1CB) {
|
||||
deinitTimer();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// The SDK and hardware take some time to actually get to our NMI code, so
|
||||
// decrement the next IRQ's timer value by a bit so we can actually catch the
|
||||
// real CPU cycle counter we want for the waveforms.
|
||||
#if F_CPU == 80000000
|
||||
#define DELTAIRQ (microsecondsToClockCycles(3))
|
||||
#else
|
||||
#define DELTAIRQ (microsecondsToClockCycles(2))
|
||||
#endif
|
||||
|
||||
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
|
||||
// are generating. In the common case (1 PWM) these may be the same pin and
|
||||
// we can avoid looking at the other pins.
|
||||
static int startPin = 0;
|
||||
static int endPin = 0;
|
||||
|
||||
uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
||||
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
||||
|
||||
if (waveformToEnable || waveformToDisable) {
|
||||
// Handle enable/disable requests from main app.
|
||||
waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off
|
||||
waveformState &= ~waveformToEnable; // And clear the state of any just started
|
||||
waveformToEnable = 0;
|
||||
waveformToDisable = 0;
|
||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||
startPin = __builtin_ffs(waveformEnabled) - 1;
|
||||
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
|
||||
endPin = 32 - __builtin_clz(waveformEnabled);
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
if (waveformEnabled) {
|
||||
do {
|
||||
nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
||||
for (int i = startPin; i <= endPin; i++) {
|
||||
uint32_t mask = 1<<i;
|
||||
|
||||
// If it's not on, ignore!
|
||||
if (!(waveformEnabled & mask)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Waveform *wave = &waveform[i];
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
|
||||
// Disable any waveforms that are done
|
||||
if (wave->expiryCycle) {
|
||||
int32_t expiryToGo = wave->expiryCycle - now;
|
||||
if (expiryToGo < 0) {
|
||||
// Done, remove!
|
||||
waveformEnabled &= ~mask;
|
||||
if (i == 16) {
|
||||
GP16O &= ~1;
|
||||
} else {
|
||||
ClearGPIO(mask);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for toggles
|
||||
int32_t cyclesToGo = wave->nextServiceCycle - now;
|
||||
if (cyclesToGo < 0) {
|
||||
waveformState ^= mask;
|
||||
if (waveformState & mask) {
|
||||
if (i == 16) {
|
||||
GP16O |= 1; // GPIO16 write slow as it's RMW
|
||||
} else {
|
||||
SetGPIO(mask);
|
||||
}
|
||||
wave->nextServiceCycle = now + wave->nextTimeHighCycles;
|
||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles);
|
||||
} else {
|
||||
if (i == 16) {
|
||||
GP16O &= ~1; // GPIO16 write slow as it's RMW
|
||||
} else {
|
||||
ClearGPIO(mask);
|
||||
}
|
||||
wave->nextServiceCycle = now + wave->nextTimeLowCycles;
|
||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles);
|
||||
}
|
||||
} else {
|
||||
uint32_t deltaCycles = wave->nextServiceCycle - now;
|
||||
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles);
|
||||
int32_t cyclesLeftTimeout = timeoutCycle - now;
|
||||
done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0);
|
||||
} while (!done);
|
||||
} // if (waveformEnabled)
|
||||
|
||||
if (timer1CB) {
|
||||
nextEventCycles = min_u32(nextEventCycles, timer1CB());
|
||||
}
|
||||
|
||||
if (nextEventCycles < microsecondsToClockCycles(10)) {
|
||||
nextEventCycles = microsecondsToClockCycles(10);
|
||||
}
|
||||
nextEventCycles -= DELTAIRQ;
|
||||
|
||||
// Do it here instead of global function to save time and because we know it's edge-IRQ
|
||||
#if F_CPU == 160000000
|
||||
T1L = nextEventCycles >> 1; // Already know we're in range by MAXIRQUS
|
||||
#else
|
||||
T1L = nextEventCycles; // Already know we're in range by MAXIRQUS
|
||||
#endif
|
||||
TEIE |= TEIE1; // Edge int enable
|
||||
}
|
||||
|
||||
};
|
@ -2,6 +2,7 @@
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
-- Default, PWM locked version --
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
@ -22,6 +23,30 @@
|
||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
----------
|
||||
|
||||
-- Phase locked version --
|
||||
Copyright (c) 2020 Dirk O. Kaar.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||
set to 1-shot mode and is always loaded with the time until the next edge
|
||||
of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||
timer. This allows for removing interrupt jitter and delay as the counter
|
||||
always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
----------
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
@ -47,20 +72,41 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Call this function in your setup() to cause the phase locked version of the generator to
|
||||
// be linked in automatically. Otherwise, the default PWM locked version will be used.
|
||||
void enablePhaseLockedWaveform(void);
|
||||
|
||||
|
||||
// Start or change a waveform of the specified high and low times on specific pin.
|
||||
// If runtimeUS > 0 then automatically stop it after that many usecs.
|
||||
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
|
||||
// full period.
|
||||
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
||||
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
|
||||
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
||||
// under load, for applications where frequency or duty cycle must not change, leave false.
|
||||
// Returns true or false on success or failure.
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS = 0,
|
||||
// Following parameters are ignored unless in PhaseLocked mode
|
||||
int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
|
||||
|
||||
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
|
||||
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles.
|
||||
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next
|
||||
// full period.
|
||||
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
||||
// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that.
|
||||
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
||||
// under load, for applications where frequency or duty cycle must not change, leave false.
|
||||
// Returns true or false on success or failure.
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys = 0,
|
||||
// Following parameters are ignored unless in PhaseLocked mode
|
||||
int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
|
||||
|
||||
// Stop a waveform, if any, on the specified pin.
|
||||
// Returns true or false on success or failure.
|
||||
int stopWaveform(uint8_t pin);
|
||||
|
||||
// Add a callback function to be called on *EVERY* timer1 trigger. The
|
||||
// callback returns the number of microseconds until the next desired call.
|
||||
// callback must return the number of CPU clock cycles until the next desired call.
|
||||
// However, since it is called every timer1 interrupt, it may be called
|
||||
// again before this period. It should therefore use the ESP Cycle Counter
|
||||
// to determine whether or not to perform an operation.
|
||||
@ -69,6 +115,12 @@ int stopWaveform(uint8_t pin);
|
||||
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
|
||||
void setTimer1Callback(uint32_t (*fn)());
|
||||
|
||||
|
||||
// Internal-only calls, not for applications
|
||||
extern void _setPWMFreq(uint32_t freq);
|
||||
extern bool _stopPWM(uint8_t pin);
|
||||
extern bool _setPWM(int pin, uint32_t val, uint32_t range);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
444
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal file
444
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal file
@ -0,0 +1,444 @@
|
||||
/*
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
Copyright (c) 2020 Dirk O. Kaar.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||
set to 1-shot mode and is always loaded with the time until the next edge
|
||||
of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||
timer. This allows for removing interrupt jitter and delay as the counter
|
||||
always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||
clock cycle time, or an interval measured in clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "core_esp8266_waveform.h"
|
||||
#include <Arduino.h>
|
||||
#include "debug.h"
|
||||
#include "ets_sys.h"
|
||||
#include <atomic>
|
||||
|
||||
|
||||
extern "C" void enablePhaseLockedWaveform (void)
|
||||
{
|
||||
// Does nothing, added to app to enable linking these versions
|
||||
// of the waveform functions instead of the default.
|
||||
DEBUGV("Enabling phase locked waveform generator\n");
|
||||
}
|
||||
|
||||
// No-op calls to override the PWM implementation
|
||||
extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; }
|
||||
extern "C" bool _stopPWM_weak(int pin) { (void) pin; return false; }
|
||||
extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; }
|
||||
|
||||
|
||||
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
|
||||
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
|
||||
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
|
||||
// Maximum servicing time for any single IRQ
|
||||
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
|
||||
// The latency between in-ISR rearming of the timer and the earliest firing
|
||||
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
|
||||
// The SDK and hardware take some time to actually get to our NMI code
|
||||
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
|
||||
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
|
||||
|
||||
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
|
||||
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
|
||||
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
|
||||
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
|
||||
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3};
|
||||
|
||||
// Waveform generator can create tones, PWM, and servos
|
||||
typedef struct {
|
||||
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count
|
||||
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
|
||||
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
|
||||
int32_t adjDutyCcys; // Temporary correction for next period
|
||||
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
|
||||
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
|
||||
WaveformMode mode;
|
||||
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
|
||||
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
|
||||
} Waveform;
|
||||
|
||||
namespace {
|
||||
|
||||
static struct {
|
||||
Waveform pins[17]; // State of all possible pins
|
||||
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||
|
||||
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
|
||||
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
|
||||
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
|
||||
|
||||
uint32_t(*timer1CB)() = nullptr;
|
||||
|
||||
bool timer1Running = false;
|
||||
|
||||
uint32_t nextEventCcy;
|
||||
} waveform;
|
||||
|
||||
}
|
||||
|
||||
// Interrupt on/off control
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
||||
|
||||
// Non-speed critical bits
|
||||
#pragma GCC optimize ("Os")
|
||||
|
||||
static void initTimer() {
|
||||
timer1_disable();
|
||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||
waveform.timer1Running = true;
|
||||
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
|
||||
}
|
||||
|
||||
static void ICACHE_RAM_ATTR deinitTimer() {
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
waveform.timer1Running = false;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Set a callback. Pass in NULL to stop it
|
||||
void setTimer1Callback_weak(uint32_t (*fn)()) {
|
||||
waveform.timer1CB = fn;
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (!waveform.timer1Running && fn) {
|
||||
initTimer();
|
||||
} else if (waveform.timer1Running && !fn && !waveform.enabled) {
|
||||
deinitTimer();
|
||||
}
|
||||
}
|
||||
|
||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||
// first, then it will immediately begin.
|
||||
int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
|
||||
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
|
||||
uint32_t periodCcys = highCcys + lowCcys;
|
||||
if (periodCcys < MAXIRQTICKSCCYS) {
|
||||
if (!highCcys) {
|
||||
periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
||||
}
|
||||
else if (!lowCcys) {
|
||||
highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
||||
}
|
||||
}
|
||||
// sanity checks, including mixed signed/unsigned arithmetic safety
|
||||
if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
|
||||
static_cast<int32_t>(periodCcys) <= 0 ||
|
||||
static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) {
|
||||
return false;
|
||||
}
|
||||
Waveform& wave = waveform.pins[pin];
|
||||
wave.dutyCcys = highCcys;
|
||||
wave.adjDutyCcys = 0;
|
||||
wave.periodCcys = periodCcys;
|
||||
wave.autoPwm = autoPwm;
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
const uint32_t pinBit = 1UL << pin;
|
||||
if (!(waveform.enabled & pinBit)) {
|
||||
// wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
|
||||
wave.nextPeriodCcy = phaseOffsetCcys;
|
||||
wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
|
||||
wave.mode = WaveformMode::INIT;
|
||||
wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
|
||||
if (!wave.dutyCcys) {
|
||||
// If initially at zero duty cycle, force GPIO off
|
||||
if (pin == 16) {
|
||||
GP16O = 0;
|
||||
}
|
||||
else {
|
||||
GPOC = pinBit;
|
||||
}
|
||||
}
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
waveform.toSetBits = 1UL << pin;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
if (!waveform.timer1Running) {
|
||||
initTimer();
|
||||
}
|
||||
else if (T1V > IRQLATENCYCCYS) {
|
||||
// Must not interfere if Timer is due shortly
|
||||
timer1_write(IRQLATENCYCCYS);
|
||||
}
|
||||
}
|
||||
else {
|
||||
wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
|
||||
if (runTimeCcys) {
|
||||
wave.mode = WaveformMode::UPDATEEXPIRY;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
waveform.toSetBits = 1UL << pin;
|
||||
}
|
||||
}
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
while (waveform.toSetBits) {
|
||||
delay(0); // Wait for waveform to update
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stops a waveform on a pin
|
||||
ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
|
||||
// Can't possibly need to stop anything if there is no timer active
|
||||
if (!waveform.timer1Running) {
|
||||
return false;
|
||||
}
|
||||
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
||||
// If they send >=32, then the shift will result in 0 and it will also return false
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
const uint32_t pinBit = 1UL << pin;
|
||||
if (waveform.enabled & pinBit) {
|
||||
waveform.toDisableBits = 1UL << pin;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
// Must not interfere if Timer is due shortly
|
||||
if (T1V > IRQLATENCYCCYS) {
|
||||
timer1_write(IRQLATENCYCCYS);
|
||||
}
|
||||
while (waveform.toDisableBits) {
|
||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
if (!waveform.enabled && !waveform.timer1CB) {
|
||||
deinitTimer();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Speed critical bits
|
||||
#pragma GCC optimize ("O2")
|
||||
|
||||
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
|
||||
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
|
||||
static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
|
||||
if (ISCPUFREQ160MHZ) {
|
||||
return isCPU2X ? ccys : (ccys >> 1);
|
||||
}
|
||||
else {
|
||||
return isCPU2X ? (ccys << 1) : ccys;
|
||||
}
|
||||
}
|
||||
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||
const uint32_t isrStartCcy = ESP.getCycleCount();
|
||||
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
|
||||
const bool isCPU2X = CPU2X & 1;
|
||||
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
|
||||
// Handle enable/disable requests from main app.
|
||||
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
|
||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||
waveform.toDisableBits = 0;
|
||||
}
|
||||
|
||||
if (waveform.toSetBits) {
|
||||
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
|
||||
Waveform& wave = waveform.pins[toSetPin];
|
||||
switch (wave.mode) {
|
||||
case WaveformMode::INIT:
|
||||
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
|
||||
if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) {
|
||||
wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
wave.nextPeriodCcy = waveform.nextEventCcy;
|
||||
}
|
||||
if (!wave.expiryCcy) {
|
||||
wave.mode = WaveformMode::INFINITE;
|
||||
break;
|
||||
}
|
||||
// fall through
|
||||
case WaveformMode::UPDATEEXPIRY:
|
||||
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
|
||||
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
|
||||
wave.mode = WaveformMode::EXPIRES;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
waveform.toSetBits = 0;
|
||||
}
|
||||
|
||||
// Exit the loop if the next event, if any, is sufficiently distant.
|
||||
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
|
||||
uint32_t busyPins = waveform.enabled;
|
||||
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
|
||||
|
||||
uint32_t now = ESP.getCycleCount();
|
||||
uint32_t isrNextEventCcy = now;
|
||||
while (busyPins) {
|
||||
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
|
||||
waveform.nextEventCcy = isrNextEventCcy;
|
||||
break;
|
||||
}
|
||||
isrNextEventCcy = waveform.nextEventCcy;
|
||||
uint32_t loopPins = busyPins;
|
||||
while (loopPins) {
|
||||
const int pin = __builtin_ffsl(loopPins) - 1;
|
||||
const uint32_t pinBit = 1UL << pin;
|
||||
loopPins ^= pinBit;
|
||||
|
||||
Waveform& wave = waveform.pins[pin];
|
||||
|
||||
if (clockDrift) {
|
||||
wave.endDutyCcy += clockDrift;
|
||||
wave.nextPeriodCcy += clockDrift;
|
||||
wave.expiryCcy += clockDrift;
|
||||
}
|
||||
|
||||
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
|
||||
if (WaveformMode::EXPIRES == wave.mode &&
|
||||
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
|
||||
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
|
||||
// Disable any waveforms that are done
|
||||
waveform.enabled ^= pinBit;
|
||||
busyPins ^= pinBit;
|
||||
}
|
||||
else {
|
||||
const int32_t overshootCcys = now - waveNextEventCcy;
|
||||
if (overshootCcys >= 0) {
|
||||
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
|
||||
if (waveform.states & pinBit) {
|
||||
// active configuration and forward are 100% duty
|
||||
if (wave.periodCcys == wave.dutyCcys) {
|
||||
wave.nextPeriodCcy += periodCcys;
|
||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
if (wave.autoPwm) {
|
||||
wave.adjDutyCcys += overshootCcys;
|
||||
}
|
||||
waveform.states ^= pinBit;
|
||||
if (16 == pin) {
|
||||
GP16O = 0;
|
||||
}
|
||||
else {
|
||||
GPOC = pinBit;
|
||||
}
|
||||
}
|
||||
waveNextEventCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
wave.nextPeriodCcy += periodCcys;
|
||||
if (!wave.dutyCcys) {
|
||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
|
||||
if (dutyCcys <= wave.adjDutyCcys) {
|
||||
dutyCcys >>= 1;
|
||||
wave.adjDutyCcys -= dutyCcys;
|
||||
}
|
||||
else if (wave.adjDutyCcys) {
|
||||
dutyCcys -= wave.adjDutyCcys;
|
||||
wave.adjDutyCcys = 0;
|
||||
}
|
||||
wave.endDutyCcy = now + dutyCcys;
|
||||
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
|
||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
waveform.states |= pinBit;
|
||||
if (16 == pin) {
|
||||
GP16O = 1;
|
||||
}
|
||||
else {
|
||||
GPOS = pinBit;
|
||||
}
|
||||
}
|
||||
waveNextEventCcy = wave.endDutyCcy;
|
||||
}
|
||||
|
||||
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
|
||||
waveNextEventCcy = wave.expiryCcy;
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
|
||||
busyPins ^= pinBit;
|
||||
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
|
||||
waveform.nextEventCcy = waveNextEventCcy;
|
||||
}
|
||||
}
|
||||
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
|
||||
isrNextEventCcy = waveNextEventCcy;
|
||||
}
|
||||
}
|
||||
now = ESP.getCycleCount();
|
||||
}
|
||||
clockDrift = 0;
|
||||
}
|
||||
|
||||
int32_t callbackCcys = 0;
|
||||
if (waveform.timer1CB) {
|
||||
callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X);
|
||||
}
|
||||
now = ESP.getCycleCount();
|
||||
int32_t nextEventCcys = waveform.nextEventCcy - now;
|
||||
// Account for unknown duration of timer1CB().
|
||||
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
|
||||
waveform.nextEventCcy = now + callbackCcys;
|
||||
nextEventCcys = callbackCcys;
|
||||
}
|
||||
|
||||
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||
int32_t deltaIrqCcys = DELTAIRQCCYS;
|
||||
int32_t irqLatencyCcys = IRQLATENCYCCYS;
|
||||
if (isCPU2X) {
|
||||
nextEventCcys >>= 1;
|
||||
deltaIrqCcys >>= 1;
|
||||
irqLatencyCcys >>= 1;
|
||||
}
|
||||
|
||||
// Firing timer too soon, the NMI occurs before ISR has returned.
|
||||
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
|
||||
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
|
||||
nextEventCcys = irqLatencyCcys;
|
||||
}
|
||||
else {
|
||||
nextEventCcys -= deltaIrqCcys;
|
||||
}
|
||||
|
||||
// Register access is fast and edge IRQ was configured before.
|
||||
T1L = nextEventCcys;
|
||||
}
|
666
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal file
666
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal file
@ -0,0 +1,666 @@
|
||||
/*
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1
|
||||
is set to 1-shot mode and is always loaded with the time until the next
|
||||
edge of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not
|
||||
the timer. This allows for removing interrupt jitter and delay as the
|
||||
counter always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||
clock cycle count, or an interval measured in CPU clock cycles, but not
|
||||
TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ets_sys.h"
|
||||
#include "core_esp8266_waveform.h"
|
||||
#include "user_interface.h"
|
||||
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Maximum delay between IRQs
|
||||
#define MAXIRQUS (10000)
|
||||
|
||||
// Waveform generator can create tones, PWM, and servos
|
||||
typedef struct {
|
||||
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
||||
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
|
||||
uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
|
||||
uint32_t timeLowCycles; //
|
||||
uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
|
||||
uint32_t desiredLowCycles; //
|
||||
uint32_t lastEdge; // Cycle when this generator last changed
|
||||
} Waveform;
|
||||
|
||||
class WVFState {
|
||||
public:
|
||||
Waveform waveform[17]; // State of all possible pins
|
||||
uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||
uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||
|
||||
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
|
||||
uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
|
||||
uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
|
||||
|
||||
uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI
|
||||
uint32_t waveformNewHigh = 0;
|
||||
uint32_t waveformNewLow = 0;
|
||||
|
||||
uint32_t (*timer1CB)() = NULL;
|
||||
|
||||
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
|
||||
// are generating. In the common case (1 PWM) these may be the same pin and
|
||||
// we can avoid looking at the other pins.
|
||||
uint16_t startPin = 0;
|
||||
uint16_t endPin = 0;
|
||||
};
|
||||
static WVFState wvfState;
|
||||
|
||||
|
||||
// Ensure everything is read/written to RAM
|
||||
#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
|
||||
|
||||
// Non-speed critical bits
|
||||
#pragma GCC optimize ("Os")
|
||||
|
||||
// Interrupt on/off control
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
||||
static bool timerRunning = false;
|
||||
|
||||
static __attribute__((noinline)) void initTimer() {
|
||||
if (!timerRunning) {
|
||||
timer1_disable();
|
||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||
timerRunning = true;
|
||||
timer1_write(microsecondsToClockCycles(10));
|
||||
}
|
||||
}
|
||||
|
||||
static ICACHE_RAM_ATTR void forceTimerInterrupt() {
|
||||
if (T1L > microsecondsToClockCycles(10)) {
|
||||
T1L = microsecondsToClockCycles(10);
|
||||
}
|
||||
}
|
||||
|
||||
// PWM implementation using special purpose state machine
|
||||
//
|
||||
// Keep an ordered list of pins with the delta in cycles between each
|
||||
// element, with a terminal entry making up the remainder of the PWM
|
||||
// period. With this method sum(all deltas) == PWM period clock cycles.
|
||||
//
|
||||
// At t=0 set all pins high and set the timeout for the 1st edge.
|
||||
// On interrupt, if we're at the last element reset to t=0 state
|
||||
// Otherwise, clear that pin down and set delay for next element
|
||||
// and so forth.
|
||||
|
||||
constexpr int maxPWMs = 8;
|
||||
|
||||
// PWM machine state
|
||||
typedef struct PWMState {
|
||||
uint32_t mask; // Bitmask of active pins
|
||||
uint32_t cnt; // How many entries
|
||||
uint32_t idx; // Where the state machine is along the list
|
||||
uint8_t pin[maxPWMs + 1];
|
||||
uint32_t delta[maxPWMs + 1];
|
||||
uint32_t nextServiceCycle; // Clock cycle for next step
|
||||
struct PWMState *pwmUpdate; // Set by main code, cleared by ISR
|
||||
} PWMState;
|
||||
|
||||
static PWMState pwmState;
|
||||
static uint32_t _pwmFreq = 1000;
|
||||
static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq;
|
||||
|
||||
|
||||
// If there are no more scheduled activities, shut down Timer 1.
|
||||
// Otherwise, do nothing.
|
||||
static ICACHE_RAM_ATTR void disableIdleTimer() {
|
||||
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
timerRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the NMI that a new PWM state is available through the mailbox.
|
||||
// Wait for mailbox to be emptied (either busy or delay() as needed)
|
||||
static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
|
||||
p->pwmUpdate = nullptr;
|
||||
pwmState.pwmUpdate = p;
|
||||
MEMBARRIER();
|
||||
forceTimerInterrupt();
|
||||
while (pwmState.pwmUpdate) {
|
||||
if (idle) {
|
||||
delay(0);
|
||||
}
|
||||
MEMBARRIER();
|
||||
}
|
||||
}
|
||||
|
||||
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range);
|
||||
|
||||
|
||||
// Called when analogWriteFreq() changed to update the PWM total period
|
||||
extern void _setPWMFreq_weak(uint32_t freq) __attribute__((weak));
|
||||
void _setPWMFreq_weak(uint32_t freq) {
|
||||
_pwmFreq = freq;
|
||||
|
||||
// Convert frequency into clock cycles
|
||||
uint32_t cc = microsecondsToClockCycles(1000000UL) / freq;
|
||||
|
||||
// Simple static adjustment to bring period closer to requested due to overhead
|
||||
// Empirically determined as a constant PWM delay and a function of the number of PWMs
|
||||
#if F_CPU == 80000000
|
||||
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110;
|
||||
#else
|
||||
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75;
|
||||
#endif
|
||||
|
||||
if (cc == _pwmPeriod) {
|
||||
return; // No change
|
||||
}
|
||||
|
||||
_pwmPeriod = cc;
|
||||
|
||||
if (pwmState.cnt) {
|
||||
PWMState p; // The working copy since we can't edit the one in use
|
||||
p.mask = 0;
|
||||
p.cnt = 0;
|
||||
for (uint32_t i = 0; i < pwmState.cnt; i++) {
|
||||
auto pin = pwmState.pin[i];
|
||||
_addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles);
|
||||
}
|
||||
// Update and wait for mailbox to be emptied
|
||||
initTimer();
|
||||
_notifyPWM(&p, true);
|
||||
disableIdleTimer();
|
||||
}
|
||||
}
|
||||
static void _setPWMFreq_bound(uint32_t freq) __attribute__((weakref("_setPWMFreq_weak")));
|
||||
void _setPWMFreq(uint32_t freq) {
|
||||
_setPWMFreq_bound(freq);
|
||||
}
|
||||
|
||||
|
||||
// Helper routine to remove an entry from the state machine
|
||||
// and clean up any marked-off entries
|
||||
static void _cleanAndRemovePWM(PWMState *p, int pin) {
|
||||
uint32_t leftover = 0;
|
||||
uint32_t in, out;
|
||||
for (in = 0, out = 0; in < p->cnt; in++) {
|
||||
if ((p->pin[in] != pin) && (p->mask & (1<<p->pin[in]))) {
|
||||
p->pin[out] = p->pin[in];
|
||||
p->delta[out] = p->delta[in] + leftover;
|
||||
leftover = 0;
|
||||
out++;
|
||||
} else {
|
||||
leftover += p->delta[in];
|
||||
p->mask &= ~(1<<p->pin[in]);
|
||||
}
|
||||
}
|
||||
p->cnt = out;
|
||||
// Final pin is never used: p->pin[out] = 0xff;
|
||||
p->delta[out] = p->delta[in] + leftover;
|
||||
}
|
||||
|
||||
|
||||
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
|
||||
extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak));
|
||||
ICACHE_RAM_ATTR bool _stopPWM_weak(uint8_t pin) {
|
||||
if (!((1<<pin) & pwmState.mask)) {
|
||||
return false; // Pin not actually active
|
||||
}
|
||||
|
||||
PWMState p; // The working copy since we can't edit the one in use
|
||||
p = pwmState;
|
||||
|
||||
// In _stopPWM we just clear the mask but keep everything else
|
||||
// untouched to save IRAM. The main startPWM will handle cleanup.
|
||||
p.mask &= ~(1<<pin);
|
||||
if (!p.mask) {
|
||||
// If all have been stopped, then turn PWM off completely
|
||||
p.cnt = 0;
|
||||
}
|
||||
|
||||
// Update and wait for mailbox to be emptied, no delay (could be in ISR)
|
||||
_notifyPWM(&p, false);
|
||||
// Possibly shut down the timer completely if we're done
|
||||
disableIdleTimer();
|
||||
return true;
|
||||
}
|
||||
static bool _stopPWM_bound(uint8_t pin) __attribute__((weakref("_stopPWM_weak")));
|
||||
bool _stopPWM(uint8_t pin) {
|
||||
return _stopPWM_bound(pin);
|
||||
}
|
||||
|
||||
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) {
|
||||
// Stash the val and range so we can re-evaluate the fraction
|
||||
// should the user change PWM frequency. This allows us to
|
||||
// give as great a precision as possible. We know by construction
|
||||
// that the waveform for this pin will be inactive so we can borrow
|
||||
// memory from that structure.
|
||||
wvfState.waveform[pin].desiredHighCycles = val; // Numerator == high
|
||||
wvfState.waveform[pin].desiredLowCycles = range; // Denominator == low
|
||||
|
||||
uint32_t cc = (_pwmPeriod * val) / range;
|
||||
|
||||
// Clip to sane values in the case we go from OK to not-OK when adjusting frequencies
|
||||
if (cc == 0) {
|
||||
cc = 1;
|
||||
} else if (cc >= _pwmPeriod) {
|
||||
cc = _pwmPeriod - 1;
|
||||
}
|
||||
|
||||
if (p.cnt == 0) {
|
||||
// Starting up from scratch, special case 1st element and PWM period
|
||||
p.pin[0] = pin;
|
||||
p.delta[0] = cc;
|
||||
// Final pin is never used: p.pin[1] = 0xff;
|
||||
p.delta[1] = _pwmPeriod - cc;
|
||||
} else {
|
||||
uint32_t ttl = 0;
|
||||
uint32_t i;
|
||||
// Skip along until we're at the spot to insert
|
||||
for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
|
||||
ttl += p.delta[i];
|
||||
}
|
||||
// Shift everything out by one to make space for new edge
|
||||
for (int32_t j = p.cnt; j >= (int)i; j--) {
|
||||
p.pin[j + 1] = p.pin[j];
|
||||
p.delta[j + 1] = p.delta[j];
|
||||
}
|
||||
int off = cc - ttl; // The delta from the last edge to the one we're inserting
|
||||
p.pin[i] = pin;
|
||||
p.delta[i] = off; // Add the delta to this new pin
|
||||
p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant
|
||||
}
|
||||
p.cnt++;
|
||||
p.mask |= 1<<pin;
|
||||
}
|
||||
|
||||
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
|
||||
extern bool _setPWM_weak(int pin, uint32_t val, uint32_t range) __attribute__((weak));
|
||||
bool _setPWM_weak(int pin, uint32_t val, uint32_t range) {
|
||||
stopWaveform(pin);
|
||||
PWMState p; // Working copy
|
||||
p = pwmState;
|
||||
// Get rid of any entries for this pin
|
||||
_cleanAndRemovePWM(&p, pin);
|
||||
// And add it to the list, in order
|
||||
if (p.cnt >= maxPWMs) {
|
||||
return false; // No space left
|
||||
}
|
||||
|
||||
// Sanity check for all-on/off
|
||||
uint32_t cc = (_pwmPeriod * val) / range;
|
||||
if ((cc == 0) || (cc >= _pwmPeriod)) {
|
||||
digitalWrite(pin, cc ? HIGH : LOW);
|
||||
return true;
|
||||
}
|
||||
|
||||
_addPWMtoList(p, pin, val, range);
|
||||
|
||||
// Set mailbox and wait for ISR to copy it over
|
||||
initTimer();
|
||||
_notifyPWM(&p, true);
|
||||
disableIdleTimer();
|
||||
|
||||
// Potentially recalculate the PWM period if we've added another pin
|
||||
_setPWMFreq(_pwmFreq);
|
||||
|
||||
return true;
|
||||
}
|
||||
static bool _setPWM_bound(int pin, uint32_t val, uint32_t range) __attribute__((weakref("_setPWM_weak")));
|
||||
bool _setPWM(int pin, uint32_t val, uint32_t range) {
|
||||
return _setPWM_bound(pin, val, range);
|
||||
}
|
||||
|
||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||
// first, then it will immediately begin.
|
||||
extern int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weak));
|
||||
int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles,
|
||||
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||
(void) alignPhase;
|
||||
(void) phaseOffsetUS;
|
||||
(void) autoPwm;
|
||||
|
||||
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
||||
return false;
|
||||
}
|
||||
Waveform *wave = &wvfState.waveform[pin];
|
||||
wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0;
|
||||
if (runTimeCycles && !wave->expiryCycle) {
|
||||
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
|
||||
}
|
||||
|
||||
_stopPWM(pin); // Make sure there's no PWM live here
|
||||
|
||||
uint32_t mask = 1<<pin;
|
||||
MEMBARRIER();
|
||||
if (wvfState.waveformEnabled & mask) {
|
||||
// Make sure no waveform changes are waiting to be applied
|
||||
while (wvfState.waveformToChange) {
|
||||
delay(0); // Wait for waveform to update
|
||||
// No mem barrier here, the call to a global function implies global state updated
|
||||
}
|
||||
wvfState.waveformNewHigh = timeHighCycles;
|
||||
wvfState.waveformNewLow = timeLowCycles;
|
||||
MEMBARRIER();
|
||||
wvfState.waveformToChange = mask;
|
||||
// The waveform will be updated some time in the future on the next period for the signal
|
||||
} else { // if (!(wvfState.waveformEnabled & mask)) {
|
||||
wave->timeHighCycles = timeHighCycles;
|
||||
wave->desiredHighCycles = timeHighCycles;
|
||||
wave->timeLowCycles = timeLowCycles;
|
||||
wave->desiredLowCycles = timeLowCycles;
|
||||
wave->lastEdge = 0;
|
||||
wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
|
||||
wvfState.waveformToEnable |= mask;
|
||||
MEMBARRIER();
|
||||
initTimer();
|
||||
forceTimerInterrupt();
|
||||
while (wvfState.waveformToEnable) {
|
||||
delay(0); // Wait for waveform to update
|
||||
// No mem barrier here, the call to a global function implies global state updated
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
static int startWaveformClockCycles_bound(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weakref("startWaveformClockCycles_weak")));
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||
return startWaveformClockCycles_bound(pin, timeHighCycles, timeLowCycles, runTimeCycles, alignPhase, phaseOffsetUS, autoPwm);
|
||||
}
|
||||
|
||||
|
||||
// This version falls-thru to the proper startWaveformClockCycles call and is invariant across waveform generators
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS,
|
||||
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||
return startWaveformClockCycles_bound(pin,
|
||||
microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS),
|
||||
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
|
||||
}
|
||||
|
||||
// Set a callback. Pass in NULL to stop it
|
||||
extern void setTimer1Callback_weak(uint32_t (*fn)()) __attribute__((weak));
|
||||
void setTimer1Callback_weak(uint32_t (*fn)()) {
|
||||
wvfState.timer1CB = fn;
|
||||
if (fn) {
|
||||
initTimer();
|
||||
forceTimerInterrupt();
|
||||
}
|
||||
disableIdleTimer();
|
||||
}
|
||||
static void setTimer1Callback_bound(uint32_t (*fn)()) __attribute__((weakref("setTimer1Callback_weak")));
|
||||
void setTimer1Callback(uint32_t (*fn)()) {
|
||||
setTimer1Callback_bound(fn);
|
||||
}
|
||||
|
||||
// Stops a waveform on a pin
|
||||
extern int stopWaveform_weak(uint8_t pin) __attribute__((weak));
|
||||
ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
|
||||
// Can't possibly need to stop anything if there is no timer active
|
||||
if (!timerRunning) {
|
||||
return false;
|
||||
}
|
||||
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
||||
// If they send >=32, then the shift will result in 0 and it will also return false
|
||||
uint32_t mask = 1<<pin;
|
||||
if (wvfState.waveformEnabled & mask) {
|
||||
wvfState.waveformToDisable = mask;
|
||||
// Cancel any pending updates for this waveform, too.
|
||||
if (wvfState.waveformToChange & mask) {
|
||||
wvfState.waveformToChange = 0;
|
||||
}
|
||||
forceTimerInterrupt();
|
||||
while (wvfState.waveformToDisable) {
|
||||
MEMBARRIER(); // If it wasn't written yet, it has to be by now
|
||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||
}
|
||||
}
|
||||
disableIdleTimer();
|
||||
return true;
|
||||
}
|
||||
static int stopWaveform_bound(uint8_t pin) __attribute__((weakref("stopWaveform_weak")));
|
||||
ICACHE_RAM_ATTR int stopWaveform(uint8_t pin) {
|
||||
return stopWaveform_bound(pin);
|
||||
}
|
||||
|
||||
// Speed critical bits
|
||||
#pragma GCC optimize ("O2")
|
||||
|
||||
// Normally would not want two copies like this, but due to different
|
||||
// optimization levels the inline attribute gets lost if we try the
|
||||
// other version.
|
||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
|
||||
uint32_t ccount;
|
||||
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
||||
return ccount;
|
||||
}
|
||||
|
||||
// Find the earliest cycle as compared to right now
|
||||
static inline ICACHE_RAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
int32_t da = a - now;
|
||||
int32_t db = b - now;
|
||||
return (da < db) ? a : b;
|
||||
}
|
||||
|
||||
// The SDK and hardware take some time to actually get to our NMI code, so
|
||||
// decrement the next IRQ's timer value by a bit so we can actually catch the
|
||||
// real CPU cycle counter we want for the waveforms.
|
||||
|
||||
// The SDK also sometimes is running at a different speed the the Arduino core
|
||||
// so the ESP cycle counter is actually running at a variable speed.
|
||||
// adjust(x) takes care of adjusting a delta clock cycle amount accordingly.
|
||||
#if F_CPU == 80000000
|
||||
#define DELTAIRQ (microsecondsToClockCycles(9)/4)
|
||||
#define adjust(x) ((x) << (turbo ? 1 : 0))
|
||||
#else
|
||||
#define DELTAIRQ (microsecondsToClockCycles(9)/8)
|
||||
#define adjust(x) ((x) >> 0)
|
||||
#endif
|
||||
|
||||
// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
|
||||
#define MINIRQTIME microsecondsToClockCycles(4)
|
||||
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||
// Flag if the core is at 160 MHz, for use by adjust()
|
||||
bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
|
||||
|
||||
uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
|
||||
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
||||
|
||||
if (wvfState.waveformToEnable || wvfState.waveformToDisable) {
|
||||
// Handle enable/disable requests from main app
|
||||
wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off
|
||||
wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started
|
||||
wvfState.waveformToEnable = 0;
|
||||
wvfState.waveformToDisable = 0;
|
||||
// No mem barrier. Globals must be written to RAM on ISR exit.
|
||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||
wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
|
||||
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
|
||||
wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
|
||||
} else if (!pwmState.cnt && pwmState.pwmUpdate) {
|
||||
// Start up the PWM generator by copying from the mailbox
|
||||
pwmState.cnt = 1;
|
||||
pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0
|
||||
pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
|
||||
// No need for mem barrier here. Global must be written by IRQ exit
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
if (wvfState.waveformEnabled || pwmState.cnt) {
|
||||
do {
|
||||
nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
|
||||
|
||||
// PWM state machine implementation
|
||||
if (pwmState.cnt) {
|
||||
int32_t cyclesToGo;
|
||||
do {
|
||||
cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ();
|
||||
if (cyclesToGo < 0) {
|
||||
if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
|
||||
if (pwmState.pwmUpdate) {
|
||||
// Do the memory copy from temp to global and clear mailbox
|
||||
pwmState = *(PWMState*)pwmState.pwmUpdate;
|
||||
}
|
||||
GPOS = pwmState.mask; // Set all active pins high
|
||||
if (pwmState.mask & (1<<16)) {
|
||||
GP16O = 1;
|
||||
}
|
||||
pwmState.idx = 0;
|
||||
} else {
|
||||
do {
|
||||
// Drop the pin at this edge
|
||||
if (pwmState.mask & (1<<pwmState.pin[pwmState.idx])) {
|
||||
GPOC = 1<<pwmState.pin[pwmState.idx];
|
||||
if (pwmState.pin[pwmState.idx] == 16) {
|
||||
GP16O = 0;
|
||||
}
|
||||
}
|
||||
pwmState.idx++;
|
||||
// Any other pins at this same PWM value will have delta==0, drop them too.
|
||||
} while (pwmState.delta[pwmState.idx] == 0);
|
||||
}
|
||||
// Preserve duty cycle over PWM period by using now+xxx instead of += delta
|
||||
cyclesToGo = adjust(pwmState.delta[pwmState.idx]);
|
||||
pwmState.nextServiceCycle = GetCycleCountIRQ() + cyclesToGo;
|
||||
}
|
||||
nextEventCycle = earliest(nextEventCycle, pwmState.nextServiceCycle);
|
||||
} while (pwmState.cnt && (cyclesToGo < 100));
|
||||
}
|
||||
|
||||
for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) {
|
||||
uint32_t mask = 1<<i;
|
||||
|
||||
// If it's not on, ignore!
|
||||
if (!(wvfState.waveformEnabled & mask)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Waveform *wave = &wvfState.waveform[i];
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
|
||||
// Disable any waveforms that are done
|
||||
if (wave->expiryCycle) {
|
||||
int32_t expiryToGo = wave->expiryCycle - now;
|
||||
if (expiryToGo < 0) {
|
||||
// Done, remove!
|
||||
if (i == 16) {
|
||||
GP16O = 0;
|
||||
}
|
||||
GPOC = mask;
|
||||
wvfState.waveformEnabled &= ~mask;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for toggles
|
||||
int32_t cyclesToGo = wave->nextServiceCycle - now;
|
||||
if (cyclesToGo < 0) {
|
||||
uint32_t nextEdgeCycles;
|
||||
uint32_t desired = 0;
|
||||
uint32_t *timeToUpdate;
|
||||
wvfState.waveformState ^= mask;
|
||||
if (wvfState.waveformState & mask) {
|
||||
if (i == 16) {
|
||||
GP16O = 1;
|
||||
}
|
||||
GPOS = mask;
|
||||
|
||||
if (wvfState.waveformToChange & mask) {
|
||||
// Copy over next full-cycle timings
|
||||
wave->timeHighCycles = wvfState.waveformNewHigh;
|
||||
wave->desiredHighCycles = wvfState.waveformNewHigh;
|
||||
wave->timeLowCycles = wvfState.waveformNewLow;
|
||||
wave->desiredLowCycles = wvfState.waveformNewLow;
|
||||
wave->lastEdge = 0;
|
||||
wvfState.waveformToChange = 0;
|
||||
}
|
||||
if (wave->lastEdge) {
|
||||
desired = wave->desiredLowCycles;
|
||||
timeToUpdate = &wave->timeLowCycles;
|
||||
}
|
||||
nextEdgeCycles = wave->timeHighCycles;
|
||||
} else {
|
||||
if (i == 16) {
|
||||
GP16O = 0;
|
||||
}
|
||||
GPOC = mask;
|
||||
desired = wave->desiredHighCycles;
|
||||
timeToUpdate = &wave->timeHighCycles;
|
||||
nextEdgeCycles = wave->timeLowCycles;
|
||||
}
|
||||
if (desired) {
|
||||
desired = adjust(desired);
|
||||
int32_t err = desired - (now - wave->lastEdge);
|
||||
if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal
|
||||
err /= 2;
|
||||
*timeToUpdate += err;
|
||||
}
|
||||
}
|
||||
nextEdgeCycles = adjust(nextEdgeCycles);
|
||||
wave->nextServiceCycle = now + nextEdgeCycles;
|
||||
wave->lastEdge = now;
|
||||
}
|
||||
nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle);
|
||||
}
|
||||
|
||||
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
|
||||
uint32_t now = GetCycleCountIRQ();
|
||||
int32_t cycleDeltaNextEvent = nextEventCycle - now;
|
||||
int32_t cyclesLeftTimeout = timeoutCycle - now;
|
||||
done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0);
|
||||
} while (!done);
|
||||
} // if (wvfState.waveformEnabled)
|
||||
|
||||
if (wvfState.timer1CB) {
|
||||
nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB());
|
||||
}
|
||||
|
||||
int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ();
|
||||
|
||||
if (nextEventCycles < MINIRQTIME) {
|
||||
nextEventCycles = MINIRQTIME;
|
||||
}
|
||||
nextEventCycles -= DELTAIRQ;
|
||||
|
||||
// Do it here instead of global function to save time and because we know it's edge-IRQ
|
||||
T1L = nextEventCycles >> (turbo ? 1 : 0);
|
||||
}
|
||||
|
||||
};
|
@ -39,4 +39,16 @@ extern int __analogRead(uint8_t pin)
|
||||
|
||||
extern int analogRead(uint8_t pin) __attribute__ ((weak, alias("__analogRead")));
|
||||
|
||||
|
||||
void __analogReference(uint8_t mode)
|
||||
{
|
||||
// Only DEFAULT is supported on the ESP8266
|
||||
if (mode != DEFAULT) {
|
||||
DEBUGV("analogReference called with illegal mode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern void analogReference(uint8_t mode) __attribute__ ((weak, alias("__analogReference")));
|
||||
|
||||
};
|
||||
|
@ -82,7 +82,8 @@ extern void __pinMode(uint8_t pin, uint8_t mode) {
|
||||
}
|
||||
|
||||
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
||||
stopWaveform(pin);
|
||||
stopWaveform(pin); // Disable any Tone or startWaveform on this pin
|
||||
_stopPWM(pin); // and any analogWrites (PWM)
|
||||
if(pin < 16){
|
||||
if(val) GPOS = (1 << pin);
|
||||
else GPOC = (1 << pin);
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
// 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")));
|
||||
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -20,17 +20,34 @@
|
||||
|
||||
#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");
|
||||
}
|
||||
|
@ -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__)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
213
cores/esp8266/exc-c-wrapper-handler.S
Normal file
213
cores/esp8266/exc-c-wrapper-handler.S
Normal file
@ -0,0 +1,213 @@
|
||||
// exc-c-wrapper-handler.S, this is a reduced version of the original file at
|
||||
// https://github.com/qca/open-ath9k-htc-firmware/blob/master/sboot/magpie_1_1/sboot/athos/src/xtos/exc-c-wrapper-handler.S#L62-L67
|
||||
//
|
||||
|
||||
// exc-c-wrapper-handler.S - General Exception Handler that Dispatches C Handlers
|
||||
|
||||
// Copyright (c) 2002-2004, 2006-2007, 2010 Tensilica Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include <xtensa/coreasm.h>
|
||||
#include <xtensa/corebits.h>
|
||||
#include <xtensa/config/specreg.h>
|
||||
// #include "xtos-internal.h"
|
||||
// #ifdef SIMULATOR
|
||||
// #include <xtensa/simcall.h>
|
||||
// #endif
|
||||
|
||||
#include "xtruntime-frames.h"
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Verified that the ASM generated UEXC_xxx values match, the corresponding
|
||||
// values in `struct __exception_frame` used in the "C" code.
|
||||
//
|
||||
#include "esp8266_undocumented.h"
|
||||
.if (UEXC_pc != VERIFY_UEXC_pc)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_ps != VERIFY_UEXC_ps)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_sar != VERIFY_UEXC_sar)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_vpri != VERIFY_UEXC_vpri)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a0 != VERIFY_UEXC_a0)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a2 != VERIFY_UEXC_a2)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a3 != VERIFY_UEXC_a3)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a4 != VERIFY_UEXC_a4)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a5 != VERIFY_UEXC_a5)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a6 != VERIFY_UEXC_a6)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a7 != VERIFY_UEXC_a7)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a8 != VERIFY_UEXC_a8)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a9 != VERIFY_UEXC_a9)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a10 != VERIFY_UEXC_a10)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a11 != VERIFY_UEXC_a11)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a12 != VERIFY_UEXC_a12)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a13 != VERIFY_UEXC_a13)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a14 != VERIFY_UEXC_a14)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_a15 != VERIFY_UEXC_a15)
|
||||
.err
|
||||
.endif
|
||||
.if (UEXC_exccause != VERIFY_UEXC_exccause)
|
||||
.err
|
||||
.endif
|
||||
.if (UserFrameSize != VERIFY_UserFrameSize)
|
||||
.err
|
||||
.endif
|
||||
.if (UserFrameTotalSize != VERIFY_UserFrameTotalSize)
|
||||
.err
|
||||
.endif
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* This is the general exception assembly-level handler that dispatches C handlers.
|
||||
*/
|
||||
.section .iram.text
|
||||
.align 4
|
||||
.literal_position
|
||||
.global _xtos_c_wrapper_handler
|
||||
_xtos_c_wrapper_handler:
|
||||
|
||||
// HERE: a2, a3, a4 have been saved to exception stack frame allocated with a1 (sp).
|
||||
// a2 contains EXCCAUSE.
|
||||
s32i a5, a1, UEXC_a5 // a5 will get clobbered by ENTRY after the pseudo-CALL4
|
||||
// (a4..a15 spilled as needed; save if modified)
|
||||
|
||||
//NOTA: Possible future improvement:
|
||||
// keep interrupts disabled until we get into the handler, such that
|
||||
// we don't have to save other critical state such as EXCVADDR here.
|
||||
// @mhightower83 - This promise was broken by an "rsil a13, 0" below.
|
||||
//rsr a3, EXCVADDR
|
||||
s32i a2, a1, UEXC_exccause
|
||||
//s32i a3, a1, UEXC_excvaddr
|
||||
|
||||
// Set PS fields:
|
||||
// EXCM = 0
|
||||
// WOE = __XTENSA_CALL0_ABI__ ? 0 : 1
|
||||
// UM = 1
|
||||
// INTLEVEL = EXCM_LEVEL = 1
|
||||
// CALLINC = __XTENSA_CALL0_ABI__ ? 0 : 1
|
||||
// OWB = 0 (really, a dont care if !__XTENSA_CALL0_ABI__)
|
||||
|
||||
// movi a2, 0x23 // 0x21, PS_UM|PS_INTLEVEL(XCHAL_EXCM_LEVEL)
|
||||
// @mhightower83 - use INTLEVEL 15 instead of 3 for Arduino like interrupt support??
|
||||
movi a2, 0x2F // 0x21, PS_UM|PS_INTLEVEL(15)
|
||||
rsr a3, EPC_1
|
||||
// @mhightower83 - I assume PS.EXCM was set and now is being cleared, thus
|
||||
// allowing new exceptions and interrupts within PS_INTLEVEL to be possible.
|
||||
// We have set INTLEVEL to 15 to block any possible interrupts.
|
||||
xsr a2, PS
|
||||
|
||||
// HERE: window overflows enabled, but NOT SAFE because we're not quite
|
||||
// in a valid windowed context (haven't restored a1 yet...);
|
||||
// so don't cause any (keep to a0..a3) until we've saved critical state and restored a1:
|
||||
|
||||
// NOTE: MUST SAVE EPC1 before causing any overflows, because overflows corrupt EPC1.
|
||||
s32i a3, a1, UEXC_pc
|
||||
s32i a2, a1, UEXC_ps
|
||||
s32i a0, a1, UEXC_a0 // save the rest of the registers
|
||||
s32i a6, a1, UEXC_a6
|
||||
s32i a7, a1, UEXC_a7
|
||||
s32i a8, a1, UEXC_a8
|
||||
s32i a9, a1, UEXC_a9
|
||||
s32i a10, a1, UEXC_a10
|
||||
s32i a11, a1, UEXC_a11
|
||||
s32i a12, a1, UEXC_a12
|
||||
s32i a13, a1, UEXC_a13
|
||||
s32i a14, a1, UEXC_a14
|
||||
s32i a15, a1, UEXC_a15
|
||||
rsync // wait for WSR to PS to complete
|
||||
rsr a12, SAR
|
||||
|
||||
// @mhightower83 - I think, after the next instruction, we have the potential of
|
||||
// losing UEXC_excvaddr. Which the earlier comment said we need to preserve for
|
||||
// the exception handler. We keep interrupts off when calling the "C" exception
|
||||
// handler. For the use cases that I am looking at, this is a must. If there are
|
||||
// future use cases that need interrupts enabled, those "C" exception handlers
|
||||
// can turn them on.
|
||||
//
|
||||
// rsil a13, 0
|
||||
|
||||
movi a13, _xtos_c_handler_table // &table
|
||||
l32i a15, a1, UEXC_exccause // arg2: exccause
|
||||
s32i a12, a1, UEXC_sar
|
||||
addx4 a12, a15, a13 // a12 = table[exccause]
|
||||
l32i a12, a12, 0 // ...
|
||||
mov a2, a1 // arg1: exception parameters
|
||||
mov a3, a15 // arg2: exccause
|
||||
beqz a12, 1f // null handler => skip call
|
||||
callx0 a12 // call C exception handler for this exception
|
||||
1:
|
||||
// Now exit the handler.
|
||||
|
||||
// Restore special registers
|
||||
l32i a14, a1, UEXC_sar
|
||||
|
||||
// load early - saves two cycles - @mhightower83
|
||||
movi a0, _xtos_return_from_exc
|
||||
|
||||
// @mhightower83 - For compatibility with Arduino interrupt architecture, we
|
||||
// keep interrupts 100% disabled.
|
||||
// /*
|
||||
// * Disable interrupts while returning from the pseudo-CALL setup above,
|
||||
// * for the same reason they were disabled while doing the pseudo-CALL:
|
||||
// * this sequence restores SP such that it doesn't reflect the allocation
|
||||
// * of the exception stack frame, which we still need to return from
|
||||
// * the exception.
|
||||
// */
|
||||
// rsil a12, 1 // XCHAL_EXCM_LEVEL
|
||||
rsil a12, 15 // All levels blocked.
|
||||
wsr a14, SAR
|
||||
jx a0
|
||||
|
||||
/* FIXME: what about _GeneralException ? */
|
||||
.size _xtos_c_wrapper_handler, . - _xtos_c_wrapper_handler
|
113
cores/esp8266/exc-sethandler.cpp
Normal file
113
cores/esp8266/exc-sethandler.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Adaptation of _xtos_set_exception_handler for Arduino ESP8266 core
|
||||
*
|
||||
* This replacement for the Boot ROM `_xtos_set_exception_handler` is used to
|
||||
* install our replacement `_xtos_c_wrapper_handler`. This change protects the
|
||||
* value of `excvaddr` from corruption.
|
||||
*
|
||||
*
|
||||
* Details
|
||||
*
|
||||
* The issue, the Boot ROM "C" wrapper for exception handlers,
|
||||
* `_xtos_c_wrapper_handler`, turns interrupts back on. This leaves `excvaddr`
|
||||
* exposed to possible overwrite before it is read. For example, if an interrupt
|
||||
* is taken during the exception handler processing and the ISR handler
|
||||
* generates a new exception, the original value of `excvaddr` is lost. To
|
||||
* address this issue we have a replacement `_xtos_c_wrapper_handler` in file
|
||||
* `exc-c-wrapper-handler.S`.
|
||||
*
|
||||
* An overview, of an exception at entry: New interrupts are blocked by EXCM
|
||||
* being set. Once cleared, interrupts above the current INTLEVEL and exceptions
|
||||
* (w/o creating a DoubleException) can occur.
|
||||
*
|
||||
* Using our replacement for `_xtos_c_wrapper_handler`, INTLEVEL is raised to 15
|
||||
* with EXCM cleared.
|
||||
*
|
||||
* The original Boot ROM `_xtos_c_wrapper_handler` at entry would set INTLEVEL
|
||||
* to 3 with EXCM cleared, save registers, then do a `rsil 0` (interrupts fully
|
||||
* enabled!) just before calling the registered "C" Exception handler. Our
|
||||
* replacement keeps INTLEVEL at 15. This is needed to support the Arduino model
|
||||
* of interrupts disabled while an ISR runs.
|
||||
*
|
||||
* And we also need it for umm_malloc to work safely with an IRAM heap from an
|
||||
* ISR call. While malloc() will supply DRAM for all allocation from an ISR, we
|
||||
* want free() to safely operate from an ISR to avoid a leak potential.
|
||||
*
|
||||
* If an exception handler needs interrupts enabled, it would be done after it
|
||||
* has consumed the value of `excvaddr`. Whether such action is safe is left to
|
||||
* the exception handler writer to determine. However, with our current
|
||||
* architecture, I am not convinced it can be done safely.
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP) || defined(NEW_EXC_C_WRAPPER)
|
||||
|
||||
/*
|
||||
* The original module source code came from:
|
||||
* https://github.com/qca/open-ath9k-htc-firmware/blob/master/sboot/magpie_1_1/sboot/athos/src/xtos/exc-sethandler.c
|
||||
*
|
||||
* It has been revised to use Arduino ESP8266 core includes, types, and
|
||||
* formating.
|
||||
*/
|
||||
|
||||
/* exc-sethandler.c - register an exception handler in XTOS */
|
||||
|
||||
/*
|
||||
* Copyright (c) 1999-2006 Tensilica Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "esp8266_undocumented.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Register a C handler for the specified general exception
|
||||
* (specified EXCCAUSE value).
|
||||
*/
|
||||
fn_c_exception_handler_t _xtos_set_exception_handler(int cause, fn_c_exception_handler_t fn)
|
||||
{
|
||||
fn_c_exception_handler_t ret;
|
||||
|
||||
if( (unsigned) cause >= XCHAL_EXCCAUSE_NUM )
|
||||
return 0;
|
||||
|
||||
if( fn == 0 )
|
||||
fn = &_xtos_p_none;
|
||||
|
||||
ret = _xtos_c_handler_table[cause];
|
||||
|
||||
_xtos_exc_handler_table[cause] = ( (fn == &_xtos_p_none)
|
||||
? &_xtos_unhandled_exception
|
||||
: &_xtos_c_wrapper_handler );
|
||||
|
||||
_xtos_c_handler_table[cause] = fn;
|
||||
|
||||
if( ret == &_xtos_p_none )
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@ -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;
|
||||
}
|
||||
|
||||
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);
|
||||
// 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 + 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// We use flashWrite overload that handles proper alignment
|
||||
if (ESP.flashWrite(addr, src, size)) {
|
||||
return FLASH_HAL_OK;
|
||||
} else {
|
||||
return FLASH_HAL_WRITE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t flash_hal_erase(uint32_t addr, uint32_t size) {
|
||||
|
@ -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
|
||||
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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
192
cores/esp8266/mmu_iram.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2020 M Hightower
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "mmu_iram.h"
|
||||
#include <user_interface.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
#if (MMU_ICACHE_SIZE == 0x4000)
|
||||
#define SOC_CACHE_SIZE 0 // 16KB
|
||||
#pragma message("ICACHE size 16K")
|
||||
#else
|
||||
#define SOC_CACHE_SIZE 1 // 32KB
|
||||
#endif
|
||||
|
||||
#if (MMU_ICACHE_SIZE == 0x4000)
|
||||
/*
|
||||
* "Cache_Read_Enable" as in Instruction Read Cache enable, ICACHE.
|
||||
*
|
||||
* The Boot ROM "Cache_Read_Enable" API enables virtual execution of code in
|
||||
* flash memory via an instruction cache, ICACHE. The cache size can be set to
|
||||
* 16K or 32K, and the NONOS SDK 2.x will always set ICACHE to 32K during
|
||||
* initialization.
|
||||
*
|
||||
* When you select a 16K vs. a 32K ICACHE size, you get 48K contiguous IRAM to
|
||||
* work with. The NONOS SDK 2.x does not have an option to select 16K/32K. This
|
||||
* is where this Boot ROM wrapper for Cache_Read_Enable comes in.
|
||||
* Note, there is support for 16K/32K cache size in NONOS SDK 3.0; however, I
|
||||
* do not see an option to have it has part of your general IRAM. That SDK adds
|
||||
* it to the heap.
|
||||
*
|
||||
* With this wrapper function, we override the SDK's ICACHE size.
|
||||
* A build-time define MMU_ICACHE_SIZE selects 16K or 32K ICACHE size.
|
||||
*
|
||||
* mmu_status is used to help understand calling behavior. At some point, it
|
||||
* should be trimmed down to the essentials.
|
||||
*
|
||||
* During NONOS SDK init, it will call to enable. Then call later, to process a
|
||||
* spi_flash_get_id request, it will disable/enable around the Boot ROM SPI calls.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Arguments for Cache_Read_Enable
|
||||
*
|
||||
* The first two arguments appear to specify which 1MB block of the flash to
|
||||
* access with the ICACHE.
|
||||
*
|
||||
* The first argument, map, is partly understood. It has three values 0, 1,
|
||||
* and 2+. The value 0 selects the even 1MB block, and 1 selects the odd 1MB
|
||||
* block, in other words, bit20 of the flash address. No guesses for a value
|
||||
* of 2 or greater.
|
||||
*
|
||||
* The second argument, p, bit 21 of the flash address. Or, it may be bits 23,
|
||||
* 22, 21 of the flash address. A three-bit field is cleared in the register
|
||||
* for this argument; however, I have not seen any examples of it being used
|
||||
* that way.
|
||||
*
|
||||
* The third argument, v, holds our center of attention. A value of 0 selects
|
||||
* 16K, and a value of 1 selects a 32K ICACHE. This is the only parameter we
|
||||
* need to modify on Cache_Read_Enable calls.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Clues and Information sources
|
||||
*
|
||||
* "Cache_Read_Enable" is underdocumented. Main sources of information were from
|
||||
* rboot, zboot, https://richard.burtons.org/2015/06/12/esp8266-cache_read_enable/,
|
||||
* and other places. And some additional expermentation.
|
||||
*
|
||||
* Searching through the NONOS SDK shows nothing on this API; however, some
|
||||
* clues on what the NONOS SDK might be doing with ICACHE related calls can be
|
||||
* found in the RTOS SDK.
|
||||
* eg. ESP8266_RTOS_SDK/blob/master/components/spi_flash/src/spi_flash_raw.c
|
||||
* also calls to it in the bootloader.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ROM_Cache_Read_Enable
|
||||
#define ROM_Cache_Read_Enable 0x40004678U
|
||||
#endif
|
||||
|
||||
typedef void (*fp_Cache_Read_Enable_t)(uint8_t map, uint8_t p, uint8_t v);
|
||||
#define real_Cache_Read_Enable (reinterpret_cast<fp_Cache_Read_Enable_t>(ROM_Cache_Read_Enable))
|
||||
|
||||
void IRAM_ATTR Cache_Read_Enable(uint8_t map, uint8_t p, uint8_t v) {
|
||||
(void)v;
|
||||
real_Cache_Read_Enable(map, p, SOC_CACHE_SIZE);
|
||||
}
|
||||
|
||||
#ifdef DEV_DEBUG_PRINT
|
||||
|
||||
#if 0
|
||||
#ifndef ROM_Cache_Read_Disable
|
||||
#define ROM_Cache_Read_Disable 0x400047f0
|
||||
#endif
|
||||
|
||||
typedef void (*fp_Cache_Read_Disable_t)(void);
|
||||
#define real_Cache_Read_Disable (reinterpret_cast<fp_Cache_Read_Disable_t>(ROM_Cache_Read_Disable))
|
||||
/*
|
||||
*
|
||||
*/
|
||||
void IRAM_ATTR Cache_Read_Disable(void) {
|
||||
real_Cache_Read_Disable();
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Early adjustment for CPU crystal frequency, so debug printing will work.
|
||||
* This should not be left enabled all the time in Cashe_Read..., I am concerned
|
||||
* that there may be unknown interference with the NONOS SDK startup.
|
||||
*
|
||||
* Inspired by:
|
||||
* https://github.com/pvvx/esp8266web/blob/2e25559bc489487747205db2ef171d48326b32d4/app/sdklib/system/app_main.c#L581-L591
|
||||
*/
|
||||
extern "C" uint8_t rom_i2c_readReg(uint8_t block, uint8_t host_id, uint8_t reg_add);
|
||||
extern "C" void rom_i2c_writeReg(uint8_t block, uint8_t host_id, uint8_t reg_add, uint8_t data);
|
||||
|
||||
extern "C" void IRAM_ATTR set_pll(void)
|
||||
{
|
||||
#if !defined(F_CRYSTAL)
|
||||
#define F_CRYSTAL 26000000
|
||||
#endif
|
||||
if (F_CRYSTAL != 40000000) {
|
||||
// At Boot ROM(-BIOS) start, it assumes a 40MHz crystal.
|
||||
// If it is not, we assume a 26MHz crystal.
|
||||
// There is no support for 24MHz crustal at this time.
|
||||
if(rom_i2c_readReg(103,4,1) != 136) { // 8: 40MHz, 136: 26MHz
|
||||
// Assume 26MHz crystal
|
||||
// soc_param0: 0: 40MHz, 1: 26MHz, 2: 24MHz
|
||||
// set 80MHz PLL CPU
|
||||
rom_i2c_writeReg(103,4,1,136);
|
||||
rom_i2c_writeReg(103,4,2,145);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//C This was used to probe at different stages of boot the state of the PLL
|
||||
//C register. I think we can get rid of this one.
|
||||
extern "C" void IRAM_ATTR dbg_set_pll(void)
|
||||
{
|
||||
char r103_4_1 = rom_i2c_readReg(103,4,1);
|
||||
char r103_4_2 = rom_i2c_readReg(103,4,2);
|
||||
set_pll();
|
||||
ets_uart_printf("\nrom_i2c_readReg(103,4,1) == %u\n", r103_4_1);
|
||||
ets_uart_printf( "rom_i2c_readReg(103,4,2) == %u\n", r103_4_2);
|
||||
}
|
||||
|
||||
/*
|
||||
This helps keep the UART enabled at user_init() so we can get a few more
|
||||
messages printed.
|
||||
*/
|
||||
extern struct rst_info resetInfo;
|
||||
extern "C" void __pinMode( uint8_t pin, uint8_t mode );
|
||||
|
||||
inline bool is_gpio_persistent(void) {
|
||||
return REASON_EXCEPTION_RST <= resetInfo.reason &&
|
||||
REASON_SOFT_RESTART >= resetInfo.reason;
|
||||
}
|
||||
|
||||
extern "C" void pinMode( uint8_t pin, uint8_t mode ) {
|
||||
static bool in_initPins = true;
|
||||
if (in_initPins && (1 == pin)) {
|
||||
if (!is_gpio_persistent()) {
|
||||
/* Restore pin to TX after Power-on and EXT_RST */
|
||||
__pinMode(pin, FUNCTION_0);
|
||||
}
|
||||
in_initPins = false;
|
||||
return;
|
||||
}
|
||||
|
||||
__pinMode( pin, mode );
|
||||
}
|
||||
#endif // #ifdef DEV_DEBUG_PRINT
|
||||
|
||||
#endif // #if (MMU_ICACHE_SIZE == 0x4000)
|
||||
|
||||
};
|
221
cores/esp8266/mmu_iram.h
Normal file
221
cores/esp8266/mmu_iram.h
Normal file
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2020 M Hightower
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __MMU_IRAM_H
|
||||
#define __MMU_IRAM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <c_types.h>
|
||||
#include <assert.h>
|
||||
#include <esp8266_undocumented.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//C This turns on range checking. Is this the value you want to trigger it?
|
||||
#ifdef DEBUG_ESP_CORE
|
||||
#define DEBUG_ESP_MMU
|
||||
#endif
|
||||
|
||||
#if defined(CORE_MOCK)
|
||||
#define ets_uart_printf(...) do {} while(false)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* DEV_DEBUG_PRINT:
|
||||
* Debug printing macros for printing before before, during, and after
|
||||
* NONOS SDK initializes. May or maynot be safe during NONOS SDK
|
||||
* initialization. As in printing from functions called on by the SDK
|
||||
* during the SDK initialization.
|
||||
*
|
||||
#define DEV_DEBUG_PRINT
|
||||
*/
|
||||
|
||||
#if defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
|
||||
#include <esp8266_peri.h>
|
||||
|
||||
#define DBG_MMU_FLUSH(a) while((USS(a) >> USTXC) & 0xff) {}
|
||||
|
||||
#if defined(DEV_DEBUG_PRINT)
|
||||
extern void set_pll(void);
|
||||
extern void dbg_set_pll(void);
|
||||
|
||||
#define DBG_MMU_PRINTF(fmt, ...) \
|
||||
set_pll(); \
|
||||
uart_buff_switch(0); \
|
||||
ets_uart_printf(fmt, ##__VA_ARGS__); \
|
||||
DBG_MMU_FLUSH(0)
|
||||
|
||||
#else // ! defined(DEV_DEBUG_PRINT)
|
||||
#define DBG_MMU_PRINTF(fmt, ...) ets_uart_printf(fmt, ##__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#else // ! defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
|
||||
#define DBG_MMU_FLUSH(...) do {} while(false)
|
||||
#define DBG_MMU_PRINTF(...) do {} while(false)
|
||||
#endif // defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
bool mmu_is_iram(const void *addr) {
|
||||
#define IRAM_START 0x40100000UL
|
||||
#ifndef MMU_IRAM_SIZE
|
||||
#if defined(__GNUC__) && !defined(CORE_MOCK)
|
||||
#warning "MMU_IRAM_SIZE was undefined, setting to 0x8000UL!"
|
||||
#endif
|
||||
#define MMU_IRAM_SIZE 0x8000UL
|
||||
#endif
|
||||
#define IRAM_END (IRAM_START + MMU_IRAM_SIZE)
|
||||
|
||||
return (IRAM_START <= (uintptr_t)addr && IRAM_END > (uintptr_t)addr);
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
bool mmu_is_dram(const void *addr) {
|
||||
#define DRAM_START 0x3FF80000UL
|
||||
#define DRAM_END 0x40000000UL
|
||||
|
||||
return (DRAM_START <= (uintptr_t)addr && DRAM_END > (uintptr_t)addr);
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
bool mmu_is_icache(const void *addr) {
|
||||
#define ICACHE_START 0x40200000UL
|
||||
#define ICACHE_END (ICACHE_START + 0x100000UL)
|
||||
|
||||
return (ICACHE_START <= (uintptr_t)addr && ICACHE_END > (uintptr_t)addr);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ESP_MMU
|
||||
#define ASSERT_RANGE_TEST_WRITE(a) \
|
||||
if (mmu_is_iram(a) || mmu_is_dram(a)) { \
|
||||
} else { \
|
||||
DBG_MMU_PRINTF("\nexcvaddr: %p\n", a); \
|
||||
assert(("Outside of Range - Write" && false)); \
|
||||
}
|
||||
|
||||
#define ASSERT_RANGE_TEST_READ(a) \
|
||||
if (mmu_is_iram(a) || mmu_is_dram(a) || mmu_is_icache(a)) { \
|
||||
} else { \
|
||||
DBG_MMU_PRINTF("\nexcvaddr: %p\n", a); \
|
||||
assert(("Outside of Range - Read" && false)); \
|
||||
}
|
||||
|
||||
#else
|
||||
#define ASSERT_RANGE_TEST_WRITE(a) do {} while(false)
|
||||
#define ASSERT_RANGE_TEST_READ(a) do {} while(false)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Some inlines to allow faster random access to non32bit access of iRAM or
|
||||
* iCACHE data elements. These remove the extra time and stack space that would
|
||||
* have occured by relying on exception processing.
|
||||
*/
|
||||
static inline __attribute__((always_inline))
|
||||
uint8_t mmu_get_uint8(const void *p8) {
|
||||
ASSERT_RANGE_TEST_READ(p8);
|
||||
uint32_t val = (*(uint32_t *)((uintptr_t)p8 & ~0x3));
|
||||
uint32_t pos = ((uintptr_t)p8 & 0x3) * 8;
|
||||
val >>= pos;
|
||||
return (uint8_t)val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
uint16_t mmu_get_uint16(const uint16_t *p16) {
|
||||
ASSERT_RANGE_TEST_READ(p16);
|
||||
uint32_t val = (*(uint32_t *)((uintptr_t)p16 & ~0x3));
|
||||
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||
val >>= pos;
|
||||
return (uint16_t)val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
int16_t mmu_get_int16(const int16_t *p16) {
|
||||
ASSERT_RANGE_TEST_READ(p16);
|
||||
uint32_t val = (*(uint32_t *)((uintptr_t)p16 & ~0x3));
|
||||
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||
val >>= pos;
|
||||
return (int16_t)val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
uint8_t mmu_set_uint8(void *p8, const uint8_t val) {
|
||||
ASSERT_RANGE_TEST_WRITE(p8);
|
||||
uint32_t pos = ((uintptr_t)p8 & 0x3) * 8;
|
||||
uint32_t sval = val << pos;
|
||||
uint32_t valmask = 0x0FF << pos;
|
||||
|
||||
uint32_t *p32 = (uint32_t *)((uintptr_t)p8 & ~0x3);
|
||||
uint32_t ival = *p32;
|
||||
ival &= (~valmask);
|
||||
ival |= sval;
|
||||
*p32 = ival;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
uint16_t mmu_set_uint16(uint16_t *p16, const uint16_t val) {
|
||||
ASSERT_RANGE_TEST_WRITE(p16);
|
||||
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||
uint32_t sval = val << pos;
|
||||
uint32_t valmask = 0x0FFFF << pos;
|
||||
|
||||
uint32_t *p32 = (uint32_t *)((uintptr_t)p16 & ~0x3);
|
||||
uint32_t ival = *p32;
|
||||
ival &= (~valmask);
|
||||
ival |= sval;
|
||||
*p32 = ival;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
int16_t mmu_set_int16(int16_t *p16, const int16_t val) {
|
||||
ASSERT_RANGE_TEST_WRITE(p16);
|
||||
uint32_t sval = (uint16_t)val;
|
||||
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||
sval <<= pos;
|
||||
uint32_t valmask = 0x0FFFF << pos;
|
||||
|
||||
uint32_t *p32 = (uint32_t *)((uintptr_t)p16 & ~0x3);
|
||||
uint32_t ival = *p32;
|
||||
ival &= (~valmask);
|
||||
ival |= sval;
|
||||
*p32 = ival;
|
||||
return val;
|
||||
}
|
||||
|
||||
#if (MMU_IRAM_SIZE > 32*1024) && !defined(MMU_SEC_HEAP)
|
||||
extern void _text_end(void);
|
||||
#define MMU_SEC_HEAP mmu_sec_heap()
|
||||
#define MMU_SEC_HEAP_SIZE mmu_sec_heap_size()
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void *mmu_sec_heap(void) {
|
||||
uint32_t sec_heap = (uint32_t)_text_end + 32;
|
||||
return (void *)(sec_heap &= ~7);
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
size_t mmu_sec_heap_size(void) {
|
||||
return (size_t)0xC000UL - ((size_t)mmu_sec_heap() - 0x40100000UL);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
151
cores/esp8266/reboot_uart_dwnld.cpp
Normal file
151
cores/esp8266/reboot_uart_dwnld.cpp
Normal 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();
|
||||
}
|
||||
|
23
cores/esp8266/reboot_uart_dwnld.h
Normal file
23
cores/esp8266/reboot_uart_dwnld.h
Normal 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();
|
@ -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
|
||||
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
#ifndef __sntp_lwip2_h__
|
||||
#define __sntp_lwip2_h__
|
||||
|
||||
#include <coredecls.h>
|
||||
|
||||
#endif
|
@ -26,8 +26,6 @@
|
||||
*/
|
||||
#include <limits>
|
||||
#include "FS.h"
|
||||
#undef max
|
||||
#undef min
|
||||
#include "FSImpl.h"
|
||||
extern "C" {
|
||||
#include "spiffs/spiffs.h"
|
||||
|
62
cores/esp8266/stdlib_noniso.cpp
Normal file
62
cores/esp8266/stdlib_noniso.cpp
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
102
cores/esp8266/umm_malloc/umm_heap_select.h
Normal file
102
cores/esp8266/umm_malloc/umm_heap_select.h
Normal file
@ -0,0 +1,102 @@
|
||||
#ifndef UMM_MALLOC_SELECT_H
|
||||
#define UMM_MALLOC_SELECT_H
|
||||
|
||||
#include <umm_malloc/umm_malloc.h>
|
||||
|
||||
#ifndef ALWAYS_INLINE
|
||||
#define ALWAYS_INLINE inline __attribute__ ((always_inline))
|
||||
#endif
|
||||
|
||||
// Use FORCE_ALWAYS_INLINE to ensure HeapSelect... construtor/deconstructor
|
||||
// are placed in IRAM
|
||||
#ifdef FORCE_ALWAYS_INLINE_HEAP_SELECT
|
||||
#define MAYBE_ALWAYS_INLINE ALWAYS_INLINE
|
||||
#else
|
||||
#define MAYBE_ALWAYS_INLINE
|
||||
#endif
|
||||
|
||||
/*
|
||||
This class is modeled after interrupts.h
|
||||
|
||||
HeapSelectIram is used to temporarily select an alternate Heap.
|
||||
|
||||
{
|
||||
{
|
||||
HeapSelectIram lock;
|
||||
// allocate memory here
|
||||
}
|
||||
allocations here are from the old Heap selection
|
||||
}
|
||||
*/
|
||||
|
||||
class HeapSelect {
|
||||
public:
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelect(size_t id) { (void)id; }
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelect() {}
|
||||
#else
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelect(size_t id) : _heap_id(umm_get_current_heap_id()) {
|
||||
umm_set_heap_by_id(id);
|
||||
}
|
||||
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelect() {
|
||||
umm_set_heap_by_id(_heap_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t _heap_id;
|
||||
#endif
|
||||
};
|
||||
|
||||
class HeapSelectIram {
|
||||
public:
|
||||
#ifdef UMM_HEAP_IRAM
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelectIram() : _heap_id(umm_get_current_heap_id()) {
|
||||
umm_set_heap_by_id(UMM_HEAP_IRAM);
|
||||
}
|
||||
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelectIram() {
|
||||
umm_set_heap_by_id(_heap_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t _heap_id;
|
||||
|
||||
#else
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelectIram() {}
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelectIram() {}
|
||||
#endif
|
||||
};
|
||||
|
||||
class HeapSelectDram {
|
||||
public:
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelectDram() {}
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelectDram() {}
|
||||
#else
|
||||
MAYBE_ALWAYS_INLINE
|
||||
HeapSelectDram() : _heap_id(umm_get_current_heap_id()) {
|
||||
umm_set_heap_by_id(UMM_HEAP_DRAM);
|
||||
}
|
||||
|
||||
MAYBE_ALWAYS_INLINE
|
||||
~HeapSelectDram() {
|
||||
umm_set_heap_by_id(_heap_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t _heap_id;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // UMM_MALLOC_SELECT_H
|
@ -23,25 +23,25 @@
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
UMM_HEAP_INFO ummHeapInfo;
|
||||
// UMM_HEAP_INFO ummHeapInfo;
|
||||
|
||||
void *umm_info( void *ptr, bool force ) {
|
||||
UMM_CRITICAL_DECL(id_info);
|
||||
|
||||
if(umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
uint16_t blockNo = 0;
|
||||
|
||||
/* Protect the critical section... */
|
||||
UMM_CRITICAL_ENTRY(id_info);
|
||||
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
|
||||
/*
|
||||
* Clear out all of the entries in the ummHeapInfo structure before doing
|
||||
* any calculations..
|
||||
*/
|
||||
memset( &ummHeapInfo, 0, sizeof( ummHeapInfo ) );
|
||||
memset( &_context->info, 0, sizeof( _context->info ) );
|
||||
|
||||
DBGLOG_FORCE( force, "\n" );
|
||||
DBGLOG_FORCE( force, "+----------+-------+--------+--------+-------+--------+--------+\n" );
|
||||
@ -65,18 +65,18 @@ void *umm_info( void *ptr, bool force ) {
|
||||
while( UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK ) {
|
||||
size_t curBlocks = (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo;
|
||||
|
||||
++ummHeapInfo.totalEntries;
|
||||
ummHeapInfo.totalBlocks += curBlocks;
|
||||
++_context->info.totalEntries;
|
||||
_context->info.totalBlocks += curBlocks;
|
||||
|
||||
/* Is this a free block? */
|
||||
|
||||
if( UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK ) {
|
||||
++ummHeapInfo.freeEntries;
|
||||
ummHeapInfo.freeBlocks += curBlocks;
|
||||
ummHeapInfo.freeBlocksSquared += (curBlocks * curBlocks);
|
||||
++_context->info.freeEntries;
|
||||
_context->info.freeBlocks += curBlocks;
|
||||
_context->info.freeBlocksSquared += (curBlocks * curBlocks);
|
||||
|
||||
if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) {
|
||||
ummHeapInfo.maxFreeContiguousBlocks = curBlocks;
|
||||
if (_context->info.maxFreeContiguousBlocks < curBlocks) {
|
||||
_context->info.maxFreeContiguousBlocks = curBlocks;
|
||||
}
|
||||
|
||||
DBGLOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|NF %5d|PF %5d|\n",
|
||||
@ -98,8 +98,8 @@ void *umm_info( void *ptr, bool force ) {
|
||||
return( ptr );
|
||||
}
|
||||
} else {
|
||||
++ummHeapInfo.usedEntries;
|
||||
ummHeapInfo.usedBlocks += curBlocks;
|
||||
++_context->info.usedEntries;
|
||||
_context->info.usedBlocks += curBlocks;
|
||||
|
||||
DBGLOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|\n",
|
||||
DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)),
|
||||
@ -131,35 +131,35 @@ void *umm_info( void *ptr, bool force ) {
|
||||
DBGLOG_FORCE( force, "+----------+-------+--------+--------+-------+--------+--------+\n" );
|
||||
|
||||
DBGLOG_FORCE( force, "Total Entries %5d Used Entries %5d Free Entries %5d\n",
|
||||
ummHeapInfo.totalEntries,
|
||||
ummHeapInfo.usedEntries,
|
||||
ummHeapInfo.freeEntries );
|
||||
_context->info.totalEntries,
|
||||
_context->info.usedEntries,
|
||||
_context->info.freeEntries );
|
||||
|
||||
DBGLOG_FORCE( force, "Total Blocks %5d Used Blocks %5d Free Blocks %5d\n",
|
||||
ummHeapInfo.totalBlocks,
|
||||
ummHeapInfo.usedBlocks,
|
||||
ummHeapInfo.freeBlocks );
|
||||
_context->info.totalBlocks,
|
||||
_context->info.usedBlocks,
|
||||
_context->info.freeBlocks );
|
||||
|
||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||
|
||||
DBGLOG_FORCE( force, "Usage Metric: %5d\n", umm_usage_metric());
|
||||
DBGLOG_FORCE( force, "Fragmentation Metric: %5d\n", umm_fragmentation_metric());
|
||||
DBGLOG_FORCE( force, "Usage Metric: %5d\n", umm_usage_metric_core(_context));
|
||||
DBGLOG_FORCE( force, "Fragmentation Metric: %5d\n", umm_fragmentation_metric_core(_context));
|
||||
|
||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||
|
||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||
#if !defined(UMM_INLINE_METRICS)
|
||||
if (ummHeapInfo.freeBlocks == ummStats.free_blocks) {
|
||||
if (_context->info.freeBlocks == _context->stats.free_blocks) {
|
||||
DBGLOG_FORCE( force, "heap info Free blocks and heap statistics Free blocks match.\n");
|
||||
} else {
|
||||
DBGLOG_FORCE( force, "\nheap info Free blocks %5d != heap statistics Free Blocks %5d\n\n",
|
||||
ummHeapInfo.freeBlocks,
|
||||
ummStats.free_blocks );
|
||||
_context->info.freeBlocks,
|
||||
_context->stats.free_blocks );
|
||||
}
|
||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||
#endif
|
||||
|
||||
print_stats(force);
|
||||
umm_print_stats(force);
|
||||
#endif
|
||||
|
||||
/* Release the critical section... */
|
||||
@ -170,20 +170,29 @@ void *umm_info( void *ptr, bool force ) {
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
size_t umm_free_heap_size_core( umm_heap_context_t *_context ) {
|
||||
return (size_t)_context->info.freeBlocks * sizeof(umm_block);
|
||||
}
|
||||
|
||||
size_t umm_free_heap_size( void ) {
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
umm_info(NULL, false);
|
||||
#endif
|
||||
return (size_t)ummHeapInfo.freeBlocks * sizeof(umm_block);
|
||||
|
||||
return umm_free_heap_size_core(umm_get_current_heap());
|
||||
}
|
||||
|
||||
//C Breaking change in upstream umm_max_block_size() was changed to
|
||||
//C umm_max_free_block_size() keeping old function name for (dot) releases.
|
||||
//C TODO: update at next major release.
|
||||
//C size_t umm_max_free_block_size( void ) {
|
||||
size_t umm_max_block_size_core( umm_heap_context_t *_context ) {
|
||||
return _context->info.maxFreeContiguousBlocks * sizeof(umm_block);
|
||||
}
|
||||
|
||||
size_t umm_max_block_size( void ) {
|
||||
umm_info(NULL, false);
|
||||
return ummHeapInfo.maxFreeContiguousBlocks * sizeof(umm_block);
|
||||
return umm_max_block_size_core(umm_get_current_heap());
|
||||
}
|
||||
|
||||
/*
|
||||
@ -191,50 +200,62 @@ size_t umm_max_block_size( void ) {
|
||||
umm_fragmentation_metric() must to be preceeded by a call to umm_info(NULL, false)
|
||||
for updated results.
|
||||
*/
|
||||
int umm_usage_metric( void ) {
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
umm_info(NULL, false);
|
||||
#endif
|
||||
DBGLOG_DEBUG( "usedBlocks %d totalBlocks %d\n", umm_metrics.usedBlocks, ummHeapInfo.totalBlocks);
|
||||
if (ummHeapInfo.freeBlocks)
|
||||
return (int)((ummHeapInfo.usedBlocks * 100)/(ummHeapInfo.freeBlocks));
|
||||
int umm_usage_metric_core( umm_heap_context_t *_context ) {
|
||||
//C Note, umm_metrics also appears in the upstrean w/o definition. I suspect it is suppose to be ummHeapInfo.
|
||||
// DBGLOG_DEBUG( "usedBlocks %d totalBlocks %d\n", umm_metrics.usedBlocks, ummHeapInfo.totalBlocks);
|
||||
DBGLOG_DEBUG( "usedBlocks %d totalBlocks %d\n", _context->info.usedBlocks, _context->info.totalBlocks);
|
||||
if (_context->info.freeBlocks)
|
||||
return (int)((_context->info.usedBlocks * 100)/(_context->info.freeBlocks));
|
||||
|
||||
return -1; // no freeBlocks
|
||||
}
|
||||
|
||||
int umm_usage_metric( void ) {
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
umm_info(NULL, false);
|
||||
#endif
|
||||
|
||||
return umm_usage_metric_core(umm_get_current_heap());
|
||||
}
|
||||
uint32_t sqrt32 (uint32_t n);
|
||||
|
||||
int umm_fragmentation_metric_core( umm_heap_context_t *_context ) {
|
||||
// DBGLOG_DEBUG( "freeBlocks %d freeBlocksSquared %d\n", umm_metrics.freeBlocks, ummHeapInfo.freeBlocksSquared);
|
||||
DBGLOG_DEBUG( "freeBlocks %d freeBlocksSquared %d\n", _context->info.freeBlocks, _context->info.freeBlocksSquared);
|
||||
if (0 == _context->info.freeBlocks) {
|
||||
return 0;
|
||||
} else {
|
||||
//upstream version: return (100 - (((uint32_t)(sqrtf(ummHeapInfo.freeBlocksSquared)) * 100)/(ummHeapInfo.freeBlocks)));
|
||||
return (100 - (((uint32_t)(sqrt32(_context->info.freeBlocksSquared)) * 100)/(_context->info.freeBlocks)));
|
||||
}
|
||||
}
|
||||
|
||||
int umm_fragmentation_metric( void ) {
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
umm_info(NULL, false);
|
||||
#endif
|
||||
DBGLOG_DEBUG( "freeBlocks %d freeBlocksSquared %d\n", umm_metrics.freeBlocks, ummHeapInfo.freeBlocksSquared);
|
||||
if (0 == ummHeapInfo.freeBlocks) {
|
||||
return 0;
|
||||
} else {
|
||||
//upstream version: return (100 - (((uint32_t)(sqrtf(ummHeapInfo.freeBlocksSquared)) * 100)/(ummHeapInfo.freeBlocks)));
|
||||
return (100 - (((uint32_t)(sqrt32(ummHeapInfo.freeBlocksSquared)) * 100)/(ummHeapInfo.freeBlocks)));
|
||||
}
|
||||
|
||||
return umm_fragmentation_metric_core(umm_get_current_heap());
|
||||
}
|
||||
|
||||
#ifdef UMM_INLINE_METRICS
|
||||
static void umm_fragmentation_metric_init( void ) {
|
||||
ummHeapInfo.freeBlocks = UMM_NUMBLOCKS - 2;
|
||||
ummHeapInfo.freeBlocksSquared = ummHeapInfo.freeBlocks * ummHeapInfo.freeBlocks;
|
||||
static void umm_fragmentation_metric_init( umm_heap_context_t *_context ) {
|
||||
_context->info.freeBlocks = UMM_NUMBLOCKS - 2;
|
||||
_context->info.freeBlocksSquared = _context->info.freeBlocks * _context->info.freeBlocks;
|
||||
}
|
||||
|
||||
static void umm_fragmentation_metric_add( uint16_t c ) {
|
||||
static void umm_fragmentation_metric_add( umm_heap_context_t *_context, uint16_t c ) {
|
||||
uint16_t blocks = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) - c;
|
||||
DBGLOG_DEBUG( "Add block %d size %d to free metric\n", c, blocks);
|
||||
ummHeapInfo.freeBlocks += blocks;
|
||||
ummHeapInfo.freeBlocksSquared += (blocks * blocks);
|
||||
_context->info.freeBlocks += blocks;
|
||||
_context->info.freeBlocksSquared += (blocks * blocks);
|
||||
}
|
||||
|
||||
static void umm_fragmentation_metric_remove( uint16_t c ) {
|
||||
static void umm_fragmentation_metric_remove( umm_heap_context_t *_context, uint16_t c ) {
|
||||
uint16_t blocks = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) - c;
|
||||
DBGLOG_DEBUG( "Remove block %d size %d from free metric\n", c, blocks);
|
||||
ummHeapInfo.freeBlocks -= blocks;
|
||||
ummHeapInfo.freeBlocksSquared -= (blocks * blocks);
|
||||
_context->info.freeBlocks -= blocks;
|
||||
_context->info.freeBlocksSquared -= (blocks * blocks);
|
||||
}
|
||||
#endif // UMM_INLINE_METRICS
|
||||
|
||||
|
@ -33,13 +33,14 @@ bool umm_integrity_check(void) {
|
||||
uint16_t prev;
|
||||
uint16_t cur;
|
||||
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
/* Iterate through all free blocks */
|
||||
prev = 0;
|
||||
UMM_CRITICAL_ENTRY(id_integrity);
|
||||
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
|
||||
while(1) {
|
||||
cur = UMM_NFREE(prev);
|
||||
|
||||
|
@ -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
|
||||
|
@ -37,12 +37,12 @@
|
||||
|
||||
|
||||
#if defined(UMM_POISON_CHECK_LITE)
|
||||
static bool check_poison_neighbors( uint16_t cur );
|
||||
static bool check_poison_neighbors( umm_heap_context_t *_context, uint16_t cur );
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||
void ICACHE_FLASH_ATTR print_stats(int force);
|
||||
void ICACHE_FLASH_ATTR umm_print_stats(int force);
|
||||
#endif
|
||||
|
||||
|
||||
@ -51,4 +51,21 @@ int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) __attribute__
|
||||
#define UMM_INFO_PRINTF(fmt, ...) umm_info_safe_printf_P(PSTR4(fmt), ##__VA_ARGS__)
|
||||
// use PSTR4() instead of PSTR() to ensure 4-bytes alignment in Flash, whatever the default alignment of PSTR_ALIGN
|
||||
|
||||
|
||||
typedef struct umm_block_t umm_block;
|
||||
|
||||
struct UMM_HEAP_CONTEXT {
|
||||
umm_block *heap;
|
||||
void *heap_end;
|
||||
#if (!defined(UMM_INLINE_METRICS) && defined(UMM_STATS)) || defined(UMM_STATS_FULL)
|
||||
UMM_STATISTICS stats;
|
||||
#endif
|
||||
#ifdef UMM_INFO
|
||||
UMM_HEAP_INFO info;
|
||||
#endif
|
||||
unsigned short int numblocks;
|
||||
unsigned char id;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -63,6 +63,11 @@ extern "C" {
|
||||
#define DBGLOG_LEVEL 0
|
||||
#endif
|
||||
|
||||
// Save 104 bytes by calling umm_init() early once from app_entry()
|
||||
// Some minor UMM_CRITICAL_METRICS counts will be lost through CRT0 init.
|
||||
// #define UMM_INIT_HEAP if (!umm_heap) { umm_init(); }
|
||||
#define UMM_INIT_HEAP (void)0
|
||||
|
||||
#include "dbglog/dbglog.h"
|
||||
|
||||
//C This change is new in upstream umm_malloc.I think this would have created a
|
||||
@ -101,24 +106,146 @@ UMM_H_ATTPACKPRE typedef struct umm_block_t {
|
||||
#define UMM_BLOCKNO_MASK ((uint16_t)(0x7FFF))
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
umm_heap_context_t heap_context[UMM_NUM_HEAPS] __attribute__((section(".noinit")));
|
||||
// void *umm_heap = NULL;
|
||||
|
||||
umm_block *umm_heap = NULL;
|
||||
uint16_t umm_numblocks = 0;
|
||||
/* A stack allowing push/popping of heaps for library use */
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
|
||||
#define UMM_NUMBLOCKS (umm_numblocks)
|
||||
#else
|
||||
static size_t umm_heap_cur = UMM_HEAP_DRAM;
|
||||
static int umm_heap_stack_ptr = 0;
|
||||
static unsigned char umm_heap_stack[UMM_HEAP_STACK_DEPTH];
|
||||
#endif
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/*
|
||||
* Methods to get heap id or context
|
||||
*
|
||||
*/
|
||||
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
size_t umm_get_current_heap_id(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_get_current_heap(void) {
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
static umm_heap_context_t *umm_get_heap_by_id( size_t which ) {
|
||||
(void)which;
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_set_heap_by_id( size_t which ) {
|
||||
(void)which;
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
#else
|
||||
size_t umm_get_current_heap_id(void) {
|
||||
return umm_heap_cur;
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_get_current_heap(void) {
|
||||
return &heap_context[umm_heap_cur];
|
||||
}
|
||||
|
||||
static umm_heap_context_t *umm_get_heap_by_id( size_t which ) {
|
||||
if (which < UMM_NUM_HEAPS) {
|
||||
return &heap_context[which];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_set_heap_by_id( size_t which ) {
|
||||
umm_heap_context_t *_context = umm_get_heap_by_id(which);
|
||||
if (_context && _context->heap) {
|
||||
umm_heap_cur = which;
|
||||
return _context;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (UMM_NUM_HEAPS == 1)
|
||||
umm_heap_context_t *umm_push_heap( size_t which ) {
|
||||
(void)which;
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
umm_heap_context_t *umm_pop_heap( void ) {
|
||||
return &heap_context[0];
|
||||
}
|
||||
|
||||
int umm_get_heap_stack_index( void ) {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
umm_heap_context_t *umm_push_heap( size_t which ) {
|
||||
if (umm_heap_stack_ptr < UMM_HEAP_STACK_DEPTH) {
|
||||
umm_heap_stack[umm_heap_stack_ptr++] = umm_heap_cur;
|
||||
return umm_set_heap_by_id( which );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
umm_heap_context_t *umm_pop_heap( void ) {
|
||||
if (umm_heap_stack_ptr > 0 ) {
|
||||
return umm_set_heap_by_id(umm_heap_stack[--umm_heap_stack_ptr]);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Intended for diagnosic use
|
||||
int umm_get_heap_stack_index( void ) {
|
||||
return umm_heap_stack_ptr;
|
||||
}
|
||||
#endif
|
||||
/* ------------------------------------------------------------------------ */
|
||||
/*
|
||||
* Returns the correct heap context for a given pointer. Useful for
|
||||
* realloc or free since you may not be in the right heap to handle it.
|
||||
*
|
||||
*/
|
||||
static bool test_ptr_context( size_t which, void *ptr ) {
|
||||
return
|
||||
heap_context[which].heap &&
|
||||
ptr >= (void *)heap_context[which].heap &&
|
||||
ptr < heap_context[which].heap_end;
|
||||
}
|
||||
|
||||
static umm_heap_context_t *umm_get_ptr_context(void *ptr) {
|
||||
for (size_t i = 0; i < UMM_NUM_HEAPS; i++) {
|
||||
if (test_ptr_context( i, ptr ) ) {
|
||||
return umm_get_heap_by_id( i );
|
||||
}
|
||||
}
|
||||
|
||||
panic();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define UMM_NUMBLOCKS (_context->numblocks)
|
||||
#define UMM_BLOCK_LAST (UMM_NUMBLOCKS - 1)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* These macros evaluate to the address of the block and data respectively
|
||||
*/
|
||||
|
||||
#define UMM_BLOCK(b) (umm_heap[b])
|
||||
#define UMM_BLOCK(b) (_context->heap[b])
|
||||
#define UMM_DATA(b) (UMM_BLOCK(b).body.data)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* These macros evaluate to the index of the block - NOT the address!!!
|
||||
*/
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
#define UMM_NBLOCK(b) (UMM_BLOCK(b).header.used.next)
|
||||
#define UMM_PBLOCK(b) (UMM_BLOCK(b).header.used.prev)
|
||||
#define UMM_NFREE(b) (UMM_BLOCK(b).body.free.next)
|
||||
@ -172,7 +299,9 @@ static uint16_t umm_blocks( size_t size ) {
|
||||
*
|
||||
* Note that free pointers are NOT modified by this function.
|
||||
*/
|
||||
static void umm_split_block( uint16_t c,
|
||||
static void umm_split_block(
|
||||
umm_heap_context_t *_context,
|
||||
uint16_t c,
|
||||
uint16_t blocks,
|
||||
uint16_t new_freemask ) {
|
||||
|
||||
@ -185,7 +314,7 @@ static void umm_split_block( uint16_t c,
|
||||
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
static void umm_disconnect_from_free_list( uint16_t c ) {
|
||||
static void umm_disconnect_from_free_list( umm_heap_context_t *_context, uint16_t c ) {
|
||||
/* Disconnect this block from the FREE list */
|
||||
|
||||
UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c);
|
||||
@ -202,7 +331,7 @@ static void umm_disconnect_from_free_list( uint16_t c ) {
|
||||
* next block is free.
|
||||
*/
|
||||
|
||||
static void umm_assimilate_up( uint16_t c ) {
|
||||
static void umm_assimilate_up( umm_heap_context_t *_context, uint16_t c ) {
|
||||
|
||||
if( UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK ) {
|
||||
|
||||
@ -217,7 +346,7 @@ static void umm_assimilate_up( uint16_t c ) {
|
||||
|
||||
/* Disconnect the next block from the FREE list */
|
||||
|
||||
umm_disconnect_from_free_list( UMM_NBLOCK(c) );
|
||||
umm_disconnect_from_free_list( _context, UMM_NBLOCK(c) );
|
||||
|
||||
/* Assimilate the next block with this one */
|
||||
|
||||
@ -232,7 +361,7 @@ static void umm_assimilate_up( uint16_t c ) {
|
||||
* up before assimilating down.
|
||||
*/
|
||||
|
||||
static uint16_t umm_assimilate_down( uint16_t c, uint16_t freemask ) {
|
||||
static uint16_t umm_assimilate_down( umm_heap_context_t *_context, uint16_t c, uint16_t freemask ) {
|
||||
|
||||
// We are going to assimilate down to the previous block because
|
||||
// it was free, so remove it from the fragmentation metric
|
||||
@ -257,23 +386,18 @@ static uint16_t umm_assimilate_down( uint16_t c, uint16_t freemask ) {
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
void umm_init( void ) {
|
||||
/* init heap pointer and size, and memset it to 0 */
|
||||
umm_heap = (umm_block *)UMM_MALLOC_CFG_HEAP_ADDR;
|
||||
umm_numblocks = (UMM_MALLOC_CFG_HEAP_SIZE / sizeof(umm_block));
|
||||
memset(umm_heap, 0x00, UMM_MALLOC_CFG_HEAP_SIZE);
|
||||
|
||||
static void umm_init_stage_2( umm_heap_context_t *_context ) {
|
||||
/* setup initial blank heap structure */
|
||||
UMM_FRAGMENTATION_METRIC_INIT();
|
||||
|
||||
/* init ummStats.free_blocks */
|
||||
/* init stats.free_blocks */
|
||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||
#if defined(UMM_STATS_FULL)
|
||||
ummStats.free_blocks_min =
|
||||
ummStats.free_blocks_isr_min = UMM_NUMBLOCKS - 2;
|
||||
_context->stats.free_blocks_min =
|
||||
_context->stats.free_blocks_isr_min = UMM_NUMBLOCKS - 2;
|
||||
#endif
|
||||
#ifndef UMM_INLINE_METRICS
|
||||
ummStats.free_blocks = UMM_NUMBLOCKS - 2;
|
||||
_context->stats.free_blocks = UMM_NUMBLOCKS - 2;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -314,15 +438,89 @@ void umm_init( void ) {
|
||||
UMM_PBLOCK(UMM_BLOCK_LAST) = 1;
|
||||
}
|
||||
|
||||
|
||||
void umm_init_common( size_t id, void *start_addr, size_t size, bool zero ) {
|
||||
/* Preserve internal setup */
|
||||
umm_heap_context_t *_context = umm_get_heap_by_id(id);
|
||||
if (NULL == start_addr || NULL == _context || _context->heap) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* init heap pointer and size, and memset it to 0 */
|
||||
_context->id = id;
|
||||
_context->heap = (umm_block *)start_addr;
|
||||
_context->heap_end = (void *)((uintptr_t)start_addr + size);
|
||||
_context->numblocks = (size / sizeof(umm_block));
|
||||
|
||||
// An option for blocking the zeroing of extra heaps allows for performing
|
||||
// post-crash discovery.
|
||||
if (zero) {
|
||||
memset(_context->heap, 0x00, size);
|
||||
#if (!defined(UMM_INLINE_METRICS) && defined(UMM_STATS)) || defined(UMM_STATS_FULL)
|
||||
memset(&_context->stats, 0x00, sizeof(_context->stats));
|
||||
#endif
|
||||
|
||||
/* Set up internal data structures */
|
||||
umm_init_stage_2(_context);
|
||||
}
|
||||
}
|
||||
|
||||
void umm_init( void ) {
|
||||
// if (umm_heap) {
|
||||
// return;
|
||||
// }
|
||||
for (size_t i = 0; i < UMM_NUM_HEAPS; i++) {
|
||||
heap_context[i].heap = NULL;
|
||||
}
|
||||
memset(&heap_context[0], 0, sizeof(heap_context));
|
||||
umm_init_common( UMM_HEAP_DRAM, (void *)UMM_MALLOC_CFG_HEAP_ADDR, UMM_MALLOC_CFG_HEAP_SIZE, true );
|
||||
// umm_heap = (void *)&heap_context;
|
||||
}
|
||||
|
||||
#ifdef UMM_HEAP_IRAM
|
||||
void umm_init_iram_ex( void *addr, unsigned int size, bool zero ) {
|
||||
/* We need the main, internal heap set up first */
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
umm_init_common(UMM_HEAP_IRAM, addr, size, zero);
|
||||
}
|
||||
|
||||
void _text_end(void);
|
||||
void umm_init_iram(void) __attribute__((weak));
|
||||
|
||||
/*
|
||||
By using a weak link, it is possible to reduce the IRAM heap size with a
|
||||
user-supplied init function. This would allow the creation of a block of IRAM
|
||||
dedicated to a sketch and possibly used/preserved across reboots.
|
||||
*/
|
||||
void umm_init_iram(void) {
|
||||
umm_init_iram_ex(mmu_sec_heap(), mmu_sec_heap_size(), true);
|
||||
}
|
||||
#endif // #ifdef UMM_HEAP_IRAM
|
||||
|
||||
#ifdef UMM_HEAP_EXTERNAL
|
||||
void umm_init_vm( void *vmaddr, unsigned int vmsize ) {
|
||||
/* We need the main, internal (DRAM) heap set up first */
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
umm_init_common(UMM_HEAP_EXTERNAL, vmaddr, vmsize, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* Must be called only from within critical sections guarded by
|
||||
* UMM_CRITICAL_ENTRY() and UMM_CRITICAL_EXIT().
|
||||
*/
|
||||
|
||||
static void umm_free_core( void *ptr ) {
|
||||
static void umm_free_core( umm_heap_context_t *_context, void *ptr ) {
|
||||
|
||||
uint16_t c;
|
||||
|
||||
if (NULL == _context) {
|
||||
panic();
|
||||
return;
|
||||
}
|
||||
|
||||
STATS__FREE_REQUEST(id_free);
|
||||
/*
|
||||
* FIXME: At some point it might be a good idea to add a check to make sure
|
||||
@ -335,7 +533,7 @@ static void umm_free_core( void *ptr ) {
|
||||
|
||||
/* Figure out which block we're in. Note the use of truncated division... */
|
||||
|
||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(umm_heap[0])))/sizeof(umm_block);
|
||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(_context->heap[0])))/sizeof(umm_block);
|
||||
|
||||
DBGLOG_DEBUG( "Freeing block %6d\n", c );
|
||||
|
||||
@ -344,7 +542,7 @@ static void umm_free_core( void *ptr ) {
|
||||
|
||||
/* Now let's assimilate this block with the next one if possible. */
|
||||
|
||||
umm_assimilate_up( c );
|
||||
umm_assimilate_up( _context, c );
|
||||
|
||||
/* Then assimilate with the previous block if possible */
|
||||
|
||||
@ -352,7 +550,7 @@ static void umm_free_core( void *ptr ) {
|
||||
|
||||
DBGLOG_DEBUG( "Assimilate down to previous block, which is FREE\n" );
|
||||
|
||||
c = umm_assimilate_down(c, UMM_FREELIST_MASK);
|
||||
c = umm_assimilate_down(_context, c, UMM_FREELIST_MASK);
|
||||
} else {
|
||||
/*
|
||||
* The previous block is not a free block, so add this one to the head
|
||||
@ -376,9 +574,7 @@ static void umm_free_core( void *ptr ) {
|
||||
void umm_free( void *ptr ) {
|
||||
UMM_CRITICAL_DECL(id_free);
|
||||
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
/* If we're being asked to free a NULL pointer, well that's just silly! */
|
||||
|
||||
@ -393,7 +589,8 @@ void umm_free( void *ptr ) {
|
||||
|
||||
UMM_CRITICAL_ENTRY(id_free);
|
||||
|
||||
umm_free_core( ptr );
|
||||
/* Need to be in the heap in which this block lives */
|
||||
umm_free_core( umm_get_ptr_context( ptr ), ptr );
|
||||
|
||||
UMM_CRITICAL_EXIT(id_free);
|
||||
}
|
||||
@ -403,7 +600,7 @@ void umm_free( void *ptr ) {
|
||||
* UMM_CRITICAL_ENTRY() and UMM_CRITICAL_EXIT().
|
||||
*/
|
||||
|
||||
static void *umm_malloc_core( size_t size ) {
|
||||
static void *umm_malloc_core( umm_heap_context_t *_context, size_t size ) {
|
||||
uint16_t blocks;
|
||||
uint16_t blockSize = 0;
|
||||
|
||||
@ -414,6 +611,11 @@ static void *umm_malloc_core( size_t size ) {
|
||||
|
||||
STATS__ALLOC_REQUEST(id_malloc, size);
|
||||
|
||||
if (NULL == _context) {
|
||||
panic();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
blocks = umm_blocks( size );
|
||||
|
||||
/*
|
||||
@ -474,7 +676,8 @@ static void *umm_malloc_core( size_t size ) {
|
||||
|
||||
/* Disconnect this block from the FREE list */
|
||||
|
||||
umm_disconnect_from_free_list( cf );
|
||||
umm_disconnect_from_free_list( _context, cf );
|
||||
|
||||
} else {
|
||||
|
||||
/* It's not an exact fit and we need to split off a block. */
|
||||
@ -484,7 +687,7 @@ static void *umm_malloc_core( size_t size ) {
|
||||
* split current free block `cf` into two blocks. The first one will be
|
||||
* returned to user, so it's not free, and the second one will be free.
|
||||
*/
|
||||
umm_split_block( cf, blocks, UMM_FREELIST_MASK /*new block is free*/ );
|
||||
umm_split_block( _context, cf, blocks, UMM_FREELIST_MASK /*new block is free*/ );
|
||||
|
||||
UMM_FRAGMENTATION_METRIC_ADD(UMM_NBLOCK(cf));
|
||||
|
||||
@ -525,9 +728,61 @@ void *umm_malloc( size_t size ) {
|
||||
|
||||
void *ptr = NULL;
|
||||
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
/*
|
||||
* "Is it safe"
|
||||
*
|
||||
* Is it safe to call from an ISR? Is there a point during a malloc that a
|
||||
* an interrupt and subsequent call to malloc result in undesired results?
|
||||
*
|
||||
* Heap selection in managed by the functions umm_push_heap, umm_pop_heap,
|
||||
* umm_get_current_heap_id, and umm_set_heap_by_id. These functions are
|
||||
* responsible for getting/setting the module static variable umm_heap_cur.
|
||||
* The umm_heap_cur variable is an index that is used to select the current
|
||||
* heap context. Depending on the situation this selection can be overriddened.
|
||||
*
|
||||
* All variables for a specific Heap are in a single structure. `heap_context`
|
||||
* is an array of these structures. Each heap API function uses a function
|
||||
* local variable `_context` to hold a pointer to the selected heap structure.
|
||||
* This local pointer is referenced for all the "selected heap" operations.
|
||||
* Coupled with critical sections around global data should allow the API
|
||||
* functions to be reentrant.
|
||||
*
|
||||
* Using the `_context` name throughout made it easy to incorporate the
|
||||
* context into existing macros.
|
||||
*
|
||||
* For allocating APIs `umm_heap_cur` is used to index and select a value for
|
||||
* `_context`. If an allocation is made from an ISR, this value is ignored and
|
||||
* the heap context for DRAM is loaded. For APIs that require operating on an
|
||||
* existing allcation such as realloc and free, the heap context selected is
|
||||
* done by matching the allocation's address with that of one of the heap
|
||||
* address ranges.
|
||||
*
|
||||
* I think we are safe with multiple heaps when the non32-bit exception
|
||||
* handler is used, as long as interrupts don't get enabled. There was a
|
||||
* window in the Boot ROM "C" Exception Wrapper that would enable interrupts
|
||||
* when running our non32-exception handler; however, that should be resolved
|
||||
* by our replacement wrapper. For more information on exception handling
|
||||
* issues for IRAM see comments above `_set_exception_handler_wrapper()` in
|
||||
* `core_esp8266_non32xfer.cpp`.
|
||||
*
|
||||
* ISRs should not try and change heaps. umm_malloc will ignore the change.
|
||||
* All should be fine as long as the caller puts the heap back the way it was.
|
||||
* On return, everything must be the same. The foreground thread will continue
|
||||
* with the same information that was there before the interrupt. All malloc()
|
||||
* requests made from an ISR are fulfilled with DRAM.
|
||||
*
|
||||
* For umm_malloc, heap selection involves changing a single variable that is
|
||||
* on the calling context stack. From the umm_mallac side, that variable is
|
||||
* used to load a context pointer by index, heap ID. While an umm_malloc API
|
||||
* function is running, all heap related variables are in the context variable
|
||||
* pointer, registers, or the current stack as the request is processed. With
|
||||
* a single variable to reference for heap selection, I think it is unlikely
|
||||
* that umm_malloc can be called, with things in an unusable transition state.
|
||||
*/
|
||||
|
||||
umm_heap_context_t *_context = umm_get_current_heap();
|
||||
|
||||
/*
|
||||
* the very first thing we do is figure out if we're being asked to allocate
|
||||
@ -547,7 +802,21 @@ void *umm_malloc( size_t size ) {
|
||||
|
||||
UMM_CRITICAL_ENTRY(id_malloc);
|
||||
|
||||
ptr = umm_malloc_core( size );
|
||||
/*
|
||||
* We handle the realloc of an existing IRAM allocation from an ISR with IRAM,
|
||||
* while a new malloc from an ISR will always supply DRAM. That said, realloc
|
||||
* from an ISR is not generally safe without special locking mechanisms and is
|
||||
* not formally supported.
|
||||
*
|
||||
* Additionally, to avoid extending the IRQs disabled period, it is best to
|
||||
* use DRAM for an ISR. Each 16-bit access to IRAM that umm_malloc has to make
|
||||
* requires a pass through the exception handling logic.
|
||||
*/
|
||||
if (UMM_CRITICAL_WITHINISR(id_malloc)) {
|
||||
_context = umm_get_heap_by_id(UMM_HEAP_DRAM);
|
||||
}
|
||||
|
||||
ptr = umm_malloc_core( _context, size );
|
||||
|
||||
UMM_CRITICAL_EXIT(id_malloc);
|
||||
|
||||
@ -568,9 +837,7 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
|
||||
size_t curSize;
|
||||
|
||||
if (umm_heap == NULL) {
|
||||
umm_init();
|
||||
}
|
||||
UMM_INIT_HEAP;
|
||||
|
||||
/*
|
||||
* This code looks after the case of a NULL value for ptr. The ANSI C
|
||||
@ -592,6 +859,13 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
* we should operate the same as free.
|
||||
*/
|
||||
|
||||
/* Need to be in the heap in which this block lives */
|
||||
umm_heap_context_t *_context = umm_get_ptr_context( ptr );
|
||||
if (NULL == _context) {
|
||||
panic();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( 0 == size ) {
|
||||
DBGLOG_DEBUG( "realloc to 0 size, just free the block\n" );
|
||||
STATS__ZERO_ALLOC_REQUEST(id_realloc, size);
|
||||
@ -616,7 +890,7 @@ void *umm_realloc( void *ptr, size_t size ) {
|
||||
|
||||
/* Figure out which block we're in. Note the use of truncated division... */
|
||||
|
||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(umm_heap[0])))/sizeof(umm_block);
|
||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(_context->heap[0])))/sizeof(umm_block);
|
||||
|
||||
/* Figure out how big this block is ... the free bit is not set :-) */
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user