mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-06 15:51:25 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f59a3f71ed | ||
|
|
aaf202ba2f | ||
|
|
5c7b7b68bc | ||
|
|
28f88f016c | ||
|
|
e8bb01b547 | ||
|
|
324f59abc5 | ||
|
|
3eb33ab349 | ||
|
|
66eb0384be | ||
|
|
1f276ba70a | ||
|
|
1a9bc9d3f3 | ||
|
|
d39519b92f | ||
|
|
3e11429cac | ||
|
|
9337a78cd0 | ||
|
|
d99cc0959b | ||
|
|
02b5504e60 | ||
|
|
822c6a9489 | ||
|
|
03c022080f | ||
|
|
84198c7e70 | ||
|
|
f3d531938c | ||
|
|
b6975ab08f | ||
|
|
66afa5e551 | ||
|
|
7886a73807 | ||
|
|
ccc953318a | ||
|
|
684ae27dc2 | ||
|
|
fc71bc0588 | ||
|
|
08305b9000 | ||
|
|
69e8c0a92f | ||
|
|
8eceacc0d3 | ||
|
|
b6df410570 | ||
|
|
26b971b4a8 | ||
|
|
b766c65615 | ||
|
|
8296b88fb6 | ||
|
|
a5efbf0592 | ||
|
|
37c98dd893 | ||
|
|
770a4ff27e | ||
|
|
b6560306ba | ||
|
|
5b39c93478 | ||
|
|
086008b716 | ||
|
|
35eb50a1e6 | ||
|
|
431e35ae8c | ||
|
|
25e2a99d8e | ||
|
|
88b01ef6f0 | ||
|
|
bd8ee86f9e | ||
|
|
ef96f7a28c | ||
|
|
03f240a244 | ||
|
|
c775f31ca7 | ||
|
|
1ca4fd1fef |
No files matched your search
@@ -53,7 +53,7 @@ jobs:
|
||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/linux_${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_${{ matrix.select }}_$(date +"%Y%m%d").json"
|
||||
|
||||
c89:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
make -C test mip_tap_test
|
||||
|
||||
mip89:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
with: { fetch-depth: 2 }
|
||||
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
|
||||
mipbe:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }}
|
||||
|
||||
miprtxbe:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
name: MIP RTX be s390 port
|
||||
env:
|
||||
IPV6: 0
|
||||
@@ -204,7 +204,7 @@ jobs:
|
||||
test/health.awk < log > json
|
||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ipv6 }}_$(date +"%Y%m%d").json"
|
||||
l289:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -216,7 +216,7 @@ jobs:
|
||||
with: { fetch-depth: 2 }
|
||||
- run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
|
||||
l2be:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -272,7 +272,7 @@ jobs:
|
||||
test/health.awk < log > json
|
||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/ATcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
|
||||
atcmd89:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -285,7 +285,7 @@ jobs:
|
||||
with: { fetch-depth: 2 }
|
||||
- run: make -C test ${{ matrix.target }}
|
||||
atcmdbe:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -325,7 +325,7 @@ jobs:
|
||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/bsd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
|
||||
|
||||
bsdbe:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -343,7 +343,7 @@ jobs:
|
||||
make -C test/bsd ${{ matrix.target }}
|
||||
|
||||
s390:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -356,7 +356,7 @@ jobs:
|
||||
with: { fetch-depth: 2 }
|
||||
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && ./test/setup_mqtt_server.sh && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test s390 SSL=${{ matrix.ssl }}
|
||||
armhf:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -429,7 +429,7 @@ jobs:
|
||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/macos_test_cc_${{ matrix.ssl }}_${{ matrix.select }}_$(date +"%Y%m%d").json"
|
||||
|
||||
windows:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 1 # no parallel runs, to minimize MQTT errors
|
||||
@@ -479,7 +479,7 @@ jobs:
|
||||
- run: make -C test riscv SSL=${{ matrix.ssl }}
|
||||
|
||||
tutorials:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04 # MicroPython v1.22.2 fails with Ubuntu 26.04 GCC 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -560,7 +560,8 @@ jobs:
|
||||
# - run: make -C tutorials/${{ matrix.example.path }} build
|
||||
|
||||
wizard_examples_arm:
|
||||
runs-on: ubuntu-latest
|
||||
# Keep rp-ti on 24.04: Pico SDK 2.1.1 pioasm fails with GCC 15; update SDK
|
||||
runs-on: ${{ matrix.group == 'rp-ti' && 'ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -637,6 +638,7 @@ jobs:
|
||||
example:
|
||||
- path: micropython/esp32
|
||||
- path: esp32/uart-bridge
|
||||
- path: esp32/minimal
|
||||
name: ${{ matrix.example.path }}
|
||||
env:
|
||||
GO: 0
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
test/health.awk < log > json
|
||||
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/atcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
|
||||
atcmd89:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
with: { fetch-depth: 2 }
|
||||
- run: make -C test ${{ matrix.target }}
|
||||
atcmdbe:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: if [ "${{ matrix.target }}" == "test" ]; then ./test/setup_mqtt_server.sh ; fi && make -C test ${{ matrix.target }}
|
||||
s390:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
TFLAGS: -DMQTT_LOCALHOST
|
||||
steps:
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
- if: ${{ env.GO == 1 }}
|
||||
run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && ./test/setup_mqtt_server.sh && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test s390
|
||||
armhf:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
TFLAGS: -DMQTT_LOCALHOST
|
||||
steps:
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
/opt/homebrew/opt/mosquitto/sbin/mosquitto -c /Users/runner/work/mongoose/mongoose/test/mosquitto.conf.macos &
|
||||
make -C test test ASAN_OPTIONS=
|
||||
windows:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -186,6 +186,7 @@ jobs:
|
||||
matrix:
|
||||
example:
|
||||
- path: esp32/uart-bridge
|
||||
- path: esp32/minimal
|
||||
name: ${{ matrix.example.path }}
|
||||
env:
|
||||
GO: 0
|
||||
@@ -200,7 +201,8 @@ jobs:
|
||||
run: make -C tutorials/${{ matrix.example.path }} build
|
||||
|
||||
generic_examples_arm:
|
||||
runs-on: ubuntu-latest
|
||||
# Keep RP on 24.04: Pico SDK 2.1.1 pioasm fails with GCC 15; update SDK
|
||||
runs-on: ${{ startsWith(matrix.example.path, 'rp/') && 'ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -243,7 +245,8 @@ jobs:
|
||||
run: make -C tutorials/${{ matrix.example.path }} build
|
||||
|
||||
device_dashboard_examples_arm:
|
||||
runs-on: ubuntu-latest
|
||||
# Keep RP on 24.04: Pico SDK 2.1.1 pioasm fails with GCC 15; update SDK
|
||||
runs-on: ${{ startsWith(matrix.example.path, 'rp/') && 'ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -269,7 +272,8 @@ jobs:
|
||||
run: make -C tutorials/${{ matrix.example.path }} build
|
||||
|
||||
mqtt_dashboard_examples_arm:
|
||||
runs-on: ubuntu-latest
|
||||
# Pico SDK 2.1.0 pioasm fails with GCC 15; update SDK to unpin
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# Mongoose - Embedded Network Library
|
||||
# Mongoose: a full-stack embedded networking library
|
||||
|
||||
Mongoose is a full-stack embedded networking library for C/C++: a TCP/IP stack
|
||||
with drivers, TLS 1.3, an embedded web server (HTTP, WebSocket), MQTT and
|
||||
firmware OTA updates in two files, `mongoose.c` and `mongoose.h`. Mongoose runs on
|
||||
STM32, NXP and other microcontrollers, on bare metal or with an RTOS, with no
|
||||
external dependencies. Use it for cloud connectivity, device web dashboards,
|
||||
secure firmware updates and remote control.
|
||||
|
||||
Mongoose is an open source, two-file C networking library and embedded web server for
|
||||
microcontrollers that combines TCP/IP stack, HTTP, WebSocket, MQTT, TLS 1.3
|
||||
stack, built-in firmware OTA updates, and device-dashboard infrastructure.
|
||||
License: GPLv2 or commercial.
|
||||
Website: https://mongoose.ws/
|
||||
GitHub repo: https://github.com/cesanta/mongoose
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Mongoose - Embedded Web Server / Embedded Network Library
|
||||
# Mongoose: a full-stack embedded networking library
|
||||
|
||||
[](https://opensource.org/licenses/gpl-2.0.php)
|
||||
[](https://github.com/cesanta/mongoose/actions)
|
||||
@@ -7,11 +7,17 @@
|
||||
|
||||
<img src="https://mongoose.ws/images/logo.svg" width="48" height="48"
|
||||
align="left" style="float:left;" />
|
||||
Two files. Drop `mongoose.c` and `mongoose.h` into any C/C++ project and get a
|
||||
full network stack — HTTP, WebSocket, MQTT, TLS, firmware OTA
|
||||
updates, device dashboard — on any microcontroller or desktop OS.
|
||||
Mongoose is a full-stack embedded networking library for C/C++: a TCP/IP stack
|
||||
with drivers, TLS 1.3, an embedded web server (HTTP, WebSocket), MQTT and
|
||||
firmware OTA updates in two files, `mongoose.c` and `mongoose.h`. Mongoose runs on
|
||||
STM32, NXP and other microcontrollers, on bare metal or with an RTOS, with no
|
||||
external dependencies. Use it for cloud connectivity, device web dashboards,
|
||||
secure firmware updates and remote control.
|
||||
|
||||
On the market since 2004, used by vast number of open source and commercial
|
||||
Use it instead of lwIP + mbedTLS + httpd + an MQTT client + OTA glue code, or
|
||||
run it on top of lwIP or Zephyr.
|
||||
|
||||
On the market since 2004, used by a vast number of open source and commercial
|
||||
products - it even runs on the International Space Station! Features include:
|
||||
|
||||
- Cross-platform:
|
||||
@@ -24,7 +30,7 @@ products - it even runs on the International Space Station! Features include:
|
||||
- Source code is both ISO C and ISO C++ compliant
|
||||
- Very easy to integrate
|
||||
- Own TCP/IP stack for bare metal / RTOS, or run on top of any existing one (lwIP, Zephyr, ...)
|
||||
- Built-in TLS 1.3 ECC stack. Also can use external TLS libraries - mbedTLS, OpenSSL, or other
|
||||
- Built-in TLS 1.3 stack. Also can use external TLS libraries - mbedTLS, OpenSSL, or other
|
||||
- Does not depend on any other software to implement networking
|
||||
- Built-in firmware updates for STM32 H5, STM32 H7, NXP IMXRT, RP2040/2350, ESP32 and more
|
||||
- Commercial support with proactive security updates; [CRA-compliant](https://mongoose.ws/security/cra-compliance/)
|
||||
@@ -35,8 +41,32 @@ See https://mongoose.ws/ for complete documentation, videos, case studies, etc.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
Mongoose can work on top of any TCP/IP stack that supports BSD sockets API.
|
||||
Platforms supported by the 3rd party TCP/IP stacks:
|
||||
Mongoose has its own built-in TCP/IP stack with drivers, so it needs no
|
||||
additional software to implement networking. The built-in stack runs on bare
|
||||
metal and under any RTOS. Platforms supported by the Mongoose built-in TCP/IP
|
||||
stack:
|
||||
|
||||
| MCU | Connectivity | Notes |
|
||||
| :-- | :----------- | :---- |
|
||||
| **STM32** | Built-in Ethernet | Ethernet-capable STM32F, STM32H5, STM32H7, STM32N6, STM32C5 variants |
|
||||
| **NXP i.MX RT** | Built-in Ethernet | RT102x, RT104x, RT105x, RT106x, RT117x, RT118x (NETC) |
|
||||
| **NXP MCX** | Built-in Ethernet | MCXN94x, MCXE247 |
|
||||
| **NXP RW612** | Eth, WiFi, 802.15.4 | Separate Ethernet and radio drivers |
|
||||
| **Microchip ATSAME54** | Built-in Ethernet | GMAC driver |
|
||||
| **Renesas RA** | Built-in Ethernet | RA6M, RA8M |
|
||||
| **Infineon XMC** | Built-in Ethernet | XMC4, XMC7 |
|
||||
| **Texas Instruments** | Built-in Ethernet | TM4C, TMS570 |
|
||||
| **RP2040, RP2350, STM32, others** | CYW43xx Wi-Fi chips | SPI or SDIO; Pico W, Pico 2 W, Pico RM2, Arduino Portenta H7 |
|
||||
| **STM32, others** | ST67W6 Wi-Fi modules | SPI; for example, STM32U5 with the ST67W611M1 module |
|
||||
| **MCUs with an NXP Wi-Fi SDK port** | NXP Wi-Fi | Requires the SDK's Wi-Fi port implementing the driver's network hooks |
|
||||
| **NXP MCXW71** | Built-in 802.15.4 radio | IPv4 over IEEE 802.15.4 |
|
||||
| **NXP RW612** | Built-in 802.15.4 radio | IPv4 over IEEE 802.15.4; requires NXP radio firmware |
|
||||
| **Any MCU with SPI** | W5500 or W5100 Ethernet chips | External Wiznet MAC+PHY |
|
||||
| **MCUs connected to a cellular modem** | Cellular | AT commands and PPP; for example, SIM800 |
|
||||
| **MCUs with CMSIS Ethernet drivers** | Ethernet via CMSIS-Driver | Requires CMSIS Ethernet MAC and PHY drivers |
|
||||
|
||||
Mongoose can also run on top of any TCP/IP stack that supports the BSD
|
||||
sockets API. Platforms supported via 3rd party TCP/IP stacks:
|
||||
|
||||
| TCP/IP stack | Notes |
|
||||
| :-------------- | :------- |
|
||||
@@ -45,23 +75,6 @@ Platforms supported by the 3rd party TCP/IP stacks:
|
||||
| **Other** | Any other TCP/IP stack that supports BSD socket API, for example Amazon FreeRTOS-TCP |
|
||||
| **Linux, Mac, Windows** | Workstations, server, single board computers, embedded Linux devices running on MPUs or FPGAs |
|
||||
|
||||
Optionally, Mongoose provides its own built-in TCP/IP stack, eliminating the
|
||||
need for additional software to implement networking functionality. The
|
||||
built-in stack supports operation in both bare-metal and RTOS environments.
|
||||
Platforms supported by the Mongoose built-in TCP/IP stack:
|
||||
|
||||
| Hardware | Notes |
|
||||
| :------------- | :------- |
|
||||
| **STM32** | All STM32 MCUs with built-in Ethernet: STM32Fxx, STM32H5xx, STM32H7xx |
|
||||
| **NXP** | All NXP MCUs with built-in Ethernet: IMXRT102x, IMXRT104x, IMXRT105x, IMXRT106x, IMXRT117x, RW612, MCXN94x |
|
||||
| **Microchip** | ATSAME54 MCUs with built-int Ethernet |
|
||||
| **Renesas** | RA5M, RA6M, RA8M MCUs with built-in Ethernet |
|
||||
| **Infineon** | XMC4, XMC7 MCUs with built-in Ethernet |
|
||||
| **Texas Instruments** | TM4C, TMS570 MCUs with built-in Ethernet |
|
||||
| **Cypress WiFi** | Any MCU with CY43xx WiFi chips, like RP2040 Pico-W, RP2350 Pico2-W, Arduino Portenta |
|
||||
| **Wiznet Ethernet** | Any MCU that use Wiznet W5500 or Wiznet 5100 MAC+PHY chips |
|
||||
| **Cellular** | NRF9160, SIM800 |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
Below are quick snippets that should give an idea how simple the API is and
|
||||
@@ -158,6 +171,17 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Licensing
|
||||
|
||||
Mongoose is dual-licensed:
|
||||
- GPLv2: free for open source projects. Many popular open source projects use
|
||||
Mongoose, for example
|
||||
[SWUpdate](https://github.com/sbabic/swupdate/tree/master/mongoose), the
|
||||
embedded Linux update framework
|
||||
- Commercial: for closed-source products. Includes support directly from the
|
||||
engineers who wrote the whole stack, and early security fixes.
|
||||
See [licensing](https://mongoose.ws/licensing/)
|
||||
|
||||
## Commercial use
|
||||
- Mongoose is used by hundreds of businesses, from Fortune500 giants like
|
||||
Siemens, Schneider Electric, Broadcom, Bosch, Google, Samsung, Qualcomm, Caterpillar to the small businesses
|
||||
@@ -222,12 +246,12 @@ Technical guides and deep dives into embedded web servers, WebUI integration and
|
||||
- [lwIP vs Mongoose - TCP/IP Stack Integration Benchmark](https://mongoose.ws/articles/lwip-vs-mongoose-tcpip-stack-integration/)
|
||||
- [Embedded TCP/IP Stacks Comparison](https://mongoose.ws/embedded-tcpip-stack-comparison/)
|
||||
- [STM32H5 File System on Internal Flash: LittleFS + Mongoose](https://mongoose.ws/articles/stm32h5-file-system-on-internal-flash/)
|
||||
- [Firmware OTA and remote control on NXP FRDM-RW612](https://mongoose.ws/articles/mdash/)
|
||||
- [Firmware OTA and remote control on NXP FRDM-RW612](https://mongoose.ws/articles/rw612-mdash/)
|
||||
- [25000 WebSocket messages per second on STM32 Nucleo-H723ZG](https://mongoose.ws/articles/stm32-websocket-benchmark/)
|
||||
- [How to connect STM32 to AWS IoT Core](https://mongoose.ws/articles/stm32-aws-iot/)
|
||||
- [How to connect STM32 to Azure IoT Hub](https://mongoose.ws/articles/stm32-azure-iot-hub/)
|
||||
- [How to connect STM32 to EMQX](https://mongoose.ws/articles/stm32-emqx/)
|
||||
- [STM32 Device Management deep dive: Secure OTA, Remote Control, health monitor](https://mongoose.ws/articles/stm32-mdash-deep-dive/")
|
||||
- [STM32 Device Management deep dive: Secure OTA, Remote Control, health monitor](https://mongoose.ws/articles/stm32-mdash-deep-dive/)
|
||||
- [STM32 TCP/IP footprint: how much flash and RAM does Mongoose take?](https://mongoose.ws/articles/tcp-ip-footprint/)
|
||||
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Security Policy
|
||||
|
||||
Mongoose is a full-stack embedded networking library by Cesanta Software Ltd,
|
||||
<https://mongoose.ws>. This policy covers how to report vulnerabilities
|
||||
and how Cesanta handles them.
|
||||
|
||||
## Supported versions
|
||||
|
||||
Security fixes go to the `master` branch and the latest release.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Do not report security issues in public GitHub issues. Report them
|
||||
privately using GitHub Security Advisories:
|
||||
<https://github.com/cesanta/mongoose/security/advisories/new>
|
||||
|
||||
Include the Mongoose version or commit, steps or a proof of concept to reproduce, and
|
||||
the impact. Cesanta usually responds the same business day.
|
||||
|
||||
## Disclosure process
|
||||
|
||||
1. Cesanta reviews the report and coordinates a fix with the reporter.
|
||||
2. Eligible commercial customers receive a notification and a private fix
|
||||
one month before the CVE becomes public.
|
||||
3. The fix and the CVE are then published. The reporter is credited in the
|
||||
GitHub Security Advisory.
|
||||
|
||||
## Security testing
|
||||
|
||||
- CI on every push, unit tests built with ASAN and UBSan
|
||||
- CodeQL static analysis on every commit
|
||||
- Continuous fuzzing on OSS-Fuzz, plus nightly libFuzzer runs
|
||||
- AI-assisted security review on every pull request
|
||||
- Regular review by independent security research groups worldwide
|
||||
|
||||
## EU Cyber Resilience Act (CRA)
|
||||
|
||||
Mongoose supports CRA compliance for connected products: a documented
|
||||
vulnerability handling process (this file), TLS 1.3, signed OTA firmware
|
||||
updates, and an SBOM (`sbom.cdx.json`). Details:
|
||||
<https://mongoose.ws/security/cra-compliance/>
|
||||
+2415
-129
File diff suppressed because it is too large.
Load diff
+369
-85
@@ -165,6 +165,7 @@ extern "C" {
|
||||
|
||||
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && \
|
||||
!defined(MG_ENABLE_DRIVER_STM32H) && !defined(MG_ENABLE_DRIVER_STM32N) && \
|
||||
!defined(MG_ENABLE_DRIVER_STM32C) && \
|
||||
!defined(MG_ENABLE_DRIVER_CYW) && !defined(MG_ENABLE_DRIVER_CYW_SDIO) && \
|
||||
!defined(MG_ENABLE_DRIVER_ST67W6)
|
||||
#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7)
|
||||
@@ -173,6 +174,8 @@ extern "C" {
|
||||
#define MG_ENABLE_DRIVER_STM32H 1
|
||||
#elif defined(STM32N6)
|
||||
#define MG_ENABLE_DRIVER_STM32N 1
|
||||
#elif defined(STM32C5)
|
||||
#define MG_ENABLE_DRIVER_STM32C 1
|
||||
#else
|
||||
#error Select a driver in mongoose_config.h
|
||||
#endif
|
||||
@@ -189,70 +192,8 @@ extern "C" {
|
||||
#define MG_OTA MG_OTA_STM32H5
|
||||
#elif !defined(MG_OTA) && defined(STM32H7)
|
||||
#define MG_OTA MG_OTA_STM32H7
|
||||
#endif
|
||||
|
||||
// OTA rollback timer via IWDG. Fixed 10 s hang-detection window (prescaler
|
||||
// /1024, LSI ~32 kHz: reload = 10 * 32000 / 1024 = 312). mg_ota_poll() feeds
|
||||
// the watchdog every 500 ms while in MG_OTA_TESTING state; if the event loop
|
||||
// hangs for 10 s, the IWDG fires and the device rolls back.
|
||||
// STM32H7 names the peripheral IWDG1; all others use IWDG.
|
||||
// The total evaluation period is set by MG_OTA_ROLLBACK_TIMEOUT_SECONDS.
|
||||
// STM32H7 names the watchdog peripheral IWDG1; all other families use IWDG.
|
||||
#if defined(IWDG1) && !defined(IWDG)
|
||||
#define MG_IWDG IWDG1
|
||||
#else
|
||||
#define MG_IWDG IWDG
|
||||
#endif
|
||||
// PR=6 → /256 prescaler, RLR=1250 → ~10 s at LSI 32 kHz.
|
||||
// Works on F4/F7/H5/H7; uses direct register writes so it is safe before HAL_Init().
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_START
|
||||
#define MG_OTA_ROLLBACK_TIMER_START() \
|
||||
do { \
|
||||
MG_IWDG->KR = 0xCCCCU; /* start + LSI */ \
|
||||
MG_IWDG->KR = 0x5555U; /* unlock PR/RLR */ \
|
||||
MG_IWDG->PR = 6U; /* /256 */ \
|
||||
MG_IWDG->RLR = 1250U; /* 10 s */ \
|
||||
while (MG_IWDG->SR & (IWDG_SR_PVU | IWDG_SR_RVU)) (void) 0; \
|
||||
MG_IWDG->KR = 0xAAAAU; /* reload */ \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
// Feed (reload) the IWDG. Called from mg_ota_poll() every 500 ms.
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
|
||||
#define MG_OTA_ROLLBACK_TIMER_FEED() (MG_IWDG->KR = 0xAAAAU)
|
||||
#endif
|
||||
|
||||
// OTA state in a backup register that survives warm resets but clears on POR.
|
||||
// STATE_GET reads work without any setup. STATE_SET enables the RTC APB clock
|
||||
// and unlocks backup domain write access via direct register writes, so it is
|
||||
// safe to call from any context (before or after HAL_Init).
|
||||
// H5 (TAMP->BKP0R), H7 (RTC->BKP0R), F4/F7 (RTC->BKP0R).
|
||||
#ifndef MG_OTA_STATE_GET
|
||||
#if defined(PWR_DBPCR_DBP)
|
||||
#define MG_OTA_DBP_ENABLE() (PWR->DBPCR |= PWR_DBPCR_DBP)
|
||||
#elif defined(PWR_CR1_DBP)
|
||||
#define MG_OTA_DBP_ENABLE() (PWR->CR1 |= PWR_CR1_DBP)
|
||||
#else
|
||||
#define MG_OTA_DBP_ENABLE() (PWR->CR |= PWR_CR_DBP)
|
||||
#endif
|
||||
|
||||
#if defined(TAMP)
|
||||
#define MG_OTA_STATE_GET() \
|
||||
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, TAMP->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
|
||||
(TAMP->BKP0R = (uint32_t) (v)), TAMP->BKP0R)
|
||||
#elif defined(RCC_APB4ENR_RTCAPBEN)
|
||||
#define MG_OTA_STATE_GET() (RTC->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB4ENR |= RCC_APB4ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
|
||||
(RTC->BKP0R = (uint32_t) (v)))
|
||||
#else
|
||||
#define MG_OTA_STATE_GET() (RTC->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB1ENR |= RCC_APB1ENR_PWREN, MG_OTA_DBP_ENABLE(), \
|
||||
(RTC->BKP0R = (uint32_t) (v)))
|
||||
#endif
|
||||
#elif !defined(MG_OTA) && defined(STM32C5)
|
||||
#define MG_OTA MG_OTA_STM32C5
|
||||
#endif
|
||||
|
||||
// use HAL-defined execute-in-ram section
|
||||
@@ -312,8 +253,11 @@ extern "C" {
|
||||
#include <esp_system.h>
|
||||
#include <esp_mac.h>
|
||||
#include <esp_idf_version.h>
|
||||
// #include <esp_memory_utils.h> // For health record coredumps
|
||||
// #include <esp_core_dump.h> // For health record coredumps
|
||||
#if MG_ENABLE_HEALTH
|
||||
#include <esp_memory_utils.h>
|
||||
#include <esp_core_dump.h>
|
||||
#include <esp_partition.h>
|
||||
#endif
|
||||
|
||||
#define MG_PATH_MAX 128
|
||||
|
||||
@@ -2783,29 +2727,32 @@ int mg_http_status(const struct mg_http_message *hm);
|
||||
|
||||
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
|
||||
// Call it from an MG_EV_HTTP_HDRS handler, after matching the request, e.g.
|
||||
// with mg_match(). Returns true if it took over the connection, false if the
|
||||
// request is not POST/PUT, is chunked, or has no Content-Length.
|
||||
// with mg_match(). Always takes over the connection. Invalid requests receive
|
||||
// an HTTP error; client-side failures call mg_error(). Replies and connection
|
||||
// draining are handled internally, including callback failures.
|
||||
//
|
||||
// Callback upload_fn(hm, data, user_data) is called:
|
||||
// - at start: hm is set. Open the file, set *user_data.
|
||||
// - per chunk: data is set, 512 byte aligned except the last. Not called
|
||||
// again after a failure.
|
||||
// - at end: both NULL. Always called once, also when the connection drops
|
||||
// early, so release resources here. A truncated body looks like a complete
|
||||
// one: validate before committing.
|
||||
// Return true on success, false on error.
|
||||
// - at end: both NULL. Always called exactly once, including validation
|
||||
// failures before start and connections that drop early. Release resources
|
||||
// here. A truncated body looks like a complete one: validate before
|
||||
// committing.
|
||||
// The callback returns true on success, false on error. A failure stops
|
||||
// streaming immediately and calls the end callback before closing.
|
||||
// Example:
|
||||
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL) {
|
||||
// mg_http_stream_body(c, hm, upload, NULL));
|
||||
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL)) {
|
||||
// mg_http_stream_body(c, hm, upload, NULL);
|
||||
// }
|
||||
// Full example:
|
||||
// tutorials/http/file-upload-single-post
|
||||
// Related APIs:
|
||||
// mg_http_listen(), mg_http_next_multipart()
|
||||
// Notes:
|
||||
// After it returns true, this function hijacks the connection. Existing event
|
||||
// handler not called, hm is invalid, c->data and c->fn_data are modified
|
||||
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
// The existing event handler is no longer called. hm is invalid after this
|
||||
// call; c->data and c->fn_data are reserved for streaming state.
|
||||
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
bool (*upload_fn)(struct mg_http_message *,
|
||||
struct mg_str *, void **user_data),
|
||||
void *user_data);
|
||||
@@ -4165,6 +4112,8 @@ struct mg_dash {
|
||||
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
|
||||
// Resolves the built-in file manager's root dir for user u, e.g. per-user
|
||||
// upload directories. NULL: file manager disabled
|
||||
// - The returned directory must be nonempty and must not contain ',' or '=',
|
||||
// because it is used as an mg_http_serve_opts::root_dir mapping target.
|
||||
bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
|
||||
};
|
||||
|
||||
@@ -4227,12 +4176,14 @@ extern void mg_mqtt_poll(struct mg_mgr *);
|
||||
|
||||
|
||||
|
||||
|
||||
// Supported OTA targets
|
||||
#define MG_OTA_NONE 0 // No OTA support
|
||||
#define MG_OTA_STM32H5 1 // STM32 H5
|
||||
#define MG_OTA_STM32H7 2 // STM32 H7
|
||||
#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core
|
||||
#define MG_OTA_STM32F 4 // STM32 F7/F4/F2
|
||||
#define MG_OTA_STM32C5 5 // STM32 C5, same flash controller as H5
|
||||
#define MG_OTA_CH32V307 100 // WCH CH32V307
|
||||
#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6
|
||||
#define MG_OTA_RT1020 300 // IMXRT1020
|
||||
@@ -4242,6 +4193,7 @@ extern void mg_mqtt_poll(struct mg_mgr *);
|
||||
#define MG_OTA_RT1170 304 // IMXRT1170
|
||||
#define MG_OTA_RT1180 305 // IMXRT1180
|
||||
#define MG_OTA_MCXN 310 // MCXN947
|
||||
#define MG_OTA_MCXW71 311 // MCXW71
|
||||
#define MG_OTA_RW612 320 // FRDM-RW612
|
||||
#define MG_OTA_FLASH 900 // OTA via internal flash
|
||||
#define MG_OTA_ESP32 910 // ESP32 OTA implementation
|
||||
@@ -4271,6 +4223,18 @@ bool mg_ota_write(const void *buf, size_t len);
|
||||
// Ends the OTA process and commits the written firmware.
|
||||
bool mg_ota_end(void);
|
||||
|
||||
// Push-based OTA: mg_http_stream_body() callback that flashes the request
|
||||
//
|
||||
// ```c
|
||||
// if (ev == MG_EV_HTTP_HDRS) {
|
||||
// struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
// if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
// mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p);
|
||||
|
||||
// MG_OTA_ROLLBACK: swap firmware banks and reset. On flash-based OTA, the
|
||||
// default calls swap_fn() (no-op on single-bank) then resets. Override in
|
||||
// mongoose_config.h for platforms without mg_flash (ESP32, PicoSDK, etc.).
|
||||
@@ -4414,21 +4378,25 @@ void mg_ota_poll(struct mg_mgr *);
|
||||
|
||||
|
||||
|
||||
#if MG_ENABLE_HEALTH
|
||||
|
||||
#ifndef MG_HEALTH_MAGIC
|
||||
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
|
||||
#endif
|
||||
|
||||
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5))
|
||||
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
|
||||
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
|
||||
#endif
|
||||
|
||||
#if !defined(MG_HEALTH_DATA_SIZE) && (defined(STM32H7_DEV_ID) || defined(STM32H5))
|
||||
#define MG_HEALTH_DATA_SIZE 250
|
||||
#endif
|
||||
|
||||
#ifndef MG_HEALTH_DATA_SIZE
|
||||
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
|
||||
#define MG_HEALTH_DATA_SIZE 250
|
||||
#elif MG_ARCH == MG_ARCH_ESP32
|
||||
#define MG_HEALTH_DATA_SIZE 23
|
||||
#else
|
||||
#define MG_HEALTH_DATA_SIZE 10
|
||||
#endif
|
||||
#endif // MG_HEALTH_DATA_SIZE
|
||||
|
||||
// Places the record in RAM that survives a reset. Define in mongoose_config.h
|
||||
// to match the linker script, e.g. __attribute__((section(".mg_health")))
|
||||
@@ -4436,6 +4404,13 @@ void mg_ota_poll(struct mg_mgr *);
|
||||
#define MG_HEALTH_RAM
|
||||
#endif
|
||||
|
||||
// CPU core architecture
|
||||
enum mg_health_arch {
|
||||
MG_HEALTH_ARCH_ARM = 0,
|
||||
MG_HEALTH_ARCH_XTENSA,
|
||||
MG_HEALTH_ARCH_RISCV
|
||||
};
|
||||
|
||||
// Why the device restarted. Hardware usually reports several flags at once,
|
||||
// so these are ordered by how much they tell you: the most specific cause a
|
||||
// device reports wins
|
||||
@@ -4452,7 +4427,8 @@ enum mg_health_reason {
|
||||
|
||||
struct mg_health {
|
||||
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
|
||||
uint32_t reset_reason; // enum mg_health_reason that started this boot
|
||||
uint16_t reset_reason; // enum mg_health_reason that started this boot
|
||||
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
|
||||
uint32_t cpuid; // ARM CPU ID
|
||||
uint32_t data[MG_HEALTH_DATA_SIZE];
|
||||
};
|
||||
@@ -4485,9 +4461,72 @@ static inline const char *mg_health_reason_str(int reason) {
|
||||
}
|
||||
}
|
||||
|
||||
#if MG_ARCH == MG_ARCH_ESP32
|
||||
#ifndef CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||
#define CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH 0
|
||||
#endif
|
||||
#ifndef ESP_PARTITION_SUBTYPE_DATA_COREDUMP
|
||||
#define ESP_PARTITION_SUBTYPE_DATA_COREDUMP 0x03
|
||||
#endif
|
||||
// clang-format off
|
||||
static inline uint16_t esp2mg(esp_reset_reason_t rr) {
|
||||
switch(rr) {
|
||||
case ESP_RST_POWERON: return (uint16_t) MG_HEALTH_RESET_POWER;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
|
||||
case ESP_RST_PWR_GLITCH:
|
||||
#endif
|
||||
case ESP_RST_BROWNOUT: return (uint16_t) MG_HEALTH_RESET_BROWNOUT;
|
||||
case ESP_RST_EXT: return (uint16_t) MG_HEALTH_RESET_PIN;
|
||||
case ESP_RST_SW: return (uint16_t) MG_HEALTH_RESET_SOFTWARE;
|
||||
case ESP_RST_DEEPSLEEP: return (uint16_t) MG_HEALTH_RESET_LOWPOWER;
|
||||
case ESP_RST_INT_WDT:
|
||||
case ESP_RST_TASK_WDT:
|
||||
case ESP_RST_WDT: return (uint16_t) MG_HEALTH_RESET_WATCHDOG;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
|
||||
case ESP_RST_CPU_LOCKUP:
|
||||
#endif
|
||||
case ESP_RST_PANIC: return (uint16_t) MG_HEALTH_RESET_FAULT;
|
||||
default: break;
|
||||
}
|
||||
return (uint16_t) MG_HEALTH_RESET_UNKNOWN;
|
||||
}
|
||||
// clang-format on
|
||||
#endif
|
||||
|
||||
#define MG_HEALTH_INIT() mg_health_init()
|
||||
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
|
||||
extern void mg_health_init(void);
|
||||
extern bool mg_health_coredump_begin(size_t *size);
|
||||
extern bool mg_health_coredump_read(size_t offset, void *buf, size_t *len);
|
||||
extern bool mg_health_coredump_end(void);
|
||||
#else
|
||||
#define MG_HEALTH_INIT()
|
||||
static inline int mg_health_reason(void) {
|
||||
return 0;
|
||||
}
|
||||
static inline const char *mg_health_reason_str(int reason) {
|
||||
(void) reason;
|
||||
return "unknown";
|
||||
}
|
||||
static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg,
|
||||
va_list *ap) {
|
||||
(void) ap;
|
||||
return mg_xprintf(fn, arg, "{}");
|
||||
}
|
||||
static inline bool mg_health_coredump_begin(size_t *size) {
|
||||
(void) size;
|
||||
return false;
|
||||
}
|
||||
static inline bool mg_health_coredump_read(size_t offset, void *buf,
|
||||
size_t *len) {
|
||||
(void) offset, (void) buf, (void) len;
|
||||
return false;
|
||||
}
|
||||
static inline bool mg_health_coredump_end(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
// Cloud connector API to https://mdash.net - device management cloud
|
||||
|
||||
|
||||
@@ -4790,6 +4829,11 @@ extern struct mg_tcpip_driver mg_tcpip_driver_st67w6;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_atcmd;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// SPI bus abstraction for SPI-attached network chips (e.g. W5500).
|
||||
// Populate and assign to the driver's driver_data field.
|
||||
struct mg_tcpip_spi {
|
||||
@@ -4842,6 +4886,86 @@ struct mg_tcpip_spi {
|
||||
#endif
|
||||
|
||||
#endif
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Ethernet over radio, through a mesh. Mongoose sees an Ethernet interface,
|
||||
// the mesh splits Ethernet frames into radio frames. Two parts plug in:
|
||||
// - struct mg_radio: a radio standard, e.g. mg_radio_802154
|
||||
// - struct mg_mesh: how the mesh is built and routed
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if MG_ENABLE_TCPIP
|
||||
|
||||
// Monotonic time in microseconds.
|
||||
uint64_t mg_micros(void);
|
||||
|
||||
// A radio standard
|
||||
struct mg_radio {
|
||||
const char *name; // For logs
|
||||
size_t max_frame_size; // Largest radio frame, bytes
|
||||
uint32_t bitrate; // Bits per second on air
|
||||
size_t overhead; // Bytes on air besides the frame: preamble, FCS
|
||||
uint32_t slot_us; // Random backoff slot duration, us
|
||||
uint32_t cca_us; // Channel sensing duration, us
|
||||
uint32_t turnaround_us; // Idle check to transmission start, us
|
||||
uint32_t ifs_us; // Inter-frame spacing in us: gap between transmits
|
||||
uint8_t backoff_min; // Initial backoff exponent
|
||||
uint8_t backoff_max; // Maximum backoff exponent
|
||||
bool csma; // Use channel sensing and backoff before TX
|
||||
size_t header_len; // Radio hardware header length
|
||||
// Write the radio header, with 0xffff as the broadcast address.
|
||||
void (*print_hdr)(const struct mg_radio *radio, uint8_t *buf, uint16_t dst,
|
||||
uint16_t src);
|
||||
// Return the parsed header length, or zero for an invalid header.
|
||||
size_t (*parse_hdr)(const struct mg_radio *radio, const uint8_t *buf,
|
||||
size_t len, uint16_t *dst, uint16_t *src);
|
||||
};
|
||||
|
||||
#define MG_802154_HEADER_LEN 9 // MAC header, short addresses
|
||||
#define MG_802154_PAN_ID 0xabcd // Our network's PAN ID
|
||||
|
||||
#ifndef MG_802154_CHANNEL
|
||||
#define MG_802154_CHANNEL 15
|
||||
#endif
|
||||
|
||||
extern const struct mg_radio mg_radio_802154;
|
||||
|
||||
struct mg_mesh_node {
|
||||
struct mg_tcpip_if *radio; // Owning radio driver interface
|
||||
struct mg_tcpip_if *lan; // Bridge LAN interface, NULL for a client
|
||||
const struct mg_mesh *strategy; // Selected strategy
|
||||
size_t (*tx)(struct mg_tcpip_if *, const void *, size_t); // Radio frame TX
|
||||
size_t rx_len; // Complete Ethernet frame in the interface RX buffer
|
||||
uint64_t now; // Current driver poll time, us
|
||||
uint64_t tx_at; // Earliest next transmission, us
|
||||
void *data; // Private state owned by the strategy
|
||||
size_t ram_used; // Live mesh state bytes
|
||||
size_t ram_peak; // Peak live mesh state bytes
|
||||
uint64_t next_tick; // Next deadline in us, or UINT64_MAX
|
||||
uint16_t radio_address; // Local address supplied by the radio
|
||||
bool joined; // Client is a member of the mesh
|
||||
};
|
||||
|
||||
struct mg_mesh {
|
||||
const char *name;
|
||||
bool (*init)(struct mg_mesh_node *node, struct mg_tcpip_if *radio,
|
||||
struct mg_tcpip_if *lan, uint16_t radio_address);
|
||||
void (*tick)(struct mg_mesh_node *node, uint64_t now_microseconds);
|
||||
void (*free)(struct mg_mesh_node *node);
|
||||
size_t (*tx)(struct mg_mesh_node *, const void *, size_t); // ETH frame TX
|
||||
void (*rx)(struct mg_mesh_node *, void *, size_t); // Radio frame RX
|
||||
bool single_bridge; // Require exactly one bridge
|
||||
bool scheduled_tx; // The mesh schedules transmission turns
|
||||
const struct mg_radio *radio; // Radio configuration for this strategy
|
||||
};
|
||||
|
||||
extern const struct mg_mesh mg_mesh_default;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -4899,6 +5023,15 @@ void mg_bsd_poll(struct mg_mgr *);
|
||||
#ifndef EPIPE
|
||||
#define EPIPE 32
|
||||
#endif
|
||||
#ifndef EAFNOSUPPORT
|
||||
#define EAFNOSUPPORT 97
|
||||
#endif
|
||||
#ifndef ENOPROTOOPT
|
||||
#define ENOPROTOOPT 92
|
||||
#endif
|
||||
#ifndef ENOSPC
|
||||
#define ENOSPC 28
|
||||
#endif
|
||||
#ifndef EPROTONOSUPPORT
|
||||
#define EPROTONOSUPPORT 93
|
||||
#endif
|
||||
@@ -4980,7 +5113,9 @@ typedef struct fd_set {
|
||||
#define IPPROTO_UDP 17
|
||||
#define INADDR_ANY 0
|
||||
#define INADDR_LOOPBACK 0x7f000001
|
||||
#define INADDR_NONE ((in_addr_t) -1)
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define INET6_ADDRSTRLEN 46
|
||||
#define SOL_SOCKET 0xffff
|
||||
#define SO_REUSEADDR 2
|
||||
#define SO_REUSEPORT 15
|
||||
@@ -5174,6 +5309,75 @@ struct mg_tcpip_driver_imxrt_data {
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
|
||||
MG_ENABLE_DRIVER_MCXW71
|
||||
|
||||
struct mg_tcpip_driver_mcxw71_data {
|
||||
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||
};
|
||||
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
|
||||
|
||||
#define MG_TCPIP_DRIVER_MCXW71_INIT(manager) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_mcxw71_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mif_.ip = MG_TCPIP_IP; \
|
||||
mif_.mask = MG_TCPIP_MASK; \
|
||||
mif_.gw = MG_TCPIP_GW; \
|
||||
mif_.mgr = (manager); \
|
||||
mif_.driver = &mg_tcpip_driver_mcxw71; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
mg_tcpip_init((manager), &mif_); \
|
||||
MG_INFO(("Driver: mcxw71, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MCXW71_INIT(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MG24) && MG_ENABLE_DRIVER_MG24
|
||||
|
||||
struct mg_tcpip_driver_mg24_data {
|
||||
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||
};
|
||||
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_mg24;
|
||||
|
||||
#define MG_TCPIP_DRIVER_MG24_INIT(manager) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_mg24_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mif_.ip = MG_TCPIP_IP; \
|
||||
mif_.mask = MG_TCPIP_MASK; \
|
||||
mif_.gw = MG_TCPIP_GW; \
|
||||
mif_.mgr = (manager); \
|
||||
mif_.driver = &mg_tcpip_driver_mg24; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
mg_tcpip_init((manager), &mif_); \
|
||||
MG_INFO(("Driver: mg24, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MG24_INIT(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP && \
|
||||
@@ -5223,6 +5427,40 @@ extern struct mg_tcpip_driver mg_tcpip_driver_netc;
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_NRF52) && MG_ENABLE_DRIVER_NRF52
|
||||
|
||||
struct mg_tcpip_driver_nrf52_data {
|
||||
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||
};
|
||||
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_nrf52;
|
||||
|
||||
#define MG_TCPIP_DRIVER_NRF52_INIT(manager) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_nrf52_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mif_.ip = MG_TCPIP_IP; \
|
||||
mif_.mask = MG_TCPIP_MASK; \
|
||||
mif_.gw = MG_TCPIP_GW; \
|
||||
mif_.mgr = (manager); \
|
||||
mif_.driver = &mg_tcpip_driver_nrf52; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
mg_tcpip_init((manager), &mif_); \
|
||||
MG_INFO(("Driver: nrf52, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_NRF52_INIT(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if MG_ENABLE_TCPIP && \
|
||||
@@ -5375,6 +5613,7 @@ struct mg_tcpip_driver_rw612_data {
|
||||
#define MG_DRIVER_MDC_HOLDTIME 3
|
||||
#endif
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(mgr) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_rw612_data driver_data_; \
|
||||
@@ -5390,6 +5629,42 @@ struct mg_tcpip_driver_rw612_data {
|
||||
mg_tcpip_init(mgr, &mif_); \
|
||||
MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612W) && \
|
||||
MG_ENABLE_DRIVER_RW612W
|
||||
|
||||
struct mg_tcpip_driver_rw612w_data {
|
||||
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||
};
|
||||
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_rw612w;
|
||||
|
||||
#define MG_TCPIP_DRIVER_RW612W_INIT(manager) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_rw612w_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mif_.ip = MG_TCPIP_IP; \
|
||||
mif_.mask = MG_TCPIP_MASK; \
|
||||
mif_.gw = MG_TCPIP_GW; \
|
||||
mif_.mgr = (manager); \
|
||||
mif_.driver = &mg_tcpip_driver_rw612w; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
mg_tcpip_init((manager), &mif_); \
|
||||
MG_INFO(("Driver: rw612w, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_RW612W_INIT(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5555,6 +5830,13 @@ struct mg_tcpip_driver_stm32f_data {
|
||||
#if !defined(MG_ENABLE_DRIVER_STM32H)
|
||||
#define MG_ENABLE_DRIVER_STM32H 0
|
||||
#endif
|
||||
#if !defined(MG_ENABLE_DRIVER_STM32C)
|
||||
#define MG_ENABLE_DRIVER_STM32C 0
|
||||
#endif
|
||||
#if MG_ENABLE_DRIVER_STM32C // STM32C5: STM32H5 MAC, the IRQ is ETH1
|
||||
#undef MG_ENABLE_DRIVER_STM32H
|
||||
#define MG_ENABLE_DRIVER_STM32H 1
|
||||
#endif
|
||||
#if !defined(MG_ENABLE_DRIVER_MCXN)
|
||||
#define MG_ENABLE_DRIVER_MCXN 0
|
||||
#endif
|
||||
@@ -5594,9 +5876,11 @@ struct mg_tcpip_driver_stm32h_data {
|
||||
#define MG_DRIVER_MDC_CR 4
|
||||
#endif
|
||||
|
||||
#if MG_ENABLE_DRIVER_STM32H && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#if MG_ENABLE_DRIVER_STM32H && !MG_ENABLE_DRIVER_STM32C && \
|
||||
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn)
|
||||
#elif MG_ENABLE_DRIVER_STM32N && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#elif (MG_ENABLE_DRIVER_STM32N || MG_ENABLE_DRIVER_STM32C) && \
|
||||
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH1_IRQn)
|
||||
#elif !defined(MG_ENABLE_ETH_IRQ)
|
||||
#define MG_ENABLE_ETH_IRQ()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
</environment>
|
||||
</environments>
|
||||
<releases>
|
||||
<release date="2026-10-21" version="7.23.3">See https://github.com/cesanta/mongoose/releases/tag/7.23</release>
|
||||
<release date="2026-10-19" version="7.23.2">See https://github.com/cesanta/mongoose/releases/tag/7.23</release>
|
||||
<release date="2026-09-28" version="7.23.1">See https://github.com/cesanta/mongoose/releases/tag/7.23</release>
|
||||
<release date="2026-05-11" version="7.21.7">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
|
||||
@@ -28,7 +29,7 @@
|
||||
<keyword>IMPLEMENTED_WITH_STM32CUBEMX</keyword>
|
||||
</keywords>
|
||||
<components>
|
||||
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.23.2" maxInstances="1">
|
||||
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.23.3" maxInstances="1">
|
||||
<description>Networking Core</description>
|
||||
<files>
|
||||
<file category="doc" name="README.md"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION = 7.23.2
|
||||
VERSION = 7.23.3
|
||||
REPO_ROOT = ../..
|
||||
MX ?= /Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX
|
||||
FILES = Cesanta.I-CUBE-Mongoose.pdsc Release_Notes.html mongoose_config.h README.md .config/mongoose_parameters.json
|
||||
|
||||
+5
-64
@@ -32,6 +32,7 @@
|
||||
|
||||
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && \
|
||||
!defined(MG_ENABLE_DRIVER_STM32H) && !defined(MG_ENABLE_DRIVER_STM32N) && \
|
||||
!defined(MG_ENABLE_DRIVER_STM32C) && \
|
||||
!defined(MG_ENABLE_DRIVER_CYW) && !defined(MG_ENABLE_DRIVER_CYW_SDIO) && \
|
||||
!defined(MG_ENABLE_DRIVER_ST67W6)
|
||||
#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7)
|
||||
@@ -40,6 +41,8 @@
|
||||
#define MG_ENABLE_DRIVER_STM32H 1
|
||||
#elif defined(STM32N6)
|
||||
#define MG_ENABLE_DRIVER_STM32N 1
|
||||
#elif defined(STM32C5)
|
||||
#define MG_ENABLE_DRIVER_STM32C 1
|
||||
#else
|
||||
#error Select a driver in mongoose_config.h
|
||||
#endif
|
||||
@@ -56,70 +59,8 @@
|
||||
#define MG_OTA MG_OTA_STM32H5
|
||||
#elif !defined(MG_OTA) && defined(STM32H7)
|
||||
#define MG_OTA MG_OTA_STM32H7
|
||||
#endif
|
||||
|
||||
// OTA rollback timer via IWDG. Fixed 10 s hang-detection window (prescaler
|
||||
// /1024, LSI ~32 kHz: reload = 10 * 32000 / 1024 = 312). mg_ota_poll() feeds
|
||||
// the watchdog every 500 ms while in MG_OTA_TESTING state; if the event loop
|
||||
// hangs for 10 s, the IWDG fires and the device rolls back.
|
||||
// STM32H7 names the peripheral IWDG1; all others use IWDG.
|
||||
// The total evaluation period is set by MG_OTA_ROLLBACK_TIMEOUT_SECONDS.
|
||||
// STM32H7 names the watchdog peripheral IWDG1; all other families use IWDG.
|
||||
#if defined(IWDG1) && !defined(IWDG)
|
||||
#define MG_IWDG IWDG1
|
||||
#else
|
||||
#define MG_IWDG IWDG
|
||||
#endif
|
||||
// PR=6 → /256 prescaler, RLR=1250 → ~10 s at LSI 32 kHz.
|
||||
// Works on F4/F7/H5/H7; uses direct register writes so it is safe before HAL_Init().
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_START
|
||||
#define MG_OTA_ROLLBACK_TIMER_START() \
|
||||
do { \
|
||||
MG_IWDG->KR = 0xCCCCU; /* start + LSI */ \
|
||||
MG_IWDG->KR = 0x5555U; /* unlock PR/RLR */ \
|
||||
MG_IWDG->PR = 6U; /* /256 */ \
|
||||
MG_IWDG->RLR = 1250U; /* 10 s */ \
|
||||
while (MG_IWDG->SR & (IWDG_SR_PVU | IWDG_SR_RVU)) (void) 0; \
|
||||
MG_IWDG->KR = 0xAAAAU; /* reload */ \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
// Feed (reload) the IWDG. Called from mg_ota_poll() every 500 ms.
|
||||
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
|
||||
#define MG_OTA_ROLLBACK_TIMER_FEED() (MG_IWDG->KR = 0xAAAAU)
|
||||
#endif
|
||||
|
||||
// OTA state in a backup register that survives warm resets but clears on POR.
|
||||
// STATE_GET reads work without any setup. STATE_SET enables the RTC APB clock
|
||||
// and unlocks backup domain write access via direct register writes, so it is
|
||||
// safe to call from any context (before or after HAL_Init).
|
||||
// H5 (TAMP->BKP0R), H7 (RTC->BKP0R), F4/F7 (RTC->BKP0R).
|
||||
#ifndef MG_OTA_STATE_GET
|
||||
#if defined(PWR_DBPCR_DBP)
|
||||
#define MG_OTA_DBP_ENABLE() (PWR->DBPCR |= PWR_DBPCR_DBP)
|
||||
#elif defined(PWR_CR1_DBP)
|
||||
#define MG_OTA_DBP_ENABLE() (PWR->CR1 |= PWR_CR1_DBP)
|
||||
#else
|
||||
#define MG_OTA_DBP_ENABLE() (PWR->CR |= PWR_CR_DBP)
|
||||
#endif
|
||||
|
||||
#if defined(TAMP)
|
||||
#define MG_OTA_STATE_GET() \
|
||||
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, TAMP->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
|
||||
(TAMP->BKP0R = (uint32_t) (v)), TAMP->BKP0R)
|
||||
#elif defined(RCC_APB4ENR_RTCAPBEN)
|
||||
#define MG_OTA_STATE_GET() (RTC->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB4ENR |= RCC_APB4ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
|
||||
(RTC->BKP0R = (uint32_t) (v)))
|
||||
#else
|
||||
#define MG_OTA_STATE_GET() (RTC->BKP0R)
|
||||
#define MG_OTA_STATE_SET(v) \
|
||||
(RCC->APB1ENR |= RCC_APB1ENR_PWREN, MG_OTA_DBP_ENABLE(), \
|
||||
(RTC->BKP0R = (uint32_t) (v)))
|
||||
#endif
|
||||
#elif !defined(MG_OTA) && defined(STM32C5)
|
||||
#define MG_OTA MG_OTA_STM32C5
|
||||
#endif
|
||||
|
||||
// use HAL-defined execute-in-ram section
|
||||
|
||||
+5
-2
@@ -22,8 +22,11 @@
|
||||
#include <esp_system.h>
|
||||
#include <esp_mac.h>
|
||||
#include <esp_idf_version.h>
|
||||
// #include <esp_memory_utils.h> // For health record coredumps
|
||||
// #include <esp_core_dump.h> // For health record coredumps
|
||||
#if MG_ENABLE_HEALTH
|
||||
#include <esp_memory_utils.h>
|
||||
#include <esp_core_dump.h>
|
||||
#include <esp_partition.h>
|
||||
#endif
|
||||
|
||||
#define MG_PATH_MAX 128
|
||||
|
||||
|
||||
@@ -32,8 +32,9 @@ static struct mg_mgr *s_mgr;
|
||||
enum bsd_op {
|
||||
BSD_OP_NONE, BSD_OP_SOCKET, BSD_OP_BIND, BSD_OP_LISTEN, BSD_OP_FCNTL,
|
||||
BSD_OP_ACCEPT, BSD_OP_CONNECT, BSD_OP_RECV, BSD_OP_SEND, BSD_OP_SENDTO,
|
||||
BSD_OP_RECVFROM, BSD_OP_CLOSE, BSD_OP_POLL, BSD_OP_SELECT,
|
||||
BSD_OP_GETHOSTBYNAME,
|
||||
BSD_OP_RECVFROM, BSD_OP_CLOSE, BSD_OP_SHUTDOWN, BSD_OP_SETSOCKOPT,
|
||||
BSD_OP_GETSOCKOPT, BSD_OP_GETSOCKNAME, BSD_OP_GETPEERNAME, BSD_OP_POLL,
|
||||
BSD_OP_SELECT, BSD_OP_GETHOSTBYNAME,
|
||||
};
|
||||
|
||||
// A BSD API request from a user task. Gets sent via the RTOS queue, and
|
||||
@@ -50,6 +51,7 @@ struct mg_bsd_cmd {
|
||||
struct { int fd, backlog; } op_listen;
|
||||
struct { int fd, cmd, arg; } op_fcntl;
|
||||
struct { int fd; } op_close;
|
||||
struct { int fd, how; } op_shutdown;
|
||||
struct { int fd; const struct sockaddr *addr; socklen_t len; } op_bind;
|
||||
struct { int fd; const struct sockaddr *addr; socklen_t len; } op_connect;
|
||||
struct { int fd; struct sockaddr *addr; socklen_t *addrlen; } op_accept;
|
||||
@@ -57,6 +59,9 @@ struct mg_bsd_cmd {
|
||||
struct { int fd; const void *buf; size_t len; int flags; } op_send;
|
||||
struct { int fd; const void *buf; size_t len; int flags; const struct sockaddr *addr; socklen_t addrlen; } op_sendto;
|
||||
struct { int fd; void *buf; size_t len; int flags; struct sockaddr *src; socklen_t *addrlen; } op_recvfrom;
|
||||
struct { int fd, level, optname; const void *optval; socklen_t optlen; } op_setsockopt;
|
||||
struct { int fd, level, optname; void *optval; socklen_t *optlen; } op_getsockopt;
|
||||
struct { int fd; struct sockaddr *addr; socklen_t *addrlen; } op_getname;
|
||||
struct { struct pollfd *fds; nfds_t nfds; uint64_t deadline; } op_poll;
|
||||
struct { int nfds; fd_set *r, *w, *e; uint64_t deadline; } op_select;
|
||||
struct { const char *name; struct hostent *result; } op_gethostbyname;
|
||||
@@ -73,6 +78,7 @@ struct mg_bsd_sock {
|
||||
struct mg_connection *c;
|
||||
struct mg_bsd_sock *listener;
|
||||
bool used, bound, closed, nonblock, is_udp, orphan; // closed: c was closed
|
||||
bool error_pending, is_shut_rd, is_shut_wr;
|
||||
};
|
||||
|
||||
static struct mg_bsd_sock s_socks[MG_BSD_MAX_SOCKETS > 0 ?
|
||||
@@ -93,6 +99,21 @@ static struct mg_bsd_sock *bsd_get(int fd) {
|
||||
return s;
|
||||
}
|
||||
|
||||
static int bsd_addr4(const struct sockaddr *addr, socklen_t len) {
|
||||
if (addr == NULL || len < sizeof(struct sockaddr_in)) return EINVAL;
|
||||
return addr->sa_family == AF_INET ? 0 : EAFNOSUPPORT;
|
||||
}
|
||||
|
||||
static bool bsd_aton4(const char *str, in_addr_t *ip) {
|
||||
struct mg_addr addr;
|
||||
size_t len = strlen(str);
|
||||
|
||||
if (len == 0 || !mg_aton(mg_str_n(str, len), &addr) || addr.is_ip6)
|
||||
return false;
|
||||
memcpy(ip, addr.addr.ip, sizeof(*ip));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void bsd_attach(struct mg_bsd_sock *s, struct mg_connection *c) {
|
||||
s->c = c;
|
||||
c->fn = bsd_ev;
|
||||
@@ -178,8 +199,10 @@ static void bsd_ev(struct mg_connection *c, int ev, void *ev_data) {
|
||||
c->is_bsd = 1;
|
||||
c->is_udp = child->is_udp;
|
||||
}
|
||||
} else if (ev == MG_EV_READ && s->is_shut_rd) {
|
||||
mg_iobuf_del(&c->recv, 0, c->recv.len);
|
||||
} else if (ev == MG_EV_ERROR) {
|
||||
if (s->err == 0) s->err = EIO;
|
||||
if (s->err == 0) s->err = EIO, s->error_pending = true;
|
||||
#if MG_ENABLE_FREERTOS
|
||||
bsd_finish_waiters(s, true);
|
||||
#endif
|
||||
@@ -289,9 +312,11 @@ static int bsd_ready(struct mg_bsd_sock *s) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ;
|
||||
if (!s->is_udp && !s->c->is_connecting && !s->c->is_closing &&
|
||||
!s->c->is_draining)
|
||||
if (s->is_shut_rd || s->c->recv.len > 0 || s->c->is_draining)
|
||||
ready |= BSD_READ;
|
||||
if (s->is_shut_wr) ready |= BSD_ERROR;
|
||||
if (!s->is_shut_wr && !s->is_udp && !s->c->is_connecting &&
|
||||
!s->c->is_closing && !s->c->is_draining)
|
||||
ready |= BSD_WRITE; // Established TCP socket
|
||||
return ready;
|
||||
}
|
||||
@@ -368,9 +393,13 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case BSD_OP_BIND:
|
||||
case BSD_OP_BIND: {
|
||||
int err;
|
||||
|
||||
if ((s = bsd_get(cmd->op_bind.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
} else if ((err = bsd_addr4(cmd->op_bind.addr, cmd->op_bind.len)) != 0) {
|
||||
cmd->result = -1, cmd->err = err;
|
||||
} else {
|
||||
const struct sockaddr_in *sa =
|
||||
(const struct sockaddr_in *) cmd->op_bind.addr;
|
||||
@@ -389,6 +418,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case BSD_OP_LISTEN: {
|
||||
char url[32];
|
||||
|
||||
@@ -464,11 +494,17 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
case BSD_OP_CONNECT:
|
||||
case BSD_OP_CONNECT: {
|
||||
int err;
|
||||
|
||||
if ((s = bsd_get(cmd->op_connect.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
return true;
|
||||
}
|
||||
if ((err = bsd_addr4(cmd->op_connect.addr, cmd->op_connect.len)) != 0) {
|
||||
cmd->result = -1, cmd->err = err;
|
||||
return true;
|
||||
}
|
||||
if (!cmd->started && s->c == NULL) {
|
||||
if (s->closed || (c = bsd_new_conn(s)) == NULL) {
|
||||
cmd->result = -1, cmd->err = s->closed ? EIO : ENOMEM;
|
||||
@@ -507,6 +543,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
cmd->result = -1, cmd->err = EINPROGRESS;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
case BSD_OP_RECV:
|
||||
if ((s = bsd_get(cmd->op_recv.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
@@ -529,7 +566,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
cmd->result = -1, cmd->err = s->err;
|
||||
return true;
|
||||
}
|
||||
if (c->is_draining) { // Orderly shutdown by peer
|
||||
if (s->is_shut_rd || c->is_draining) { // Local or peer shutdown
|
||||
cmd->result = 0;
|
||||
return true;
|
||||
}
|
||||
@@ -543,8 +580,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
case BSD_OP_SEND:
|
||||
if ((s = bsd_get(cmd->op_send.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
|
||||
c->is_draining) {
|
||||
} else if (s->is_shut_wr || (c = s->c) == NULL || s->err != 0 ||
|
||||
c->is_closing || c->is_draining) {
|
||||
cmd->result = -1, cmd->err = EPIPE;
|
||||
} else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) {
|
||||
cmd->result = (int) cmd->op_send.len;
|
||||
@@ -558,11 +595,16 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
case BSD_OP_SENDTO:
|
||||
case BSD_OP_SENDTO: {
|
||||
int err;
|
||||
|
||||
if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
|
||||
c->is_draining) {
|
||||
} else if ((err = bsd_addr4(cmd->op_sendto.addr,
|
||||
cmd->op_sendto.addrlen)) != 0) {
|
||||
cmd->result = -1, cmd->err = err;
|
||||
} else if (s->is_shut_wr || (c = s->c) == NULL || s->err != 0 ||
|
||||
c->is_closing || c->is_draining) {
|
||||
cmd->result = -1, cmd->err = EPIPE;
|
||||
} else {
|
||||
const struct sockaddr_in *sa =
|
||||
@@ -583,11 +625,16 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case BSD_OP_RECVFROM:
|
||||
if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
return true;
|
||||
}
|
||||
if (s->is_shut_rd) {
|
||||
cmd->result = 0;
|
||||
return true;
|
||||
}
|
||||
if ((c = s->c) == NULL) {
|
||||
cmd->result = s->closed && s->err == 0 ? 0 : -1;
|
||||
cmd->err = s->closed ? s->err : ENOTCONN;
|
||||
@@ -595,6 +642,13 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
}
|
||||
if (c->recv.len > 0) {
|
||||
size_t n = cmd->op_recvfrom.len;
|
||||
|
||||
if (cmd->op_recvfrom.src != NULL &&
|
||||
(cmd->op_recvfrom.addrlen == NULL ||
|
||||
*cmd->op_recvfrom.addrlen < sizeof(struct sockaddr_in))) {
|
||||
cmd->result = -1, cmd->err = EINVAL;
|
||||
return true;
|
||||
}
|
||||
if (c->recv.len < n) n = c->recv.len;
|
||||
memcpy(cmd->op_recvfrom.buf, c->recv.buf, n);
|
||||
if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
|
||||
@@ -627,6 +681,98 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
cmd->result = 0;
|
||||
}
|
||||
return true;
|
||||
case BSD_OP_SHUTDOWN:
|
||||
if ((s = bsd_get(cmd->op_shutdown.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
} else if (cmd->op_shutdown.how != SHUT_RD &&
|
||||
cmd->op_shutdown.how != SHUT_WR &&
|
||||
cmd->op_shutdown.how != SHUT_RDWR) {
|
||||
cmd->result = -1, cmd->err = EINVAL;
|
||||
} else if ((c = s->c) == NULL || c->is_listening || c->is_closing ||
|
||||
(s->is_udp && !c->is_client)) {
|
||||
cmd->result = -1, cmd->err = ENOTCONN;
|
||||
} else {
|
||||
if (cmd->op_shutdown.how != SHUT_WR) {
|
||||
s->is_shut_rd = true;
|
||||
mg_iobuf_del(&c->recv, 0, c->recv.len);
|
||||
}
|
||||
if (cmd->op_shutdown.how != SHUT_RD) s->is_shut_wr = true;
|
||||
cmd->result = 0;
|
||||
}
|
||||
return true;
|
||||
case BSD_OP_SETSOCKOPT:
|
||||
if ((s = bsd_get(cmd->op_setsockopt.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
} else if (cmd->op_setsockopt.optval == NULL ||
|
||||
cmd->op_setsockopt.optlen != sizeof(int)) {
|
||||
cmd->result = -1, cmd->err = EINVAL;
|
||||
} else if ((cmd->op_setsockopt.level == SOL_SOCKET &&
|
||||
(cmd->op_setsockopt.optname == SO_REUSEADDR
|
||||
#ifdef SO_NOSIGPIPE
|
||||
|| cmd->op_setsockopt.optname == SO_NOSIGPIPE
|
||||
#endif
|
||||
)) ||
|
||||
(!s->is_udp && cmd->op_setsockopt.level == IPPROTO_TCP &&
|
||||
cmd->op_setsockopt.optname == TCP_NODELAY)) {
|
||||
cmd->result = 0;
|
||||
} else {
|
||||
cmd->result = -1, cmd->err = ENOPROTOOPT;
|
||||
}
|
||||
return true;
|
||||
case BSD_OP_GETSOCKOPT:
|
||||
if ((s = bsd_get(cmd->op_getsockopt.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
} else if (cmd->op_getsockopt.level != SOL_SOCKET ||
|
||||
cmd->op_getsockopt.optname != SO_ERROR) {
|
||||
cmd->result = -1, cmd->err = ENOPROTOOPT;
|
||||
} else if (cmd->op_getsockopt.optval == NULL ||
|
||||
cmd->op_getsockopt.optlen == NULL ||
|
||||
*cmd->op_getsockopt.optlen < sizeof(int)) {
|
||||
cmd->result = -1, cmd->err = EINVAL;
|
||||
} else {
|
||||
*(int *) cmd->op_getsockopt.optval =
|
||||
s->error_pending ? s->err : 0;
|
||||
s->error_pending = false;
|
||||
*cmd->op_getsockopt.optlen = sizeof(int);
|
||||
cmd->result = 0;
|
||||
}
|
||||
return true;
|
||||
case BSD_OP_GETSOCKNAME:
|
||||
case BSD_OP_GETPEERNAME:
|
||||
if ((s = bsd_get(cmd->op_getname.fd)) == NULL) {
|
||||
cmd->result = -1, cmd->err = EBADF;
|
||||
} else if (cmd->op_getname.addr == NULL ||
|
||||
cmd->op_getname.addrlen == NULL ||
|
||||
*cmd->op_getname.addrlen < sizeof(struct sockaddr_in)) {
|
||||
cmd->result = -1, cmd->err = EINVAL;
|
||||
} else if ((c = s->c) == NULL &&
|
||||
(cmd->op == BSD_OP_GETPEERNAME || s->closed)) {
|
||||
cmd->result = -1, cmd->err = ENOTCONN;
|
||||
} else if (cmd->op == BSD_OP_GETPEERNAME &&
|
||||
(c->is_listening || (s->is_udp && !c->is_client))) {
|
||||
cmd->result = -1, cmd->err = ENOTCONN;
|
||||
} else if (c != NULL &&
|
||||
((cmd->op == BSD_OP_GETSOCKNAME && c->loc.is_ip6) ||
|
||||
(cmd->op == BSD_OP_GETPEERNAME && c->rem.is_ip6))) {
|
||||
cmd->result = -1, cmd->err = EAFNOSUPPORT;
|
||||
} else {
|
||||
struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_getname.addr;
|
||||
|
||||
if (c == NULL) {
|
||||
*sa = s->addr;
|
||||
sa->sin_family = AF_INET;
|
||||
} else {
|
||||
const struct mg_addr *addr = cmd->op == BSD_OP_GETSOCKNAME
|
||||
? &c->loc
|
||||
: &c->rem;
|
||||
sa->sin_family = AF_INET;
|
||||
sa->sin_port = addr->port;
|
||||
sa->sin_addr.s_addr = addr->addr.ip4;
|
||||
}
|
||||
*cmd->op_getname.addrlen = sizeof(*sa);
|
||||
cmd->result = 0;
|
||||
}
|
||||
return true;
|
||||
case BSD_OP_POLL: {
|
||||
struct pollfd *p;
|
||||
int i, n = 0, ready;
|
||||
@@ -643,7 +789,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
|
||||
p->revents |= POLLIN;
|
||||
if ((p->events & POLLOUT) && (ready & BSD_WRITE))
|
||||
p->revents |= POLLOUT;
|
||||
if (s->err != 0) p->revents |= POLLERR;
|
||||
if (s->err != 0 || s->is_shut_wr) p->revents |= POLLERR;
|
||||
if (s->closed) p->revents |= POLLHUP;
|
||||
}
|
||||
if (p->revents != 0) n++;
|
||||
@@ -895,6 +1041,55 @@ int close(int fd) {
|
||||
return cmd.result;
|
||||
}
|
||||
|
||||
int shutdown(int fd, int how) {
|
||||
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
|
||||
cmd.op = BSD_OP_SHUTDOWN, cmd.op_shutdown.fd = fd,
|
||||
cmd.op_shutdown.how = how;
|
||||
bsd_dispatch(&cmd);
|
||||
errno = cmd.err;
|
||||
return cmd.result;
|
||||
}
|
||||
|
||||
int setsockopt(int fd, int level, int optname, const void *optval,
|
||||
socklen_t optlen) {
|
||||
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
|
||||
cmd.op = BSD_OP_SETSOCKOPT, cmd.op_setsockopt.fd = fd,
|
||||
cmd.op_setsockopt.level = level, cmd.op_setsockopt.optname = optname,
|
||||
cmd.op_setsockopt.optval = optval, cmd.op_setsockopt.optlen = optlen;
|
||||
bsd_dispatch(&cmd);
|
||||
errno = cmd.err;
|
||||
return cmd.result;
|
||||
}
|
||||
|
||||
int getsockopt(int fd, int level, int optname, void *optval,
|
||||
socklen_t *optlen) {
|
||||
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
|
||||
cmd.op = BSD_OP_GETSOCKOPT, cmd.op_getsockopt.fd = fd,
|
||||
cmd.op_getsockopt.level = level, cmd.op_getsockopt.optname = optname,
|
||||
cmd.op_getsockopt.optval = optval, cmd.op_getsockopt.optlen = optlen;
|
||||
bsd_dispatch(&cmd);
|
||||
errno = cmd.err;
|
||||
return cmd.result;
|
||||
}
|
||||
|
||||
int getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) {
|
||||
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
|
||||
cmd.op = BSD_OP_GETSOCKNAME, cmd.op_getname.fd = fd,
|
||||
cmd.op_getname.addr = addr, cmd.op_getname.addrlen = addrlen;
|
||||
bsd_dispatch(&cmd);
|
||||
errno = cmd.err;
|
||||
return cmd.result;
|
||||
}
|
||||
|
||||
int getpeername(int fd, struct sockaddr *addr, socklen_t *addrlen) {
|
||||
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
|
||||
cmd.op = BSD_OP_GETPEERNAME, cmd.op_getname.fd = fd,
|
||||
cmd.op_getname.addr = addr, cmd.op_getname.addrlen = addrlen;
|
||||
bsd_dispatch(&cmd);
|
||||
errno = cmd.err;
|
||||
return cmd.result;
|
||||
}
|
||||
|
||||
int poll(struct pollfd *fds, nfds_t nfds, int timeout) {
|
||||
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
|
||||
uint64_t deadline = timeout < 0 ? (uint64_t) ~0
|
||||
@@ -926,6 +1121,36 @@ struct hostent *gethostbyname(const char *name) {
|
||||
return cmd.op_gethostbyname.result;
|
||||
}
|
||||
|
||||
int inet_pton(int af, const char *src, void *dst) {
|
||||
if (af != AF_INET) {
|
||||
errno = EAFNOSUPPORT;
|
||||
return -1;
|
||||
}
|
||||
return bsd_aton4(src, dst) ? 1 : 0;
|
||||
}
|
||||
|
||||
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) {
|
||||
if (af != AF_INET) {
|
||||
errno = EAFNOSUPPORT;
|
||||
return NULL;
|
||||
}
|
||||
if (mg_snprintf(dst, size, "%M", mg_print_ip4, (void *) src) >= size) {
|
||||
errno = ENOSPC;
|
||||
return NULL;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
in_addr_t inet_addr(const char *src) {
|
||||
in_addr_t addr;
|
||||
return bsd_aton4(src, &addr) ? addr : INADDR_NONE;
|
||||
}
|
||||
|
||||
char *inet_ntoa(struct in_addr addr) {
|
||||
static char buf[INET_ADDRSTRLEN];
|
||||
return (char *) inet_ntop(AF_INET, &addr, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
#endif // MG_ENABLE_BSD_SOCKETS
|
||||
|
||||
#if MG_ENABLE_BSD_SOCKETS
|
||||
|
||||
@@ -55,6 +55,15 @@ void mg_bsd_poll(struct mg_mgr *);
|
||||
#ifndef EPIPE
|
||||
#define EPIPE 32
|
||||
#endif
|
||||
#ifndef EAFNOSUPPORT
|
||||
#define EAFNOSUPPORT 97
|
||||
#endif
|
||||
#ifndef ENOPROTOOPT
|
||||
#define ENOPROTOOPT 92
|
||||
#endif
|
||||
#ifndef ENOSPC
|
||||
#define ENOSPC 28
|
||||
#endif
|
||||
#ifndef EPROTONOSUPPORT
|
||||
#define EPROTONOSUPPORT 93
|
||||
#endif
|
||||
@@ -136,7 +145,9 @@ typedef struct fd_set {
|
||||
#define IPPROTO_UDP 17
|
||||
#define INADDR_ANY 0
|
||||
#define INADDR_LOOPBACK 0x7f000001
|
||||
#define INADDR_NONE ((in_addr_t) -1)
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define INET6_ADDRSTRLEN 46
|
||||
#define SOL_SOCKET 0xffff
|
||||
#define SO_REUSEADDR 2
|
||||
#define SO_REUSEPORT 15
|
||||
|
||||
+3
-18
@@ -533,21 +533,6 @@ static void mg_dash_handle_add(struct mg_connection *c, struct mg_dash *dash,
|
||||
}
|
||||
}
|
||||
|
||||
// Firmware upload callback for mg_http_stream_body(). Uses *p as a flag:
|
||||
// non-NULL means an OTA session is open and must be closed by mg_ota_end()
|
||||
static bool otacb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
if (hm != NULL) { // Start
|
||||
if (!mg_ota_begin(hm->body.len)) return false;
|
||||
*p = (void *) (ptrdiff_t) 1;
|
||||
return true;
|
||||
} else if (data != NULL) { // Next chunk
|
||||
return mg_ota_write(data->buf, data->len);
|
||||
}
|
||||
if (*p == NULL) return false; // End. Begin failed, nothing to finish
|
||||
*p = NULL;
|
||||
return mg_ota_end();
|
||||
}
|
||||
|
||||
static void mg_serve_fs(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_dash_user *u, bool stream) {
|
||||
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
|
||||
@@ -594,9 +579,9 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
mg_serve_fs(c, hm, u, true);
|
||||
return; // Important - the serve_fs() call can invalidate hm
|
||||
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
|
||||
// Authenticated above. If streaming started, the stream handler owns
|
||||
// the connection and c->data, so skip the marker check below
|
||||
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
|
||||
// Authenticated above. Streaming owns the connection and c->data.
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
return;
|
||||
}
|
||||
if (d->marker != '\0') mg_log_http_req(c, hm);
|
||||
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
|
||||
|
||||
@@ -96,6 +96,8 @@ struct mg_dash {
|
||||
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
|
||||
// Resolves the built-in file manager's root dir for user u, e.g. per-user
|
||||
// upload directories. NULL: file manager disabled
|
||||
// - The returned directory must be nonempty and must not contain ',' or '=',
|
||||
// because it is used as an mg_http_serve_opts::root_dir mapping target.
|
||||
bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
#include "l2_mesh.h"
|
||||
#include "log.h"
|
||||
#include "net_builtin.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
|
||||
MG_ENABLE_DRIVER_MCXW71
|
||||
|
||||
// ZLL, 802.15.4 link layer. See MCXW71 reference manual and NXP
|
||||
// ieee_802_15_4 PHY sources: PhyPacketProcessor.c, PhyPlmeData.c, PhyISR.c
|
||||
struct mg_zll {
|
||||
volatile uint32_t IRQSTS, PHY_CTRL, EVENT_TMR, TIMESTAMP, T1CMP, T2CMP,
|
||||
T2PRIMECMP, T3CMP, T4CMP, PA_PWR, CHANNEL_NUM0, LQI_AND_RSSI,
|
||||
MACSHORTADDRS0, MACLONGADDRS0_LSB, MACLONGADDRS0_MSB, RX_FRAME_FILTER,
|
||||
CCA_LQI_CTRL, CCA2_CTRL, RESERVED_0, DSM_CTRL, RESERVED_1, MACSHORTADDRS1,
|
||||
MACLONGADDRS1_LSB, MACLONGADDRS1_MSB, DUAL_PAN_CTRL, CHANNEL_NUM1,
|
||||
SAM_CTRL, SAM_TABLE, SAM_MATCH, SAM_FREE_IDX, SEQ_CTRL_STS, ACKDELAY,
|
||||
FILTERFAIL_CODE, RX_WTR_MARK, RESERVED_2, SLOT_PRELOAD, SEQ_STATE,
|
||||
TMR_PRESCALE, LENIENCY_LSB, LENIENCY_MSB, PART_ID, COEX_CTRL,
|
||||
COEX_PRIORITY, ENHACK_CTRL0;
|
||||
};
|
||||
|
||||
// TrustZone: code built for the secure state (-mcmse) uses the secure alias
|
||||
// of the radio registers, 0x5xxxxxxx. Otherwise, the non-secure alias
|
||||
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE & 2)
|
||||
#define MGI_ZLL ((struct mg_zll *) 0x58A01000)
|
||||
#define MGI_PKT_RAM ((volatile uint8_t *) 0x58A08000)
|
||||
#else
|
||||
#define MGI_ZLL ((struct mg_zll *) 0x48A01000)
|
||||
#define MGI_PKT_RAM ((volatile uint8_t *) 0x48A08000)
|
||||
#endif
|
||||
|
||||
#define MGI_TX_PB MGI_PKT_RAM // Length byte (incl. FCS), then frame
|
||||
#define MGI_RX_PB (MGI_PKT_RAM + 0x80) // Received frame, no length byte
|
||||
|
||||
#define MGI_IRQSTS_SEQIRQ MG_BIT(0)
|
||||
#define MGI_IRQSTS_RXIRQ MG_BIT(2)
|
||||
#define MGI_IRQSTS_FILTERFAIL MG_BIT(5) // Frame not for us
|
||||
#define MGI_IRQSTS_CRCVALID MG_BIT(15)
|
||||
#define MGI_IRQSTS_RX_LEN(x) (((x) >> 24) & 0x7f) // Frame length incl. FCS
|
||||
#define MGI_PHY_CTRL_XCVSEQ 7U
|
||||
#define MGI_PHY_CTRL_ALL_IRQ_MASKS 0x8007ff00U // Bits 8..18, 31: mask all IRQs
|
||||
#define MGI_PHY_CTRL_SEQMSK MG_BIT(8) // Mask the sequence end IRQ
|
||||
#define MGI_PHY_CTRL_TRCV_MSK MG_BIT(31) // Mask all transceiver IRQs
|
||||
#define MGI_IRQN 53 // RF_802_15_4_IRQn
|
||||
#define MGI_NVIC_ISER ((volatile uint32_t *) 0xE000E100) // Enable IRQ
|
||||
#define MGI_NVIC_ICER ((volatile uint32_t *) 0xE000E180) // Disable IRQ
|
||||
#define MGI_SEQ_STATE_MASK 0x1fU
|
||||
#define MGI_SEQ_STATE_RX_WU 0x10U // RX warm up
|
||||
#define MGI_SEQ_CTRL_NO_RX_RECYCLE MG_BIT(5)
|
||||
#define MGI_FRAME_FILTER 0x70bU // Beacon, data, command; versions 0..2
|
||||
|
||||
// PHY_CTRL[XCVSEQ]
|
||||
enum { MGI_SEQ_IDLE = 0, MGI_SEQ_RX = 1, MGI_SEQ_TX = 2 };
|
||||
#define MGI_SEND_TIMEOUT_MS 60
|
||||
|
||||
static struct mg_tcpip_if *s_mcxw71_ifp;
|
||||
static struct mg_queue s_mcxw71_rx;
|
||||
static char s_mcxw71_queue[2048];
|
||||
static volatile bool s_mcxw71_txing, s_mcxw71_done;
|
||||
static uint64_t s_mcxw71_sendstart;
|
||||
|
||||
// Stop the current sequence, wait for the sequencer to go idle. See PhyAbort()
|
||||
static void mg_mcxw71_abort(void) {
|
||||
if (MGI_ZLL->PHY_CTRL & MGI_PHY_CTRL_XCVSEQ) {
|
||||
while ((MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) == MGI_SEQ_STATE_RX_WU)
|
||||
(void) 0;
|
||||
MGI_ZLL->PHY_CTRL &= ~MGI_PHY_CTRL_XCVSEQ;
|
||||
while (MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) (void) 0;
|
||||
}
|
||||
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
|
||||
}
|
||||
|
||||
// Start receiving, with the sequence end IRQ on, see RF_802_15_4_IRQHandler()
|
||||
static void mg_mcxw71_listen(void) {
|
||||
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
|
||||
MGI_ZLL->PHY_CTRL |= MGI_SEQ_RX;
|
||||
MGI_ZLL->PHY_CTRL &= ~(MGI_PHY_CTRL_SEQMSK | MGI_PHY_CTRL_TRCV_MSK);
|
||||
MGI_NVIC_ISER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
|
||||
}
|
||||
|
||||
// Keep the IRQ handler out while we change the radio. The NVIC disable is
|
||||
// atomic, so PHY_CTRL updates here and there do not race
|
||||
static void mg_mcxw71_lock(void) {
|
||||
MGI_NVIC_ICER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
|
||||
__asm volatile("dsb; isb" ::: "memory");
|
||||
}
|
||||
|
||||
// Start the TX sequence for the loaded frame. Its end: see the IRQ handler
|
||||
static void mg_mcxw71_go(void) {
|
||||
MGI_ZLL->PHY_CTRL |= MGI_PHY_CTRL_TRCV_MSK | MGI_PHY_CTRL_SEQMSK;
|
||||
mg_mcxw71_abort();
|
||||
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
|
||||
MGI_ZLL->PHY_CTRL |= MGI_SEQ_TX;
|
||||
MGI_ZLL->PHY_CTRL &= ~(MGI_PHY_CTRL_SEQMSK | MGI_PHY_CTRL_TRCV_MSK);
|
||||
}
|
||||
|
||||
void RF_802_15_4_IRQHandler(void);
|
||||
void RF_802_15_4_IRQHandler(void) {
|
||||
uint32_t irqsts = MGI_ZLL->IRQSTS;
|
||||
if ((irqsts & MGI_IRQSTS_SEQIRQ) != 0) {
|
||||
mg_mcxw71_abort();
|
||||
if (s_mcxw71_txing) {
|
||||
s_mcxw71_txing = false;
|
||||
s_mcxw71_done = true;
|
||||
} else if ((irqsts & (MGI_IRQSTS_RXIRQ | MGI_IRQSTS_FILTERFAIL |
|
||||
MGI_IRQSTS_CRCVALID)) ==
|
||||
(MGI_IRQSTS_RXIRQ | MGI_IRQSTS_CRCVALID)) {
|
||||
uint8_t frame[125];
|
||||
size_t i, n = MGI_IRQSTS_RX_LEN(irqsts);
|
||||
n = n >= 2 ? n - 2 : 0;
|
||||
if (n > 0 && n <= sizeof(frame)) {
|
||||
for (i = 0; i < n; i++) frame[i] = MGI_RX_PB[i];
|
||||
{
|
||||
char *dst;
|
||||
if (mg_queue_book(&s_mcxw71_rx, &dst, n) >= n) {
|
||||
memcpy(dst, frame, n);
|
||||
mg_queue_add(&s_mcxw71_rx, n);
|
||||
} else {
|
||||
s_mcxw71_ifp->ndrop++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mg_mcxw71_listen();
|
||||
}
|
||||
}
|
||||
|
||||
static size_t mgi_mcxw71_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
|
||||
size_t len) {
|
||||
size_t i;
|
||||
(void) ifp;
|
||||
if (len > 125 || s_mcxw71_txing) return 0;
|
||||
mg_mcxw71_lock();
|
||||
MGI_TX_PB[0] = (uint8_t) (len + 2); // FCS is added by hardware
|
||||
for (i = 0; i < len; i++)
|
||||
MGI_TX_PB[i + 1] = ((const uint8_t *) buf)[i]; // 8-bit access only
|
||||
s_mcxw71_done = false;
|
||||
s_mcxw71_txing = true;
|
||||
s_mcxw71_sendstart = mg_millis();
|
||||
mg_mcxw71_go();
|
||||
MGI_NVIC_ISER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool mgi_mcxw71_radio_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||
struct mg_mesh_node *node = data->mesh;
|
||||
s_mcxw71_ifp = ifp;
|
||||
MGI_ZLL->SEQ_CTRL_STS |= MGI_SEQ_CTRL_NO_RX_RECYCLE;
|
||||
MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS;
|
||||
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
|
||||
MGI_ZLL->SAM_CTRL = 0;
|
||||
MGI_ZLL->RX_FRAME_FILTER = MGI_FRAME_FILTER;
|
||||
MGI_ZLL->CHANNEL_NUM0 = MG_802154_CHANNEL;
|
||||
MGI_ZLL->MACSHORTADDRS0 =
|
||||
((uint32_t) node->radio_address << 16) | MG_802154_PAN_ID;
|
||||
MGI_ZLL->PA_PWR = 0x3f;
|
||||
mg_mcxw71_listen();
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t mgi_mcxw71_tx(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||
return data->mesh->strategy->tx(data->mesh, buf, len);
|
||||
}
|
||||
|
||||
static size_t mgi_mcxw71_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||
size_t len = data->mesh->rx_len;
|
||||
if (len > cap) return 0;
|
||||
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||
memcpy(buf, ifp->recv_queue.buf, len);
|
||||
data->mesh->rx_len = 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool mgi_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||
struct mg_mesh_node *node = data->mesh;
|
||||
uint64_t now = mg_micros();
|
||||
char *buf;
|
||||
size_t len;
|
||||
(void) s1;
|
||||
if (s_mcxw71_txing &&
|
||||
mg_millis() > s_mcxw71_sendstart + MGI_SEND_TIMEOUT_MS) {
|
||||
mg_mcxw71_lock();
|
||||
if (s_mcxw71_txing) {
|
||||
mg_mcxw71_abort();
|
||||
s_mcxw71_txing = false;
|
||||
s_mcxw71_done = true;
|
||||
mg_mcxw71_listen();
|
||||
} else {
|
||||
MGI_NVIC_ISER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
|
||||
}
|
||||
}
|
||||
if (s_mcxw71_done) {
|
||||
s_mcxw71_done = false;
|
||||
node->tx_at = now + node->strategy->radio->ifs_us;
|
||||
}
|
||||
node->now = now;
|
||||
while (node->rx_len == 0 && (len = mg_queue_next(&s_mcxw71_rx, &buf)) != 0) {
|
||||
node->strategy->rx(node, buf, len);
|
||||
mg_queue_del(&s_mcxw71_rx, len);
|
||||
}
|
||||
node->strategy->tick(node, now);
|
||||
return node->joined;
|
||||
}
|
||||
|
||||
static bool mgi_mcxw71_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
|
||||
const struct mg_mesh *strategy = data->strategy;
|
||||
uint16_t address;
|
||||
address =
|
||||
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
|
||||
if (strategy == NULL) strategy = &mg_mesh_default;
|
||||
data->mesh = calloc(1, sizeof(*data->mesh));
|
||||
if (data->mesh == NULL) return false;
|
||||
if (!strategy->init(data->mesh, ifp, NULL, address)) {
|
||||
free(data->mesh);
|
||||
data->mesh = NULL;
|
||||
return false;
|
||||
}
|
||||
data->mesh->strategy = strategy;
|
||||
data->mesh->tx = mgi_mcxw71_radio_tx;
|
||||
mg_queue_init(&s_mcxw71_rx, s_mcxw71_queue, sizeof(s_mcxw71_queue));
|
||||
if (mgi_mcxw71_radio_init(ifp)) return true;
|
||||
strategy->free(data->mesh);
|
||||
free(data->mesh);
|
||||
data->mesh = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct mg_tcpip_driver mg_tcpip_driver_mcxw71 = {
|
||||
mgi_mcxw71_init, mgi_mcxw71_tx, mgi_mcxw71_rx, mgi_mcxw71_poll};
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#pragma once
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
|
||||
MG_ENABLE_DRIVER_MCXW71
|
||||
|
||||
struct mg_tcpip_driver_mcxw71_data {
|
||||
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||
};
|
||||
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
|
||||
|
||||
#define MG_TCPIP_DRIVER_MCXW71_INIT(manager) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_mcxw71_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mif_.ip = MG_TCPIP_IP; \
|
||||
mif_.mask = MG_TCPIP_MASK; \
|
||||
mif_.gw = MG_TCPIP_GW; \
|
||||
mif_.mgr = (manager); \
|
||||
mif_.driver = &mg_tcpip_driver_mcxw71; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
mg_tcpip_init((manager), &mif_); \
|
||||
MG_INFO(("Driver: mcxw71, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MCXW71_INIT(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,183 @@
|
||||
#include "net_builtin.h"
|
||||
#include "l2_mesh.h"
|
||||
#include "log.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MG24) && MG_ENABLE_DRIVER_MG24
|
||||
|
||||
#include <rail.h>
|
||||
#include <rail_ieee802154.h>
|
||||
|
||||
// RAIL delivers raw frames and transmit completion from interrupt context.
|
||||
#define MG_MG24_TX_TIMEOUT_US 60000
|
||||
|
||||
static struct mg_tcpip_if *s_mg24_ifp;
|
||||
static RAIL_Handle_t s_mg24_rail;
|
||||
static struct mg_queue s_mg24_rx;
|
||||
static char s_mg24_rxbuf[2048];
|
||||
static volatile bool s_mg24_txing, s_mg24_tx_done;
|
||||
static RAIL_Time_t s_mg24_txstart; // Send start, us
|
||||
static uint8_t s_mg24_txfifo[256] __attribute__((aligned(4))); // Power of 2
|
||||
|
||||
// Copy a received PSDU, stripping the PHR and any stored FCS.
|
||||
static void mg_mg24_rx_frame(RAIL_Handle_t h) {
|
||||
RAIL_RxPacketInfo_t info;
|
||||
uint8_t f[128];
|
||||
size_t n;
|
||||
char *dst;
|
||||
if (RAIL_GetRxPacketInfo(h, RAIL_RX_PACKET_HANDLE_NEWEST, &info) ==
|
||||
RAIL_RX_PACKET_HANDLE_INVALID ||
|
||||
info.packetStatus != RAIL_RX_PACKET_READY_SUCCESS ||
|
||||
info.packetBytes <= 1 || info.packetBytes > sizeof(f)) {
|
||||
return;
|
||||
}
|
||||
RAIL_CopyRxPacket(f, &info);
|
||||
n = info.packetBytes - 1U; // Strip PHR
|
||||
if (n == f[0]) n -= 2; // FCS stored, strip it
|
||||
if (mg_queue_book(&s_mg24_rx, &dst, n) >= n) {
|
||||
memcpy(dst, f + 1, n);
|
||||
mg_queue_add(&s_mg24_rx, n);
|
||||
} else {
|
||||
s_mg24_ifp->ndrop++;
|
||||
}
|
||||
}
|
||||
|
||||
// RAIL event callback, runs in interrupt context
|
||||
static void mg_mg24_events(RAIL_Handle_t h, RAIL_Events_t events) {
|
||||
if (events & RAIL_EVENT_CAL_NEEDED) {
|
||||
RAIL_Calibrate(h, NULL, RAIL_CAL_ALL_PENDING);
|
||||
}
|
||||
if (events & RAIL_EVENT_RX_PACKET_RECEIVED) mg_mg24_rx_frame(h);
|
||||
if (events & RAIL_EVENTS_TX_COMPLETION) s_mg24_tx_done = true;
|
||||
}
|
||||
|
||||
// RAIL adds the FCS.
|
||||
static size_t mgi_mg24_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
|
||||
size_t len) {
|
||||
uint8_t phr = (uint8_t) (len + 2); // FCS is added by hardware
|
||||
(void) ifp;
|
||||
if (len > 125 || s_mg24_txing) return 0;
|
||||
RAIL_WriteTxFifo(s_mg24_rail, &phr, 1, true); // Reset FIFO, write PHR
|
||||
RAIL_WriteTxFifo(s_mg24_rail, (const uint8_t *) buf, (uint16_t) len, false);
|
||||
s_mg24_tx_done = false;
|
||||
s_mg24_txing = true, s_mg24_txstart = RAIL_GetTime();
|
||||
if (RAIL_StartTx(s_mg24_rail, MG_802154_CHANNEL, RAIL_TX_OPTIONS_DEFAULT,
|
||||
NULL) != RAIL_STATUS_NO_ERROR) {
|
||||
s_mg24_txing = false;
|
||||
return 0;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool mgi_mg24_radio_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||
struct mg_mesh_node *node = data->mesh;
|
||||
RAIL_Config_t cfg = {0};
|
||||
RAIL_IEEE802154_Config_t cfg154 = {0};
|
||||
RAIL_TxPowerConfig_t pwr;
|
||||
RAIL_StateTransitions_t to_rx = {RAIL_RF_STATE_RX, RAIL_RF_STATE_RX};
|
||||
RAIL_Events_t events = RAIL_EVENT_CAL_NEEDED | RAIL_EVENT_RX_PACKET_RECEIVED |
|
||||
RAIL_EVENTS_TX_COMPLETION;
|
||||
cfg.eventsCallback = mg_mg24_events;
|
||||
if ((s_mg24_rail = RAIL_Init(&cfg, NULL)) == NULL) {
|
||||
MG_ERROR(("RAIL_Init failed"));
|
||||
return false;
|
||||
}
|
||||
cfg154.ackConfig.enable = false;
|
||||
cfg154.ackConfig.ackTimeout = 864;
|
||||
cfg154.ackConfig.rxTransitions = to_rx;
|
||||
cfg154.ackConfig.txTransitions = to_rx;
|
||||
cfg154.timings.idleToRx = 100, cfg154.timings.idleToTx = 100;
|
||||
cfg154.timings.rxToTx = 192, cfg154.timings.txToRx = 182;
|
||||
cfg154.framesMask = RAIL_IEEE802154_ACCEPT_STANDARD_FRAMES;
|
||||
cfg154.promiscuousMode = false; // Filter by PAN ID and our short address
|
||||
pwr.mode = RAIL_TX_POWER_MODE_2P4GIG_HP, pwr.voltage = 3300;
|
||||
pwr.rampTime = 10;
|
||||
if (RAIL_ConfigCal(s_mg24_rail, RAIL_CAL_ALL) != RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_ConfigEvents(s_mg24_rail, RAIL_EVENTS_ALL, events) !=
|
||||
RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_IEEE802154_Config2p4GHzRadio(s_mg24_rail) != RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_IEEE802154_Init(s_mg24_rail, &cfg154) != RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_IEEE802154_SetPanId(s_mg24_rail, MG_802154_PAN_ID, 0) !=
|
||||
RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_IEEE802154_SetShortAddress(s_mg24_rail, node->radio_address, 0) !=
|
||||
RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_SetTxTransitions(s_mg24_rail, &to_rx) != RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_SetRxTransitions(s_mg24_rail, &to_rx) != RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_ConfigTxPower(s_mg24_rail, &pwr) != RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_SetTxPower(s_mg24_rail, RAIL_TX_POWER_LEVEL_2P4_HP_MAX) !=
|
||||
RAIL_STATUS_NO_ERROR ||
|
||||
RAIL_SetTxFifo(s_mg24_rail, s_mg24_txfifo, 0, sizeof(s_mg24_txfifo)) !=
|
||||
sizeof(s_mg24_txfifo)) {
|
||||
MG_ERROR(("RAIL setup failed"));
|
||||
return false;
|
||||
}
|
||||
s_mg24_ifp = ifp;
|
||||
return RAIL_StartRx(s_mg24_rail, MG_802154_CHANNEL, NULL) ==
|
||||
RAIL_STATUS_NO_ERROR;
|
||||
}
|
||||
|
||||
static size_t mgi_mg24_tx(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||
return data->mesh->strategy->tx(data->mesh, buf, len);
|
||||
}
|
||||
|
||||
static size_t mgi_mg24_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||
size_t len = data->mesh->rx_len;
|
||||
if (len > cap) return 0;
|
||||
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||
memcpy(buf, ifp->recv_queue.buf, len);
|
||||
data->mesh->rx_len = 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool mgi_mg24_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||
struct mg_mesh_node *node = data->mesh;
|
||||
uint64_t now = mg_micros();
|
||||
char *buf;
|
||||
size_t len;
|
||||
(void) s1;
|
||||
node->now = now;
|
||||
if (s_mg24_txing && (s_mg24_tx_done || RAIL_GetTime() - s_mg24_txstart >
|
||||
MG_MG24_TX_TIMEOUT_US)) {
|
||||
if (!s_mg24_tx_done) {
|
||||
RAIL_Idle(s_mg24_rail, RAIL_IDLE_ABORT, true);
|
||||
RAIL_StartRx(s_mg24_rail, MG_802154_CHANNEL, NULL);
|
||||
}
|
||||
s_mg24_txing = false;
|
||||
node->tx_at = now + node->strategy->radio->ifs_us;
|
||||
}
|
||||
while (node->rx_len == 0 && (len = mg_queue_next(&s_mg24_rx, &buf)) != 0) {
|
||||
node->strategy->rx(node, buf, len);
|
||||
mg_queue_del(&s_mg24_rx, len);
|
||||
}
|
||||
node->strategy->tick(node, now);
|
||||
return node->joined;
|
||||
}
|
||||
static bool mgi_mg24_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
|
||||
const struct mg_mesh *strategy = data->strategy;
|
||||
uint16_t address =
|
||||
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
|
||||
if (strategy == NULL) strategy = &mg_mesh_default;
|
||||
data->mesh = calloc(1, sizeof(*data->mesh));
|
||||
if (data->mesh == NULL) return false;
|
||||
if (!strategy->init(data->mesh, ifp, NULL, address)) {
|
||||
free(data->mesh);
|
||||
data->mesh = NULL;
|
||||
return false;
|
||||
}
|
||||
data->mesh->strategy = strategy;
|
||||
data->mesh->tx = mgi_mg24_radio_tx;
|
||||
mg_queue_init(&s_mg24_rx, s_mg24_rxbuf, sizeof(s_mg24_rxbuf));
|
||||
if (mgi_mg24_radio_init(ifp)) return true;
|
||||
data->mesh->strategy->free(data->mesh);
|
||||
free(data->mesh);
|
||||
data->mesh = NULL;
|
||||
return false;
|
||||
}
|
||||
struct mg_tcpip_driver mg_tcpip_driver_mg24 = {mgi_mg24_init, mgi_mg24_tx,
|
||||
mgi_mg24_rx, mgi_mg24_poll};
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#pragma once
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MG24) && MG_ENABLE_DRIVER_MG24
|
||||
|
||||
struct mg_tcpip_driver_mg24_data {
|
||||
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||
};
|
||||
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_mg24;
|
||||
|
||||
#define MG_TCPIP_DRIVER_MG24_INIT(manager) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_mg24_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mif_.ip = MG_TCPIP_IP; \
|
||||
mif_.mask = MG_TCPIP_MASK; \
|
||||
mif_.gw = MG_TCPIP_GW; \
|
||||
mif_.mgr = (manager); \
|
||||
mif_.driver = &mg_tcpip_driver_mg24; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
mg_tcpip_init((manager), &mif_); \
|
||||
MG_INFO(("Driver: mg24, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MG24_INIT(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,179 @@
|
||||
#include "net_builtin.h"
|
||||
#include "l2_mesh.h"
|
||||
#include "log.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_NRF52) && MG_ENABLE_DRIVER_NRF52
|
||||
#include <nrf.h>
|
||||
|
||||
static struct mg_tcpip_if *s_nrf52_ifp;
|
||||
static struct mg_queue s_nrf52_rx;
|
||||
static char s_nrf52_queue[2048];
|
||||
static uint8_t s_nrf52_rxbuf[128] __attribute__((aligned(4)));
|
||||
static uint8_t s_nrf52_txbuf[128] __attribute__((aligned(4)));
|
||||
static volatile bool s_nrf52_txing, s_nrf52_done;
|
||||
static uint64_t s_nrf52_started;
|
||||
|
||||
static void mgi_nrf52_listen(void) {
|
||||
NRF_RADIO->PACKETPTR = (uint32_t) s_nrf52_rxbuf;
|
||||
NRF_RADIO->SHORTS =
|
||||
RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk;
|
||||
NRF_RADIO->EVENTS_FRAMESTART = 0;
|
||||
NRF_RADIO->EVENTS_END = 0;
|
||||
NRF_RADIO->TASKS_RXEN = 1;
|
||||
}
|
||||
static void mgi_nrf52_disable(void) {
|
||||
NRF_RADIO->SHORTS = 0;
|
||||
NRF_RADIO->EVENTS_DISABLED = 0;
|
||||
NRF_RADIO->TASKS_DISABLE = 1;
|
||||
while (!NRF_RADIO->EVENTS_DISABLED) (void) 0;
|
||||
NRF_RADIO->EVENTS_DISABLED = 0;
|
||||
}
|
||||
void RADIO_IRQHandler(void);
|
||||
void RADIO_IRQHandler(void) {
|
||||
if (!NRF_RADIO->EVENTS_DISABLED) return;
|
||||
NRF_RADIO->EVENTS_DISABLED = 0;
|
||||
if (s_nrf52_txing) {
|
||||
s_nrf52_txing = false;
|
||||
s_nrf52_done = true;
|
||||
} else if (NRF_RADIO->CRCSTATUS) {
|
||||
struct mg_tcpip_driver_nrf52_data *data = s_nrf52_ifp->driver_data;
|
||||
struct mg_mesh_node *node = data->mesh;
|
||||
size_t len = s_nrf52_rxbuf[0] >= 2 ? s_nrf52_rxbuf[0] - 2U : 0;
|
||||
uint8_t *frame = s_nrf52_rxbuf + 1;
|
||||
uint16_t dst, src;
|
||||
if (len <= 125 &&
|
||||
mg_radio_802154.parse_hdr(&mg_radio_802154, frame, len, &dst, &src) !=
|
||||
0 &&
|
||||
(dst == 0xffffu || dst == node->radio_address)) {
|
||||
char *dst;
|
||||
if (mg_queue_book(&s_nrf52_rx, &dst, len) >= len) {
|
||||
memcpy(dst, frame, len);
|
||||
mg_queue_add(&s_nrf52_rx, len);
|
||||
} else {
|
||||
s_nrf52_ifp->ndrop++;
|
||||
}
|
||||
}
|
||||
}
|
||||
mgi_nrf52_listen();
|
||||
}
|
||||
static size_t mgi_nrf52_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
|
||||
size_t len) {
|
||||
(void) ifp;
|
||||
if (len > 125) return 0;
|
||||
NVIC_DisableIRQ(RADIO_IRQn);
|
||||
if (s_nrf52_txing || NRF_RADIO->EVENTS_FRAMESTART) {
|
||||
NVIC_EnableIRQ(RADIO_IRQn);
|
||||
return 0;
|
||||
}
|
||||
mgi_nrf52_disable();
|
||||
s_nrf52_txbuf[0] = (uint8_t) (len + 2);
|
||||
memcpy(s_nrf52_txbuf + 1, buf, len);
|
||||
s_nrf52_done = false;
|
||||
s_nrf52_txing = true;
|
||||
s_nrf52_started = mg_micros();
|
||||
NRF_RADIO->PACKETPTR = (uint32_t) s_nrf52_txbuf;
|
||||
NRF_RADIO->SHORTS =
|
||||
RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk;
|
||||
NRF_RADIO->TASKS_TXEN = 1;
|
||||
NVIC_EnableIRQ(RADIO_IRQn);
|
||||
return len;
|
||||
}
|
||||
static bool mgi_nrf52_radio_init(struct mg_tcpip_if *ifp) {
|
||||
s_nrf52_ifp = ifp;
|
||||
NRF_RADIO->POWER = 1;
|
||||
NRF_RADIO->MODE = RADIO_MODE_MODE_Ieee802154_250Kbit;
|
||||
NRF_RADIO->PCNF0 = (8UL << RADIO_PCNF0_LFLEN_Pos) |
|
||||
(RADIO_PCNF0_PLEN_32bitZero << RADIO_PCNF0_PLEN_Pos) |
|
||||
RADIO_PCNF0_CRCINC_Msk;
|
||||
NRF_RADIO->PCNF1 = 127;
|
||||
NRF_RADIO->CRCCNF = 0x202;
|
||||
NRF_RADIO->CRCPOLY = 0x11021;
|
||||
NRF_RADIO->CRCINIT = 0;
|
||||
NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_DTX_Center << RADIO_MODECNF0_DTX_Pos) |
|
||||
RADIO_MODECNF0_RU_Fast;
|
||||
NRF_RADIO->SFD = 0xa7;
|
||||
NRF_RADIO->FREQUENCY = 5 * (MG_802154_CHANNEL - 10);
|
||||
NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos8dBm;
|
||||
// Correlator threshold 45 matches the RADIO reset value. A threshold of
|
||||
// 20 caused false busy detections and persistent L2 retries.
|
||||
NRF_RADIO->CCACTRL =
|
||||
(RADIO_CCACTRL_CCAMODE_CarrierMode << RADIO_CCACTRL_CCAMODE_Pos) |
|
||||
(2UL << RADIO_CCACTRL_CCACORRCNT_Pos) |
|
||||
(45UL << RADIO_CCACTRL_CCACORRTHRES_Pos);
|
||||
NRF_RADIO->INTENSET = RADIO_INTENSET_DISABLED_Msk;
|
||||
NVIC_SetPriority(RADIO_IRQn, 0);
|
||||
NVIC_EnableIRQ(RADIO_IRQn);
|
||||
mgi_nrf52_listen();
|
||||
return true;
|
||||
}
|
||||
static size_t mgi_nrf52_tx(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
|
||||
return data->mesh->strategy->tx(data->mesh, buf, len);
|
||||
}
|
||||
|
||||
static size_t mgi_nrf52_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
|
||||
size_t len = data->mesh->rx_len;
|
||||
if (len > cap) return 0;
|
||||
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||
memcpy(buf, ifp->recv_queue.buf, len);
|
||||
data->mesh->rx_len = 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool mgi_nrf52_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
|
||||
struct mg_mesh_node *node = data->mesh;
|
||||
uint64_t now = mg_micros();
|
||||
char *buf;
|
||||
size_t len;
|
||||
(void) s1;
|
||||
if (s_nrf52_txing && mg_micros() - s_nrf52_started > 60000) {
|
||||
NVIC_DisableIRQ(RADIO_IRQn);
|
||||
mgi_nrf52_disable();
|
||||
s_nrf52_txing = false;
|
||||
s_nrf52_done = true;
|
||||
mgi_nrf52_listen();
|
||||
NVIC_EnableIRQ(RADIO_IRQn);
|
||||
}
|
||||
if (s_nrf52_done) {
|
||||
s_nrf52_done = false;
|
||||
node->tx_at = now + node->strategy->radio->ifs_us;
|
||||
}
|
||||
node->now = now;
|
||||
while (node->rx_len == 0 && (len = mg_queue_next(&s_nrf52_rx, &buf)) != 0) {
|
||||
node->strategy->rx(node, buf, len);
|
||||
mg_queue_del(&s_nrf52_rx, len);
|
||||
}
|
||||
node->strategy->tick(node, now);
|
||||
return node->joined;
|
||||
}
|
||||
|
||||
static bool mgi_nrf52_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
|
||||
const struct mg_mesh *strategy = data->strategy;
|
||||
uint16_t address;
|
||||
address =
|
||||
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
|
||||
if (strategy == NULL) strategy = &mg_mesh_default;
|
||||
data->mesh = calloc(1, sizeof(*data->mesh));
|
||||
if (data->mesh == NULL) return false;
|
||||
if (!strategy->init(data->mesh, ifp, NULL, address)) {
|
||||
free(data->mesh);
|
||||
data->mesh = NULL;
|
||||
return false;
|
||||
}
|
||||
data->mesh->strategy = strategy;
|
||||
data->mesh->tx = mgi_nrf52_radio_tx;
|
||||
mg_queue_init(&s_nrf52_rx, s_nrf52_queue, sizeof(s_nrf52_queue));
|
||||
if (mgi_nrf52_radio_init(ifp)) return true;
|
||||
strategy->free(data->mesh);
|
||||
free(data->mesh);
|
||||
data->mesh = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct mg_tcpip_driver mg_tcpip_driver_nrf52 = {mgi_nrf52_init, mgi_nrf52_tx,
|
||||
mgi_nrf52_rx, mgi_nrf52_poll};
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#pragma once
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_NRF52) && MG_ENABLE_DRIVER_NRF52
|
||||
|
||||
struct mg_tcpip_driver_nrf52_data {
|
||||
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||
};
|
||||
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_nrf52;
|
||||
|
||||
#define MG_TCPIP_DRIVER_NRF52_INIT(manager) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_nrf52_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mif_.ip = MG_TCPIP_IP; \
|
||||
mif_.mask = MG_TCPIP_MASK; \
|
||||
mif_.gw = MG_TCPIP_GW; \
|
||||
mif_.mgr = (manager); \
|
||||
mif_.driver = &mg_tcpip_driver_nrf52; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
mg_tcpip_init((manager), &mif_); \
|
||||
MG_INFO(("Driver: nrf52, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_NRF52_INIT(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -27,6 +27,7 @@ struct mg_tcpip_driver_rw612_data {
|
||||
#define MG_DRIVER_MDC_HOLDTIME 3
|
||||
#endif
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(mgr) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_rw612_data driver_data_; \
|
||||
@@ -42,5 +43,6 @@ struct mg_tcpip_driver_rw612_data {
|
||||
mg_tcpip_init(mgr, &mif_); \
|
||||
MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,513 @@
|
||||
#include "net_builtin.h"
|
||||
#include "l2_mesh.h"
|
||||
#include "log.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612W) && \
|
||||
MG_ENABLE_DRIVER_RW612W
|
||||
|
||||
#include "sha256.h"
|
||||
|
||||
#ifdef ENET
|
||||
#undef ENET
|
||||
#endif
|
||||
#include <fsl_ocotp.h>
|
||||
|
||||
// The 802.15.4 radio runs NXP combo firmware on CPU2, an OpenThread RCP. We
|
||||
// are CPU3. Messages go through the IMU mailbox: IMU messages carry IMUMC
|
||||
// messages, IMUMC messages carry HDLC framed Spinel. See NXP sources:
|
||||
// mcuxsdk-core drivers/imu/fsl_imu.c, mcux-component
|
||||
// imu_adapter/fsl_adapter_imu.c, connectivity framework
|
||||
// platform/rw61x/fwk_platform_hdlc.c; OpenThread src/lib/spinel/spinel.h
|
||||
|
||||
// IMU message FIFO registers, one block per direction. See fsl_imu.h
|
||||
struct mg_rw612_imu {
|
||||
volatile uint32_t WR_MSG, RD_MSG, STATUS, CNTL, RD_MSG_DBG;
|
||||
};
|
||||
#define MGI_IMU ((struct mg_rw612_imu *) 0x443ff804) // CPU3 -> CPU2
|
||||
#define MGI_IMU_PEER ((struct mg_rw612_imu *) 0x443ff818) // CPU2 -> CPU3
|
||||
#define MGI_IMU_LOCKED MG_BIT(0) // STATUS: FIFO locked
|
||||
#define MGI_IMU_ALMOST_FULL MG_BIT(1) // STATUS: FIFO almost full
|
||||
#define MGI_IMU_EMPTY MG_BIT(3) // STATUS: FIFO empty
|
||||
#define MGI_IMU_WR_PTR(s) (((s) >> 16) & 15U)
|
||||
#define MGI_IMU_INT_CLR MG_BIT(0) // CNTL: clear message ready IRQ
|
||||
#define MGI_IMU_FLUSH MG_BIT(16) // CNTL: flush send FIFO
|
||||
#define MGI_IMU_WAIT_FOR_ACK MG_BIT(17) // CNTL: lock send FIFO
|
||||
#define MGI_IMU_WATERMARK(x) ((uint32_t) (x) << 20)
|
||||
#define MGI_IMU_WATERMARK_MASK (15U << 20)
|
||||
|
||||
// IMU message: header, then payload words. See IMU_Msg_t
|
||||
struct mg_rw612_msg {
|
||||
uint8_t type, subtype, seq, len; // len: number of payload words
|
||||
uint32_t payload[8];
|
||||
};
|
||||
|
||||
// Mailbox in SMU2 shared memory, at the address CPU2 firmware expects
|
||||
struct mg_rw612_mbox {
|
||||
uint32_t magic[2]; // Sync pattern: [0] CPU2, [1] us
|
||||
struct mg_rw612_msg q[2][16]; // Message slots: [0] CPU2 -> us, [1] ours
|
||||
};
|
||||
#define MGI_MBOX ((volatile struct mg_rw612_mbox *) 0x443c0000)
|
||||
#define MGI_TXBUF 0x443c1508U // Our IMUMC buffers: 16 x 264 bytes
|
||||
#define MGI_TXBUF_CNT 16
|
||||
#define MGI_TXBUF_LEN 264U
|
||||
#define MGI_SLEEP_FLAG (*(volatile uint32_t *) 0x443c2588)
|
||||
#define MGI_SYNC_MAGIC 0xabcdef89U
|
||||
|
||||
// IMUMC message, carried in IMU payload words. See imumc_std_msg
|
||||
struct mg_rw612_imumc {
|
||||
uint32_t src, dst; // Endpoint addresses
|
||||
uint16_t rfu, idx; // Reserved
|
||||
uint16_t len; // Payload length, bytes
|
||||
uint16_t flags; // Message flags
|
||||
uint32_t data; // Payload buffer address
|
||||
};
|
||||
#define MGI_EPT_LOCAL 10 // Our 802.15.4 endpoint
|
||||
#define MGI_EPT_REMOTE 20 // Firmware 802.15.4 endpoint
|
||||
|
||||
enum { MGI_MSG_CONTROL = 0, MGI_MSG_IMUMC = 8 }; // IMU message types
|
||||
enum { // Control message subtypes
|
||||
MGI_CTL_SYNC = 1,
|
||||
MGI_CTL_SYNC_ACK = 2,
|
||||
MGI_CTL_ACK_FOR_UNLOCK = 3,
|
||||
MGI_CTL_CMD_BUF_ADDR = 6,
|
||||
MGI_CTL_EPT_QUERY = 11,
|
||||
MGI_CTL_EPT_QUERY_RSP = 12,
|
||||
MGI_CTL_BUF_FREE = 13
|
||||
};
|
||||
|
||||
// Spinel commands, properties and statuses we use
|
||||
enum { MGI_CMD_RESET = 1, MGI_CMD_GET = 2, MGI_CMD_SET = 3, MGI_CMD_IS = 6 };
|
||||
enum {
|
||||
MGI_PROP_LAST_STATUS = 0,
|
||||
MGI_PROP_NCP_VERSION = 2,
|
||||
MGI_PROP_PHY_ENABLED = 0x20,
|
||||
MGI_PROP_PHY_CHAN = 0x21,
|
||||
MGI_PROP_SADDR = 0x35, // Short address, little endian
|
||||
MGI_PROP_PANID = 0x36,
|
||||
MGI_PROP_RAW_STREAM = 0x37,
|
||||
MGI_PROP_PROMISCUOUS = 0x38,
|
||||
MGI_PROP_STREAM_RAW = 0x71
|
||||
};
|
||||
#define MGI_RESET_STACK 2 // RESET argument: reset the RCP stack only
|
||||
#define MGI_STATUS_RESET_MIN 112 // LAST_STATUS: RCP has reset
|
||||
#define MGI_STATUS_RESET_MAX 127
|
||||
#define MGI_TX_TIMEOUT_MS 200 // Wait for transmit done, incl. retries
|
||||
#define MGI_RSP_TIMEOUT_MS 500
|
||||
|
||||
// Driver state, one radio
|
||||
struct mg_rw612 {
|
||||
bool up; // IMU link synchronised
|
||||
bool ept_ready; // IMUMC endpoints connected
|
||||
bool reset; // RCP reported a reset
|
||||
uint8_t seq; // IMU message sequence number
|
||||
uint16_t free_bufs; // Free IMUMC buffers, bit per buffer
|
||||
uint8_t hdlc[300]; // HDLC frame being received, unescaped
|
||||
size_t hlen; // Bytes in hdlc
|
||||
bool esc; // Previous byte was an escape
|
||||
uint8_t tid; // Spinel transaction ID of the pending request
|
||||
bool rsp; // Response to the pending request received
|
||||
uint32_t rsp_status; // Its status, if LAST_STATUS, else 0
|
||||
char version[64]; // RCP version string
|
||||
bool txing; // A send is on, see mg_rw612_start()
|
||||
uint64_t txstart; // Its start, ms
|
||||
struct mg_tcpip_if *ifp; // Our interface, for the L2 receive pool
|
||||
};
|
||||
static struct mg_rw612 s_rw612;
|
||||
static struct mg_queue s_rw612w_rx;
|
||||
static char s_rw612w_queue[2048];
|
||||
|
||||
static void mg_rw612_imu_send(uint8_t type, uint8_t subtype,
|
||||
const uint32_t *payload, uint8_t len) {
|
||||
volatile struct mg_rw612_msg *m;
|
||||
uint64_t expire = mg_millis() + 100;
|
||||
uint8_t i;
|
||||
while ((MGI_IMU->STATUS & (MGI_IMU_ALMOST_FULL | MGI_IMU_LOCKED)) &&
|
||||
mg_millis() < expire) {
|
||||
(void) 0;
|
||||
}
|
||||
m = &MGI_MBOX->q[1][MGI_IMU_WR_PTR(MGI_IMU->STATUS)];
|
||||
m->type = type, m->subtype = subtype, m->seq = s_rw612.seq++, m->len = len;
|
||||
for (i = 0; i < len; i++) m->payload[i] = payload[i];
|
||||
MGI_IMU->WR_MSG = (uint32_t) (uintptr_t) m;
|
||||
}
|
||||
|
||||
static void mg_rw612_control(uint8_t subtype, uint32_t a, uint32_t b,
|
||||
uint8_t len) {
|
||||
uint32_t payload[2] = {a, b};
|
||||
mg_rw612_imu_send(MGI_MSG_CONTROL, subtype, payload, len);
|
||||
}
|
||||
|
||||
static void mg_rw612_queue_rx(const uint8_t *frame, size_t len) {
|
||||
{
|
||||
char *dst;
|
||||
if (mg_queue_book(&s_rw612w_rx, &dst, len) >= len) {
|
||||
memcpy(dst, frame, len);
|
||||
mg_queue_add(&s_rw612w_rx, len);
|
||||
} else {
|
||||
s_rw612.ifp->ndrop++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spinel packed unsigned integer: 7 bits per byte, LSB first
|
||||
static size_t mg_rw612_unpack(const uint8_t *p, size_t len, uint32_t *v) {
|
||||
size_t i;
|
||||
*v = 0;
|
||||
for (i = 0; i < len && i < 4; i++) {
|
||||
*v |= (uint32_t) (p[i] & 0x7f) << (7 * i);
|
||||
if ((p[i] & 0x80) == 0) return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle a received Spinel frame: header, command, property, value
|
||||
static void mg_rw612_spinel(const uint8_t *f, size_t len) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
uint32_t cmd, prop, status = 0;
|
||||
size_t n, ofs = 1;
|
||||
uint8_t tid = f[0] & 15;
|
||||
if (len < 3 || (n = mg_rw612_unpack(f + ofs, len - ofs, &cmd)) == 0) return;
|
||||
ofs += n;
|
||||
if ((n = mg_rw612_unpack(f + ofs, len - ofs, &prop)) == 0) return;
|
||||
ofs += n;
|
||||
if (cmd != MGI_CMD_IS) return;
|
||||
if (prop == MGI_PROP_STREAM_RAW && tid == 0 && len - ofs >= 2) {
|
||||
size_t flen = (size_t) (f[ofs] | (f[ofs + 1] << 8)); // Incl. FCS
|
||||
if (flen >= 2 && ofs + 2 + flen <= len) {
|
||||
mg_rw612_queue_rx(f + ofs + 2, flen - 2);
|
||||
}
|
||||
} else if (prop == MGI_PROP_LAST_STATUS) {
|
||||
mg_rw612_unpack(f + ofs, len - ofs, &status);
|
||||
if (tid == 0 && status >= MGI_STATUS_RESET_MIN &&
|
||||
status <= MGI_STATUS_RESET_MAX) {
|
||||
s->reset = true;
|
||||
}
|
||||
} else if (prop == MGI_PROP_NCP_VERSION) {
|
||||
n = len - ofs < sizeof(s->version) - 1 ? len - ofs : sizeof(s->version) - 1;
|
||||
memcpy(s->version, f + ofs, n);
|
||||
s->version[n] = '\0';
|
||||
}
|
||||
if (tid != 0 && tid == s->tid) s->rsp = true, s->rsp_status = status;
|
||||
}
|
||||
|
||||
// HDLC-lite: 0x7e delimits frames, 0x7d escapes, FCS-16 at the end
|
||||
static uint16_t mg_rw612_fcs(uint16_t fcs, uint8_t c) {
|
||||
int i;
|
||||
fcs ^= c;
|
||||
for (i = 0; i < 8; i++) fcs = (fcs & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1;
|
||||
return fcs;
|
||||
}
|
||||
|
||||
static void mg_rw612_hdlc_rx(const uint8_t *p, size_t len) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
size_t i, j;
|
||||
for (i = 0; i < len; i++) {
|
||||
uint8_t c = p[i];
|
||||
if (c == 0x7e) {
|
||||
if (s->hlen >= 3) {
|
||||
uint16_t fcs = 0xffff;
|
||||
for (j = 0; j < s->hlen; j++) fcs = mg_rw612_fcs(fcs, s->hdlc[j]);
|
||||
if (fcs == 0xf0b8) mg_rw612_spinel(s->hdlc, s->hlen - 2);
|
||||
}
|
||||
s->hlen = 0, s->esc = false;
|
||||
} else if (c == 0x7d) {
|
||||
s->esc = true;
|
||||
} else if (s->hlen < sizeof(s->hdlc)) {
|
||||
s->hdlc[s->hlen++] = s->esc ? c ^ 0x20 : c;
|
||||
s->esc = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mg_rw612_control_rx(volatile struct mg_rw612_msg *m) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
switch (m->subtype) {
|
||||
case MGI_CTL_ACK_FOR_UNLOCK:
|
||||
MGI_IMU->CNTL &= ~MGI_IMU_WAIT_FOR_ACK;
|
||||
break;
|
||||
case MGI_CTL_SYNC: // As HAL_ImuCtrlHandler(): acknowledge twice
|
||||
mg_rw612_control(MGI_CTL_SYNC_ACK, 0, 0, 0);
|
||||
mg_rw612_control(MGI_CTL_SYNC_ACK, 0, 0, 0);
|
||||
s->up = true;
|
||||
break;
|
||||
case MGI_CTL_SYNC_ACK:
|
||||
case MGI_CTL_CMD_BUF_ADDR:
|
||||
s->up = true;
|
||||
break;
|
||||
case MGI_CTL_EPT_QUERY: { // Firmware asks if our endpoint exists
|
||||
bool ours = m->payload[0] == MGI_EPT_LOCAL;
|
||||
mg_rw612_control(MGI_CTL_EPT_QUERY_RSP, m->payload[0], ours, 2);
|
||||
if (ours) s->ept_ready = true;
|
||||
break;
|
||||
}
|
||||
case MGI_CTL_EPT_QUERY_RSP:
|
||||
if (m->payload[0] == MGI_EPT_REMOTE && m->payload[1] != 0) {
|
||||
s->ept_ready = true;
|
||||
}
|
||||
break;
|
||||
case MGI_CTL_BUF_FREE: { // Firmware is done with one of our buffers
|
||||
uint32_t i = (m->payload[0] - MGI_TXBUF) / MGI_TXBUF_LEN;
|
||||
if (i < MGI_TXBUF_CNT) s->free_bufs |= (uint16_t) MG_BIT(i);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Process all received IMU messages. See HAL_ImuReceive()
|
||||
static void mg_rw612_poll_imu(void) {
|
||||
bool unlock = false;
|
||||
while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) {
|
||||
struct mg_rw612_msg m;
|
||||
volatile struct mg_rw612_msg *p;
|
||||
uint8_t i;
|
||||
MGI_SLEEP_FLAG &= ~1U;
|
||||
p = (volatile struct mg_rw612_msg *) (uintptr_t) MGI_IMU->RD_MSG;
|
||||
if (MGI_IMU_PEER->STATUS & MGI_IMU_LOCKED) unlock = true;
|
||||
if (p == NULL) continue;
|
||||
m.type = p->type, m.subtype = p->subtype, m.len = p->len;
|
||||
for (i = 0; i < 8; i++) m.payload[i] = p->payload[i];
|
||||
if (m.type == MGI_MSG_CONTROL) {
|
||||
mg_rw612_control_rx(&m);
|
||||
} else if (m.type == MGI_MSG_IMUMC) {
|
||||
struct mg_rw612_imumc im;
|
||||
memcpy(&im, m.payload, sizeof(im));
|
||||
if (im.dst == MGI_EPT_LOCAL) {
|
||||
mg_rw612_hdlc_rx((const uint8_t *) (uintptr_t) im.data, im.len);
|
||||
}
|
||||
mg_rw612_control(MGI_CTL_BUF_FREE, im.data, 0, 1);
|
||||
}
|
||||
}
|
||||
MGI_IMU->CNTL |= MGI_IMU_INT_CLR;
|
||||
MGI_SLEEP_FLAG |= 1U;
|
||||
if (unlock) mg_rw612_control(MGI_CTL_ACK_FOR_UNLOCK, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Send bytes to the firmware endpoint, in IMUMC buffer sized chunks
|
||||
static bool mg_rw612_imumc_send(const uint8_t *p, size_t len) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
while (len > 0) {
|
||||
size_t n = len < MGI_TXBUF_LEN ? len : MGI_TXBUF_LEN;
|
||||
uint64_t expire = mg_millis() + 100;
|
||||
struct mg_rw612_imumc im;
|
||||
uint32_t i;
|
||||
while (s->free_bufs == 0) {
|
||||
mg_rw612_poll_imu();
|
||||
if (mg_millis() > expire) return false;
|
||||
}
|
||||
for (i = 0; (s->free_bufs & MG_BIT(i)) == 0; i++) (void) 0;
|
||||
s->free_bufs &= (uint16_t) ~MG_BIT(i);
|
||||
memset(&im, 0, sizeof(im));
|
||||
im.src = MGI_EPT_LOCAL, im.dst = MGI_EPT_REMOTE, im.len = (uint16_t) n;
|
||||
im.data = MGI_TXBUF + i * MGI_TXBUF_LEN;
|
||||
memcpy((void *) (uintptr_t) im.data, p, n);
|
||||
mg_rw612_imu_send(MGI_MSG_IMUMC, 0, (uint32_t *) &im, sizeof(im) / 4);
|
||||
p += n, len -= n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send a Spinel frame, HDLC encoded, with a new transaction ID. Its
|
||||
// response sets rsp, see mg_rw612_spinel()
|
||||
static bool mg_rw612_post(uint8_t *f, size_t len) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
uint8_t buf[2 * 160 + 6];
|
||||
uint16_t fcs = 0xffff;
|
||||
size_t i, n = 0;
|
||||
s->tid = (uint8_t) (s->tid % 15 + 1);
|
||||
f[0] = (uint8_t) (0x80 | s->tid); // Header: flag, interface 0, TID
|
||||
s->rsp = false;
|
||||
buf[n++] = 0x7e;
|
||||
for (i = 0; i < len + 2 && n + 3 < sizeof(buf); i++) {
|
||||
uint8_t c;
|
||||
if (i < len) {
|
||||
c = f[i], fcs = mg_rw612_fcs(fcs, c);
|
||||
} else {
|
||||
c = (uint8_t) (i == len ? ~fcs & 0xff : ~fcs >> 8);
|
||||
}
|
||||
if (c == 0x7e || c == 0x7d || c == 0x11 || c == 0x13 || c == 0xf8) {
|
||||
buf[n++] = 0x7d, c ^= 0x20;
|
||||
}
|
||||
buf[n++] = c;
|
||||
}
|
||||
buf[n++] = 0x7e;
|
||||
return mg_rw612_imumc_send(buf, n);
|
||||
}
|
||||
|
||||
// Send a Spinel frame and wait for its response. Return false on timeout
|
||||
static bool mg_rw612_request(uint8_t *f, size_t len, uint32_t timeout_ms) {
|
||||
uint64_t expire = mg_millis() + timeout_ms;
|
||||
if (!mg_rw612_post(f, len)) return false;
|
||||
while (!s_rw612.rsp && mg_millis() < expire) mg_rw612_poll_imu();
|
||||
return s_rw612.rsp;
|
||||
}
|
||||
|
||||
static bool mg_rw612_set(uint32_t prop, const uint8_t *val, size_t len) {
|
||||
uint8_t f[16];
|
||||
if (len > sizeof(f) - 3) return false;
|
||||
f[1] = MGI_CMD_SET, f[2] = (uint8_t) prop; // Properties used are < 0x80
|
||||
memcpy(f + 3, val, len);
|
||||
return mg_rw612_request(f, len + 3, MGI_RSP_TIMEOUT_MS) &&
|
||||
s_rw612.rsp_status == 0;
|
||||
}
|
||||
|
||||
// Log an init error, return false
|
||||
static bool mg_rw612_fail(const char *msg) {
|
||||
MG_ERROR(("%s", msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start one raw transmission. The RCP adds the FCS and reports completion.
|
||||
static size_t mgi_rw612w_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
|
||||
size_t len) {
|
||||
uint8_t f[3 + 2 + 127 + 4];
|
||||
size_t n = 3;
|
||||
if (len > 125 || s_rw612.txing) return 0;
|
||||
f[1] = MGI_CMD_SET, f[2] = MGI_PROP_STREAM_RAW;
|
||||
f[n++] = (uint8_t) (len + 2), f[n++] = 0; // PSDU length, incl. FCS
|
||||
memcpy(f + n, buf, len), n += len;
|
||||
f[n++] = 0, f[n++] = 0; // FCS, filled in by the radio
|
||||
(void) ifp;
|
||||
f[n++] = MG_802154_CHANNEL;
|
||||
f[n++] = 0; // Max CSMA backoffs
|
||||
f[n++] = 0; // Max frame retries
|
||||
f[n++] = 0; // CSMA disabled for scheduled turns
|
||||
if (!mg_rw612_post(f, n)) return 0;
|
||||
s_rw612.txing = true, s_rw612.txstart = mg_millis();
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool mgi_rw612w_radio_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_rw612 *s = &s_rw612;
|
||||
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||
struct mg_mesh_node *node = data->mesh;
|
||||
uint8_t f[8], one = 1, zero = 0, pan[2], addr[2], channel = MG_802154_CHANNEL;
|
||||
uint64_t expire;
|
||||
if (MGI_MBOX->magic[0] != MGI_SYNC_MAGIC) {
|
||||
MG_ERROR(("Radio firmware is not running on CPU2"));
|
||||
return false;
|
||||
}
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->free_bufs = 0xffff, s->ifp = ifp;
|
||||
// Reset our send FIFO, drop stale messages. See IMU_Init()
|
||||
MGI_IMU->CNTL |= MGI_IMU_FLUSH;
|
||||
MGI_IMU->CNTL &= ~(MGI_IMU_FLUSH | MGI_IMU_WAIT_FOR_ACK);
|
||||
MGI_IMU->CNTL =
|
||||
(MGI_IMU->CNTL & ~MGI_IMU_WATERMARK_MASK) | MGI_IMU_WATERMARK(15);
|
||||
while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) (void) MGI_IMU->RD_MSG;
|
||||
// Synchronise the IMU link, then connect the IMUMC endpoints
|
||||
MGI_MBOX->magic[1] = MGI_SYNC_MAGIC;
|
||||
mg_rw612_control(MGI_CTL_SYNC, 0, 0, 0);
|
||||
expire = mg_millis() + 1000;
|
||||
while (!s->up && mg_millis() < expire) mg_rw612_poll_imu();
|
||||
if (!s->up) return mg_rw612_fail("IMU sync timeout");
|
||||
expire = mg_millis() + 2000;
|
||||
while (!s->ept_ready && mg_millis() < expire) {
|
||||
uint64_t retry = mg_millis() + 100;
|
||||
mg_rw612_control(MGI_CTL_EPT_QUERY, MGI_EPT_REMOTE, 0, 1);
|
||||
while (!s->ept_ready && mg_millis() < retry) mg_rw612_poll_imu();
|
||||
}
|
||||
if (!s->ept_ready) return mg_rw612_fail("IMUMC endpoint timeout");
|
||||
// Reset the RCP stack, wait for its reset status, read its version
|
||||
f[1] = MGI_CMD_RESET, f[2] = MGI_RESET_STACK;
|
||||
mg_rw612_request(f, 3, MGI_RSP_TIMEOUT_MS);
|
||||
expire = mg_millis() + 1000;
|
||||
while (!s->reset && mg_millis() < expire) mg_rw612_poll_imu();
|
||||
if (!s->reset) return mg_rw612_fail("RCP reset timeout");
|
||||
f[1] = MGI_CMD_GET, f[2] = MGI_PROP_NCP_VERSION;
|
||||
mg_rw612_request(f, 3, MGI_RSP_TIMEOUT_MS);
|
||||
MG_INFO(("RCP: %s", s->version));
|
||||
if (!mg_rw612_set(MGI_PROP_PHY_ENABLED, &one, 1)) {
|
||||
return mg_rw612_fail("PHY enable failed");
|
||||
}
|
||||
pan[0] = (uint8_t) MG_802154_PAN_ID;
|
||||
pan[1] = (uint8_t) (MG_802154_PAN_ID >> 8);
|
||||
addr[0] = (uint8_t) node->radio_address;
|
||||
addr[1] = (uint8_t) (node->radio_address >> 8);
|
||||
if (!mg_rw612_set(MGI_PROP_PHY_CHAN, &channel, 1) ||
|
||||
!mg_rw612_set(MGI_PROP_PANID, pan, 2) ||
|
||||
!mg_rw612_set(MGI_PROP_SADDR, addr, 2) ||
|
||||
!mg_rw612_set(MGI_PROP_PROMISCUOUS, &zero, 1)) {
|
||||
return mg_rw612_fail("Channel, PAN ID or address set failed");
|
||||
}
|
||||
if (!mg_rw612_set(MGI_PROP_RAW_STREAM, &one, 1)) {
|
||||
return mg_rw612_fail("Raw stream enable failed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t mgi_rw612w_tx(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||
return data->mesh->strategy->tx(data->mesh, buf, len);
|
||||
}
|
||||
|
||||
static size_t mgi_rw612w_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||
size_t len = data->mesh->rx_len;
|
||||
if (len > cap) return 0;
|
||||
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||
memcpy(buf, ifp->recv_queue.buf, len);
|
||||
data->mesh->rx_len = 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool mgi_rw612w_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||
struct mg_mesh_node *node = data->mesh;
|
||||
uint64_t now = mg_micros();
|
||||
char *buf;
|
||||
size_t len;
|
||||
(void) s1;
|
||||
bool was_sending = s_rw612.txing;
|
||||
mg_rw612_poll_imu();
|
||||
if (s_rw612.txing &&
|
||||
(s_rw612.rsp || mg_millis() > s_rw612.txstart + MGI_TX_TIMEOUT_MS)) {
|
||||
if (!s_rw612.rsp) MG_ERROR(("TX timeout"));
|
||||
s_rw612.txing = false;
|
||||
}
|
||||
if (was_sending && !s_rw612.txing)
|
||||
node->tx_at = now + node->strategy->radio->ifs_us;
|
||||
node->now = now;
|
||||
while (node->rx_len == 0 && (len = mg_queue_next(&s_rw612w_rx, &buf)) != 0) {
|
||||
node->strategy->rx(node, buf, len);
|
||||
mg_queue_del(&s_rw612w_rx, len);
|
||||
}
|
||||
node->strategy->tick(node, now);
|
||||
return node->joined;
|
||||
}
|
||||
|
||||
static bool mgi_rw612w_init(struct mg_tcpip_if *ifp) {
|
||||
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
|
||||
const struct mg_mesh *strategy = data->strategy;
|
||||
uint8_t uid[FSL_OCOTP_UID_LENGTH] = {0}, digest[32];
|
||||
uint32_t uid_len = sizeof(uid);
|
||||
uint16_t address;
|
||||
OCOTP_ReadUniqueID(uid, &uid_len);
|
||||
mg_sha256(digest, uid, sizeof(uid));
|
||||
memcpy(ifp->mac, digest, 6);
|
||||
ifp->mac[0] = 0x02;
|
||||
address =
|
||||
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
|
||||
if (strategy == NULL) strategy = &mg_mesh_default;
|
||||
data->mesh = calloc(1, sizeof(*data->mesh));
|
||||
if (data->mesh == NULL) return false;
|
||||
if (!strategy->init(data->mesh, ifp, NULL, address)) {
|
||||
free(data->mesh);
|
||||
data->mesh = NULL;
|
||||
return false;
|
||||
}
|
||||
data->mesh->strategy = strategy;
|
||||
data->mesh->tx = mgi_rw612w_radio_tx;
|
||||
mg_queue_init(&s_rw612w_rx, s_rw612w_queue, sizeof(s_rw612w_queue));
|
||||
if (mgi_rw612w_radio_init(ifp)) return true;
|
||||
strategy->free(data->mesh);
|
||||
free(data->mesh);
|
||||
data->mesh = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct mg_tcpip_driver mg_tcpip_driver_rw612w = {
|
||||
mgi_rw612w_init, mgi_rw612w_tx, mgi_rw612w_rx, mgi_rw612w_poll};
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#pragma once
|
||||
|
||||
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612W) && \
|
||||
MG_ENABLE_DRIVER_RW612W
|
||||
|
||||
struct mg_tcpip_driver_rw612w_data {
|
||||
struct mg_mesh_node *mesh; // Driver-owned runtime state
|
||||
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
|
||||
};
|
||||
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_rw612w;
|
||||
|
||||
#define MG_TCPIP_DRIVER_RW612W_INIT(manager) \
|
||||
do { \
|
||||
static struct mg_tcpip_driver_rw612w_data driver_data_; \
|
||||
static struct mg_tcpip_if mif_; \
|
||||
MG_SET_MAC_ADDRESS(mif_.mac); \
|
||||
mif_.ip = MG_TCPIP_IP; \
|
||||
mif_.mask = MG_TCPIP_MASK; \
|
||||
mif_.gw = MG_TCPIP_GW; \
|
||||
mif_.mgr = (manager); \
|
||||
mif_.driver = &mg_tcpip_driver_rw612w; \
|
||||
mif_.driver_data = &driver_data_; \
|
||||
mg_tcpip_init((manager), &mif_); \
|
||||
MG_INFO(("Driver: rw612w, MAC: %M", mg_print_mac, mif_.mac)); \
|
||||
} while (0)
|
||||
|
||||
#ifndef MG_TCPIP_DRIVER_INIT
|
||||
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_RW612W_INIT(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -365,7 +365,7 @@ static bool st67w6_init(uint8_t *mac) {
|
||||
}
|
||||
// set clock, who cares ???
|
||||
if (!st67w6_at_cmd("AT+GET_CLOCK\r\n", 14)) return false;
|
||||
MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // TODO(scaprile): --> VERBOSE
|
||||
MG_VERBOSE(("%.*s", s_at_resp_len, at_resp));
|
||||
// BT-ENABLED DEPENDENCY
|
||||
|
||||
// MODULE DEPENDENCY
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#if MG_ENABLE_TCPIP && (MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || \
|
||||
MG_ENABLE_DRIVER_STM32N)
|
||||
// STM32C: vendor modded single-queue Synopsys v5.4, same settings as STM32H
|
||||
// STM32H: vendor modded single-queue Synopsys v4.2
|
||||
// STM32N: dual-queue GbE Synopsys v5.2 with no hash table option, 64-bit AXI
|
||||
// MCXNx4x: dual-queue Synopsys v5.2 with no hash table option
|
||||
@@ -310,7 +311,7 @@ static uint32_t s_rxno;
|
||||
#if MG_ENABLE_DRIVER_MCXN
|
||||
void ETHERNET_IRQHandler(void);
|
||||
void ETHERNET_IRQHandler(void) {
|
||||
#elif MG_ENABLE_DRIVER_STM32H
|
||||
#elif MG_ENABLE_DRIVER_STM32H && !MG_ENABLE_DRIVER_STM32C
|
||||
void ETH_IRQHandler(void);
|
||||
void ETH_IRQHandler(void) {
|
||||
#else
|
||||
@@ -339,8 +340,10 @@ void ETH1_IRQHandler(void) {
|
||||
}
|
||||
ETH->DMACSR =
|
||||
MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing
|
||||
ETH->DMACRDTPR =
|
||||
(uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1]; // and resume RX
|
||||
// Resume RX: tail is the last descriptor given back to DMA. It must differ
|
||||
// from the DMA current descriptor, or some DMAs, e.g. STM32C5, stay suspended
|
||||
ETH->DMACRDTPR = (uint32_t) (uintptr_t) &s_rxdesc[(s_rxno + ETH_DESC_CNT - 1) %
|
||||
ETH_DESC_CNT];
|
||||
}
|
||||
|
||||
struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
|
||||
|
||||
+11
-2
@@ -4,6 +4,13 @@
|
||||
#if !defined(MG_ENABLE_DRIVER_STM32H)
|
||||
#define MG_ENABLE_DRIVER_STM32H 0
|
||||
#endif
|
||||
#if !defined(MG_ENABLE_DRIVER_STM32C)
|
||||
#define MG_ENABLE_DRIVER_STM32C 0
|
||||
#endif
|
||||
#if MG_ENABLE_DRIVER_STM32C // STM32C5: STM32H5 MAC, the IRQ is ETH1
|
||||
#undef MG_ENABLE_DRIVER_STM32H
|
||||
#define MG_ENABLE_DRIVER_STM32H 1
|
||||
#endif
|
||||
#if !defined(MG_ENABLE_DRIVER_MCXN)
|
||||
#define MG_ENABLE_DRIVER_MCXN 0
|
||||
#endif
|
||||
@@ -43,9 +50,11 @@ struct mg_tcpip_driver_stm32h_data {
|
||||
#define MG_DRIVER_MDC_CR 4
|
||||
#endif
|
||||
|
||||
#if MG_ENABLE_DRIVER_STM32H && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#if MG_ENABLE_DRIVER_STM32H && !MG_ENABLE_DRIVER_STM32C && \
|
||||
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn)
|
||||
#elif MG_ENABLE_DRIVER_STM32N && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#elif (MG_ENABLE_DRIVER_STM32N || MG_ENABLE_DRIVER_STM32C) && \
|
||||
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
|
||||
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH1_IRQn)
|
||||
#elif !defined(MG_ENABLE_ETH_IRQ)
|
||||
#define MG_ENABLE_ETH_IRQ()
|
||||
|
||||
@@ -214,7 +214,12 @@ size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt,
|
||||
n++;
|
||||
} else if (c == 's') {
|
||||
char *p = va_arg(*ap, char *);
|
||||
if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
|
||||
if (p != NULL && pr != ~0U) {
|
||||
for (j = 0; j < pr && p[j] != '\0'; j++) ; // portable strnlen()
|
||||
pr = j;
|
||||
} else {
|
||||
pr = p == NULL ? 0 : strlen(p);
|
||||
}
|
||||
for (j = 0; !minus && pr < w && j + pr < w; j++)
|
||||
n += scpy(out, param, &pad, 1);
|
||||
n += scpy(out, param, p, pr);
|
||||
|
||||
+103
-37
@@ -1,12 +1,66 @@
|
||||
#include "config.h"
|
||||
#include "health.h"
|
||||
|
||||
#if MG_ENABLE_HEALTH
|
||||
#if MG_ARCH == MG_ARCH_ESP32
|
||||
// reusable for other platforms, currently ESP32-only
|
||||
struct mg_health_coredump {
|
||||
const void *ptr;
|
||||
size_t poffset;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static struct mg_health_coredump s_coredump;
|
||||
#endif
|
||||
|
||||
bool mg_health_coredump_begin(size_t *size) {
|
||||
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||
const esp_partition_t *p = esp_partition_find_first(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL);
|
||||
size_t address = 0, len = 0;
|
||||
esp_err_t err = esp_core_dump_image_get(&address, &len);
|
||||
if (size == NULL || s_coredump.ptr != NULL || err != ESP_OK || p == NULL)
|
||||
return false;
|
||||
s_coredump.ptr = p;
|
||||
s_coredump.poffset = address - p->address;
|
||||
s_coredump.size = len;
|
||||
*size = len;
|
||||
return true;
|
||||
#else
|
||||
(void) size;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) {
|
||||
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||
const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr;
|
||||
if (p == NULL || offset > s_coredump.size)
|
||||
return false;
|
||||
if (*len > s_coredump.size - offset) *len = s_coredump.size - offset;
|
||||
return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == ESP_OK;
|
||||
#else
|
||||
(void) offset, (void) buf, (void) len;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool mg_health_coredump_end(void) {
|
||||
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||
bool ok = s_coredump.ptr != NULL;
|
||||
memset(&s_coredump, 0, sizeof(s_coredump));
|
||||
return ok;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// The one health record. Lives in RAM that survives a warm reset, see
|
||||
// MG_HEALTH_RAM and the .mg_health region in the linker script
|
||||
struct mg_health mg_health_record MG_HEALTH_RAM;
|
||||
|
||||
// STM32 H723 support. Needs the _estack symbol from the linker script
|
||||
#if MG_ENABLE_HEALTH && (defined(STM32H7_DEV_ID) || defined(STM32H5))
|
||||
// STM32 H7, H5, C5 support. Needs the _estack symbol from the linker script
|
||||
#if defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5)
|
||||
// Fault handler body. Runs in exception context: no printf, no malloc, no
|
||||
// blocking calls. Copies the unwind registers and raw stack into the health
|
||||
// record, then resets.
|
||||
@@ -48,52 +102,57 @@ void BusFault_Handler(void) __attribute__((alias("HardFault_Handler")));
|
||||
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
|
||||
#endif
|
||||
|
||||
// Start a new boot: validate the record, count the boot, store the reset
|
||||
// reason. The current boot moves to prev_* slots first, so the previous
|
||||
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
|
||||
// Start a new boot: validate the record. Called by MG_HEALTH_INIT()
|
||||
void mg_health_init(void) {
|
||||
char magic[] = MG_HEALTH_MAGIC;
|
||||
// struct mg_health *h = mg_health_get();
|
||||
if (!mg_health_valid()) { // First boot, or RAM lost its contents
|
||||
memset(&mg_health_record, 0, sizeof(mg_health_record));
|
||||
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
|
||||
}
|
||||
|
||||
#if 0 && MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||
{
|
||||
#if MG_ARCH == MG_ARCH_ESP32
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_XTENSA;
|
||||
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_RISCV;
|
||||
#endif
|
||||
esp_reset_reason_t rr = esp_reset_reason();
|
||||
mg_health_record.reset_reason = esp2mg(rr);
|
||||
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0
|
||||
MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n"));
|
||||
#else
|
||||
// After a crash, copy the backtrace from the core dump in flash: the same
|
||||
// addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
|
||||
// The core dump stays in flash, so check the reset reason, otherwise an
|
||||
// old crash is reported on every boot
|
||||
static esp_core_dump_summary_t s; // Big on RISC-V, keep it off the stack
|
||||
esp_reset_reason_t rr = esp_reset_reason();
|
||||
esp_core_dump_summary_t *s = calloc(1, sizeof(*s));
|
||||
uint32_t *out = mg_health_record.data;
|
||||
size_t n = 0, max = MG_HEALTH_DATA_SIZE;
|
||||
if (rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT && rr != ESP_RST_TASK_WDT)
|
||||
return; // Not a crash that went through the panic handler
|
||||
if (max == 0 || esp_core_dump_get_summary(&s) != ESP_OK) return;
|
||||
|
||||
mg_health_record.cpuid = 42; // ESP32
|
||||
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
|
||||
|
||||
size_t n = 0;
|
||||
memset(out, 0, sizeof(mg_health_record.data));
|
||||
out[n++] = s.exc_pc;
|
||||
if (s == NULL) return;
|
||||
if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
|
||||
rr != ESP_RST_TASK_WDT) // Not a crash caught by the panic handler
|
||||
|| esp_core_dump_get_summary(s) != ESP_OK) {
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
out[n++] = s->exc_pc;
|
||||
memcpy(&out[n], s->exc_task, 16), n += 4;
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
for (uint32_t i = 0; i < s.exc_bt_info.depth && n < max; i++) {
|
||||
if (s.exc_bt_info.bt[i] != s.exc_pc) out[n++] = s.exc_bt_info.bt[i];
|
||||
}
|
||||
out[n++] = s->ex_info.exc_a[0];
|
||||
out[n++] = s->ex_info.exc_a[1];
|
||||
size_t len = s->exc_bt_info.depth;
|
||||
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
|
||||
memcpy(&out[n], s->exc_bt_info.bt, 4 * len); // sizeof(uint32_t)
|
||||
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
// No unwinder: take the return address, then words on the stack that
|
||||
// point into executable memory (a heuristic, may include stale values)
|
||||
if (n < max) out[n++] = s.ex_info.ra;
|
||||
for (uint32_t i = 0; i + 4 <= s.exc_bt_info.dump_size && n < max; i += 4) {
|
||||
uint32_t w;
|
||||
memcpy(&w, &s.exc_bt_info.stackdump[i], sizeof(w)); // Byte array
|
||||
if (esp_ptr_executable((void *) (uintptr_t) w)) out[n++] = w;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
out[n++] = s->ex_info.exc_ra;
|
||||
out[n++] = s->ex_info.exc_sp;
|
||||
size_t len = s->exc_bt_info.dump_size / 4 ; // sizeof(uint32_t)
|
||||
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
|
||||
memcpy(&out[n], s->exc_bt_info.stackdump, 4 * len);
|
||||
#endif
|
||||
free(s);
|
||||
#endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||
#endif // MG_ARCH_ESP32
|
||||
}
|
||||
|
||||
static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||
@@ -107,11 +166,17 @@ static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||
size_t len = 0;
|
||||
if (mg_health_valid() &&
|
||||
mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT) {
|
||||
(mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_FAULT
|
||||
#if MG_ARCH == MG_ARCH_ESP32
|
||||
|| mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_WATCHDOG
|
||||
#endif
|
||||
)) {
|
||||
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
|
||||
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%u,%m:[%M]}", //
|
||||
MG_ESC("version"), mg_health_record.magic[3], //
|
||||
MG_ESC("cpuid"), mg_health_record.cpuid, //
|
||||
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%hu,%m:%hu,%m:%u,%m:[%M]}", //
|
||||
MG_ESC("version"), mg_health_record.magic[3], //
|
||||
MG_ESC("arch"), mg_health_record.arch, //
|
||||
MG_ESC("reason"), mg_health_record.reset_reason, //
|
||||
MG_ESC("cpuid"), mg_health_record.cpuid, //
|
||||
MG_ESC("data"), print_uint32, n, mg_health_record.data);
|
||||
} else {
|
||||
len += mg_xprintf(fn, arg, "{}");
|
||||
@@ -119,3 +184,4 @@ size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||
(void) ap;
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
+81
-6
@@ -5,21 +5,25 @@
|
||||
#include "arch.h"
|
||||
#include "printf.h"
|
||||
|
||||
#if MG_ENABLE_HEALTH
|
||||
|
||||
#ifndef MG_HEALTH_MAGIC
|
||||
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
|
||||
#endif
|
||||
|
||||
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5))
|
||||
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
|
||||
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
|
||||
#endif
|
||||
|
||||
#if !defined(MG_HEALTH_DATA_SIZE) && (defined(STM32H7_DEV_ID) || defined(STM32H5))
|
||||
#define MG_HEALTH_DATA_SIZE 250
|
||||
#endif
|
||||
|
||||
#ifndef MG_HEALTH_DATA_SIZE
|
||||
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
|
||||
#define MG_HEALTH_DATA_SIZE 250
|
||||
#elif MG_ARCH == MG_ARCH_ESP32
|
||||
#define MG_HEALTH_DATA_SIZE 23
|
||||
#else
|
||||
#define MG_HEALTH_DATA_SIZE 10
|
||||
#endif
|
||||
#endif // MG_HEALTH_DATA_SIZE
|
||||
|
||||
// Places the record in RAM that survives a reset. Define in mongoose_config.h
|
||||
// to match the linker script, e.g. __attribute__((section(".mg_health")))
|
||||
@@ -27,6 +31,13 @@
|
||||
#define MG_HEALTH_RAM
|
||||
#endif
|
||||
|
||||
// CPU core architecture
|
||||
enum mg_health_arch {
|
||||
MG_HEALTH_ARCH_ARM = 0,
|
||||
MG_HEALTH_ARCH_XTENSA,
|
||||
MG_HEALTH_ARCH_RISCV
|
||||
};
|
||||
|
||||
// Why the device restarted. Hardware usually reports several flags at once,
|
||||
// so these are ordered by how much they tell you: the most specific cause a
|
||||
// device reports wins
|
||||
@@ -43,7 +54,8 @@ enum mg_health_reason {
|
||||
|
||||
struct mg_health {
|
||||
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
|
||||
uint32_t reset_reason; // enum mg_health_reason that started this boot
|
||||
uint16_t reset_reason; // enum mg_health_reason that started this boot
|
||||
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
|
||||
uint32_t cpuid; // ARM CPU ID
|
||||
uint32_t data[MG_HEALTH_DATA_SIZE];
|
||||
};
|
||||
@@ -76,6 +88,69 @@ static inline const char *mg_health_reason_str(int reason) {
|
||||
}
|
||||
}
|
||||
|
||||
#if MG_ARCH == MG_ARCH_ESP32
|
||||
#ifndef CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||
#define CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH 0
|
||||
#endif
|
||||
#ifndef ESP_PARTITION_SUBTYPE_DATA_COREDUMP
|
||||
#define ESP_PARTITION_SUBTYPE_DATA_COREDUMP 0x03
|
||||
#endif
|
||||
// clang-format off
|
||||
static inline uint16_t esp2mg(esp_reset_reason_t rr) {
|
||||
switch(rr) {
|
||||
case ESP_RST_POWERON: return (uint16_t) MG_HEALTH_RESET_POWER;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
|
||||
case ESP_RST_PWR_GLITCH:
|
||||
#endif
|
||||
case ESP_RST_BROWNOUT: return (uint16_t) MG_HEALTH_RESET_BROWNOUT;
|
||||
case ESP_RST_EXT: return (uint16_t) MG_HEALTH_RESET_PIN;
|
||||
case ESP_RST_SW: return (uint16_t) MG_HEALTH_RESET_SOFTWARE;
|
||||
case ESP_RST_DEEPSLEEP: return (uint16_t) MG_HEALTH_RESET_LOWPOWER;
|
||||
case ESP_RST_INT_WDT:
|
||||
case ESP_RST_TASK_WDT:
|
||||
case ESP_RST_WDT: return (uint16_t) MG_HEALTH_RESET_WATCHDOG;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
|
||||
case ESP_RST_CPU_LOCKUP:
|
||||
#endif
|
||||
case ESP_RST_PANIC: return (uint16_t) MG_HEALTH_RESET_FAULT;
|
||||
default: break;
|
||||
}
|
||||
return (uint16_t) MG_HEALTH_RESET_UNKNOWN;
|
||||
}
|
||||
// clang-format on
|
||||
#endif
|
||||
|
||||
#define MG_HEALTH_INIT() mg_health_init()
|
||||
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
|
||||
extern void mg_health_init(void);
|
||||
extern bool mg_health_coredump_begin(size_t *size);
|
||||
extern bool mg_health_coredump_read(size_t offset, void *buf, size_t *len);
|
||||
extern bool mg_health_coredump_end(void);
|
||||
#else
|
||||
#define MG_HEALTH_INIT()
|
||||
static inline int mg_health_reason(void) {
|
||||
return 0;
|
||||
}
|
||||
static inline const char *mg_health_reason_str(int reason) {
|
||||
(void) reason;
|
||||
return "unknown";
|
||||
}
|
||||
static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg,
|
||||
va_list *ap) {
|
||||
(void) ap;
|
||||
return mg_xprintf(fn, arg, "{}");
|
||||
}
|
||||
static inline bool mg_health_coredump_begin(size_t *size) {
|
||||
(void) size;
|
||||
return false;
|
||||
}
|
||||
static inline bool mg_health_coredump_read(size_t offset, void *buf,
|
||||
size_t *len) {
|
||||
(void) offset, (void) buf, (void) len;
|
||||
return false;
|
||||
}
|
||||
static inline bool mg_health_coredump_end(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
+46
-34
@@ -734,13 +734,15 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
|
||||
static bool uri2path(const char *r, struct mg_str uri, char *buf, size_t len) {
|
||||
struct mg_str k, v, part, s = mg_str(r), u = {NULL, 0}, d = u;
|
||||
size_t n;
|
||||
bool matched = false;
|
||||
while (mg_span(s, &part, &s, ',')) {
|
||||
if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0);
|
||||
if (v.len == 0) v = k, k = mg_str("/"), u = k, d = v;
|
||||
if (uri.len < k.len) continue;
|
||||
if (mg_strcmp(k, mg_str_n(uri.buf, k.len)) != 0) continue;
|
||||
u = k, d = v;
|
||||
u = k, d = v, matched = true;
|
||||
}
|
||||
if (!matched) return false;
|
||||
n = mg_snprintf(buf, len, "%.*s", (int) d.len, d.buf);
|
||||
if (len == 0 || n + 2 >= len) return false; // Path overflow
|
||||
if (n > 0 && buf[n - 1] != '/') buf[n++] = '/', buf[n] = '\0'; // Add slash
|
||||
@@ -879,8 +881,8 @@ void mg_http_serve_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
fs->rm(path); // MG_FS_WRITE appends, not truncates: drop any old file
|
||||
if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
|
||||
MG_ERROR(("Cannot open %s for upload", path));
|
||||
} else if (!mg_http_stream_body(c, hm, serve_upload_cb, fd)) {
|
||||
mg_fs_close(fd);
|
||||
} else {
|
||||
mg_http_stream_body(c, hm, serve_upload_cb, fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1014,6 +1016,7 @@ struct mg_upload_state {
|
||||
size_t received; // Number of body bytes received so far
|
||||
void *user_data; // Passed to cb, free for cb to use
|
||||
bool failed; // Set when cb returned false
|
||||
bool finished; // End callback has been called
|
||||
};
|
||||
|
||||
// Event handler installed by mg_http_stream_body(). Feeds the body to the
|
||||
@@ -1021,18 +1024,18 @@ struct mg_upload_state {
|
||||
// handler: the connection is closed after the reply.
|
||||
static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
|
||||
bool (*fn)(struct mg_http_message *, struct mg_str *, void **) =
|
||||
(bool (*)(struct mg_http_message *, struct mg_str *, void **))
|
||||
(ptrdiff_t) c->fn_data;
|
||||
(void) ev_data;
|
||||
if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
|
||||
if (us->received < us->expected) {
|
||||
if (!us->finished) {
|
||||
// Connection dropped mid-upload. Let cb clean up, e.g. call mg_ota_end()
|
||||
us->received = us->expected; // MG_EV_ERROR is followed by MG_EV_CLOSE
|
||||
us->finished = true; // MG_EV_ERROR is followed by MG_EV_CLOSE
|
||||
fn(NULL, NULL, &us->user_data);
|
||||
}
|
||||
} else if (ev == MG_EV_READ && !c->is_draining) {
|
||||
if (c->recv.len > 0 && us->received < us->expected) {
|
||||
} else if (ev == MG_EV_READ && !c->is_draining && !us->finished) {
|
||||
if (!us->failed && c->recv.len > 0 && us->received < us->expected) {
|
||||
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
|
||||
size_t left = us->expected - us->received, r = c->recv.len;
|
||||
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
|
||||
@@ -1043,10 +1046,13 @@ static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
us->received += aligned;
|
||||
mg_iobuf_del(&c->recv, 0, aligned); // Shift the unprocessed tail
|
||||
}
|
||||
if (us->received >= us->expected) {
|
||||
if (!us->finished && (us->failed || us->received >= us->expected)) {
|
||||
// Finished upload. Always call cb, even after error, so it can clean up
|
||||
us->finished = true;
|
||||
us->failed |= !fn(NULL, NULL, &us->user_data);
|
||||
if (!c->is_client) {
|
||||
if (c->is_client && us->failed) {
|
||||
mg_error(c, "Body streaming failed");
|
||||
} else if (!c->is_client) {
|
||||
mg_http_reply(c, us->failed ? 500 : 200, NULL, "%zu %s\n",
|
||||
us->received, us->failed ? "fail" : "ok");
|
||||
}
|
||||
@@ -1055,48 +1061,54 @@ static void mg_stream_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
}
|
||||
|
||||
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
bool (*cb)(struct mg_http_message *, struct mg_str *,
|
||||
void **user_data),
|
||||
void **),
|
||||
void *user_data) {
|
||||
struct mg_upload_state *us = (struct mg_upload_state *) c->data;
|
||||
struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
|
||||
bool handled = false;
|
||||
int code = 0;
|
||||
size_t head_len = hm->head.len;
|
||||
|
||||
// Catch upload requests early, without buffering whole body. Called on the
|
||||
// MG_EV_HTTP_HDRS event: we've received all HTTP headers but not necessarily
|
||||
// the full HTTP body
|
||||
if (hm->body.len == (size_t) ~0 ||
|
||||
(te != NULL && mg_strcasecmp(*te, mg_str("chunked")) == 0)) {
|
||||
// Unknown length or chunked, cannot stream. Leave it to the caller
|
||||
} else if (!c->is_client &&
|
||||
mg_strcmp(hm->method, mg_str("POST")) != 0 &&
|
||||
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
|
||||
// Not an upload request. Leave it to the caller
|
||||
if (!c->is_client && mg_strcmp(hm->method, mg_str("POST")) != 0 &&
|
||||
mg_strcmp(hm->method, mg_str("PUT")) != 0) {
|
||||
code = 405;
|
||||
} else if (mg_http_get_header(hm, "Transfer-Encoding") != NULL) {
|
||||
code = 400;
|
||||
} else if (hm->body.len == (size_t) ~0 ||
|
||||
(!c->is_client && mg_http_get_header(hm, "Content-Length") == NULL)) {
|
||||
code = 411;
|
||||
} else if (sizeof(*us) > sizeof(c->data)) {
|
||||
if (!c->is_client) { // Nobody to reply to on a client connection
|
||||
c->pfn = NULL;
|
||||
mg_http_reply(c, 500, NULL, "data too small\n");
|
||||
c->is_draining = 1;
|
||||
cb(NULL, NULL, &user_data); // Let cb release user_data, start skipped
|
||||
handled = true;
|
||||
code = 500;
|
||||
}
|
||||
c->pfn = NULL; // Take ownership even when validation fails
|
||||
if (code != 0) {
|
||||
c->fn = NULL;
|
||||
c->fn_data = NULL;
|
||||
mg_iobuf_del(&c->recv, 0, c->recv.len);
|
||||
cb(NULL, NULL, &user_data); // Release resources even if start was skipped
|
||||
if (c->is_client) {
|
||||
mg_error(c, "%s", mg_http_status_code_str(code));
|
||||
} else {
|
||||
mg_http_reply(c, code, code == 405 ? "Allow: POST, PUT\r\n" : NULL, "");
|
||||
}
|
||||
c->is_draining = 1;
|
||||
} else {
|
||||
c->pfn = NULL; // Silence HTTP protocol handler
|
||||
memset(us, 0, sizeof(*us));
|
||||
us->expected = hm->body.len; // Store number of bytes we expect
|
||||
us->user_data = user_data;
|
||||
c->fn = mg_stream_handler;
|
||||
c->fn_data = (void *) (ptrdiff_t) cb;
|
||||
us->failed = !cb(hm, NULL, &us->user_data); // Call before deleting headers
|
||||
if (mg_http_get_header(hm, "Expect") != NULL) {
|
||||
if (!c->is_client && !us->failed && !us->finished &&
|
||||
mg_http_get_header(hm, "Expect") != NULL) {
|
||||
mg_http_reply(c, 100, NULL, ""); // If curl expects, we continue
|
||||
}
|
||||
mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers
|
||||
c->fn = mg_stream_handler; // We own the connection from now
|
||||
c->fn_data = (void *) (ptrdiff_t) cb;
|
||||
mg_iobuf_del(&c->recv, 0, head_len); // Delete HTTP headers
|
||||
mg_call(c, MG_EV_READ, &c->recv.len); // Process the body bytes we have
|
||||
handled = true;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
static bool is_hex_digit(int c) {
|
||||
|
||||
+14
-11
@@ -205,29 +205,32 @@ int mg_http_status(const struct mg_http_message *hm);
|
||||
|
||||
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
|
||||
// Call it from an MG_EV_HTTP_HDRS handler, after matching the request, e.g.
|
||||
// with mg_match(). Returns true if it took over the connection, false if the
|
||||
// request is not POST/PUT, is chunked, or has no Content-Length.
|
||||
// with mg_match(). Always takes over the connection. Invalid requests receive
|
||||
// an HTTP error; client-side failures call mg_error(). Replies and connection
|
||||
// draining are handled internally, including callback failures.
|
||||
//
|
||||
// Callback upload_fn(hm, data, user_data) is called:
|
||||
// - at start: hm is set. Open the file, set *user_data.
|
||||
// - per chunk: data is set, 512 byte aligned except the last. Not called
|
||||
// again after a failure.
|
||||
// - at end: both NULL. Always called once, also when the connection drops
|
||||
// early, so release resources here. A truncated body looks like a complete
|
||||
// one: validate before committing.
|
||||
// Return true on success, false on error.
|
||||
// - at end: both NULL. Always called exactly once, including validation
|
||||
// failures before start and connections that drop early. Release resources
|
||||
// here. A truncated body looks like a complete one: validate before
|
||||
// committing.
|
||||
// The callback returns true on success, false on error. A failure stops
|
||||
// streaming immediately and calls the end callback before closing.
|
||||
// Example:
|
||||
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL) {
|
||||
// mg_http_stream_body(c, hm, upload, NULL));
|
||||
// if (ev == MG_EV_HTTP_HDRS && mg_match(hm->uri, mg_str("/fs/*"), NULL)) {
|
||||
// mg_http_stream_body(c, hm, upload, NULL);
|
||||
// }
|
||||
// Full example:
|
||||
// tutorials/http/file-upload-single-post
|
||||
// Related APIs:
|
||||
// mg_http_listen(), mg_http_next_multipart()
|
||||
// Notes:
|
||||
// After it returns true, this function hijacks the connection. Existing event
|
||||
// handler not called, hm is invalid, c->data and c->fn_data are modified
|
||||
bool mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
// The existing event handler is no longer called. hm is invalid after this
|
||||
// call; c->data and c->fn_data are reserved for streaming state.
|
||||
void mg_http_stream_body(struct mg_connection *c, struct mg_http_message *hm,
|
||||
bool (*upload_fn)(struct mg_http_message *,
|
||||
struct mg_str *, void **user_data),
|
||||
void *user_data);
|
||||
+616
@@ -0,0 +1,616 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "l2_mesh.h"
|
||||
#include "log.h"
|
||||
#include "printf.h"
|
||||
#include "util.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP
|
||||
|
||||
#define MGI_ETH_MAX 1514
|
||||
#define MGI_TX_DST_LEN 2
|
||||
|
||||
// Mesh header, after the radio header: type, then burst: the fragment's
|
||||
// index << 4 | the last fragment's index. Common to every radio
|
||||
#define MG_MESH_HDR_LEN 2
|
||||
|
||||
#define MG_MESH_FRAME_HDR_LEN (MG_802154_HEADER_LEN + MG_MESH_HDR_LEN)
|
||||
|
||||
// MAC header, 9 bytes: frame control 0x8841, a data frame, PAN ID
|
||||
// compression, short destination and source addresses; sequence number, 0,
|
||||
// as nothing uses it yet; PAN ID; destination; source. Little endian, as
|
||||
// the standard says
|
||||
static void mgi_802154_print_hdr(const struct mg_radio *radio, uint8_t *buf,
|
||||
uint16_t dst, uint16_t src) {
|
||||
buf[0] = 0x41, buf[1] = 0x88, buf[2] = 0;
|
||||
buf[3] = (uint8_t) MG_802154_PAN_ID;
|
||||
buf[4] = (uint8_t) (MG_802154_PAN_ID >> 8);
|
||||
buf[5] = (uint8_t) dst, buf[6] = (uint8_t) (dst >> 8);
|
||||
buf[7] = (uint8_t) src, buf[8] = (uint8_t) (src >> 8);
|
||||
(void) radio;
|
||||
}
|
||||
|
||||
static size_t mgi_802154_parse_hdr(const struct mg_radio *radio,
|
||||
const uint8_t *buf, size_t len,
|
||||
uint16_t *dst, uint16_t *src) {
|
||||
(void) radio;
|
||||
if (len < MG_802154_HEADER_LEN || buf[0] != 0x41 || buf[1] != 0x88 ||
|
||||
buf[3] != (uint8_t) MG_802154_PAN_ID ||
|
||||
buf[4] != (uint8_t) (MG_802154_PAN_ID >> 8)) {
|
||||
return 0;
|
||||
}
|
||||
*dst = (uint16_t) (buf[5] | buf[6] << 8);
|
||||
*src = (uint16_t) (buf[7] | buf[8] << 8);
|
||||
return MG_802154_HEADER_LEN;
|
||||
}
|
||||
|
||||
const struct mg_radio mg_radio_802154 = {
|
||||
"802.15.4", // name
|
||||
125, // max_frame_size: 127 minus FCS
|
||||
250000, // bitrate
|
||||
8, // overhead
|
||||
320, // slot_us
|
||||
128, // cca_us
|
||||
100, // turnaround_us
|
||||
640, // ifs_us
|
||||
3, // backoff_min
|
||||
5, // backoff_max
|
||||
true, // csma
|
||||
MG_802154_HEADER_LEN, // header_len
|
||||
mgi_802154_print_hdr, // print_hdr
|
||||
mgi_802154_parse_hdr // parse_hdr
|
||||
};
|
||||
|
||||
static uint64_t mgi_mesh_b_airtime(size_t len) {
|
||||
const struct mg_radio *radio = &mg_radio_802154;
|
||||
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
|
||||
radio->bitrate;
|
||||
}
|
||||
|
||||
// Each queue record holds a destination and a complete burst.
|
||||
static size_t mgi_mesh_b_queue(struct mg_mesh_node *node, struct mg_queue *q,
|
||||
const void *buf, size_t len, uint16_t dst,
|
||||
uint8_t type) {
|
||||
const struct mg_radio *radio = &mg_radio_802154;
|
||||
const uint8_t *eth = (const uint8_t *) buf;
|
||||
size_t hdr = MG_MESH_FRAME_HDR_LEN, cap = radio->max_frame_size - hdr;
|
||||
size_t frames, size, i;
|
||||
char *record;
|
||||
uint8_t *out;
|
||||
if (len < 14 || len > MGI_ETH_MAX) return 0;
|
||||
frames = (len + cap - 1) / cap;
|
||||
size = MGI_TX_DST_LEN + len + frames * (1 + hdr);
|
||||
if (mg_queue_next(q, NULL) == 0) mg_queue_init(q, q->buf, q->size);
|
||||
if (mg_queue_book(q, &record, size) < size) return 0;
|
||||
out = (uint8_t *) record;
|
||||
*out++ = (uint8_t) (dst >> 8);
|
||||
*out++ = (uint8_t) dst;
|
||||
for (i = 0; i < frames; i++) {
|
||||
size_t chunk = len - i * cap;
|
||||
if (chunk > cap) chunk = cap;
|
||||
*out++ = (uint8_t) (hdr + chunk);
|
||||
radio->print_hdr(radio, out, dst, node->radio_address);
|
||||
out[radio->header_len] = type;
|
||||
out[radio->header_len + 1] = (uint8_t) (i << 4 | (frames - 1));
|
||||
memcpy(out + hdr, eth + i * cap, chunk);
|
||||
out += hdr + chunk;
|
||||
}
|
||||
mg_queue_add(q, size);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#define MGI_JOIN_WINDOW_US 3000
|
||||
#define MGI_JOIN_TURN_US 192
|
||||
#define MGI_JOIN_JITTER_US 640
|
||||
#define MGI_DATA_QUEUE_SIZE 2048
|
||||
#define MGI_TX_RETRY_US 32
|
||||
// Our frames: the radio header, with the radio addresses, then the mesh
|
||||
// header MG_MESH_HDR_LEN, then the payload
|
||||
#define MGI_JOIN_UP_LEN \
|
||||
(MG_MESH_FRAME_HDR_LEN + 6) // MGI_JOIN_UP's payload: MAC addr
|
||||
#define MGI_MAX_FRAGMENTS 14
|
||||
|
||||
enum { MGI_JOIN_DOWN = 1, MGI_JOIN_UP, MGI_DATA_DOWN, MGI_DATA_UP };
|
||||
|
||||
enum { MGI_TURN_IDLE, MGI_TURN_JOIN, MGI_TURN_DATA };
|
||||
|
||||
struct mgi_mesh_b_data {
|
||||
uint8_t rx_buf[MGI_ETH_MAX]; // Ethernet frame being reassembled
|
||||
uint8_t rx_expected; // Expected index | last index, 0xff if idle
|
||||
struct mg_mesh_node *node; // Owner of the TCP/IP interface
|
||||
struct mg_queue tx; // One record per burst of RF fragments
|
||||
size_t tx_offset; // Next queued fragment; zero for an empty frame
|
||||
uint64_t tx_at; // Next fragment or JOIN transmission time
|
||||
uint64_t deadline; // End of the current JOIN or DATA turn
|
||||
struct mgi_mesh_b_peer *fanout; // Next broadcast recipient
|
||||
char tx_buf[MGI_DATA_QUEUE_SIZE]; // Transmit queue storage
|
||||
uint32_t random; // PRNG state for join timing
|
||||
struct mgi_mesh_b_peer *members; // Bridge membership list
|
||||
struct mgi_mesh_b_peer *current; // Member whose turn is active or next
|
||||
int8_t missed_rounds; // -1 after a grant; otherwise completed rounds missed
|
||||
uint16_t coordinator; // Coordinator radio address
|
||||
uint8_t phase; // MGI_TURN_IDLE, MGI_TURN_JOIN, or MGI_TURN_DATA
|
||||
};
|
||||
|
||||
struct mgi_mesh_b_peer {
|
||||
struct mgi_mesh_b_peer *next; // Next member in round-robin order
|
||||
uint16_t radio_address; // Source address supplied in MGI_JOIN_UP
|
||||
uint8_t mac[6]; // Ethernet MAC supplied in MGI_JOIN_UP
|
||||
uint8_t misses; // Turns in a row it did not answer
|
||||
};
|
||||
|
||||
static uint32_t mgi_mesh_b_random(struct mg_mesh_node *node,
|
||||
struct mgi_mesh_b_data *data) {
|
||||
uint32_t *random = &data->random;
|
||||
if (*random == 0) {
|
||||
size_t i;
|
||||
*random = (uint32_t) mg_now();
|
||||
for (i = 0; i < 6; i++) *random = *random * 33u + node->radio->mac[i];
|
||||
if (*random == 0) *random = 1;
|
||||
}
|
||||
*random ^= *random << 13;
|
||||
*random ^= *random >> 17;
|
||||
*random ^= *random << 5;
|
||||
return *random;
|
||||
}
|
||||
|
||||
static const struct mg_radio *const s_mesh_default_radio = &mg_radio_802154;
|
||||
|
||||
// Read a frame's radio addresses to dst and src, see mg_radio::parse_hdr(), and
|
||||
// return its type, from the mesh header. 0: not a frame of ours
|
||||
static unsigned mgi_mesh_b_frame_parse(const uint8_t *frame, size_t len,
|
||||
uint16_t *dst, uint16_t *src) {
|
||||
if (len < MG_MESH_FRAME_HDR_LEN ||
|
||||
s_mesh_default_radio->parse_hdr(s_mesh_default_radio, frame, len, dst,
|
||||
src) !=
|
||||
s_mesh_default_radio->header_len) {
|
||||
return 0;
|
||||
}
|
||||
return frame[s_mesh_default_radio->header_len];
|
||||
}
|
||||
|
||||
// Write a frame's headers: the radio's, to dst from src, then ours: type,
|
||||
// and burst: high nibble is the index, low nibble is the last index
|
||||
static void mgi_mesh_b_frame_header(uint8_t *frame, uint16_t dst, uint16_t src,
|
||||
uint8_t type, uint8_t burst) {
|
||||
size_t h = s_mesh_default_radio->header_len;
|
||||
s_mesh_default_radio->print_hdr(s_mesh_default_radio, frame, dst, src);
|
||||
frame[h] = type, frame[h + 1] = burst;
|
||||
}
|
||||
|
||||
static bool mgi_mesh_b_control_send(struct mg_mesh_node *node, uint16_t dst,
|
||||
uint8_t command) {
|
||||
struct mg_tcpip_if *ifp = node->radio;
|
||||
uint8_t frame[MGI_JOIN_UP_LEN] = {0};
|
||||
size_t len = MG_MESH_FRAME_HDR_LEN;
|
||||
mgi_mesh_b_frame_header(frame, dst, node->radio_address, command, 0);
|
||||
if (command == MGI_JOIN_UP) {
|
||||
memcpy(frame + MG_MESH_FRAME_HDR_LEN, node->radio->mac, 6);
|
||||
len = MGI_JOIN_UP_LEN;
|
||||
}
|
||||
return node->now >= node->tx_at && node->tx(ifp, frame, len) == len;
|
||||
}
|
||||
|
||||
static size_t mgi_mesh_b_fragment_capacity(void) {
|
||||
return s_mesh_default_radio->max_frame_size - MG_MESH_FRAME_HDR_LEN;
|
||||
}
|
||||
|
||||
static uint64_t mgi_mesh_b_burst_time(size_t len) {
|
||||
size_t cap = mgi_mesh_b_fragment_capacity();
|
||||
size_t frames = len == 0 ? 1 : (len + cap - 1) / cap;
|
||||
return (frames - 1) *
|
||||
(mgi_mesh_b_airtime(s_mesh_default_radio->max_frame_size) +
|
||||
s_mesh_default_radio->ifs_us) +
|
||||
mgi_mesh_b_airtime(MG_MESH_FRAME_HDR_LEN + len - (frames - 1) * cap);
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_tx_finish(struct mgi_mesh_b_data *data) {
|
||||
if (data->tx_offset != 0) {
|
||||
if (data->fanout != NULL) data->fanout = data->fanout->next;
|
||||
if (data->fanout == NULL) {
|
||||
size_t len = mg_queue_next(&data->tx, NULL);
|
||||
mg_queue_del(&data->tx, len);
|
||||
}
|
||||
}
|
||||
data->tx_offset = 0;
|
||||
data->tx_at = (uint64_t) -1;
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_tx_step(struct mgi_mesh_b_data *data, uint64_t now) {
|
||||
struct mg_mesh_node *node = data->node;
|
||||
struct mg_tcpip_if *radio = node->radio;
|
||||
uint8_t empty[MG_MESH_FRAME_HDR_LEN], *frame = empty;
|
||||
size_t len = MG_MESH_FRAME_HDR_LEN, record_len = 0;
|
||||
uint16_t dst;
|
||||
uint8_t command;
|
||||
if (now < data->tx_at) return;
|
||||
if (now >= data->deadline) {
|
||||
mgi_mesh_b_tx_finish(data);
|
||||
return;
|
||||
}
|
||||
dst = node->lan == NULL ? data->coordinator : data->current->radio_address;
|
||||
command = node->lan == NULL ? MGI_DATA_UP : MGI_DATA_DOWN;
|
||||
if (data->tx_offset != 0) {
|
||||
char *record;
|
||||
uint8_t *part;
|
||||
record_len = mg_queue_next(&data->tx, &record);
|
||||
part = (uint8_t *) record + data->tx_offset;
|
||||
len = part[0];
|
||||
frame = part + 1;
|
||||
s_mesh_default_radio->print_hdr(s_mesh_default_radio, frame, dst,
|
||||
node->radio_address);
|
||||
} else {
|
||||
mgi_mesh_b_frame_header(frame, dst, node->radio_address, command, 0);
|
||||
}
|
||||
if (now < data->node->tx_at || data->node->tx(radio, frame, len) != len) {
|
||||
data->tx_at = now + MGI_TX_RETRY_US;
|
||||
return;
|
||||
}
|
||||
if (data->tx_offset == 0 || (data->tx_offset += len + 1) == record_len) {
|
||||
mgi_mesh_b_tx_finish(data);
|
||||
} else {
|
||||
data->tx_at = now + mgi_mesh_b_airtime(len) + s_mesh_default_radio->ifs_us;
|
||||
}
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_tx_begin(struct mgi_mesh_b_data *data, bool burst,
|
||||
uint64_t now, uint64_t deadline) {
|
||||
data->phase = MGI_TURN_DATA;
|
||||
data->tx_offset = burst ? MGI_TX_DST_LEN : 0;
|
||||
data->tx_at = now;
|
||||
data->deadline = deadline;
|
||||
}
|
||||
|
||||
// Return the assembled length, zero for an empty frame, or -1 if incomplete
|
||||
// or invalid. Full non-final fragments make the write offset derivable.
|
||||
static int mgi_mesh_b_data_receive(struct mgi_mesh_b_data *data,
|
||||
const uint8_t *frame, size_t len,
|
||||
uint64_t now) {
|
||||
unsigned burst = frame[MG_MESH_FRAME_HDR_LEN - 1], index = burst >> 4,
|
||||
last = burst & 15;
|
||||
size_t chunk = len - MG_MESH_FRAME_HDR_LEN,
|
||||
cap = mgi_mesh_b_fragment_capacity();
|
||||
size_t offset = index * cap;
|
||||
if (index == 0) {
|
||||
data->rx_expected = (uint8_t) burst;
|
||||
if (data->node->lan == NULL)
|
||||
data->deadline = now + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
|
||||
2 * s_mesh_default_radio->ifs_us + MGI_JOIN_TURN_US;
|
||||
}
|
||||
if (last >= MGI_MAX_FRAGMENTS || index > last || burst != data->rx_expected ||
|
||||
chunk > cap || now > data->deadline || (index < last && chunk != cap) ||
|
||||
(last != 0 && chunk == 0) || offset + chunk > sizeof(data->rx_buf)) {
|
||||
data->rx_expected = 0xff;
|
||||
return -1;
|
||||
}
|
||||
memcpy(data->rx_buf + offset, frame + MG_MESH_FRAME_HDR_LEN, chunk);
|
||||
if (index < last) {
|
||||
data->rx_expected = (uint8_t) (burst + 0x10);
|
||||
return -1;
|
||||
}
|
||||
data->rx_expected = 0xff;
|
||||
return offset + chunk == 0 || offset + chunk >= 14 ? (int) (offset + chunk)
|
||||
: -1;
|
||||
}
|
||||
|
||||
// Bridge scheduling and Ethernet forwarding.
|
||||
|
||||
#define MGI_MAX_MISSES 3 // Turns in a row without an answer: remove the member
|
||||
|
||||
static uint16_t mgi_mesh_b_join_field(const uint8_t *p) {
|
||||
return (uint16_t) ((unsigned) p[0] * 256 + p[1]);
|
||||
}
|
||||
|
||||
// Return chaddr for an Ethernet DHCP server reply, even when broadcast.
|
||||
static const uint8_t *mgi_mesh_b_dhcp_client(const uint8_t *frame, size_t len) {
|
||||
size_t ip_len, ihl, udp_len;
|
||||
const uint8_t *udp, *dhcp;
|
||||
if (len < 34 || mgi_mesh_b_join_field(frame + 12) != 0x0800 ||
|
||||
(frame[14] >> 4) != 4 || frame[23] != 17 ||
|
||||
(mgi_mesh_b_join_field(frame + 20) & 0x3fff) != 0)
|
||||
return NULL;
|
||||
ip_len = mgi_mesh_b_join_field(frame + 16);
|
||||
ihl = (size_t) (frame[14] & 15) * 4;
|
||||
if (ihl < 20 || ip_len > len - 14 || ip_len < ihl + 8 + 240) return NULL;
|
||||
udp = frame + 14 + ihl;
|
||||
udp_len = mgi_mesh_b_join_field(udp + 4);
|
||||
if (mgi_mesh_b_join_field(udp) != 67 ||
|
||||
mgi_mesh_b_join_field(udp + 2) != 68 || udp_len < 8 + 240 ||
|
||||
udp_len > ip_len - ihl)
|
||||
return NULL;
|
||||
dhcp = udp + 8;
|
||||
if (dhcp[0] != 2 || dhcp[1] != 1 || dhcp[2] != 6 ||
|
||||
memcmp(dhcp + 236, "\x63\x82\x53\x63", 4) != 0)
|
||||
return NULL;
|
||||
return dhcp + 28;
|
||||
}
|
||||
|
||||
static size_t mgi_mesh_b_enqueue(struct mg_mesh_node *node, const void *buf,
|
||||
size_t len) {
|
||||
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
|
||||
const uint8_t *frame = (const uint8_t *) buf;
|
||||
if (node->lan == NULL)
|
||||
return mgi_mesh_b_queue(node, &data->tx, buf, len, data->coordinator,
|
||||
MGI_DATA_UP);
|
||||
if (len >= 14 && len <= MGI_ETH_MAX) {
|
||||
const uint8_t *client = mgi_mesh_b_dhcp_client(frame, len);
|
||||
const uint8_t *dst = client == NULL ? frame : client;
|
||||
bool multicast = client == NULL && (frame[0] & 1) != 0;
|
||||
struct mgi_mesh_b_peer *peer;
|
||||
for (peer = data->members; peer != NULL; peer = peer->next) {
|
||||
if (multicast || memcmp(dst, peer->mac, 6) == 0) {
|
||||
uint16_t addr = multicast ? 0xffffu : peer->radio_address;
|
||||
return mgi_mesh_b_queue(node, &data->tx, buf, len, addr, MGI_DATA_DOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_bridge_queue_lan(struct mg_mesh_node *bridge) {
|
||||
struct mg_queue *rx = &bridge->lan->recv_queue;
|
||||
char *buf;
|
||||
size_t len;
|
||||
while ((len = mg_queue_next(rx, &buf)) > 0) {
|
||||
bridge->strategy->tx(bridge, buf, len);
|
||||
mg_queue_del(rx, len);
|
||||
}
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_bridge_rx(uint64_t now, struct mg_mesh_node *b,
|
||||
const uint8_t *frame, size_t len) {
|
||||
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) b->data;
|
||||
struct mgi_mesh_b_peer *peer;
|
||||
uint16_t dst = 0, src = 0;
|
||||
unsigned command = mgi_mesh_b_frame_parse(frame, len, &dst, &src);
|
||||
if (command == MGI_DATA_UP && dst == b->radio_address &&
|
||||
data->phase == MGI_TURN_DATA && data->current != NULL &&
|
||||
src == data->current->radio_address && now <= data->deadline) {
|
||||
int received = mgi_mesh_b_data_receive(data, frame, len, now);
|
||||
if (received < 0) return;
|
||||
if (received != 0) {
|
||||
b->lan->driver->tx(data->rx_buf, (size_t) received, b->lan);
|
||||
}
|
||||
data->current->misses = 0;
|
||||
data->current = data->current->next;
|
||||
data->phase = MGI_TURN_IDLE;
|
||||
data->deadline = now;
|
||||
return;
|
||||
}
|
||||
if (len != MGI_JOIN_UP_LEN || command != MGI_JOIN_UP ||
|
||||
dst != b->radio_address || data->phase != MGI_TURN_JOIN ||
|
||||
now >= data->deadline) {
|
||||
return;
|
||||
}
|
||||
if (src == 0 || src == 0xffffu) return;
|
||||
for (peer = data->members; peer != NULL; peer = peer->next) {
|
||||
if (peer->radio_address == src) break;
|
||||
}
|
||||
if (peer == NULL) {
|
||||
peer = (struct mgi_mesh_b_peer *) calloc(1, sizeof(*peer));
|
||||
if (peer == NULL) return;
|
||||
peer->radio_address = src;
|
||||
peer->next = data->members;
|
||||
data->members = peer;
|
||||
}
|
||||
memcpy(peer->mac, frame + MG_MESH_FRAME_HDR_LEN, sizeof(peer->mac));
|
||||
}
|
||||
|
||||
// Member peer did not answer its turn. After MGI_MAX_MISSES in a row, it is
|
||||
// gone: remove it, and its queued downlink frames
|
||||
static void mgi_mesh_b_member_missed(struct mgi_mesh_b_data *data,
|
||||
struct mgi_mesh_b_peer *peer) {
|
||||
if (++peer->misses < MGI_MAX_MISSES) return;
|
||||
MG_INFO(("Member %M removed: no answer %d times", mg_print_mac, peer->mac,
|
||||
MGI_MAX_MISSES));
|
||||
if (data->fanout == peer) {
|
||||
data->fanout = peer->next;
|
||||
if (data->fanout == NULL) {
|
||||
size_t len = mg_queue_next(&data->tx, NULL);
|
||||
mg_queue_del(&data->tx, len);
|
||||
}
|
||||
}
|
||||
LIST_DELETE(struct mgi_mesh_b_peer, &data->members, peer);
|
||||
free(peer);
|
||||
}
|
||||
|
||||
// A broadcast occupies one record and is sent once to each member in list
|
||||
// order.
|
||||
static size_t mgi_mesh_b_bridge_burst(struct mgi_mesh_b_data *data,
|
||||
struct mgi_mesh_b_peer *peer) {
|
||||
char *record;
|
||||
size_t len;
|
||||
while ((len = mg_queue_next(&data->tx, &record)) != 0) {
|
||||
uint16_t dst = mgi_mesh_b_join_field((const uint8_t *) record);
|
||||
struct mgi_mesh_b_peer *member;
|
||||
if (dst == 0xffffu) {
|
||||
if (data->fanout == NULL) data->fanout = data->members;
|
||||
return data->fanout == peer ? len : 0;
|
||||
}
|
||||
if (dst == peer->radio_address) return len;
|
||||
for (member = data->members; member != NULL; member = member->next) {
|
||||
if (member->radio_address == dst) return 0;
|
||||
}
|
||||
mg_queue_del(&data->tx, len); // Its destination has left.
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t mgi_mesh_b_queued_burst_time(struct mgi_mesh_b_data *data,
|
||||
size_t len) {
|
||||
char *record;
|
||||
size_t frames;
|
||||
if (len == 0) return mgi_mesh_b_burst_time(0);
|
||||
mg_queue_next(&data->tx, &record);
|
||||
// Fragment zero carries the final index. Count overhead and IFS per frame.
|
||||
frames =
|
||||
((uint8_t) record[MGI_TX_DST_LEN + MG_MESH_FRAME_HDR_LEN] & 15u) + 1u;
|
||||
return mgi_mesh_b_airtime(len - MGI_TX_DST_LEN - frames +
|
||||
(frames - 1) * s_mesh_default_radio->overhead) +
|
||||
(frames - 1) * s_mesh_default_radio->ifs_us;
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_bridge_io(uint64_t now, struct mg_mesh_node *b) {
|
||||
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) b->data;
|
||||
mgi_mesh_b_bridge_queue_lan(b);
|
||||
if (now >= data->deadline) {
|
||||
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
|
||||
if (data->phase == MGI_TURN_DATA && data->current != NULL) {
|
||||
struct mgi_mesh_b_peer *peer = data->current;
|
||||
data->current = peer->next;
|
||||
mgi_mesh_b_member_missed(data, peer);
|
||||
} else if (data->phase == MGI_TURN_JOIN) {
|
||||
data->current = data->members;
|
||||
}
|
||||
data->phase = MGI_TURN_IDLE;
|
||||
if (data->current != NULL) {
|
||||
size_t burst = mgi_mesh_b_bridge_burst(data, data->current);
|
||||
uint64_t down = mgi_mesh_b_queued_burst_time(data, burst);
|
||||
data->rx_expected = 0xff;
|
||||
mgi_mesh_b_tx_begin(data, burst != 0, now,
|
||||
now + down + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
|
||||
2 * s_mesh_default_radio->ifs_us +
|
||||
MGI_JOIN_TURN_US);
|
||||
} else if (mgi_mesh_b_control_send(b, 0xffffu, MGI_JOIN_DOWN)) {
|
||||
data->phase = MGI_TURN_JOIN;
|
||||
data->deadline = now + MGI_JOIN_WINDOW_US;
|
||||
}
|
||||
if (data->deadline <= now) data->deadline = now + MGI_JOIN_TURN_US;
|
||||
}
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_bridge_free(struct mgi_mesh_b_data *data) {
|
||||
while (data->members != NULL) {
|
||||
struct mgi_mesh_b_peer *peer = data->members;
|
||||
data->members = peer->next;
|
||||
free(peer);
|
||||
}
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_node_rx(struct mg_mesh_node *n, uint64_t now,
|
||||
uint8_t *frame, size_t len) {
|
||||
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) n->data;
|
||||
uint16_t dst = 0, src = 0;
|
||||
unsigned command = mgi_mesh_b_frame_parse(frame, len, &dst, &src);
|
||||
if (command == MGI_JOIN_DOWN && len == MG_MESH_FRAME_HDR_LEN &&
|
||||
dst == 0xffffu) {
|
||||
if (n->joined && src == data->coordinator &&
|
||||
++data->missed_rounds >= MGI_MAX_MISSES) {
|
||||
n->joined = false;
|
||||
data->phase = MGI_TURN_IDLE;
|
||||
data->tx_at = (uint64_t) -1;
|
||||
data->tx_offset = 0;
|
||||
data->rx_expected = 0xff;
|
||||
}
|
||||
if (!n->joined && (mgi_mesh_b_random(n, data) % 64) == 0) {
|
||||
// Spread contenders across windows as well as within the reply window.
|
||||
data->coordinator = src;
|
||||
data->phase = MGI_TURN_JOIN;
|
||||
data->tx_at = now + MGI_JOIN_TURN_US +
|
||||
mgi_mesh_b_random(n, data) % MGI_JOIN_JITTER_US;
|
||||
}
|
||||
} else if (command == MGI_DATA_DOWN && data->coordinator != 0 &&
|
||||
dst == n->radio_address && src == data->coordinator) {
|
||||
int received = mgi_mesh_b_data_receive(data, frame, len, now);
|
||||
if (received < 0) return;
|
||||
n->joined = true;
|
||||
data->missed_rounds =
|
||||
-1; // This round had a grant; do not count it as missed.
|
||||
if (received != 0) {
|
||||
struct mg_tcpip_if *ifp = n->radio;
|
||||
if (n->rx_len != 0 || (size_t) received > ifp->recv_queue.size) {
|
||||
ifp->ndrop++;
|
||||
} else {
|
||||
memcpy(ifp->recv_queue.buf, data->rx_buf, (size_t) received);
|
||||
n->rx_len = (size_t) received;
|
||||
}
|
||||
}
|
||||
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
|
||||
mgi_mesh_b_tx_begin(data, mg_queue_next(&data->tx, NULL) != 0, now,
|
||||
now + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
|
||||
2 * s_mesh_default_radio->ifs_us +
|
||||
MGI_JOIN_TURN_US);
|
||||
}
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_input(struct mg_mesh_node *node, void *buf, size_t len) {
|
||||
uint64_t ready = node->now + node->strategy->radio->turnaround_us;
|
||||
if (node->tx_at < ready) node->tx_at = ready;
|
||||
node->next_tick = node->now;
|
||||
if (node->lan != NULL)
|
||||
mgi_mesh_b_bridge_rx(node->now, node, (const uint8_t *) buf, len);
|
||||
else
|
||||
mgi_mesh_b_node_rx(node, node->now, (uint8_t *) buf, len);
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_tick(struct mg_mesh_node *node, uint64_t now) {
|
||||
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
|
||||
node->now = now;
|
||||
if (node->lan != NULL) {
|
||||
mgi_mesh_b_bridge_io(now, node);
|
||||
mgi_mesh_b_tx_step(data, now);
|
||||
node->next_tick = data->deadline;
|
||||
if (data->tx_at < node->next_tick) node->next_tick = data->tx_at;
|
||||
return;
|
||||
}
|
||||
if (data->phase == MGI_TURN_JOIN) {
|
||||
if (data->tx_at <= now) {
|
||||
if (now - data->tx_at <= MGI_JOIN_TURN_US && !node->joined)
|
||||
mgi_mesh_b_control_send(node, data->coordinator, MGI_JOIN_UP);
|
||||
data->tx_at = (uint64_t) -1;
|
||||
data->phase = MGI_TURN_IDLE;
|
||||
}
|
||||
} else {
|
||||
mgi_mesh_b_tx_step(data, now);
|
||||
}
|
||||
node->next_tick = data->tx_at;
|
||||
}
|
||||
|
||||
static bool mgi_mesh_b_init(struct mg_mesh_node *node,
|
||||
struct mg_tcpip_if *radio, struct mg_tcpip_if *lan,
|
||||
uint16_t radio_address) {
|
||||
struct mgi_mesh_b_data *data =
|
||||
(struct mgi_mesh_b_data *) calloc(1, sizeof(*data));
|
||||
memset(node, 0, sizeof(*node));
|
||||
node->radio = radio;
|
||||
node->lan = lan;
|
||||
node->radio_address = radio_address;
|
||||
node->next_tick = (uint64_t) -1;
|
||||
node->ram_used = node->ram_peak = sizeof(*node);
|
||||
if (data == NULL) return false;
|
||||
node->ram_used += sizeof(*data);
|
||||
node->ram_peak = node->ram_used;
|
||||
data->rx_expected = 0xff;
|
||||
node->data = data;
|
||||
data->node = node;
|
||||
data->tx_at = (uint64_t) -1;
|
||||
mg_queue_init(&data->tx, data->tx_buf, sizeof(data->tx_buf));
|
||||
if (lan != NULL) node->next_tick = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mgi_mesh_b_free(struct mg_mesh_node *node) {
|
||||
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
|
||||
if (data == NULL) return;
|
||||
mgi_mesh_b_bridge_free(data);
|
||||
free(data);
|
||||
node->data = NULL;
|
||||
}
|
||||
|
||||
const struct mg_mesh mg_mesh_default = {
|
||||
"default",
|
||||
mgi_mesh_b_init,
|
||||
mgi_mesh_b_tick,
|
||||
mgi_mesh_b_free,
|
||||
mgi_mesh_b_enqueue,
|
||||
mgi_mesh_b_input,
|
||||
true,
|
||||
true,
|
||||
&mg_radio_802154,
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Ethernet over radio, through a mesh. Mongoose sees an Ethernet interface,
|
||||
// the mesh splits Ethernet frames into radio frames. Two parts plug in:
|
||||
// - struct mg_radio: a radio standard, e.g. mg_radio_802154
|
||||
// - struct mg_mesh: how the mesh is built and routed
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "net_builtin.h"
|
||||
|
||||
#if MG_ENABLE_TCPIP
|
||||
|
||||
// Monotonic time in microseconds.
|
||||
uint64_t mg_micros(void);
|
||||
|
||||
// A radio standard
|
||||
struct mg_radio {
|
||||
const char *name; // For logs
|
||||
size_t max_frame_size; // Largest radio frame, bytes
|
||||
uint32_t bitrate; // Bits per second on air
|
||||
size_t overhead; // Bytes on air besides the frame: preamble, FCS
|
||||
uint32_t slot_us; // Random backoff slot duration, us
|
||||
uint32_t cca_us; // Channel sensing duration, us
|
||||
uint32_t turnaround_us; // Idle check to transmission start, us
|
||||
uint32_t ifs_us; // Inter-frame spacing in us: gap between transmits
|
||||
uint8_t backoff_min; // Initial backoff exponent
|
||||
uint8_t backoff_max; // Maximum backoff exponent
|
||||
bool csma; // Use channel sensing and backoff before TX
|
||||
size_t header_len; // Radio hardware header length
|
||||
// Write the radio header, with 0xffff as the broadcast address.
|
||||
void (*print_hdr)(const struct mg_radio *radio, uint8_t *buf, uint16_t dst,
|
||||
uint16_t src);
|
||||
// Return the parsed header length, or zero for an invalid header.
|
||||
size_t (*parse_hdr)(const struct mg_radio *radio, const uint8_t *buf,
|
||||
size_t len, uint16_t *dst, uint16_t *src);
|
||||
};
|
||||
|
||||
#define MG_802154_HEADER_LEN 9 // MAC header, short addresses
|
||||
#define MG_802154_PAN_ID 0xabcd // Our network's PAN ID
|
||||
|
||||
#ifndef MG_802154_CHANNEL
|
||||
#define MG_802154_CHANNEL 15
|
||||
#endif
|
||||
|
||||
extern const struct mg_radio mg_radio_802154;
|
||||
|
||||
struct mg_mesh_node {
|
||||
struct mg_tcpip_if *radio; // Owning radio driver interface
|
||||
struct mg_tcpip_if *lan; // Bridge LAN interface, NULL for a client
|
||||
const struct mg_mesh *strategy; // Selected strategy
|
||||
size_t (*tx)(struct mg_tcpip_if *, const void *, size_t); // Radio frame TX
|
||||
size_t rx_len; // Complete Ethernet frame in the interface RX buffer
|
||||
uint64_t now; // Current driver poll time, us
|
||||
uint64_t tx_at; // Earliest next transmission, us
|
||||
void *data; // Private state owned by the strategy
|
||||
size_t ram_used; // Live mesh state bytes
|
||||
size_t ram_peak; // Peak live mesh state bytes
|
||||
uint64_t next_tick; // Next deadline in us, or UINT64_MAX
|
||||
uint16_t radio_address; // Local address supplied by the radio
|
||||
bool joined; // Client is a member of the mesh
|
||||
};
|
||||
|
||||
struct mg_mesh {
|
||||
const char *name;
|
||||
bool (*init)(struct mg_mesh_node *node, struct mg_tcpip_if *radio,
|
||||
struct mg_tcpip_if *lan, uint16_t radio_address);
|
||||
void (*tick)(struct mg_mesh_node *node, uint64_t now_microseconds);
|
||||
void (*free)(struct mg_mesh_node *node);
|
||||
size_t (*tx)(struct mg_mesh_node *, const void *, size_t); // ETH frame TX
|
||||
void (*rx)(struct mg_mesh_node *, void *, size_t); // Radio frame RX
|
||||
bool single_bridge; // Require exactly one bridge
|
||||
bool scheduled_tx; // The mesh schedules transmission turns
|
||||
const struct mg_radio *radio; // Radio configuration for this strategy
|
||||
};
|
||||
|
||||
extern const struct mg_mesh mg_mesh_default;
|
||||
|
||||
#endif
|
||||
+33
@@ -89,6 +89,7 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
mg_iobuf_free(&io);
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
mg_health_coredump_end();
|
||||
mg_mdash_conn = NULL;
|
||||
MG_INFO(("disconnected, ram %lu", 0));
|
||||
}
|
||||
@@ -146,6 +147,35 @@ static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
|
||||
// MG_ESC("percent"));
|
||||
}
|
||||
|
||||
static void rpc_coredump_begin(struct mg_rpc_req *r) {
|
||||
size_t size = 0;
|
||||
if (mg_health_coredump_begin(&size)) {
|
||||
mg_rpc_ok(r, "{%m:%llu}", MG_ESC("size"), (uint64_t) size);
|
||||
} else {
|
||||
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
|
||||
}
|
||||
}
|
||||
|
||||
static void rpc_coredump_read(struct mg_rpc_req *r) {
|
||||
uint8_t buf[768];
|
||||
size_t len = sizeof(buf);
|
||||
size_t offset = (size_t) mg_json_get_long(r->frame, "$.params.offset", -1);
|
||||
if (mg_health_coredump_read(offset, buf, &len)) {
|
||||
mg_rpc_ok(r, "{%m:\"%M\"}", MG_ESC("data"), mg_print_base64,
|
||||
(int) len, buf);
|
||||
} else {
|
||||
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
|
||||
}
|
||||
}
|
||||
|
||||
static void rpc_coredump_end(struct mg_rpc_req *r) {
|
||||
if (mg_health_coredump_end()) {
|
||||
mg_rpc_ok(r, "true");
|
||||
} else {
|
||||
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
|
||||
}
|
||||
}
|
||||
|
||||
void mg_mdash_init(struct mg_mgr *mgr) {
|
||||
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL);
|
||||
@@ -153,6 +183,9 @@ void mg_mdash_init(struct mg_mgr *mgr) {
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, NULL);
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Write"), mg_mdash_rpc_ota_write, NULL);
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Begin"), rpc_coredump_begin, NULL);
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Read"), rpc_coredump_read, NULL);
|
||||
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.End"), rpc_coredump_end, NULL);
|
||||
}
|
||||
|
||||
void mg_mdash_poll(struct mg_mgr *mgr) {
|
||||
|
||||
+19
-8
@@ -10,8 +10,14 @@
|
||||
#define MG_TCPIP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
|
||||
#endif
|
||||
|
||||
#ifndef MG_TCPIP_ACK_MS
|
||||
#define MG_TCPIP_ACK_MS 150 // Timeout for ACKing
|
||||
#endif
|
||||
|
||||
#ifndef MG_TCPIP_ARP_MS
|
||||
#define MG_TCPIP_ARP_MS 100 // Timeout for ARP response
|
||||
#endif
|
||||
|
||||
#define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment
|
||||
#define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection
|
||||
#define MG_TCPIP_RTX_MS 1000 // Initial retransmission timeout (RFC-6298, 2.1)
|
||||
@@ -364,6 +370,7 @@ static const struct mg_addr ip6_allrouters = {
|
||||
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}}, 0, 0, true};
|
||||
static const struct mg_addr ip6_allnodes = {
|
||||
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}}, 0, 0, true};
|
||||
static const uint8_t locpx[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
#define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1])
|
||||
#endif
|
||||
@@ -418,7 +425,7 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
|
||||
if (ifp->is_ip_changed) {
|
||||
struct mg_connection *c;
|
||||
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
|
||||
if (!c->is_listening && !c->is_udp) c->is_closing = 1;
|
||||
if (!c->is_listening && !c->is_udp && !c->loc.is_ip6) c->is_closing = 1;
|
||||
}
|
||||
ifp->is_ip_changed = false;
|
||||
}
|
||||
@@ -734,6 +741,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
: pkt->dhcp->siaddr ? pkt->dhcp->siaddr
|
||||
: pkt->ip->src);
|
||||
ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
|
||||
ifp->lease_expire = ifp->now + 5000; // No ACK by then: start over
|
||||
} else if (msgtype == 5) { // DHCPACK
|
||||
if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP
|
||||
uint64_t rand;
|
||||
@@ -1014,12 +1022,13 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
|
||||
if (pkt->pay.len < sizeof(*ra)) return;
|
||||
if (ifp->state6 == MG_TCPIP_STATE_UP) {
|
||||
MG_DEBUG(("Received NDP RA")); // fill gw6 address
|
||||
MG_DEBUG(("Received NDP RA")); // fill gw6 address if RFC 4861 6.1.2
|
||||
if (memcmp(pkt->ip6->src, locpx, 8) != 0 || pkt->ip6->hops != 255) return;
|
||||
// parse options
|
||||
while (opt_left >= 2) {
|
||||
uint8_t type = opts[0], len = opts[1];
|
||||
size_t length = (size_t) len * 8;
|
||||
if (length == 0 || length > opt_left) break; // malformed
|
||||
if (length == 0 || length > opt_left) return; // RFC 4861 6.1.2
|
||||
if (type == 1 && length >= 8) {
|
||||
// Received router's L2 address
|
||||
if (!mg_l2_ip6get(ifp->l2type, l2, opts + 2, len)) break;
|
||||
@@ -1100,7 +1109,7 @@ static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
}
|
||||
} break;
|
||||
case 134: // Router Advertisement
|
||||
rx_ndp_ra(ifp, pkt);
|
||||
if (pkt->icmp6->code == 0) rx_ndp_ra(ifp, pkt); // RFC 4861 6.1.2
|
||||
break;
|
||||
case 135: // Neighbor Solicitation
|
||||
rx_ndp_ns(ifp, pkt);
|
||||
@@ -1137,7 +1146,7 @@ static void onstate6change(struct mg_tcpip_if *ifp) {
|
||||
if (ifp->is_ip6_changed) {
|
||||
struct mg_connection *c;
|
||||
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
|
||||
if (!c->is_listening && !c->is_udp) c->is_closing = 1;
|
||||
if (!c->is_listening && !c->is_udp && c->loc.is_ip6) c->is_closing = 1;
|
||||
}
|
||||
ifp->is_ip6_changed = false;
|
||||
}
|
||||
@@ -2041,6 +2050,9 @@ static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
if (ifp->enable_dhcp_client && s1) {
|
||||
if (ifp->state == MG_TCPIP_STATE_UP) {
|
||||
tx_dhcp_discover(ifp); // INIT (4.4.1)
|
||||
} else if (ifp->state == MG_TCPIP_STATE_REQ &&
|
||||
ifp->now >= ifp->lease_expire) {
|
||||
ifp->state = MG_TCPIP_STATE_UP; // REQUEST or ACK lost: DISCOVER again
|
||||
} else if (ifp->state == MG_TCPIP_STATE_READY &&
|
||||
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
|
||||
if (ifp->now >= ifp->lease_expire) {
|
||||
@@ -2082,12 +2094,11 @@ static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
|
||||
bool cur_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN);
|
||||
bool cur_l2 = (ifp->state6 >= MG_TCPIP_STATE_UP);
|
||||
const uint8_t px[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0}; // RFC-4291 2.5.6
|
||||
if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed
|
||||
if (ifp->l2type == MG_TCPIP_L2_ETH && l2_up && ifp->ip6ll[0] == 0 &&
|
||||
ifp->ip6ll[1] == 0) { // gen ll address
|
||||
mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac);
|
||||
memcpy(ifp->ip6ll, px, 8); // RFC-2464 5
|
||||
memcpy(ifp->ip6ll, locpx, 8); // RFC-4291 2.5.6; RFC-2464 5
|
||||
} // just got our link local address if we didn't have one.
|
||||
// If static configuration is used, global addresses,
|
||||
// prefix length, and gw are already filled at this point.
|
||||
@@ -2107,7 +2118,7 @@ static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
|
||||
if ((ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) &&
|
||||
ifp->state6 == MG_TCPIP_STATE_UP && ifp->ip6ll[0] == 0 &&
|
||||
ifp->ip6ll[1] != 0) { // IPV6CP has got an IFCID, gen ll address
|
||||
memcpy(ifp->ip6ll, px, 8); // RFC-5072 5
|
||||
memcpy(ifp->ip6ll, locpx, 8); // RFC-5072 5
|
||||
onstate6change(ifp);
|
||||
} // just got our link local address if we didn't have one.
|
||||
}
|
||||
|
||||
@@ -155,6 +155,11 @@ extern struct mg_tcpip_driver mg_tcpip_driver_st67w6;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_atcmd;
|
||||
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
|
||||
|
||||
#include "drivers/mg24.h"
|
||||
#include "drivers/mcxw71.h"
|
||||
#include "drivers/nrf52.h"
|
||||
#include "drivers/rw612w.h"
|
||||
|
||||
// SPI bus abstraction for SPI-attached network chips (e.g. W5500).
|
||||
// Populate and assign to the driver's driver_data field.
|
||||
struct mg_tcpip_spi {
|
||||
|
||||
@@ -208,9 +208,7 @@ static void s_firmware_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
(unsigned long) hm->body.len, (unsigned long) s_ota->size);
|
||||
} else {
|
||||
MG_DEBUG(("Beginning OTA (%lu bytes)", (unsigned long) s_ota->size));
|
||||
if (!mg_http_stream_body(c, hm, s_firmware_cb, NULL)) {
|
||||
mg_error(c, "Cannot stream firmware");
|
||||
}
|
||||
mg_http_stream_body(c, hm, s_firmware_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_ERROR) {
|
||||
s_ota->fn((char *) ev_data);
|
||||
@@ -243,6 +241,21 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *json_url,
|
||||
}
|
||||
}
|
||||
|
||||
// Uses *p as a flag: non-NULL means an OTA session is open and must be
|
||||
// closed by mg_ota_end()
|
||||
bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
if (hm != NULL) { // Start
|
||||
if (!mg_ota_begin(hm->body.len)) return false;
|
||||
*p = (void *) (ptrdiff_t) 1;
|
||||
return true;
|
||||
} else if (data != NULL) { // Next chunk
|
||||
return mg_ota_write(data->buf, data->len);
|
||||
}
|
||||
if (*p == NULL) return false; // End. Begin failed, nothing to finish
|
||||
*p = NULL;
|
||||
return mg_ota_end();
|
||||
}
|
||||
|
||||
void mg_ota_poll(struct mg_mgr *mgr) {
|
||||
static uint64_t t = 5000; // Fire first time 5 sec after boot
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "arch.h"
|
||||
#include "http.h"
|
||||
#include "net.h"
|
||||
|
||||
// Supported OTA targets
|
||||
@@ -34,6 +35,7 @@
|
||||
#define MG_OTA_STM32H7 2 // STM32 H7
|
||||
#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core
|
||||
#define MG_OTA_STM32F 4 // STM32 F7/F4/F2
|
||||
#define MG_OTA_STM32C5 5 // STM32 C5, same flash controller as H5
|
||||
#define MG_OTA_CH32V307 100 // WCH CH32V307
|
||||
#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6
|
||||
#define MG_OTA_RT1020 300 // IMXRT1020
|
||||
@@ -43,6 +45,7 @@
|
||||
#define MG_OTA_RT1170 304 // IMXRT1170
|
||||
#define MG_OTA_RT1180 305 // IMXRT1180
|
||||
#define MG_OTA_MCXN 310 // MCXN947
|
||||
#define MG_OTA_MCXW71 311 // MCXW71
|
||||
#define MG_OTA_RW612 320 // FRDM-RW612
|
||||
#define MG_OTA_FLASH 900 // OTA via internal flash
|
||||
#define MG_OTA_ESP32 910 // ESP32 OTA implementation
|
||||
@@ -72,6 +75,18 @@ bool mg_ota_write(const void *buf, size_t len);
|
||||
// Ends the OTA process and commits the written firmware.
|
||||
bool mg_ota_end(void);
|
||||
|
||||
// Push-based OTA: mg_http_stream_body() callback that flashes the request
|
||||
//
|
||||
// ```c
|
||||
// if (ev == MG_EV_HTTP_HDRS) {
|
||||
// struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
// if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
// mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p);
|
||||
|
||||
// MG_OTA_ROLLBACK: swap firmware banks and reset. On flash-based OTA, the
|
||||
// default calls swap_fn() (no-op on single-bank) then resets. Override in
|
||||
// mongoose_config.h for platforms without mg_flash (ESP32, PicoSDK, etc.).
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
#include "flash.h"
|
||||
#include "log.h"
|
||||
#include "ota.h"
|
||||
|
||||
#if MG_OTA == MG_OTA_MCXW71
|
||||
|
||||
// MCXW71SRM, section 11.2: ROM flash API, 8 KB sectors, 16-byte phrases,
|
||||
// 128-byte pages. Only the CM33 application flash is used; RF flash and IFR
|
||||
// (including the write-once ROM configuration) are left alone.
|
||||
struct mg_mcxw71_flash_config {
|
||||
struct {
|
||||
uint32_t base, size, blocks;
|
||||
uint32_t ifr_base, ifr_size;
|
||||
} mem[2];
|
||||
};
|
||||
|
||||
struct mg_mcxw71_flash_api {
|
||||
uint32_t (*init)(struct mg_mcxw71_flash_config *);
|
||||
uint32_t (*erase)(struct mg_mcxw71_flash_config *, void *, uint32_t, uint32_t,
|
||||
uint32_t);
|
||||
uint32_t (*phrase)(struct mg_mcxw71_flash_config *, void *, uint32_t,
|
||||
uint32_t *, uint32_t);
|
||||
uint32_t (*page)(struct mg_mcxw71_flash_config *, void *, uint32_t,
|
||||
uint32_t *, uint32_t);
|
||||
};
|
||||
|
||||
// The flash API pointer is the second entry in the ROM API table.
|
||||
#define MG_MCXW71_FLASH_API \
|
||||
(*(const struct mg_mcxw71_flash_api *const *) 0x14816fe4U)
|
||||
#define MG_MCXW71_ERASE_KEY 0x6b65666cU // FOUR_CHAR_CODE('l', 'f', 'e', 'k')
|
||||
|
||||
static struct mg_mcxw71_flash_config s_mcxw71_config;
|
||||
static size_t s_mcxw71_size, s_mcxw71_received, s_mcxw71_pending;
|
||||
static uint32_t s_mcxw71_buf[4];
|
||||
static bool s_mcxw71_failed;
|
||||
|
||||
static bool mg_mcxw71_write(void *, const void *, size_t);
|
||||
static bool mg_mcxw71_swap(void);
|
||||
static struct mg_flash s_mg_flash_mcxw71 = {
|
||||
(void *) 0, 0x100000, 8192, 16, mg_mcxw71_write, mg_mcxw71_swap};
|
||||
struct mg_flash *mg_flash = &s_mg_flash_mcxw71;
|
||||
|
||||
// Everything called while flash is busy, or after the application has been
|
||||
// erased, must execute from RAM or ROM. Do not log or call libc here.
|
||||
MG_IRAM static bool mg_mcxw71_write(void *addr, const void *buf, size_t len) {
|
||||
uintptr_t dst = (uintptr_t) addr;
|
||||
const volatile uint8_t *src = (const volatile uint8_t *) buf;
|
||||
uint32_t irq, status = 0;
|
||||
if (dst >= s_mg_flash_mcxw71.size || len > s_mg_flash_mcxw71.size - dst ||
|
||||
(dst % 16) != 0 || (len % 16) != 0)
|
||||
return false;
|
||||
|
||||
irq = __get_PRIMASK();
|
||||
__disable_irq();
|
||||
while (len > 0 && status == 0) {
|
||||
uint32_t words[32]; // ROM needs a word-aligned source in RAM
|
||||
volatile uint8_t *p = (volatile uint8_t *) words;
|
||||
size_t i, n = (dst % 128) == 0 && len >= 128 ? 128 : 16;
|
||||
for (i = 0; i < n; i++) p[i] = src[i];
|
||||
|
||||
// Match the MCUXpresso ROM wrappers' cache maintenance. Interrupts stay
|
||||
// masked until the flash command completes and stale cache data is gone.
|
||||
MCM->CPCR2 |= MCM_CPCR2_CCBC_MASK;
|
||||
__DSB();
|
||||
__ISB();
|
||||
if ((dst % 8192) == 0) {
|
||||
status = MG_MCXW71_FLASH_API->erase(
|
||||
&s_mcxw71_config, FMU0, (uint32_t) dst, 8192, MG_MCXW71_ERASE_KEY);
|
||||
}
|
||||
if (status == 0) {
|
||||
if (n == 128) {
|
||||
status = MG_MCXW71_FLASH_API->page(&s_mcxw71_config, FMU0,
|
||||
(uint32_t) dst, words, 128);
|
||||
} else {
|
||||
status = MG_MCXW71_FLASH_API->phrase(&s_mcxw71_config, FMU0,
|
||||
(uint32_t) dst, words, 16);
|
||||
}
|
||||
}
|
||||
MCM->CPCR2 |= MCM_CPCR2_CCBC_MASK;
|
||||
__DSB();
|
||||
__ISB();
|
||||
dst += n, src += n, len -= n;
|
||||
}
|
||||
__set_PRIMASK(irq);
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
static bool mg_mcxw71_swap(void) {
|
||||
// The common helper verifies the staged image before the RAM copy below.
|
||||
// A failed write must never commit, even if all expected bytes arrived.
|
||||
return !s_mcxw71_failed;
|
||||
}
|
||||
|
||||
MG_IRAM static void mg_mcxw71_apply(size_t size) {
|
||||
size_t ofs, half = s_mg_flash_mcxw71.size / 2;
|
||||
__disable_irq();
|
||||
SysTick->CTRL = 0;
|
||||
for (ofs = 0; ofs < size; ofs += 8192) {
|
||||
if (!mg_mcxw71_write((void *) (uintptr_t) ofs,
|
||||
(const void *) (uintptr_t) (half + ofs), 8192)) {
|
||||
// The application may already be erased. Stay in RAM for debugger
|
||||
// recovery rather than return to partially overwritten flash.
|
||||
for (;;) __NOP();
|
||||
}
|
||||
}
|
||||
__DSB();
|
||||
SCB->AIRCR = 0x05fa0004U;
|
||||
__DSB();
|
||||
for (;;) __NOP();
|
||||
}
|
||||
|
||||
bool mg_ota_begin(size_t new_firmware_size) {
|
||||
uint32_t status;
|
||||
if (s_mcxw71_size != 0 || new_firmware_size == 0) return false;
|
||||
status = MG_MCXW71_FLASH_API->init(&s_mcxw71_config);
|
||||
if (status != 0 || s_mcxw71_config.mem[0].base != 0 ||
|
||||
s_mcxw71_config.mem[0].size < 16384 ||
|
||||
(s_mcxw71_config.mem[0].size % 16384) != 0) {
|
||||
MG_ERROR(("Flash init failed: %lu", (unsigned long) status));
|
||||
return false;
|
||||
}
|
||||
s_mg_flash_mcxw71.size = s_mcxw71_config.mem[0].size;
|
||||
if (!mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxw71)) return false;
|
||||
s_mcxw71_size = new_firmware_size;
|
||||
s_mcxw71_received = s_mcxw71_pending = 0;
|
||||
s_mcxw71_failed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mg_ota_write(const void *buf, size_t len) {
|
||||
const uint8_t *p = (const uint8_t *) buf;
|
||||
uint8_t *pending = (uint8_t *) s_mcxw71_buf;
|
||||
if (s_mcxw71_size == 0 || s_mcxw71_failed) return false;
|
||||
if (len > s_mcxw71_size - s_mcxw71_received || (len > 0 && buf == NULL)) {
|
||||
s_mcxw71_failed = true;
|
||||
return false;
|
||||
}
|
||||
s_mcxw71_received += len;
|
||||
if (s_mcxw71_pending > 0) {
|
||||
size_t n = 16 - s_mcxw71_pending;
|
||||
if (n > len) n = len;
|
||||
if (n > 0) memcpy(pending + s_mcxw71_pending, p, n);
|
||||
s_mcxw71_pending += n;
|
||||
if (n > 0) p += n;
|
||||
len -= n;
|
||||
if (s_mcxw71_pending == 16) {
|
||||
s_mcxw71_failed = !mg_ota_flash_write(pending, 16, &s_mg_flash_mcxw71);
|
||||
s_mcxw71_pending = 0;
|
||||
}
|
||||
}
|
||||
if (!s_mcxw71_failed && len >= 16) {
|
||||
size_t n = MG_ROUND_DOWN(len, 16);
|
||||
s_mcxw71_failed = !mg_ota_flash_write(p, n, &s_mg_flash_mcxw71);
|
||||
p += n, len -= n;
|
||||
}
|
||||
if (!s_mcxw71_failed && len > 0) {
|
||||
memcpy(pending + s_mcxw71_pending, p, len);
|
||||
s_mcxw71_pending += len;
|
||||
}
|
||||
return !s_mcxw71_failed;
|
||||
}
|
||||
|
||||
bool mg_ota_end(void) {
|
||||
bool ok;
|
||||
size_t size = s_mcxw71_size;
|
||||
if (size == 0) return false;
|
||||
if (s_mcxw71_received != size) s_mcxw71_failed = true;
|
||||
if (!s_mcxw71_failed && s_mcxw71_pending > 0) {
|
||||
s_mcxw71_failed =
|
||||
!mg_ota_flash_write(s_mcxw71_buf, s_mcxw71_pending, &s_mg_flash_mcxw71);
|
||||
}
|
||||
ok = mg_ota_flash_end(&s_mg_flash_mcxw71);
|
||||
s_mcxw71_size = s_mcxw71_pending = 0;
|
||||
if (ok) {
|
||||
MG_INFO(("Applying %lu bytes from staging flash", (unsigned long) size));
|
||||
mg_mcxw71_apply(size);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#include "log.h"
|
||||
#include "ota.h"
|
||||
|
||||
#if MG_OTA == MG_OTA_STM32H5
|
||||
#if MG_OTA == MG_OTA_STM32H5 || MG_OTA == MG_OTA_STM32C5
|
||||
|
||||
static bool mg_stm32h5_write(void *, const void *, size_t);
|
||||
static bool mg_stm32h5_swap(void);
|
||||
|
||||
+6
-6
@@ -1552,8 +1552,8 @@ static int mg_tls_parse_cert_der(void *buf, size_t dersz,
|
||||
return -1;
|
||||
}
|
||||
if (mg_der_next(&field, &after) <= 0 || ((after.type != 0x17 || after.len != 13) && (after.type != 0x18 || after.len != 15))) return -1;
|
||||
if (memcmp(after.value, "99991231235959Z", 15) == 0) { // RFC-5280 4.1.2.5
|
||||
MG_ERROR(("No well-defined expiration date"));
|
||||
if (after.len == 15 && memcmp(after.value, "99991231235959Z", 15) == 0) {
|
||||
MG_ERROR(("No well-defined expiration date")); // RFC-5280 4.1.2.5
|
||||
return -1;
|
||||
}
|
||||
if (now > (t = asnt2t(after.value, after.type))) {
|
||||
@@ -1641,7 +1641,7 @@ static int mg_tls_parse_cert_der(void *buf, size_t dersz,
|
||||
}
|
||||
if (i.type != 0x04) return -1;
|
||||
if (mg_der_next(&i, &b) <= 0 || b.type != 0x03) return -1;
|
||||
if (!(b.value[1] & MG_BIT(2))) certsign = false;
|
||||
if (b.len < 2 || !(b.value[1] & MG_BIT(2))) certsign = false;
|
||||
}
|
||||
}
|
||||
if (ca && certsign) cert->is_ca = true;
|
||||
@@ -2055,15 +2055,15 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
|
||||
mg_error(c, "certverify scheme %04x does not match the key", sigalg);
|
||||
return -1;
|
||||
}
|
||||
if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0) {
|
||||
if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0 || seq.type != 0x30) {
|
||||
mg_error(c, "verification message is not an ASN.1 DER sequence");
|
||||
return -1;
|
||||
}
|
||||
if (mg_der_to_tlv(seq.value, seq.len, &r) < 0) {
|
||||
if (mg_der_next(&seq, &r) <= 0 || r.type != 2) {
|
||||
mg_error(c, "missing first part of the signature");
|
||||
return -1;
|
||||
}
|
||||
if (mg_der_to_tlv(r.value + r.len, seq.len - r.len, &s) < 0) {
|
||||
if (mg_der_next(&seq, &s) <= 0 || s.type != 2) {
|
||||
mg_error(c, "missing second part of the signature");
|
||||
return -1;
|
||||
}
|
||||
|
||||
+3
-2
@@ -261,8 +261,9 @@ uint64_t mg_ntohll(uint64_t net) {
|
||||
}
|
||||
|
||||
void mg_delayms(unsigned int ms) {
|
||||
uint64_t to = mg_millis() + ms + 1;
|
||||
while (mg_millis() < to) (void) 0;
|
||||
uint64_t now, to = mg_millis() + ms + 1;
|
||||
while ((now = mg_millis()) < to)
|
||||
if (now + ms + 1 < to) to = now + ms + 1;
|
||||
}
|
||||
|
||||
#if MG_ENABLE_CUSTOM_CALLOC
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ IPV6 ?= 1
|
||||
MDASH ?= 1
|
||||
ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common
|
||||
ASAN_OPTIONS ?= detect_leaks=1
|
||||
TUTORIALS_EMBEDDED := $(dir $(wildcard ../tutorials/esp32/*/Makefile)) $(dir $(wildcard ../tutorials/infineon/*/Makefile)) $(dir $(wildcard ../tutorials/microchip/*/*/Makefile)) $(dir $(wildcard ../tutorials/nordic/*/Makefile)) $(dir $(wildcard ../tutorials/nxp/*/*/Makefile)) $(dir $(wildcard ../tutorials/pico-sdk/*/Makefile)) $(dir $(wildcard ../tutorials/rp/*/Makefile)) $(dir $(wildcard ../tutorials/renesas/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/*/Makefile)) $(dir $(wildcard ../tutorials/ti/*/Makefile)) $(dir $(wildcard ../tutorials/wch/*/Makefile))
|
||||
TUTORIALS_EMBEDDED := $(dir $(wildcard ../tutorials/esp32/*/Makefile)) $(dir $(wildcard ../tutorials/infineon/*/Makefile)) $(dir $(wildcard ../tutorials/microchip/*/*/Makefile)) $(dir $(wildcard ../tutorials/nordic/*/Makefile)) $(dir $(wildcard ../tutorials/nordic/*/*/Makefile)) $(dir $(wildcard ../tutorials/nxp/*/*/Makefile)) $(dir $(wildcard ../tutorials/pico-sdk/*/Makefile)) $(dir $(wildcard ../tutorials/rp/*/Makefile)) $(dir $(wildcard ../tutorials/renesas/*/Makefile)) $(dir $(wildcard ../tutorials/silabs/*/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/*/Makefile)) $(dir $(wildcard ../tutorials/ti/*/Makefile)) $(dir $(wildcard ../tutorials/wch/*/Makefile))
|
||||
TUTORIALS_ZEPHYR := $(dir $(wildcard ../tutorials/zephyr/*/Makefile))
|
||||
TUTORIALS := ../tutorials/micropython/ $(filter-out $(TUTORIALS_EMBEDDED) $(TUTORIALS_ZEPHYR), $(dir $(wildcard ../tutorials/*/*/Makefile)))
|
||||
TUTORIALS_QUICK := $(filter-out ../tutorials/micropython/ ../tutorials/tcpip/pcap-driver/, $(TUTORIALS))
|
||||
@@ -98,7 +98,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c)
|
||||
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
|
||||
|
||||
mongoose.h: $(HDRS) Makefile
|
||||
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/lfs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_aes128.h src/tls_p256.h src/tls_p384.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
|
||||
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/lfs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_aes128.h src/tls_p256.h src/tls_p384.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/l2_mesh.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
|
||||
|
||||
# Check that all external (exported) symbols have "mg_" prefix
|
||||
mg_prefix: mongoose.c mongoose.h
|
||||
|
||||
+11
-2
@@ -92,7 +92,7 @@ fail:
|
||||
exit(1);
|
||||
}
|
||||
|
||||
enum wait_mode { WAIT_ABORT, WAIT_DRAIN, WAIT_HOLD, WAIT_CLOSE };
|
||||
enum wait_mode { WAIT_ABORT, WAIT_DRAIN, WAIT_HOLD, WAIT_CLOSE, WAIT_SEND };
|
||||
|
||||
// Wait for output, optionally draining through the final marker.
|
||||
static int wait_data(const char *host, enum wait_mode mode) {
|
||||
@@ -117,6 +117,13 @@ static int wait_data(const char *host, enum wait_mode mode) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
if (mode == WAIT_SEND) {
|
||||
usleep(100000); // Let the server shut down its read side
|
||||
n = send(fd, "x", 1, 0);
|
||||
usleep(500000); // Keep peer connected for server SHUT_WR
|
||||
close(fd);
|
||||
return n == 1 ? 0 : 1;
|
||||
}
|
||||
if (mode == WAIT_CLOSE) {
|
||||
n = recv(fd, buf, sizeof(buf), 0);
|
||||
close(fd);
|
||||
@@ -151,10 +158,12 @@ int main(int argc, char *argv[]) {
|
||||
return wait_data(argv[1], WAIT_HOLD);
|
||||
if (argc == 3 && strcmp(argv[2], "expectclose") == 0)
|
||||
return wait_data(argv[1], WAIT_CLOSE);
|
||||
if (argc == 3 && strcmp(argv[2], "sendlater") == 0)
|
||||
return wait_data(argv[1], WAIT_SEND);
|
||||
if (argc == 3) connections = atoi(argv[2]);
|
||||
if ((argc != 2 && argc != 3) || connections < 1 ||
|
||||
connections > NUM_CONNECTIONS) {
|
||||
fprintf(stderr, "usage: %s IP [connections|waitandabort|waitanddrain|hold|expectclose]\n",
|
||||
fprintf(stderr, "usage: %s IP [connections|waitandabort|waitanddrain|hold|expectclose|sendlater]\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
+139
-14
@@ -91,6 +91,22 @@ static void wait_ready(struct mg_mgr *mgr) {
|
||||
MG_INFO(("Interface started, starting main loop"));
|
||||
}
|
||||
|
||||
static void test_inet(void) {
|
||||
struct in_addr addr;
|
||||
char buf[INET_ADDRSTRLEN];
|
||||
char *str;
|
||||
|
||||
ASSERT(inet_pton(AF_INET, "192.0.2.1", &addr) == 1);
|
||||
ASSERT(inet_ntop(AF_INET, &addr, buf, sizeof(buf)) != NULL);
|
||||
ASSERT(strcmp(buf, "192.0.2.1") == 0);
|
||||
str = inet_ntoa(addr);
|
||||
ASSERT(str != NULL);
|
||||
ASSERT(strcmp(str, "192.0.2.1") == 0);
|
||||
ASSERT(inet_addr("192.0.2.1") == addr.s_addr);
|
||||
ASSERT(inet_pton(AF_INET, "not-an-ip", &addr) == 0);
|
||||
ASSERT(inet_pton(AF_INET6, "::1", &addr) < 0 && errno == EAFNOSUPPORT);
|
||||
}
|
||||
|
||||
#if !MG_ENABLE_FREERTOS
|
||||
static bool s_fail_alloc;
|
||||
|
||||
@@ -132,9 +148,10 @@ static volatile bool s_ready_waiting, s_ready_done;
|
||||
static volatile bool s_backlog_ready, s_backlog_full, s_backlog_accept;
|
||||
static volatile bool s_tx_held, s_tx_waits, s_tx_fail, s_tx_error;
|
||||
static volatile bool s_send_waiting;
|
||||
static volatile bool s_send_done, s_send_expect_error;
|
||||
static volatile bool s_send_done, s_send_expect_error, s_send_expect_shutdown;
|
||||
static volatile bool s_recv_start, s_recv_waiting, s_recv_done;
|
||||
static volatile bool s_recv_expect_error;
|
||||
static volatile bool s_shutdown_waiting, s_shutdown_done;
|
||||
static size_t (*s_tx)(const void *, size_t, struct mg_tcpip_if *);
|
||||
|
||||
struct client_data {
|
||||
@@ -152,6 +169,7 @@ static void select_task(void *args);
|
||||
static void readiness_task(void *args);
|
||||
static void backlog_task(void *args);
|
||||
static void tx_gate_task(void *args);
|
||||
static void shutdown_task(void *args);
|
||||
|
||||
struct atask_args {
|
||||
bool listener_nonblock;
|
||||
@@ -300,8 +318,13 @@ static void tx_gate_task(void *args) {
|
||||
// receiver/sender > network task > gate: final send is waiting
|
||||
while (!s_send_waiting) vTaskDelay(1);
|
||||
ASSERT(!s_send_done);
|
||||
if (s_send_expect_error) s_tx_error = s_tx_fail = true;
|
||||
else s_tx_held = false;
|
||||
if (s_send_expect_shutdown) {
|
||||
ASSERT(shutdown((int) (uintptr_t) args, SHUT_WR) == 0);
|
||||
} else if (s_send_expect_error) {
|
||||
s_tx_error = s_tx_fail = true;
|
||||
} else {
|
||||
s_tx_held = false;
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
(void) args;
|
||||
}
|
||||
@@ -342,15 +365,19 @@ static void send_task(void *args) {
|
||||
n = send(fd, buf, sizeof(buf), MSG_DONTWAIT);
|
||||
ASSERT(n < 0 && errno == EAGAIN); // Same full c->send: MSG_DONTWAIT
|
||||
|
||||
if (!s_send_expect_error) {
|
||||
if (!s_send_expect_error && !s_send_expect_shutdown) {
|
||||
s_recv_start = true; // O_NONBLOCK is per socket; now start the receiver
|
||||
while (!s_recv_waiting) vTaskDelay(1);
|
||||
}
|
||||
s_send_waiting = true; // Gate task releases TX after this send blocks
|
||||
n = send(fd, &marker, sizeof(marker), 0);
|
||||
if (s_send_expect_error) ASSERT(n < 0 && errno == EPIPE);
|
||||
else ASSERT(n == (ssize_t) sizeof(marker));
|
||||
if (s_send_expect_error || s_send_expect_shutdown) {
|
||||
ASSERT(n < 0 && errno == EPIPE);
|
||||
} else {
|
||||
ASSERT(n == (ssize_t) sizeof(marker));
|
||||
}
|
||||
s_send_done = true;
|
||||
if (s_send_expect_shutdown) close(fd);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
@@ -388,7 +415,7 @@ static void atask(void *args) {
|
||||
ASSERT(result == pdPASS);
|
||||
}
|
||||
if (cfg->gate != NULL) {
|
||||
result = xTaskCreate(cfg->gate, "gate", 256, NULL,
|
||||
result = xTaskCreate(cfg->gate, "gate", 256, (void *) (uintptr_t) fd,
|
||||
configMAX_PRIORITIES - 3, NULL);
|
||||
ASSERT(result == pdPASS);
|
||||
}
|
||||
@@ -514,6 +541,59 @@ static void backlog_task(void *args) {
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void shutdown_task(void *args) {
|
||||
struct sockaddr_in local, peer;
|
||||
struct timeval zero = {0, 0};
|
||||
struct pollfd p;
|
||||
fd_set e;
|
||||
socklen_t len;
|
||||
char buf;
|
||||
int fd, lfd;
|
||||
|
||||
(void) args;
|
||||
lfd = open_listener(5, true);
|
||||
s_shutdown_waiting = true;
|
||||
while ((fd = accept(lfd, NULL, NULL)) < 0) {
|
||||
ASSERT(errno == EAGAIN);
|
||||
vTaskDelay(1);
|
||||
}
|
||||
len = sizeof(local);
|
||||
ASSERT(getsockname(lfd, (struct sockaddr *) &local, &len) == 0);
|
||||
ASSERT(len == sizeof(local) && local.sin_family == AF_INET &&
|
||||
local.sin_port == htons(1234) &&
|
||||
local.sin_addr.s_addr == INADDR_ANY);
|
||||
len = sizeof(peer);
|
||||
ASSERT(getpeername(lfd, (struct sockaddr *) &peer, &len) < 0 &&
|
||||
errno == ENOTCONN);
|
||||
len = sizeof(local);
|
||||
ASSERT(getsockname(fd, (struct sockaddr *) &local, &len) == 0);
|
||||
ASSERT(len == sizeof(local) && local.sin_family == AF_INET &&
|
||||
local.sin_port == htons(1234));
|
||||
len = sizeof(peer);
|
||||
ASSERT(getpeername(fd, (struct sockaddr *) &peer, &len) == 0);
|
||||
ASSERT(len == sizeof(peer) && peer.sin_family == AF_INET &&
|
||||
peer.sin_port != 0);
|
||||
ASSERT(shutdown(lfd, SHUT_RDWR) < 0 && errno == ENOTCONN);
|
||||
ASSERT(shutdown(fd, -1) < 0 && errno == EINVAL);
|
||||
ASSERT(shutdown(fd, SHUT_RD) == 0);
|
||||
ASSERT(recv(fd, &buf, sizeof(buf), 0) == 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(200)); // Delayed client write must be discarded
|
||||
ASSERT(recv(fd, &buf, sizeof(buf), 0) == 0);
|
||||
ASSERT(shutdown(fd, SHUT_WR) == 0);
|
||||
ASSERT(send(fd, &buf, sizeof(buf), 0) < 0 && errno == EPIPE);
|
||||
FD_ZERO(&e);
|
||||
FD_SET(fd, &e);
|
||||
ASSERT(select(fd + 1, NULL, NULL, &e, &zero) == 1);
|
||||
ASSERT(FD_ISSET(fd, &e));
|
||||
p.fd = fd, p.events = POLLOUT, p.revents = 0;
|
||||
ASSERT(poll(&p, 1, 0) == 1);
|
||||
ASSERT(p.revents == POLLERR);
|
||||
ASSERT(close(fd) == 0);
|
||||
ASSERT(close(lfd) == 0);
|
||||
s_shutdown_done = true;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
// task2: tests owner.
|
||||
static void task2(void *args) {
|
||||
struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager
|
||||
@@ -555,7 +635,16 @@ static void task2(void *args) {
|
||||
DASHBOARD("select_child_write");
|
||||
DASHBOARD("poll_child_write");
|
||||
|
||||
#if 0
|
||||
s_error = false;
|
||||
s_shutdown_waiting = s_shutdown_done = false;
|
||||
ASSERT(xTaskCreate(shutdown_task, "shutdown", 512, NULL,
|
||||
configMAX_PRIORITIES - 1, NULL) == pdPASS);
|
||||
while (!s_shutdown_waiting) vTaskDelay(1);
|
||||
run_client(ip, "sendlater");
|
||||
while (!s_shutdown_done) vTaskDelay(1);
|
||||
DASHBOARD("shutdown_read");
|
||||
DASHBOARD("shutdown_write");
|
||||
|
||||
s_error = false;
|
||||
s_backlog_ready = s_backlog_full = s_backlog_accept = false;
|
||||
ASSERT(xTaskCreate(backlog_task, "backlog", 512, NULL,
|
||||
@@ -570,12 +659,6 @@ static void task2(void *args) {
|
||||
while (!client_done(first, &status)) vTaskDelay(1);
|
||||
check_client(status);
|
||||
DASHBOARD("listen_backlog");
|
||||
#else
|
||||
(void) first;
|
||||
(void) second;
|
||||
(void) status;
|
||||
(void) backlog_task;
|
||||
#endif
|
||||
|
||||
s_error = false;
|
||||
// atask at higher priority than task1
|
||||
@@ -676,12 +759,27 @@ static void task2(void *args) {
|
||||
while (s_wait) vTaskDelay(pdMS_TO_TICKS(10));
|
||||
s_tx_held = s_tx_fail = s_tx_error = s_send_expect_error = false;
|
||||
|
||||
s_error = false;
|
||||
s_wait = s_tx_held = s_tx_waits = s_tx_fail = s_tx_error = false;
|
||||
s_send_waiting = s_send_done = s_send_expect_error = false;
|
||||
s_send_expect_shutdown = true;
|
||||
ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_send_error_server,
|
||||
configMAX_PRIORITIES - 1, NULL) == pdPASS);
|
||||
run_client(ip, "hold");
|
||||
ASSERT(s_send_done);
|
||||
DASHBOARD("shutdown_send_wakeup");
|
||||
s_wait = true;
|
||||
while (s_wait) vTaskDelay(pdMS_TO_TICKS(10));
|
||||
s_tx_held = s_send_expect_shutdown = false;
|
||||
|
||||
mg_free(ip);
|
||||
|
||||
s_error = false;
|
||||
{
|
||||
fd_set e;
|
||||
struct pollfd p;
|
||||
socklen_t len;
|
||||
int err;
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
struct sockaddr_in sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
@@ -701,6 +799,12 @@ static void task2(void *args) {
|
||||
ASSERT(poll(&p, 1, 0) == 1);
|
||||
ASSERT((p.revents & POLLERR) != 0);
|
||||
ASSERT((p.revents & POLLOUT) == 0);
|
||||
len = sizeof(err);
|
||||
ASSERT(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) == 0);
|
||||
ASSERT(len == sizeof(err) && err == EIO);
|
||||
len = sizeof(err), err = -1;
|
||||
ASSERT(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) == 0);
|
||||
ASSERT(len == sizeof(err) && err == 0);
|
||||
ASSERT(close(fd) == 0);
|
||||
}
|
||||
DASHBOARD("select_error");
|
||||
@@ -950,6 +1054,25 @@ static void nortos_test(struct mg_mgr *mgr) {
|
||||
DASHBOARD("nortos_poll_children");
|
||||
DASHBOARD("nortos_echo");
|
||||
|
||||
lfd = open_listener(5, true);
|
||||
pid = start_client(ip, "hold");
|
||||
fd = nortos_accept(mgr, lfd);
|
||||
ASSERT(shutdown(fd, SHUT_RD) == 0);
|
||||
ASSERT(recv(fd, &buf, sizeof(buf), 0) == 0);
|
||||
ASSERT(shutdown(fd, SHUT_WR) == 0);
|
||||
ASSERT(send(fd, &buf, sizeof(buf), 0) < 0 && errno == EPIPE);
|
||||
FD_ZERO(&e);
|
||||
FD_SET(fd, &e);
|
||||
ASSERT(select(fd + 1, NULL, NULL, &e, &zero) == 1);
|
||||
ASSERT(FD_ISSET(fd, &e));
|
||||
p.fd = fd, p.events = POLLOUT, p.revents = 0;
|
||||
ASSERT(poll(&p, 1, 0) == 1);
|
||||
ASSERT(p.revents == POLLERR);
|
||||
nortos_wait_client(mgr, pid);
|
||||
ASSERT(close(fd) == 0);
|
||||
ASSERT(close(lfd) == 0);
|
||||
DASHBOARD("nortos_shutdown");
|
||||
|
||||
fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
@@ -990,6 +1113,8 @@ static bool bsd_x_test(struct mg_tcpip_driver *driver, void *driver_data, const
|
||||
|
||||
mg_tcpip_init(&mgr, &mif);
|
||||
ASSERT(mgr.ifp != NULL); // driver init succeeded
|
||||
test_inet();
|
||||
DASHBOARD("inet");
|
||||
MG_INFO(("Init done, starting interface"));
|
||||
usleep(200000); // 200 ms
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Mesh simulator. `make` builds and runs an example
|
||||
ROOT = ../..
|
||||
PROG ?= simulator
|
||||
SRCS = simulator.c mesh_a.c $(ROOT)/mongoose.c
|
||||
HDRS = simulator.h $(ROOT)/mongoose.h
|
||||
WARN = -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common \
|
||||
-Wconversion -Wundef -Wmissing-prototypes -Wstrict-prototypes
|
||||
DEFS = -DMG_ENABLE_TCPIP=1 -DMG_ENABLE_TCPIP_DRIVER_INIT=0 -DMG_ENABLE_IPV6=0 \
|
||||
-DMG_ENABLE_CUSTOM_MILLIS=1 -DMG_ENABLE_TCPIP_TCPRTX=1 \
|
||||
-DMG_IO_SIZE=512 \
|
||||
-DMG_TCPIP_ARP_MS=1000 -DMG_TCPIP_ACK_MS=500 # Slow radio: wait longer
|
||||
CFLAGS ?= -O2 -g0 $(WARN)
|
||||
X ?= 100
|
||||
Y ?= 100
|
||||
ROWS ?= 10
|
||||
COLS ?= 10
|
||||
|
||||
# Nodes at every corner of a ROWS x COLS grid over X,Y, edges included
|
||||
GRID = $(shell for r in $$(seq 0 $(ROWS)); do \
|
||||
for c in $$(seq 0 $(COLS)); do \
|
||||
echo -node $$((c * $(X) / $(COLS))),$$((r * $(Y) / $(ROWS))); \
|
||||
done; done)
|
||||
|
||||
all: $(PROG)
|
||||
$(RUN) ./$(PROG) -area $(X),$(Y) \
|
||||
-bridge $$(($(X) / 2)),$$(($(Y) / 2)) \
|
||||
$(GRID) $(ARGS)
|
||||
|
||||
$(PROG): $(SRCS) $(HDRS) Makefile
|
||||
$(CC) $(SRCS) $(CFLAGS) $(DEFS) -I. -I$(ROOT) -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) $(PROG).dSYM *.exe
|
||||
|
||||
.PHONY: all clean
|
||||
@@ -0,0 +1,324 @@
|
||||
# Ethernet over a radio mesh
|
||||
|
||||
## The idea
|
||||
|
||||
Small radio devices, e.g. IEEE 802.15.4, sub-GHz or LoRa boards, should run
|
||||
Mongoose as if they were on an ordinary Ethernet LAN: get an IP address over
|
||||
DHCP, answer HTTP, talk MQTT. No 6LoWPAN, no Thread, no special stack.
|
||||
|
||||
To do that, the radio mesh looks like an Ethernet switch to Mongoose.
|
||||
One or more bridges connect the radio network to the wired LAN: a bridge has
|
||||
an Ethernet port and a radio. A node in range of a bridge talks to it
|
||||
directly; a node out of range reaches a bridge through other nodes, which
|
||||
forward its frames. That makes the radio network a mesh. With several
|
||||
bridges, each node uses one of them, e.g. the nearest; the mesh covers a
|
||||
bigger area, and keeps working if a bridge goes down. A PC on the LAN sees
|
||||
the radio nodes as regular hosts on the same LAN.
|
||||
|
||||
```
|
||||
PC --- LAN --- bridge ~~~ radio nodes
|
||||
```
|
||||
|
||||
## Two parts plug in
|
||||
|
||||
- `struct mg_radio` describes frame size, bitrate, airtime overhead, signal
|
||||
parameters and channel-access timings.
|
||||
- `struct mg_mesh` has `init`, `tick`, `free`, `tx` and `rx` callbacks. The most important
|
||||
one is the `tick()` callback which is called with a microsecond timestamp,
|
||||
obtained from `mg_micros()`, and handles routing and mesh management.
|
||||
The mesh queues and fragments Ethernet frames and reassembles incoming radio
|
||||
frames. Driver `tx()` queues Ethernet into the mesh; `rx()` returns a
|
||||
reassembled Ethernet frame. A private callback transmits one radio frame.
|
||||
|
||||
The default slotted mesh in `../../src/l2_mesh.c` uses the bridge as a coordinator.
|
||||
It opens windows for new nodes to join and gives joined nodes DATA turns
|
||||
in round-robin order.
|
||||
|
||||
Mongoose calls driver's `poll()`. Radio driver calls `mg_micros()` to get
|
||||
the current timestamp and calls `mg_mesh :: tick()`.
|
||||
|
||||
The goal is to swap radios and mesh strategies without changing application
|
||||
code. The slotted star deliberately omits radio ACKs to avoid
|
||||
the extra airtime and radio turnaround. Other strategies may use them.
|
||||
|
||||
## Why a simulator
|
||||
|
||||
Real boards are slow to test with, and you can't put 500 of them on a desk.
|
||||
The simulator runs a whole network in one process: every node is a real
|
||||
Mongoose instance, only the radio and the air between nodes are emulated.
|
||||
That lets us try mesh strategies with many nodes, and measure how long it
|
||||
takes nodes to join, get an address, and answer a request.
|
||||
|
||||
## Virtual time
|
||||
|
||||
The simulator exposes its virtual clock through `mg_micros()`.
|
||||
TCP/IP reads the same clock through `mg_millis()`, rounded down to milliseconds.
|
||||
Nothing waits for real time. `-t SEC` sets the duration, 10 minutes by default.
|
||||
|
||||
Radio channel checks, transmission starts and ends run at their scheduled
|
||||
microsecond timestamps. The clock jumps to the earliest radio event, mesh
|
||||
deadline, or periodic stack poll.
|
||||
|
||||
`STEP_MICROSECONDS` is 1000: periodic polling services Mongoose's internal
|
||||
stack timers every millisecond. Packet delivery triggers immediate work:
|
||||
the simulator repeatedly polls DHCP, the tester, bridges and nodes at the
|
||||
same virtual timestamp until that work drains. Ethernet forwarding and
|
||||
radio receive processing therefore do not wait for the periodic tick.
|
||||
Internal timer handling can still be delayed by up to about 1 ms.
|
||||
|
||||
## The setup
|
||||
|
||||
```
|
||||
DHCP server ---------+
|
||||
10.0.0.1 |
|
||||
02:01:00:00:00:00 +-- LAN ---- bridge ~~~ nodes
|
||||
|
|
||||
test node --------+
|
||||
10.0.0.2
|
||||
02:02:00:00:00:00
|
||||
```
|
||||
|
||||
The LAN is assumed infinitely fast, see "How frames move on the LAN" below.
|
||||
Two nodes are on it:
|
||||
|
||||
- The DHCP server, 10.0.0.1, MAC 02:01:00:00:00:00, gives each node an
|
||||
address made from its MAC: 02:00:00:X:Y:Z gets 10.X+1.Y.Z, so node
|
||||
addresses never clash with the LAN nodes' 10.0.0.x. It also answers
|
||||
ARP requests for its advertised gateway IP, 10.0.0.1, so nodes can resolve
|
||||
the gateway MAC and stop retrying. It does not implement IP routing,
|
||||
and does not have a full Mongoose instance
|
||||
- The test node, 10.0.0.2, MAC 02:02:00:00:00:00, is a Mongoose
|
||||
instance. It waits for the mesh to settle, 60 seconds by default
|
||||
(`-w SEC`), then sends `GET /` to each node in turn, one at a time, at the
|
||||
node's own IP address. It wraps back to the first node and keeps querying
|
||||
until five seconds before the end, when it stops starting new requests.
|
||||
An in-flight request may still complete or time out during the remaining
|
||||
time. Runs lasting five seconds or less start no requests. A node with
|
||||
no address, a connection error, or a request exceeding 10 seconds counts
|
||||
as failed. The summary reports successful and failed attempts, plus
|
||||
minimum, mean and maximum response times for successful requests. A
|
||||
request still pending at the end is not included in those counts.
|
||||
|
||||
Bridges are on the LAN too, and get every frame, as if in promiscuous mode:
|
||||
a frame for one of their radio nodes isn't addressed to the bridge itself.
|
||||
A bridge forwards every frame from the LAN to the radio and every frame
|
||||
from the radio to the LAN.
|
||||
|
||||
Bridges and radio nodes get MACs 02:00:00:xx:xx:xx, numbered from 1 in
|
||||
command-line order: 02:00:00:00:00:01, 02:00:00:00:00:02, and so on.
|
||||
|
||||
### How frames move on the LAN
|
||||
|
||||
Every LAN node, the DHCP server, the test node and each bridge, has a
|
||||
LAN interface, `lif`: a `struct mg_tcpip_if` with a receive queue. All of
|
||||
them use the same driver, `s_lan_driver`. There are no cables and no switch:
|
||||
|
||||
- Sending. A node sends a frame with its driver's `tx`, which is
|
||||
`lan_tx()`. It looks at the destination MAC and copies the frame into the
|
||||
receive queues of the nodes it is for:
|
||||
- a broadcast or multicast goes to every node but the sender;
|
||||
- a unicast goes to the node with that MAC;
|
||||
- bridges get every frame, as a real bridge in promiscuous mode would:
|
||||
a frame for a radio node is addressed to the node, not to its bridge.
|
||||
- Receiving. Each node takes the frames from its own queue when
|
||||
the main loop polls it:
|
||||
- the test node runs Mongoose, which reads the queue by itself, as it
|
||||
would with a real Ethernet driver that queues frames from an interrupt;
|
||||
- the DHCP server reads it in `dhcp_poll()`, and replies by sending
|
||||
through its own `lif`;
|
||||
- bridges read it in `bridges_poll()`, and forward it to the radio.
|
||||
|
||||
Enqueuing marks immediate work pending. The main loop drains that work
|
||||
without advancing virtual time, so the LAN has no simulated propagation,
|
||||
serialization or polling delay. Sending never calls receiver code directly;
|
||||
replies are processed by subsequent polls at the same timestamp.
|
||||
|
||||
For example, a node asking for an address:
|
||||
|
||||
1. The node's DHCP DISCOVER crosses the radio to a bridge, which sends it
|
||||
out of its `lif`. It is a broadcast, so `lan_tx()` puts it into the
|
||||
queues of the DHCP server, the test node and the other bridges.
|
||||
2. On its next poll, the DHCP server reads it and sends an OFFER, also a
|
||||
broadcast, which lands in the test node's and the bridges' queues.
|
||||
3. On their next poll, the bridges read the OFFER and send it over the
|
||||
radio to the node. The test node's Mongoose drops it: the test node
|
||||
has a fixed address and no DHCP client.
|
||||
|
||||
### How frames move on the radio
|
||||
|
||||
The simulator keeps one accepted transmission per sender in a singly linked
|
||||
list. A busy sender's driver returns zero, so the mesh retains the fragment
|
||||
until the driver accepts it. Different senders can still overlap on air.
|
||||
|
||||
1. A sender waits a random number of backoff slots, then listens for
|
||||
`cca_us` to check whether the channel was idle throughout that interval.
|
||||
2. If busy, it increases its backoff exponent up to `backoff_max`, waits
|
||||
again, and repeats. There is no channel-access attempt limit yet.
|
||||
3. If idle, it schedules transmission after `turnaround_us`. Another sender
|
||||
can also pass its check before either starts: the channel check does not
|
||||
reserve the air.
|
||||
4. Transmission takes `(len + overhead) * 8 / bitrate` seconds, rounded up
|
||||
to a microsecond. Overlapping transmissions are both marked collided.
|
||||
Channel checks at the same timestamp run before transmission starts,
|
||||
so list order cannot make one simultaneous check win.
|
||||
5. At the end, a collided frame is dropped. Otherwise it is queued for all
|
||||
other nodes for broadcast/multicast, or the matching node for unicast.
|
||||
Every other bridge receives it too. Receive processing happens at the
|
||||
same virtual timestamp.
|
||||
|
||||
A receiver drops a frame if any part of that frame overlaps its own channel
|
||||
backoff, sensing, turnaround or transmission interval, even if it is
|
||||
available again when the frame ends.
|
||||
The radio summary reports total sent and collided frames, and lost receptions.
|
||||
Losses include range or random loss, plus missed unicast receptions when the
|
||||
destination was transmitting.
|
||||
|
||||
The 802.15.4 definition uses 250 kbit/s, 8 bytes overhead, 320 us backoff
|
||||
slots, 128 us channel sensing and 100 us turnaround. The initial backoff
|
||||
exponent is 3 (0–7 slots), increasing to 5 (0–31 slots) on busy checks.
|
||||
After transmitting, a sender waits the radio's inter-frame spacing,
|
||||
`mg_radio::ifs_us`, 640 us for 802.15.4, before starting its next frame's
|
||||
backoff. This wait applies even if the frame collided or the sender's
|
||||
queue was temporarily empty. Reception remains enabled during the
|
||||
inter-frame wait.
|
||||
The backoff generator is seeded from the wall clock.
|
||||
|
||||
The mesh limits each transmission to 125 bytes plus the 2-byte FCS.
|
||||
Overhead and inter-frame spacing apply to each fragment.
|
||||
A receiver must be within `-range METRES` of the sender (default 185 m).
|
||||
`-loss PERCENT` adds independent random loss for each in-range reception
|
||||
(default 0%). The radio summary counts range and random losses together.
|
||||
Collisions and channel sensing still apply across the whole simulated air.
|
||||
|
||||
## Files
|
||||
|
||||
| File | What |
|
||||
|------|------|
|
||||
| `../../src/l2_mesh.h`, `../../src/l2_mesh.c` | `struct mg_radio`, `struct mg_mesh`, shared node initialization, and IEEE 802.15.4 radio |
|
||||
| `mesh_a.c` | Alternative unslotted star strategy |
|
||||
| `simulator.h`, `simulator.c` | The simulator |
|
||||
| `Makefile` | Builds and runs the simulator |
|
||||
|
||||
## Where we are
|
||||
|
||||
The slotted strategy uses direct radio hops between nodes and a bridge.
|
||||
The simulator models airtime, half-duplex reception and collisions.
|
||||
|
||||
- Frames outside reception range are not received, so nodes out of a bridge's
|
||||
range never join. But collisions and channel sensing are still global:
|
||||
every overlap is a collision, wherever the receiver is, and a busy
|
||||
channel is busy for everyone.
|
||||
- Mesh routing remains unimplemented: bridge selection, forwarding through other
|
||||
nodes and recovery are not implemented.
|
||||
|
||||
Next steps include distance-aware collision and channel sensing, mesh
|
||||
routing and bridge filtering.
|
||||
|
||||
## Strategy A — unslotted (simulator default)
|
||||
|
||||
Each sender contends for the channel using CSMA. The mesh queues radio
|
||||
fragments and submits them one at a time. The receiver reassembles bursts
|
||||
per sender, and the bridge forwards Ethernet frames between LAN and radio.
|
||||
|
||||
## Default strategy — slotted mesh
|
||||
|
||||
One bridge coordinates the network and starts with an empty member list.
|
||||
It broadcasts a JOIN beacon to open a 3 ms window. Unjoined nodes choose
|
||||
windows at random and add up to 640 µs of jitter before sending their JOIN
|
||||
request with their Ethernet MAC to the bridge. The bridge adds each new
|
||||
radio address and its Ethernet MAC to the head of its member list.
|
||||
|
||||
After the JOIN window, the bridge grants each member a DATA turn in list
|
||||
order, then opens another JOIN window. The bridge advances as soon as it
|
||||
reassembles a valid uplink. If the reply is missing, the turn times out
|
||||
after allowing for the downlink and a maximum-size uplink burst, including
|
||||
inter-frame spacing. A member must answer every turn,
|
||||
with an empty `DATA_UP` if it has nothing to send. A member that misses 3
|
||||
turns in a row is removed. With no
|
||||
members, the bridge opens JOIN windows back to back. A node recognizes its
|
||||
first `DATA_DOWN` as
|
||||
confirmation that it joined.
|
||||
|
||||
Slotted nodes have a separate TCP/IP interface that reports link up after
|
||||
the first grant. Each mesh interface has one 2 KiB TX queue of split
|
||||
radio bursts. The bridge shares that queue among members and sends the
|
||||
head burst in its destination's turn. It directs DHCP replies by the
|
||||
client MAC in `chaddr` and keeps one copy of other multicast and broadcast
|
||||
bursts until each member has had its turn. A frame that does not fit is
|
||||
rejected in full. The hardware and simulator drivers accept one raw frame
|
||||
at a time. JOIN messages fit in a single radio frame. A DATA
|
||||
burst carries one Ethernet frame across up to 14 radio frames. Each
|
||||
fragment has a 9-byte short-address MAC header, a one-byte type, and a
|
||||
byte containing the fragment index and final index in the high and low
|
||||
nibbles. Empty DATA messages use a single header-only frame. The node
|
||||
starts its uplink after receiving the whole downlink, and the bridge
|
||||
forwards only complete uplinks to the LAN.
|
||||
|
||||
## Running
|
||||
|
||||
```sh
|
||||
make # 100x100 m, a bridge, 11x11 nodes
|
||||
make X=200 Y=50 ROWS=2 COLS=8 # Another area and grid
|
||||
```
|
||||
|
||||
`make` puts a bridge in the middle of the area, and a node at every corner
|
||||
of a `ROWS` x `COLS` grid over it. The simulator itself takes:
|
||||
|
||||
```
|
||||
./mesh_sim -area X,Y,Z -bridge X,Y,Z -node X,Y,Z ... -t SEC -w SEC -m A|default -range METRES -loss PERCENT
|
||||
```
|
||||
|
||||
Positions are in metres; `X,Y` means `Z` is 0. Repeat `-bridge` and `-node`
|
||||
for more.
|
||||
|
||||
## Hardware
|
||||
|
||||
The FRDM-RW612 example is the Ethernet bridge. The XIAO MG24 and
|
||||
FRDM-MCXW71 examples are radio clients. All three use the slotted mesh.
|
||||
|
||||
```sh
|
||||
make -C tutorials/nxp/frdm-rw612/bridge
|
||||
make -C tutorials/silabs/xiao-mg24/minimal
|
||||
make -C tutorials/nxp/frdm-mcxw71/minimal
|
||||
```
|
||||
|
||||
Use each tutorial's `flash` target. Connect the
|
||||
bridge to an Ethernet LAN with a DHCP server. Find the client's assigned
|
||||
IP in its serial log, then send an HTTP GET to that IP from the LAN.
|
||||
|
||||
All three boards use channel 15 and PAN `0xabcd` by default. Each board derives its radio address from its Ethernet MAC.
|
||||
MG24 is a node only; its radio address is derived from its MAC.
|
||||
|
||||
The drivers in `src/drivers` send and receive raw 802.15.4 frames.
|
||||
They use the same slotted mesh implementation as the simulator.
|
||||
The board driver is initialized by `mg_mgr_init()`. The RW612 bridge
|
||||
has its own `bridge/main.c`, which initializes and polls the two drivers
|
||||
directly, without an event manager or local IP stack.
|
||||
|
||||
## Hardware integration
|
||||
|
||||
The default mesh and radio drivers are included in `mongoose.c` and `mongoose.h`.
|
||||
Radio drivers live in `src/drivers/`: `mg24`, `mcxw71`, `rw612w`,
|
||||
and `nrf52`. MG24 driver data contains its mesh state and `strategy`; it has
|
||||
no bridge LAN interface.
|
||||
A NULL strategy selects `mg_mesh_default`, named `"default"`; assigning another
|
||||
`const struct mg_mesh *` overrides it. Hardware callbacks transmit and receive
|
||||
raw radio frames; the selected strategy owns joining, scheduling and Ethernet
|
||||
fragmentation. Driver initialization goes through `mg_tcpip_init()`.
|
||||
|
||||
Board examples are under `tutorials/nxp/frdm-rw612/bridge`,
|
||||
`tutorials/nxp/frdm-mcxw71/minimal`, `tutorials/silabs/xiao-mg24/minimal`, and
|
||||
`tutorials/nordic/xiao-52840/minimal`. Their HALs supply `mg_micros()`.
|
||||
|
||||
The simulator and all radio drivers keep the hardware driver installed throughout the
|
||||
interface lifetime. Mesh state belongs to `driver_data`. Driver `tx()` queues
|
||||
Ethernet frames; `poll()` feeds raw received frames to the mesh and calls
|
||||
strategy `tick()`. The strategy sends scheduled fragments through the driver's
|
||||
private `radio_tx` callback. Driver `rx()` returns a reassembled Ethernet frame
|
||||
using the stack's existing receive buffer. There is no mesh polling timer,
|
||||
copied radio interface, or mesh-specific change in `net_builtin.*`.
|
||||
The simulator explicitly frees its mesh state after `mg_mgr_free()`; board
|
||||
drivers keep their state for the firmware lifetime. MG24, MCXW71 and nRF52 are
|
||||
radio nodes. The RW612 bridge application owns Ethernet initialization, its RX
|
||||
queue, promiscuous mode and link polling, and attaches the LAN to the mesh.
|
||||
The RW612W driver owns only the radio interface.
|
||||
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
// Radio header followed by type and fragment index/last index.
|
||||
#define MGI_FRAME_HDR_LEN (MG_802154_HEADER_LEN + 2)
|
||||
|
||||
#define MGI_ETH_MAX 1514
|
||||
#define MGI_TX_DST_LEN 2
|
||||
|
||||
#define MGI_RX_TIMEOUT_US 1000000
|
||||
#define MGI_TX_QUEUE_SIZE 4096
|
||||
#define MGI_DATA_TAG \
|
||||
0x80u // High bit identifies DATA, low seven bits tag a burst
|
||||
|
||||
struct mgi_mesh_a_rx {
|
||||
struct mgi_mesh_a_rx *next;
|
||||
uint64_t expires;
|
||||
uint16_t src;
|
||||
uint8_t tag, index, last;
|
||||
size_t len;
|
||||
uint8_t buf[MGI_ETH_MAX];
|
||||
};
|
||||
|
||||
struct mgi_mesh_a_data {
|
||||
struct mg_mesh_node *node;
|
||||
struct mg_tcpip_if *ethernet;
|
||||
struct mgi_mesh_a_rx *rx;
|
||||
uint8_t tag;
|
||||
struct mg_queue tx;
|
||||
char tx_buf[MGI_TX_QUEUE_SIZE];
|
||||
size_t tx_offset;
|
||||
uint64_t tx_at;
|
||||
};
|
||||
|
||||
static const struct mg_radio *const s_radio = &mg_radio_802154;
|
||||
|
||||
static uint64_t mgi_mesh_a_airtime(size_t len) {
|
||||
const struct mg_radio *radio = &mg_radio_802154;
|
||||
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
|
||||
radio->bitrate;
|
||||
}
|
||||
|
||||
// Each queue record holds a destination and a complete burst.
|
||||
static size_t mgi_mesh_a_queue(struct mg_mesh_node *node, struct mg_queue *q,
|
||||
const void *buf, size_t len, uint16_t dst, uint8_t type) {
|
||||
const struct mg_radio *radio = &mg_radio_802154;
|
||||
const uint8_t *eth = (const uint8_t *) buf;
|
||||
size_t hdr = MGI_FRAME_HDR_LEN, cap = radio->max_frame_size - hdr;
|
||||
size_t frames, size, i;
|
||||
char *record;
|
||||
uint8_t *out;
|
||||
if (len < 14 || len > MGI_ETH_MAX) return 0;
|
||||
frames = (len + cap - 1) / cap;
|
||||
size = MGI_TX_DST_LEN + len + frames * (1 + hdr);
|
||||
if (mg_queue_next(q, NULL) == 0) mg_queue_init(q, q->buf, q->size);
|
||||
if (mg_queue_book(q, &record, size) < size) return 0;
|
||||
out = (uint8_t *) record;
|
||||
*out++ = (uint8_t) (dst >> 8);
|
||||
*out++ = (uint8_t) dst;
|
||||
for (i = 0; i < frames; i++) {
|
||||
size_t chunk = len - i * cap;
|
||||
if (chunk > cap) chunk = cap;
|
||||
*out++ = (uint8_t) (hdr + chunk);
|
||||
radio->print_hdr(radio, out, dst, node->radio_address);
|
||||
out[radio->header_len] = type;
|
||||
out[radio->header_len + 1] = (uint8_t) (i << 4 | (frames - 1));
|
||||
memcpy(out + hdr, eth + i * cap, chunk);
|
||||
out += hdr + chunk;
|
||||
}
|
||||
mg_queue_add(q, size);
|
||||
return len;
|
||||
}
|
||||
|
||||
static size_t mgi_mesh_a_send(struct mgi_mesh_a_data *data, const void *buf,
|
||||
size_t len) {
|
||||
uint8_t tag = (uint8_t) ((data->tag + 1) & 0x7f);
|
||||
size_t queued = mgi_mesh_a_queue(data->node, &data->tx, buf, len, 0xffffu,
|
||||
(uint8_t) (MGI_DATA_TAG | tag));
|
||||
if (queued != 0) data->tag = tag;
|
||||
return queued;
|
||||
}
|
||||
|
||||
static void mgi_mesh_a_tx(struct mgi_mesh_a_data *data, uint64_t now) {
|
||||
struct mg_tcpip_if *radio = data->node->radio;
|
||||
char *record;
|
||||
size_t size = mg_queue_next(&data->tx, &record);
|
||||
if (size == 0) return;
|
||||
if (now >= data->tx_at) {
|
||||
const uint8_t *part = (const uint8_t *) record + data->tx_offset;
|
||||
size_t len = part[0];
|
||||
if (now >= data->node->tx_at &&
|
||||
data->node->tx(radio, part + 1, len) == len) {
|
||||
data->tx_offset += len + 1;
|
||||
if (data->tx_offset == size) {
|
||||
mg_queue_del(&data->tx, size);
|
||||
data->tx_offset = MGI_TX_DST_LEN;
|
||||
}
|
||||
data->tx_at = now + mgi_mesh_a_airtime(len) + s_radio->ifs_us;
|
||||
} else {
|
||||
data->tx_at = now + s_radio->slot_us;
|
||||
}
|
||||
}
|
||||
if (mg_queue_next(&data->tx, NULL) != 0) data->node->next_tick = data->tx_at;
|
||||
}
|
||||
|
||||
static size_t mgi_mesh_a_enqueue(struct mg_mesh_node *node, const void *buf,
|
||||
size_t len) {
|
||||
return mgi_mesh_a_send(node->data, buf, len);
|
||||
}
|
||||
|
||||
static void mgi_mesh_a_deliver(struct mgi_mesh_a_data *data, uint8_t *buf,
|
||||
size_t len) {
|
||||
struct mg_tcpip_if *lan = data->node->lan;
|
||||
if (len < 14) return;
|
||||
if (lan != NULL) {
|
||||
lan->driver->tx(buf, len, lan);
|
||||
} else {
|
||||
struct mg_tcpip_if *ifp = data->node->radio;
|
||||
if (data->node->rx_len != 0 || len > ifp->recv_queue.size) {
|
||||
ifp->ndrop++;
|
||||
} else {
|
||||
memcpy(ifp->recv_queue.buf, buf, len);
|
||||
data->node->rx_len = len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mgi_mesh_a_rx_free(struct mgi_mesh_a_data *data,
|
||||
struct mgi_mesh_a_rx **p) {
|
||||
struct mgi_mesh_a_rx *r = *p;
|
||||
*p = r->next;
|
||||
data->node->ram_used -= sizeof(*r);
|
||||
free(r);
|
||||
}
|
||||
|
||||
static void mgi_mesh_a_receive(struct mgi_mesh_a_data *data, uint8_t *frame,
|
||||
size_t len, uint64_t now) {
|
||||
struct mgi_mesh_a_rx **p = &data->rx, *r;
|
||||
size_t cap = s_radio->max_frame_size - MGI_FRAME_HDR_LEN, chunk;
|
||||
uint16_t dst, src;
|
||||
uint8_t tag, index, last;
|
||||
if (len <= MGI_FRAME_HDR_LEN || len > s_radio->max_frame_size ||
|
||||
s_radio->parse_hdr(s_radio, frame, len, &dst, &src) !=
|
||||
s_radio->header_len ||
|
||||
(dst != 0xffffu && dst != data->node->radio_address))
|
||||
return;
|
||||
tag = frame[MGI_FRAME_HDR_LEN - 2];
|
||||
index = frame[MGI_FRAME_HDR_LEN - 1] >> 4;
|
||||
last = frame[MGI_FRAME_HDR_LEN - 1] & 15;
|
||||
chunk = len - MGI_FRAME_HDR_LEN;
|
||||
if ((tag & MGI_DATA_TAG) == 0 || index > last ||
|
||||
last > (MGI_ETH_MAX - 1) / cap || (index < last && chunk != cap))
|
||||
return;
|
||||
if (index == 0 &&
|
||||
(chunk < 14 ||
|
||||
(data->node->lan == NULL && (frame[MGI_FRAME_HDR_LEN] & 1) == 0 &&
|
||||
memcmp(frame + MGI_FRAME_HDR_LEN, data->ethernet->mac, 6) != 0)))
|
||||
return;
|
||||
while (*p != NULL && (*p)->src != src) p = &(*p)->next;
|
||||
if (index == 0) {
|
||||
if (*p != NULL) mgi_mesh_a_rx_free(data, p);
|
||||
if (last == 0) {
|
||||
mgi_mesh_a_deliver(data, frame + MGI_FRAME_HDR_LEN, chunk);
|
||||
return;
|
||||
}
|
||||
r = calloc(1, sizeof(*r));
|
||||
if (r == NULL) return;
|
||||
data->node->ram_used += sizeof(*r);
|
||||
if (data->node->ram_peak < data->node->ram_used)
|
||||
data->node->ram_peak = data->node->ram_used;
|
||||
r->next = *p;
|
||||
*p = r;
|
||||
r->src = src;
|
||||
r->tag = tag;
|
||||
r->last = last;
|
||||
}
|
||||
r = *p;
|
||||
if (r == NULL || r->tag != tag) return;
|
||||
if (r->index != index || r->last != last || chunk > sizeof(r->buf) - r->len) {
|
||||
mgi_mesh_a_rx_free(data, p);
|
||||
return;
|
||||
}
|
||||
memcpy(r->buf + r->len, frame + MGI_FRAME_HDR_LEN, chunk);
|
||||
r->len += chunk;
|
||||
r->index++;
|
||||
r->expires = now + MGI_RX_TIMEOUT_US;
|
||||
if (index == last) {
|
||||
mgi_mesh_a_deliver(data, r->buf, r->len);
|
||||
mgi_mesh_a_rx_free(data, p);
|
||||
}
|
||||
}
|
||||
|
||||
static void mgi_mesh_a_input(struct mg_mesh_node *node, void *buf, size_t len) {
|
||||
uint64_t ready = node->now + node->strategy->radio->turnaround_us;
|
||||
if (node->tx_at < ready) node->tx_at = ready;
|
||||
node->next_tick = node->now;
|
||||
mgi_mesh_a_receive(node->data, buf, len, node->now);
|
||||
}
|
||||
|
||||
static void mgi_mesh_a_tick(struct mg_mesh_node *node, uint64_t now) {
|
||||
struct mgi_mesh_a_data *data = node->data;
|
||||
struct mgi_mesh_a_rx **p = &data->rx;
|
||||
size_t len;
|
||||
node->now = now;
|
||||
node->next_tick = (uint64_t) -1;
|
||||
while (*p != NULL) {
|
||||
if ((*p)->expires <= now)
|
||||
mgi_mesh_a_rx_free(data, p);
|
||||
else
|
||||
p = &(*p)->next;
|
||||
}
|
||||
if (node->lan != NULL) {
|
||||
struct mg_queue *q = &node->lan->recv_queue;
|
||||
char *buf;
|
||||
while ((len = mg_queue_next(q, &buf)) > 0) {
|
||||
mgi_mesh_a_send(data, buf, len);
|
||||
mg_queue_del(q, len);
|
||||
}
|
||||
}
|
||||
mgi_mesh_a_tx(data, now);
|
||||
for (p = &data->rx; *p != NULL; p = &(*p)->next) {
|
||||
if ((*p)->expires < node->next_tick) node->next_tick = (*p)->expires;
|
||||
}
|
||||
}
|
||||
|
||||
static void mgi_mesh_a_free(struct mg_mesh_node *node) {
|
||||
struct mgi_mesh_a_data *data = node->data;
|
||||
if (data == NULL) return;
|
||||
while (data->rx != NULL) mgi_mesh_a_rx_free(data, &data->rx);
|
||||
free(data);
|
||||
node->data = NULL;
|
||||
}
|
||||
|
||||
static bool mgi_mesh_a_init(struct mg_mesh_node *node,
|
||||
struct mg_tcpip_if *radio, struct mg_tcpip_if *lan,
|
||||
uint16_t radio_address) {
|
||||
struct mgi_mesh_a_data *data = calloc(1, sizeof(*data));
|
||||
memset(node, 0, sizeof(*node));
|
||||
node->radio = radio;
|
||||
node->lan = lan;
|
||||
node->radio_address = radio_address;
|
||||
node->next_tick = (uint64_t) -1;
|
||||
node->ram_used = node->ram_peak = sizeof(*node);
|
||||
if (data == NULL) return false;
|
||||
node->ram_used += sizeof(*data);
|
||||
node->ram_peak = node->ram_used;
|
||||
data->node = node;
|
||||
data->tx_offset = MGI_TX_DST_LEN;
|
||||
node->data = data;
|
||||
mg_queue_init(&data->tx, data->tx_buf, sizeof(data->tx_buf));
|
||||
data->ethernet = radio;
|
||||
node->joined = lan == NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct mg_mesh mg_mesh_a = {
|
||||
"A", mgi_mesh_a_init, mgi_mesh_a_tick, mgi_mesh_a_free,
|
||||
mgi_mesh_a_enqueue, mgi_mesh_a_input, false, false, &mg_radio_802154,
|
||||
};
|
||||
@@ -0,0 +1,776 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Radio mesh simulator. Bridges and radio nodes have positions in an area. Time
|
||||
// is virtual: see mg_millis(). On the LAN: a DHCP server, and a test node that
|
||||
// sends HTTP requests to the nodes
|
||||
|
||||
#include "simulator.h"
|
||||
|
||||
#define STEP_MICROSECONDS 1000 // Periodic stack timer polling only
|
||||
|
||||
static const struct mg_mesh *s_mesh = &mg_mesh_default;
|
||||
static struct simulator s_sim; // The simulator
|
||||
|
||||
// Mongoose's clock: virtual time. A minute of network traffic takes far less
|
||||
// than a minute to simulate
|
||||
uint64_t mg_micros(void) {
|
||||
return s_sim.now;
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return mg_micros() / 1000;
|
||||
}
|
||||
|
||||
// Parse "X,Y,Z", or "X,Y" with Z 0. Return false if it is not that
|
||||
static bool parse_pos(const char *s, struct pos *p) {
|
||||
int n = 0; // Characters parsed
|
||||
p->x = p->y = p->z = 0;
|
||||
return (sscanf(s, "%lf,%lf,%lf%n", &p->x, &p->y, &p->z, &n) == 3 ||
|
||||
sscanf(s, "%lf,%lf%n", &p->x, &p->y, &n) == 2) &&
|
||||
s[n] == 0;
|
||||
}
|
||||
|
||||
// Is position p inside the area of sim
|
||||
static bool inside(const struct simulator *sim, const struct pos *p) {
|
||||
return p->x >= 0 && p->x <= sim->area.x && p->y >= 0 && p->y <= sim->area.y &&
|
||||
p->z >= 0 && p->z <= sim->area.z;
|
||||
}
|
||||
|
||||
static void usage(const struct simulator *sim, const char *prog) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s OPTIONS\n"
|
||||
"Positions: X,Y,Z in metres, or X,Y with Z 0\n"
|
||||
" -area X,Y,Z - area, a box, default: %g,%g,%g\n"
|
||||
" -bridge X,Y,Z - a bridge; repeat for more\n"
|
||||
" -node X,Y,Z - a radio node; repeat for more\n"
|
||||
" -t SEC - simulated time, seconds, default: %g\n"
|
||||
" -w SEC - mesh settle time before requests, default: %g\n"
|
||||
" -range METRES - radio range, default: %g\n"
|
||||
" -loss PERCENT - random reception loss, default: %g\n"
|
||||
" -m A|default - mesh strategy, default: default\n",
|
||||
prog, sim->area.x, sim->area.y, sim->area.z,
|
||||
(double) (int64_t) sim->duration / 1e6,
|
||||
(double) (int64_t) sim->tester.wait / 1e6, sim->radio_range,
|
||||
sim->radio_loss);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// The next radio MAC, to mac: 02:00:00:xx:xx:xx, numbered from 1
|
||||
static void next_mac(struct simulator *sim, uint8_t *mac) {
|
||||
uint32_t n = ++sim->last_mac;
|
||||
mac[0] = 2, mac[1] = mac[2] = 0, mac[3] = (uint8_t) (n >> 16);
|
||||
mac[4] = (uint8_t) (n >> 8), mac[5] = (uint8_t) n;
|
||||
}
|
||||
|
||||
// Add a bridge at position s. Return false if s is not a position
|
||||
static bool add_bridge(struct simulator *sim, const char *s) {
|
||||
struct bridge *b = (struct bridge *) calloc(1, sizeof(*b));
|
||||
if (b == NULL || !parse_pos(s, &b->pos)) return free(b), false;
|
||||
if (sim->last_mac >= 0xffffu - 1u) return free(b), false;
|
||||
next_mac(sim, b->rif.mac);
|
||||
b->radio.address = (uint16_t) sim->last_mac;
|
||||
LIST_ADD_TAIL(struct bridge, &sim->bridges, b);
|
||||
sim->num_bridges++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add a radio node at position s. Return false if s is not a position
|
||||
static bool add_node(struct simulator *sim, const char *s) {
|
||||
struct radio_node *n = (struct radio_node *) calloc(1, sizeof(*n));
|
||||
if (n == NULL || !parse_pos(s, &n->pos)) return free(n), false;
|
||||
if (sim->last_mac >= 0xffffu - 1u) return free(n), false;
|
||||
next_mac(sim, n->rif.mac);
|
||||
n->radio.address = (uint16_t) sim->last_mac;
|
||||
LIST_ADD_TAIL(struct radio_node, &sim->nodes, n);
|
||||
sim->num_nodes++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exit if position x is outside the area of sim
|
||||
static void check_inside(const struct simulator *sim, const struct pos *x) {
|
||||
if (!inside(sim, x)) {
|
||||
fprintf(stderr, "%g,%g,%g is outside the area\n", x->x, x->y, x->z);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all bridges and nodes are inside the area of sim
|
||||
static void check_area(const struct simulator *sim) {
|
||||
struct bridge *b;
|
||||
struct radio_node *n;
|
||||
for (b = sim->bridges; b != NULL; b = b->next) check_inside(sim, &b->pos);
|
||||
for (n = sim->nodes; n != NULL; n = n->next) check_inside(sim, &n->pos);
|
||||
}
|
||||
|
||||
#define LAN_QUEUE_SIZE (64 * 1024) // Receive queue of a LAN interface
|
||||
|
||||
// Put frame buf, len bytes, into the receive queue of LAN interface to, if
|
||||
// it is not the sender, from
|
||||
static void lan_put(struct mg_tcpip_if *to, struct mg_tcpip_if *from,
|
||||
const void *buf, size_t len) {
|
||||
if (to != from) {
|
||||
mg_tcpip_qwrite((void *) buf, len, to);
|
||||
s_sim.work_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
// LAN driver, all LAN interfaces share it. The LAN is infinitely fast: tx
|
||||
// puts the frame straight into the receive queues of the interfaces it is
|
||||
// for: all, if broadcast; else the one with the destination MAC. Bridges get
|
||||
// every frame: some are for their radio nodes. Each node takes frames
|
||||
// from its queue when polled, see dhcp_poll() and the mesh bridge callback;
|
||||
// Mongoose does it for the test node
|
||||
static size_t lan_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
const uint8_t *dst = (const uint8_t *) buf;
|
||||
bool all = dst[0] & 1; // Broadcast, multicast
|
||||
struct bridge *b;
|
||||
if (all || memcmp(dst, s_sim.dhcp.lif.mac, 6) == 0) {
|
||||
lan_put(&s_sim.dhcp.lif, ifp, buf, len);
|
||||
}
|
||||
if (all || memcmp(dst, s_sim.tester.lif.mac, 6) == 0) {
|
||||
lan_put(&s_sim.tester.lif, ifp, buf, len);
|
||||
}
|
||||
for (b = s_sim.bridges; b != NULL; b = b->next) {
|
||||
lan_put(&b->lif, ifp, buf, len);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool lan_init(struct mg_tcpip_if *ifp) {
|
||||
(void) ifp;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lan_link(struct mg_tcpip_if *ifp, bool s1) {
|
||||
(void) ifp, (void) s1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct mg_tcpip_driver s_lan_driver = {lan_init, lan_tx, NULL, lan_link};
|
||||
|
||||
// Internet checksum of len bytes
|
||||
static uint16_t ip_csum(const uint8_t *buf, size_t len) {
|
||||
uint32_t sum = 0;
|
||||
size_t i;
|
||||
for (i = 0; i + 1 < len; i += 2) sum += (uint32_t) (buf[i] << 8 | buf[i + 1]);
|
||||
if (len & 1) sum += (uint32_t) (buf[len - 1] << 8);
|
||||
while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
|
||||
return (uint16_t) ~sum;
|
||||
}
|
||||
|
||||
// A node's address, from its MAC 02:00:00:X:Y:Z: 10.X+1.Y.Z, so it never
|
||||
// clashes with the LAN nodes' 10.0.0.x
|
||||
static void node_ip(const uint8_t *mac, uint8_t *ip) {
|
||||
ip[0] = 10, ip[1] = (uint8_t) (mac[3] + 1), ip[2] = mac[4], ip[3] = mac[5];
|
||||
}
|
||||
|
||||
// DHCP server, a frame from its queue: a DISCOVER gets an OFFER, a REQUEST
|
||||
// an ACK. Replies are broadcast. The address comes from the client's MAC
|
||||
static void dhcp_rx(struct simulator *sim, const uint8_t *buf, size_t len) {
|
||||
static const uint8_t opts[] = {53, 1, 0, // Message type, set below
|
||||
1, 4, 255, 0, 0, 0, // Mask
|
||||
3, 4, 10, 0, 0, 1, // Router
|
||||
54, 4, 10, 0, 0, 1, // Server
|
||||
51, 4, 0, 0, 14, 16, // Lease, 3600 s
|
||||
255};
|
||||
uint8_t r[14 + 20 + 8 + 240 + sizeof(opts)], type = 0;
|
||||
const uint8_t *dh, *p, *end = buf + len;
|
||||
size_t ihl, rlen = sizeof(r);
|
||||
if ((buf[0] & 1) == 0) return; // Bcast
|
||||
if (len < 34 || buf[12] != 8 || buf[13] != 0 || buf[23] != 17) return; // UDP
|
||||
ihl = (size_t) (buf[14] & 15) * 4, dh = buf + 14 + ihl + 8; // DHCP
|
||||
if (dh + 240 > end || buf[14 + ihl + 2] != 0 || buf[14 + ihl + 3] != 67) {
|
||||
return;
|
||||
}
|
||||
for (p = dh + 240; p + 2 <= end && p[0] != 255; p += p[0] ? p[1] + 2 : 1) {
|
||||
if (p[0] == 53 && p[1] == 1 && p + 3 <= end) type = p[2];
|
||||
}
|
||||
if (type != 1 && type != 3) return; // DISCOVER, REQUEST
|
||||
memset(r, 0, sizeof(r));
|
||||
memset(r, 0xff, 6), memcpy(r + 6, sim->dhcp.lif.mac, 6),
|
||||
r[12] = 8; // Ethernet
|
||||
r[14] = 0x45, r[16] = (uint8_t) ((rlen - 14) >> 8); // IPv4
|
||||
r[17] = (uint8_t) (rlen - 14), r[22] = 64, r[23] = 17;
|
||||
r[26] = 10, r[29] = 1, memset(r + 30, 0xff, 4); // 10.0.0.1 -> bcast
|
||||
r[24] = (uint8_t) (ip_csum(r + 14, 20) >> 8);
|
||||
r[25] = (uint8_t) ip_csum(r + 14, 20);
|
||||
r[35] = 67, r[37] = 68; // UDP, csum 0: none
|
||||
r[38] = (uint8_t) ((rlen - 34) >> 8), r[39] = (uint8_t) (rlen - 34);
|
||||
r[42] = 2, r[43] = 1, r[44] = 6; // DHCP: reply, Ethernet
|
||||
memcpy(r + 46, dh + 4, 4); // xid
|
||||
memcpy(r + 52, dh + 10, 2); // flags
|
||||
node_ip(dh + 28, r + 58); // yiaddr
|
||||
r[62] = 10, r[65] = 1; // siaddr
|
||||
memcpy(r + 70, dh + 28, 16); // chaddr
|
||||
r[278] = 99, r[279] = 130, r[280] = 83, r[281] = 99; // Magic
|
||||
memcpy(r + 282, opts, sizeof(opts));
|
||||
r[284] = type == 1 ? 2 : 5; // OFFER, ACK
|
||||
sim->dhcp.lif.driver->tx(r, rlen, &sim->dhcp.lif);
|
||||
}
|
||||
|
||||
// The DHCP node also owns the advertised gateway IP, 10.0.0.1.
|
||||
static bool dhcp_arp(struct simulator *sim, const uint8_t *f, size_t n) {
|
||||
static const uint8_t header[] = {0, 1, 8, 0, 6, 4, 0, 1};
|
||||
static const uint8_t ip[] = {10, 0, 0, 1};
|
||||
uint8_t reply[60];
|
||||
if (n < 42 || f[12] != 8 || f[13] != 6 ||
|
||||
memcmp(f + 14, header, sizeof(header)) != 0 ||
|
||||
memcmp(f + 38, ip, sizeof(ip)) != 0)
|
||||
return false;
|
||||
memset(reply, 0, sizeof(reply));
|
||||
memcpy(reply, f + 6, 6);
|
||||
memcpy(reply + 6, sim->dhcp.lif.mac, 6);
|
||||
memcpy(reply + 12, f + 12, 8);
|
||||
reply[21] = 2;
|
||||
memcpy(reply + 22, sim->dhcp.lif.mac, 6);
|
||||
memcpy(reply + 28, ip, sizeof(ip));
|
||||
memcpy(reply + 32, f + 22, 10);
|
||||
sim->dhcp.lif.driver->tx(reply, sizeof(reply), &sim->dhcp.lif);
|
||||
return true;
|
||||
}
|
||||
|
||||
// DHCP server's work: take the frames from its queue
|
||||
static void dhcp_poll(struct simulator *sim) {
|
||||
char *buf;
|
||||
size_t len;
|
||||
while ((len = mg_queue_next(&sim->dhcp.lif.recv_queue, &buf)) > 0) {
|
||||
if (!dhcp_arp(sim, (const uint8_t *) buf, len)) {
|
||||
dhcp_rx(sim, (const uint8_t *) buf, len);
|
||||
}
|
||||
mg_queue_del(&sim->dhcp.lif.recv_queue, len);
|
||||
}
|
||||
}
|
||||
|
||||
// The request on the way finished: ok, or failed
|
||||
static void tester_done(struct lan_tester *t, uint64_t now, bool ok) {
|
||||
double ms = (double) (int64_t) (now - t->start) / 1000;
|
||||
if (ok) {
|
||||
t->ok++, t->sum += ms;
|
||||
if (t->ok == 1 || ms < t->min) t->min = ms;
|
||||
if (ms > t->max) t->max = ms;
|
||||
} else {
|
||||
t->failed++;
|
||||
}
|
||||
if (t->c != NULL) t->c->is_closing = 1, t->c->fn_data = NULL;
|
||||
t->c = NULL;
|
||||
s_sim.work_pending = true;
|
||||
}
|
||||
|
||||
// HTTP client: GET /, the reply ends the request
|
||||
static void tester_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct lan_tester *t = (struct lan_tester *) c->fn_data;
|
||||
if (t == NULL) return; // Done with
|
||||
if (ev == MG_EV_CONNECT) {
|
||||
mg_printf(c, "GET / HTTP/1.0\r\n\r\n");
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
tester_done(t, s_sim.now, true);
|
||||
} else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
|
||||
tester_done(t, s_sim.now, false);
|
||||
}
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
// Test node's work: Mongoose, then requests, one at a time, once the mesh
|
||||
// had time to settle. Cycle through nodes until five seconds before the end.
|
||||
static void tester_poll(struct simulator *sim) {
|
||||
struct radio_node *n;
|
||||
char url[64];
|
||||
mg_mgr_poll(&sim->tester.mgr, 0);
|
||||
if (sim->tester.c != NULL && sim->now - sim->tester.start >
|
||||
(uint64_t) sim->request_timeout_ms * 1000) {
|
||||
tester_done(&sim->tester, sim->now, false);
|
||||
}
|
||||
if (sim->now < sim->tester.wait || sim->tester.c != NULL ||
|
||||
sim->tester.next == NULL || sim->duration <= 5000000U ||
|
||||
sim->now >= sim->duration - 5000000U)
|
||||
return;
|
||||
n = sim->tester.next;
|
||||
sim->tester.next = n->next == NULL ? sim->nodes : n->next;
|
||||
if (n->mgr.ifp == NULL) return;
|
||||
sim->tester.start = sim->now;
|
||||
if (n->mgr.ifp->ip == 0) {
|
||||
// Wait for the next poll rather than endlessly cycling at this time.
|
||||
sim->tester.failed++;
|
||||
return;
|
||||
}
|
||||
mg_snprintf(url, sizeof(url), "http://%M/", mg_print_ip4, &n->mgr.ifp->ip);
|
||||
if ((sim->tester.c = mg_http_connect(&sim->tester.mgr, url, tester_fn,
|
||||
&sim->tester)) == NULL) {
|
||||
tester_done(&sim->tester, sim->now, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up sim: defaults, LAN nodes
|
||||
static void init(struct simulator *sim) {
|
||||
static const uint8_t dmac[6] = {2, 1, 0, 0, 0, 0}, // DHCP server MAC
|
||||
tmac[6] = {2, 2, 0, 0, 0, 0}; // Test node MAC
|
||||
memset(sim, 0, sizeof(*sim));
|
||||
sim->area.x = sim->area.y = 100;
|
||||
sim->radio_range = 185;
|
||||
sim->loss_random = 1;
|
||||
sim->duration = 600 * 1000000U, sim->tester.wait = 60 * 1000000U;
|
||||
sim->request_timeout_ms = 10000;
|
||||
memcpy(sim->dhcp.lif.mac, dmac, 6), sim->dhcp.lif.driver = &s_lan_driver;
|
||||
mg_queue_init(&sim->dhcp.lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
|
||||
LAN_QUEUE_SIZE);
|
||||
memcpy(sim->tester.lif.mac, tmac, 6);
|
||||
sim->tester.lif.ip = MG_IPV4(10, 0, 0, 2),
|
||||
sim->tester.lif.mask = MG_IPV4(255, 0, 0, 0);
|
||||
sim->tester.lif.driver = &s_lan_driver,
|
||||
sim->tester.lif.enable_mac_check = true;
|
||||
sim->tester.lif.recv_queue.size = 128 * 1024;
|
||||
}
|
||||
|
||||
// The driver queues each submitted frame for transmission over the air.
|
||||
// All nodes hear one channel. CCA and turnaround are separate events:
|
||||
// two idle checks can succeed before either node starts transmitting.
|
||||
// Inter-frame spacing is per sender, not a reservation of the shared air.
|
||||
uint64_t radio_airtime(size_t len) {
|
||||
const struct mg_radio *radio = s_mesh->radio;
|
||||
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
|
||||
radio->bitrate;
|
||||
}
|
||||
|
||||
static void radio_schedule(struct simulator *sim, struct radio_frame *f,
|
||||
bool busy) {
|
||||
struct radio_port *port = (struct radio_port *) f->from->driver_data;
|
||||
const struct mg_radio *radio = s_mesh->radio;
|
||||
bool checked = f->state == RADIO_CCA;
|
||||
if (s_mesh->scheduled_tx || !radio->csma || (checked && !busy)) {
|
||||
f->state = RADIO_START;
|
||||
f->due = sim->now + (checked ? radio->turnaround_us : 0);
|
||||
return;
|
||||
}
|
||||
if (!checked) {
|
||||
f->backoff = radio->backoff_min;
|
||||
} else if (f->backoff < radio->backoff_max) {
|
||||
f->backoff++;
|
||||
}
|
||||
if (port->random == 0) {
|
||||
size_t i;
|
||||
port->random = (uint32_t) mg_now();
|
||||
for (i = 0; i < 6; i++) {
|
||||
port->random = port->random * 33u + f->from->mac[i];
|
||||
}
|
||||
if (port->random == 0) port->random = 1;
|
||||
}
|
||||
port->random ^= port->random << 13;
|
||||
port->random ^= port->random >> 17;
|
||||
port->random ^= port->random << 5;
|
||||
f->state = RADIO_BACKOFF;
|
||||
f->due = sim->now + (port->random % (1U << f->backoff)) * radio->slot_us;
|
||||
if (f->due > sim->now) port->rx_off_until = f->due;
|
||||
}
|
||||
|
||||
static size_t radio_send(struct mg_tcpip_if *ifp, const void *buf, size_t len) {
|
||||
struct radio_port *port = (struct radio_port *) ifp->driver_data;
|
||||
struct simulator *sim = port->sim;
|
||||
struct radio_frame *f, **p = &sim->air_head;
|
||||
const struct mg_radio *radio = s_mesh->radio;
|
||||
if (len < radio->header_len || len > sizeof(f->buf) ||
|
||||
len > radio->max_frame_size || port->tx_ready_at > sim->now ||
|
||||
radio->bitrate == 0)
|
||||
return 0;
|
||||
while (*p != NULL) {
|
||||
if ((*p)->from == ifp) return 0;
|
||||
p = &(*p)->next;
|
||||
}
|
||||
if ((f = (struct radio_frame *) calloc(1, sizeof(*f))) == NULL) return 0;
|
||||
f->from = ifp, f->len = len;
|
||||
memcpy(f->buf, buf, len);
|
||||
*p = f;
|
||||
sim->work_pending = true;
|
||||
radio_schedule(sim, f, false);
|
||||
return len;
|
||||
}
|
||||
|
||||
static void radio_receive(struct radio_port *port,
|
||||
const struct radio_frame *f) {
|
||||
char *buf;
|
||||
if (mg_queue_book(&port->rx, &buf, f->len) >= f->len) {
|
||||
memcpy(buf, f->buf, f->len);
|
||||
mg_queue_add(&port->rx, f->len);
|
||||
port->sim->work_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Test range and sample independent interference loss for this reception.
|
||||
static bool visible(struct simulator *sim, const struct pos *a,
|
||||
const struct pos *b) {
|
||||
double dx = a->x - b->x, dy = a->y - b->y, dz = a->z - b->z;
|
||||
double range = sim->radio_range;
|
||||
if (dx * dx + dy * dy + dz * dz > range * range) return false;
|
||||
if (sim->radio_loss == 0) return true;
|
||||
sim->loss_random ^= sim->loss_random << 13;
|
||||
sim->loss_random ^= sim->loss_random >> 17;
|
||||
sim->loss_random ^= sim->loss_random << 5;
|
||||
return (double) sim->loss_random * (100.0 / 4294967296.0) >= sim->radio_loss;
|
||||
}
|
||||
|
||||
static void radio_deliver_to(struct radio_port *port,
|
||||
const struct radio_port *from,
|
||||
const struct radio_frame *f, uint16_t dst) {
|
||||
bool all = dst == 0xffffu;
|
||||
if (port == from || (!all && port->address != dst)) return;
|
||||
if (!visible(port->sim, from->pos, port->pos)) {
|
||||
port->sim->air_lost++;
|
||||
} else if (port->tx_until > f->start) {
|
||||
if (!all) port->sim->air_lost++;
|
||||
} else if (!f->collided && port->rx_off_until <= f->start) {
|
||||
radio_receive(port, f);
|
||||
}
|
||||
}
|
||||
|
||||
static void radio_deliver(struct simulator *sim, const struct radio_frame *f) {
|
||||
const struct mg_radio *radio = s_mesh->radio;
|
||||
const struct radio_port *from = f->from->driver_data;
|
||||
uint16_t dst, src;
|
||||
struct bridge *b;
|
||||
struct radio_node *n;
|
||||
if (radio->parse_hdr(radio, f->buf, f->len, &dst, &src) == 0) return;
|
||||
for (b = sim->bridges; b != NULL; b = b->next)
|
||||
radio_deliver_to(&b->radio, from, f, dst);
|
||||
for (n = sim->nodes; n != NULL; n = n->next)
|
||||
radio_deliver_to(&n->radio, from, f, dst);
|
||||
}
|
||||
|
||||
// Process ends, then all channel checks, then starts at this timestamp.
|
||||
// Checks therefore see the same air state regardless of list order.
|
||||
static void radio_poll(struct simulator *sim) {
|
||||
struct radio_frame **p = &sim->air_head, *f, *g;
|
||||
while ((f = *p) != NULL) {
|
||||
if (f->state != RADIO_END || f->due > sim->now) {
|
||||
p = &f->next;
|
||||
continue;
|
||||
}
|
||||
if (f->collided) {
|
||||
sim->air_collisions++;
|
||||
}
|
||||
radio_deliver(sim, f);
|
||||
*p = f->next;
|
||||
free(f);
|
||||
}
|
||||
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||
if (f->state == RADIO_BACKOFF && f->due <= sim->now) {
|
||||
f->state = RADIO_CCA;
|
||||
f->due = sim->now + s_mesh->radio->cca_us;
|
||||
((struct radio_port *) f->from->driver_data)->rx_off_until = f->due;
|
||||
}
|
||||
}
|
||||
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||
if (f->state != RADIO_CCA || f->due > sim->now) continue;
|
||||
radio_schedule(sim, f,
|
||||
sim->air_busy_until > sim->now - s_mesh->radio->cca_us);
|
||||
if (f->state == RADIO_START) {
|
||||
((struct radio_port *) f->from->driver_data)->rx_off_until = f->due;
|
||||
}
|
||||
}
|
||||
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||
struct radio_port *port = f->from->driver_data;
|
||||
if (f->state != RADIO_START || f->due > sim->now) continue;
|
||||
f->state = RADIO_END;
|
||||
f->start = sim->now;
|
||||
f->due = sim->now + radio_airtime(f->len);
|
||||
port->rx_off_until = port->tx_until = f->due;
|
||||
port->tx_ready_at = f->due + s_mesh->radio->ifs_us;
|
||||
for (g = sim->air_head; g != NULL; g = g->next) {
|
||||
if (g != f && g->state == RADIO_END && g->due > sim->now) {
|
||||
f->collided = g->collided = true;
|
||||
}
|
||||
}
|
||||
if (f->due > sim->air_busy_until) sim->air_busy_until = f->due;
|
||||
sim->air_sent++;
|
||||
}
|
||||
}
|
||||
|
||||
// Radio events keep microsecond precision between periodic timer polls.
|
||||
static uint64_t radio_next(const struct simulator *sim, uint64_t next) {
|
||||
const struct radio_frame *f;
|
||||
for (f = sim->air_head; f != NULL; f = f->next) {
|
||||
if (f->due < next) next = f->due;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
static size_t radio_receive_frame(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||
struct radio_port *port = (struct radio_port *) ifp->driver_data;
|
||||
char *src;
|
||||
size_t len;
|
||||
while ((len = mg_queue_next(&port->rx, &src)) > 0) {
|
||||
if (len <= cap) memcpy(buf, src, len);
|
||||
mg_queue_del(&port->rx, len);
|
||||
if (len <= cap) {
|
||||
if (mg_queue_next(&port->rx, &src) > 0) port->sim->work_pending = true;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t radio_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
struct radio_port *port = ifp->driver_data;
|
||||
return port->mesh->strategy->tx(port->mesh, buf, len);
|
||||
}
|
||||
|
||||
static size_t radio_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
|
||||
struct radio_port *port = ifp->driver_data;
|
||||
size_t len = port->mesh->rx_len;
|
||||
if (len > cap) return 0;
|
||||
if (len != 0 && buf != ifp->recv_queue.buf)
|
||||
memcpy(buf, ifp->recv_queue.buf, len);
|
||||
port->mesh->rx_len = 0;
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool radio_driver_poll(struct mg_tcpip_if *ifp, bool s1) {
|
||||
struct radio_port *port = ifp->driver_data;
|
||||
uint8_t frame[128];
|
||||
size_t len;
|
||||
(void) s1;
|
||||
port->mesh->now = mg_micros();
|
||||
while (port->mesh->rx_len == 0 &&
|
||||
(len = radio_receive_frame(frame, sizeof(frame), ifp)) != 0)
|
||||
port->mesh->strategy->rx(port->mesh, frame, len);
|
||||
s_mesh->tick(port->mesh, mg_micros());
|
||||
return port->mesh->joined;
|
||||
}
|
||||
|
||||
static bool radio_init(struct mg_tcpip_if *ifp) {
|
||||
struct radio_port *port = ifp->driver_data;
|
||||
char *buf = malloc(LAN_QUEUE_SIZE);
|
||||
if (buf == NULL) return false;
|
||||
mg_queue_init(&port->rx, buf, LAN_QUEUE_SIZE);
|
||||
port->mesh = calloc(1, sizeof(*port->mesh));
|
||||
if (port->mesh == NULL ||
|
||||
!s_mesh->init(port->mesh, ifp, port->lan, port->address)) {
|
||||
free(port->mesh);
|
||||
port->mesh = NULL;
|
||||
free(buf);
|
||||
port->rx.buf = NULL;
|
||||
return false;
|
||||
}
|
||||
port->mesh->strategy = s_mesh;
|
||||
port->mesh->tx = radio_send;
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct mg_tcpip_driver s_radio_driver = {
|
||||
radio_init, radio_tx, radio_rx, radio_driver_poll};
|
||||
|
||||
#define MG_TCPIP_DRIVER_SIM_RADIO_INIT(manager, interface) \
|
||||
do { \
|
||||
struct mg_tcpip_if *mif_ = (interface); \
|
||||
mif_->mgr = (manager); \
|
||||
mg_tcpip_init((manager), mif_); \
|
||||
MG_INFO(("Driver: sim_radio, MAC: %M", mg_print_mac, mif_->mac)); \
|
||||
} while (0)
|
||||
|
||||
#define MG_TCPIP_DRIVER_SIM_LAN_INIT(manager, interface) \
|
||||
do { \
|
||||
struct mg_tcpip_if *mif_ = (interface); \
|
||||
mg_tcpip_init((manager), mif_); \
|
||||
MG_INFO(("Driver: sim_lan, MAC: %M", mg_print_mac, mif_->mac)); \
|
||||
} while (0)
|
||||
|
||||
// A node's HTTP server: the test node requests GET /
|
||||
static void node_http_ev_handler(struct mg_connection *c, int ev,
|
||||
void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
mg_http_reply(c, 200, "", "Hi from Mongoose, my MAC is %M\n", mg_print_mac,
|
||||
c->fn_data);
|
||||
}
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
// Start each node's Mongoose: its radio interface, no IP address, so DHCP,
|
||||
// and an HTTP server
|
||||
static void nodes_init(struct simulator *sim) {
|
||||
struct radio_node *n;
|
||||
for (n = sim->nodes; n != NULL; n = n->next) {
|
||||
n->rif.driver = &s_radio_driver;
|
||||
n->rif.driver_data = &n->radio;
|
||||
n->radio.sim = sim;
|
||||
n->radio.pos = &n->pos;
|
||||
mg_mgr_init(&n->mgr);
|
||||
MG_TCPIP_DRIVER_SIM_RADIO_INIT(&n->mgr, &n->rif);
|
||||
if (n->mgr.ifp != NULL) {
|
||||
mg_http_listen(&n->mgr, "http://0.0.0.0:80", node_http_ev_handler,
|
||||
n->mgr.ifp->mac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start each bridge: its LAN and radio drivers, with no IP stack.
|
||||
static void bridges_init(struct simulator *sim) {
|
||||
struct bridge *b;
|
||||
for (b = sim->bridges; b != NULL; b = b->next) {
|
||||
b->lif.driver = &s_lan_driver, b->rif.driver = &s_radio_driver;
|
||||
b->rif.driver_data = &b->radio;
|
||||
b->radio.sim = sim;
|
||||
b->radio.pos = &b->pos;
|
||||
mg_mgr_init(&b->mgr);
|
||||
b->radio.lan = &b->lif;
|
||||
MG_TCPIP_DRIVER_SIM_RADIO_INIT(&b->mgr, &b->rif);
|
||||
mg_queue_init(&b->lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
|
||||
LAN_QUEUE_SIZE);
|
||||
b->lif.driver->init(&b->lif);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
uint64_t poll_at = 0, mesh_at = (uint64_t) -1;
|
||||
char *pending;
|
||||
clock_t started; // CPU time, to show how fast the simulation runs
|
||||
int i;
|
||||
|
||||
init(&s_sim);
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (i + 1 >= argc) {
|
||||
usage(&s_sim, argv[0]);
|
||||
} else if (strcmp(argv[i], "-area") == 0) {
|
||||
if (!parse_pos(argv[++i], &s_sim.area)) usage(&s_sim, argv[0]);
|
||||
} else if (strcmp(argv[i], "-bridge") == 0) {
|
||||
if (!add_bridge(&s_sim, argv[++i])) usage(&s_sim, argv[0]);
|
||||
} else if (strcmp(argv[i], "-node") == 0) {
|
||||
if (!add_node(&s_sim, argv[++i])) usage(&s_sim, argv[0]);
|
||||
} else if (strcmp(argv[i], "-t") == 0) {
|
||||
s_sim.duration = (uint64_t) (atof(argv[++i]) * 1e6);
|
||||
} else if (strcmp(argv[i], "-w") == 0) {
|
||||
s_sim.tester.wait = (uint64_t) (atof(argv[++i]) * 1e6);
|
||||
} else if (strcmp(argv[i], "-range") == 0 ||
|
||||
strcmp(argv[i], "-loss") == 0) {
|
||||
bool loss = strcmp(argv[i], "-loss") == 0;
|
||||
const char *arg = argv[++i];
|
||||
char *end;
|
||||
double value = strtod(arg, &end);
|
||||
if (end == arg || *end != '\0' ||
|
||||
!(value >= 0 && value <= (loss ? 100 : 1e100)))
|
||||
usage(&s_sim, argv[0]);
|
||||
if (loss)
|
||||
s_sim.radio_loss = value;
|
||||
else
|
||||
s_sim.radio_range = value;
|
||||
} else if (strcmp(argv[i], "-m") == 0) {
|
||||
const char *name = argv[++i];
|
||||
if (strcmp(name, mg_mesh_a.name) == 0)
|
||||
s_mesh = &mg_mesh_a;
|
||||
else if (strcmp(name, mg_mesh_default.name) == 0)
|
||||
s_mesh = &mg_mesh_default;
|
||||
else
|
||||
usage(&s_sim, argv[0]);
|
||||
} else {
|
||||
usage(&s_sim, argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Area %gx%gx%g m, radio %s, mesh %s, bridges: %d, nodes: %d\n",
|
||||
s_sim.area.x, s_sim.area.y, s_sim.area.z, s_mesh->radio->name,
|
||||
s_mesh->name, s_sim.num_bridges, s_sim.num_nodes);
|
||||
check_area(&s_sim);
|
||||
if (s_mesh->single_bridge && s_sim.num_bridges != 1) {
|
||||
fprintf(stderr, "%s requires exactly one bridge\n", s_mesh->name);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// mg_log_set(MG_LL_INFO);
|
||||
|
||||
mg_mgr_init(&s_sim.tester.mgr);
|
||||
MG_TCPIP_DRIVER_SIM_LAN_INIT(&s_sim.tester.mgr, &s_sim.tester.lif);
|
||||
s_sim.tester.next = s_sim.nodes;
|
||||
bridges_init(&s_sim);
|
||||
nodes_init(&s_sim);
|
||||
|
||||
// Drain packet processing at the current time. Only idle timer polling
|
||||
// waits for STEP_MICROSECONDS; radio deadlines remain exact.
|
||||
started = clock();
|
||||
while (s_sim.now < s_sim.duration) {
|
||||
radio_poll(&s_sim);
|
||||
if (s_sim.now == poll_at || s_sim.now == mesh_at || s_sim.work_pending) {
|
||||
do {
|
||||
s_sim.work_pending = false;
|
||||
dhcp_poll(&s_sim);
|
||||
tester_poll(&s_sim);
|
||||
{
|
||||
struct bridge *b;
|
||||
struct radio_node *n;
|
||||
mesh_at = (uint64_t) -1;
|
||||
for (b = s_sim.bridges; b != NULL; b = b->next) {
|
||||
mg_mgr_poll(&b->mgr, 0);
|
||||
if (b->radio.mesh->next_tick < mesh_at)
|
||||
mesh_at = b->radio.mesh->next_tick;
|
||||
}
|
||||
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||
mg_mgr_poll(&n->mgr, 0);
|
||||
if (n->radio.mesh->next_tick < mesh_at)
|
||||
mesh_at = n->radio.mesh->next_tick;
|
||||
}
|
||||
}
|
||||
if (mg_queue_next(&s_sim.tester.lif.recv_queue, &pending) > 0) {
|
||||
s_sim.work_pending = true;
|
||||
}
|
||||
} while (s_sim.work_pending);
|
||||
if (s_sim.now == poll_at) poll_at += STEP_MICROSECONDS;
|
||||
}
|
||||
s_sim.now = radio_next(&s_sim, mesh_at < poll_at ? mesh_at : poll_at);
|
||||
if (s_sim.now > s_sim.duration) s_sim.now = s_sim.duration;
|
||||
}
|
||||
printf("Requests: %d ok, %d failed, of %d nodes", s_sim.tester.ok,
|
||||
s_sim.tester.failed, s_sim.num_nodes);
|
||||
if (s_sim.tester.ok > 0) {
|
||||
printf("; ms: min %.1f, mean %.1f, max %.1f", s_sim.tester.min,
|
||||
s_sim.tester.sum / s_sim.tester.ok, s_sim.tester.max);
|
||||
}
|
||||
printf("\nSimulated %g s in %.3f s", (double) (int64_t) s_sim.now / 1e6,
|
||||
(double) (clock() - started) / CLOCKS_PER_SEC);
|
||||
if (s_sim.nodes != NULL) {
|
||||
const struct radio_node *n;
|
||||
size_t min = (size_t) -1, max = 0;
|
||||
double total = 0;
|
||||
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||
size_t peak = n->radio.mesh->ram_peak;
|
||||
if (peak < min) min = peak;
|
||||
if (peak > max) max = peak;
|
||||
total += (double) peak;
|
||||
}
|
||||
printf("; peak mesh RAM per node (bytes): min %zu, max %zu, avg %.1f", min,
|
||||
max, total / s_sim.num_nodes);
|
||||
}
|
||||
putchar('\n');
|
||||
|
||||
while (s_sim.air_head != NULL) {
|
||||
struct radio_frame *f = s_sim.air_head;
|
||||
s_sim.air_head = f->next;
|
||||
free(f);
|
||||
}
|
||||
printf("Radio: %lu sent, %lu collided, %lu lost\n",
|
||||
s_sim.air_sent, s_sim.air_collisions, s_sim.air_lost);
|
||||
{
|
||||
struct radio_node *n;
|
||||
int count = 0, joined = 0;
|
||||
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||
if (n->mgr.ifp != NULL && n->mgr.ifp->ip != 0) count++;
|
||||
if (n->radio.mesh->joined) joined++;
|
||||
}
|
||||
printf("Nodes with IP: %d of %d, joined: %d of %d\n", count,
|
||||
s_sim.num_nodes, joined, s_sim.num_nodes);
|
||||
}
|
||||
{
|
||||
struct bridge *b;
|
||||
struct radio_node *n;
|
||||
mg_log_set(MG_LL_NONE);
|
||||
for (b = s_sim.bridges; b != NULL; b = b->next) {
|
||||
mg_mgr_free(&b->mgr);
|
||||
s_mesh->free(b->radio.mesh);
|
||||
free(b->radio.mesh);
|
||||
free(b->radio.rx.buf);
|
||||
}
|
||||
for (n = s_sim.nodes; n != NULL; n = n->next) {
|
||||
mg_mgr_free(&n->mgr);
|
||||
s_mesh->free(n->radio.mesh);
|
||||
free(n->radio.mesh);
|
||||
free(n->radio.rx.buf);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
extern const struct mg_mesh mg_mesh_a;
|
||||
|
||||
// A position in metres
|
||||
struct pos {
|
||||
double x, y, z; // Coordinates
|
||||
};
|
||||
|
||||
struct simulator;
|
||||
|
||||
// Private state of the simulated radio driver.
|
||||
struct radio_port {
|
||||
struct simulator *sim;
|
||||
struct mg_tcpip_if *lan; // Bridge LAN interface, NULL for a node
|
||||
const struct pos *pos; // Location of the radio
|
||||
uint64_t tx_until; // Latest transmission end, us
|
||||
uint64_t tx_ready_at; // Inter-frame wait ends, us
|
||||
uint64_t rx_off_until; // Latest backoff/sensing/turnaround/TX end, us
|
||||
struct mg_mesh_node *mesh; // Mesh state owned by the driver
|
||||
struct mg_queue rx;
|
||||
uint32_t random; // Driver backoff PRNG
|
||||
uint16_t address; // Preassigned radio address
|
||||
};
|
||||
|
||||
// A bridge: forwards frames between the LAN and the radio, no IP stack
|
||||
struct bridge {
|
||||
struct bridge *next; // Next in simulator::bridges
|
||||
struct pos pos; // Where it is
|
||||
struct mg_mgr mgr; // Polls the raw radio driver
|
||||
struct mg_tcpip_if lif; // LAN side: LAN driver, gets every LAN frame
|
||||
struct mg_tcpip_if rif; // Radio side: radio driver, gets every frame
|
||||
struct radio_port radio;
|
||||
};
|
||||
|
||||
// A radio node: Mongoose on a radio interface
|
||||
struct radio_node {
|
||||
struct radio_node *next; // Next in simulator::nodes
|
||||
struct pos pos; // Where it is
|
||||
struct mg_mgr mgr; // Mongoose event manager
|
||||
struct mg_tcpip_if rif; // Radio interface. rif.ip from DHCP, 0: none yet
|
||||
struct radio_port radio;
|
||||
};
|
||||
|
||||
struct simulator;
|
||||
|
||||
// LAN node: a DHCP server, 10.0.0.1. Gives each client an address made
|
||||
// from its MAC, see node_ip()
|
||||
struct lan_dhcp_server {
|
||||
struct mg_tcpip_if lif; // Its LAN interface, no Mongoose on it
|
||||
};
|
||||
|
||||
// LAN node: the test node, 10.0.0.2, a Mongoose instance. Waits for
|
||||
// the mesh to settle, then cycles GET / through nodes until 5 s before the end
|
||||
struct lan_tester {
|
||||
struct mg_mgr mgr; // Its Mongoose
|
||||
struct mg_tcpip_if lif; // Its LAN interface
|
||||
uint64_t wait; // Mesh settle time: start requests then, us
|
||||
struct radio_node *next; // Node to request next, wraps at the end
|
||||
struct mg_connection *c; // Request on the way, NULL: none
|
||||
uint64_t start; // When it started, us
|
||||
int ok, failed; // Requests done, by result
|
||||
double min, max, sum; // Times of successful requests, ms
|
||||
};
|
||||
|
||||
enum { RADIO_BACKOFF, RADIO_CCA, RADIO_START, RADIO_END };
|
||||
|
||||
// One radio frame waiting on the simulated radio channel
|
||||
struct radio_frame {
|
||||
struct radio_frame *next;
|
||||
struct mg_tcpip_if *from;
|
||||
uint64_t due; // Next radio event, us
|
||||
uint64_t start; // Transmission start, for receive overlap checks
|
||||
uint8_t state, backoff;
|
||||
bool collided;
|
||||
size_t len;
|
||||
uint8_t buf[1540];
|
||||
};
|
||||
|
||||
uint64_t radio_airtime(size_t len);
|
||||
|
||||
// Simulator state
|
||||
struct simulator {
|
||||
struct radio_frame *air_head; // Pending and active frames, singly linked
|
||||
uint64_t air_busy_until; // Last transmission end, for channel sensing
|
||||
unsigned long air_sent, air_collisions;
|
||||
unsigned long air_lost; // Receptions lost to range, random loss or TX overlap
|
||||
double radio_range; // Maximum reception distance, metres
|
||||
double radio_loss; // In-range reception loss probability, percent
|
||||
uint32_t loss_random; // Reception-loss PRNG state
|
||||
bool work_pending; // Drain immediate work before advancing time
|
||||
struct pos area; // A box: all positions inside
|
||||
struct bridge *bridges; // Bridges, a list
|
||||
struct radio_node *nodes; // Radio nodes, a list
|
||||
int num_bridges, num_nodes; // How many
|
||||
uint64_t now; // Virtual time, us since the start
|
||||
uint64_t duration; // Run this long, us
|
||||
uint32_t last_mac; // Low 3 bytes of the last MAC given
|
||||
int request_timeout_ms; // A request that takes longer failed
|
||||
struct lan_dhcp_server dhcp; // DHCP server on the LAN
|
||||
struct lan_tester tester; // Test node on the LAN
|
||||
};
|
||||
+16
-8
@@ -5,6 +5,9 @@
|
||||
|
||||
#include "mip_x_test.c"
|
||||
|
||||
static bool drop_tcp_segment(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp);
|
||||
|
||||
struct port_driver_data {
|
||||
int sockfd;
|
||||
struct sockaddr_in port_addr;
|
||||
@@ -16,6 +19,7 @@ static size_t sock_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
ssize_t received = recv(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
|
||||
usleep(1); // This is to avoid 100% CPU
|
||||
if (received < 0) return 0;
|
||||
if (drop_tcp_segment(buf, (size_t) received, ifp)) return 0;
|
||||
return (size_t) received;
|
||||
}
|
||||
|
||||
@@ -37,15 +41,19 @@ static bool is_tcp_segment(const void *buf, size_t len) {
|
||||
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
|
||||
}
|
||||
|
||||
static size_t sock_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
static bool drop_tcp_segment(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
uint32_t random;
|
||||
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss
|
||||
mg_random(&random, sizeof(random));
|
||||
if (random % 10 < 3) {
|
||||
MG_DEBUG(("dropping TCP segment"));
|
||||
return len;
|
||||
}
|
||||
}
|
||||
if (!ifp->enable_tcp_retransmit || !is_tcp_segment(buf, len)) return false;
|
||||
// Introduce a 30% segment loss
|
||||
mg_random(&random, sizeof(random));
|
||||
if (random % 10 >= 3) return false;
|
||||
MG_DEBUG(("dropping TCP segment"));
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t sock_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
if (drop_tcp_segment(buf, len, ifp)) return len;
|
||||
ssize_t res = send(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
|
||||
if (res < 0) {
|
||||
MG_ERROR(("sock_tx failed: %d", errno));
|
||||
|
||||
+15
-8
@@ -13,12 +13,15 @@
|
||||
|
||||
#include "mip_x_test.c"
|
||||
|
||||
static bool drop_tcp_segment(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp);
|
||||
|
||||
// MIP TUNTAP driver
|
||||
static size_t tap_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
ssize_t received = read(*(int *) ifp->driver_data, buf, len);
|
||||
usleep(1); // This is to avoid 100% CPU
|
||||
if (received < 0) return 0;
|
||||
if (drop_tcp_segment(buf, (size_t) received, ifp)) return 0;
|
||||
return (size_t) received;
|
||||
}
|
||||
|
||||
@@ -40,15 +43,19 @@ static bool is_tcp_segment(const void *buf, size_t len) {
|
||||
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
|
||||
}
|
||||
|
||||
static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
static bool drop_tcp_segment(const void *buf, size_t len,
|
||||
struct mg_tcpip_if *ifp) {
|
||||
uint32_t random;
|
||||
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss
|
||||
mg_random(&random, sizeof(random));
|
||||
if (random % 10 < 3) {
|
||||
MG_DEBUG(("dropping TCP segment"));
|
||||
return len;
|
||||
}
|
||||
}
|
||||
if (!ifp->enable_tcp_retransmit || !is_tcp_segment(buf, len)) return false;
|
||||
// Introduce a 30% segment loss
|
||||
mg_random(&random, sizeof(random));
|
||||
if (random % 10 >= 3) return false;
|
||||
MG_DEBUG(("dropping TCP segment"));
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
if (drop_tcp_segment(buf, len, ifp)) return len;
|
||||
ssize_t res = write(*(int *) ifp->driver_data, buf, len);
|
||||
if (res < 0) {
|
||||
MG_ERROR(("tap_tx failed: %d", errno));
|
||||
|
||||
@@ -80,6 +80,10 @@ static void test_statechange(void) {
|
||||
onstatechange(&iface);
|
||||
ASSERT(executed == true);
|
||||
executed = false;
|
||||
iface.enable_dhcp_client = true; // DHCP REQUEST unanswered: start over
|
||||
iface.state = MG_TCPIP_STATE_REQ, iface.lease_expire = 0;
|
||||
mg_ip_poll(&iface, true);
|
||||
ASSERT(iface.state == MG_TCPIP_STATE_UP);
|
||||
}
|
||||
#if MG_ENABLE_IPV6
|
||||
static void mif6_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
|
||||
|
||||
+69
-47
@@ -76,6 +76,7 @@ static int s_num_tests = 0;
|
||||
static bool s_error = false;
|
||||
|
||||
#define POLL_LIMIT(n) (mgr->ifp->enable_tcp_retransmit ? (n) * 10 : (n))
|
||||
#define RTX_ATTEMPTS(mgr) ((mgr)->ifp->enable_tcp_retransmit ? 3 : 1)
|
||||
|
||||
#ifdef NO_ABORT
|
||||
static int s_abort = 0;
|
||||
@@ -151,13 +152,15 @@ static void eh1(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct fetch_data {
|
||||
char *buf;
|
||||
const char *url;
|
||||
int code, closed;
|
||||
int code;
|
||||
bool connected, received, closed;
|
||||
};
|
||||
|
||||
static void fcb(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct fetch_data *fd = (struct fetch_data *) c->fn_data;
|
||||
if (ev == MG_EV_CONNECT) {
|
||||
MG_DEBUG(("CONNECT"));
|
||||
fd->connected = true;
|
||||
if (mg_url_is_ssl(fd->url)) {
|
||||
struct mg_tls_opts opts;
|
||||
memset(&opts, 0, sizeof(opts)); // read CA from packed_fs
|
||||
@@ -173,6 +176,7 @@ static void fcb(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
fd->received = true;
|
||||
snprintf(fd->buf, FETCH_BUF_SIZE, "%.*s", (int) hm->message.len,
|
||||
hm->message.buf);
|
||||
fd->code = atoi(hm->uri.buf);
|
||||
@@ -191,32 +195,38 @@ static void fcb(struct mg_connection *c, int ev, void *ev_data) {
|
||||
|
||||
static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
|
||||
const char *fmt, ...) {
|
||||
struct fetch_data fd = {buf, url, 0, 0};
|
||||
int i;
|
||||
int i, attempt;
|
||||
struct mg_connection *c = NULL;
|
||||
va_list ap;
|
||||
mg_mgr_poll(mgr, 0); // update ifp->now to avoid ARP lookup using an old
|
||||
// timestamp (from an ancient call in other test)
|
||||
mg_mgr_poll(mgr, 0); // (prior idling collects lots of frames)
|
||||
c = mg_http_connect(mgr, url, fcb, &fd);
|
||||
ASSERT(c != NULL);
|
||||
va_start(ap, fmt);
|
||||
mg_vprintf(c, fmt, &ap);
|
||||
va_end(ap);
|
||||
buf[0] = '\0';
|
||||
// - TLS: multiple (small) records: allow enough loops so mg_mgr_poll can
|
||||
// process buffered records when no more frames are coming in
|
||||
for (i = 0; i < POLL_LIMIT(500) && buf[0] == '\0' && !fd.closed; i++) {
|
||||
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
|
||||
struct fetch_data fd = {buf, url, 0, false, false, false};
|
||||
mg_mgr_poll(mgr, 0); // update ifp->now to avoid ARP lookup using an old
|
||||
// timestamp (from an ancient call in other test)
|
||||
mg_mgr_poll(mgr, 0); // (prior idling collects lots of frames)
|
||||
c = mg_http_connect(mgr, url, fcb, &fd);
|
||||
ASSERT(c != NULL);
|
||||
va_start(ap, fmt);
|
||||
mg_vprintf(c, fmt, &ap);
|
||||
va_end(ap);
|
||||
buf[0] = '\0';
|
||||
// - TLS: multiple (small) records: allow enough loops so mg_mgr_poll can
|
||||
// process buffered records when no more frames are coming in
|
||||
for (i = 0; i < POLL_LIMIT(500) && buf[0] == '\0' && !fd.closed; i++) {
|
||||
mg_mgr_poll(mgr, 0);
|
||||
usleep(5000); // 5 ms. Slow down poll loop to ensure packet transit, but
|
||||
// allow enough loops to get the ARP response, otherwise,
|
||||
// given enough traffic, the timer expires before we get a
|
||||
// chance to see the response
|
||||
}
|
||||
if (!fd.closed) // failed, force closure and conn removal, and loop end
|
||||
((struct connstate *) (c + 1))->is_closing = 1;
|
||||
mg_mgr_poll(mgr, 0);
|
||||
usleep(5000); // 5 ms. Slow down poll loop to ensure packet transit, but
|
||||
// allow enough loops to get the ARP response, otherwise,
|
||||
// given enough traffic, the timer expires before we get a
|
||||
// chance to see the response
|
||||
if (!(fd.connected && fd.closed && !fd.received) ||
|
||||
attempt + 1 == RTX_ATTEMPTS(mgr))
|
||||
return fd.code;
|
||||
MG_INFO(("HTTP retry %d", attempt + 2));
|
||||
}
|
||||
if (!fd.closed) // failed, force closure and conn removal, and loop end
|
||||
((struct connstate *) (c + 1))->is_closing = 1;
|
||||
mg_mgr_poll(mgr, 0);
|
||||
return fd.code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_http_client(struct mg_mgr *mgr) {
|
||||
@@ -256,13 +266,14 @@ static char s_topic[16];
|
||||
|
||||
struct mqtt_data {
|
||||
char *url;
|
||||
bool passed;
|
||||
bool connected, opened, passed;
|
||||
};
|
||||
|
||||
static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mqtt_data *data = (struct mqtt_data *) c->fn_data;
|
||||
if (ev == MG_EV_CONNECT) {
|
||||
MG_DEBUG(("CONNECT"));
|
||||
data->connected = true;
|
||||
#if MG_TLS
|
||||
struct mg_tls_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
@@ -277,6 +288,7 @@ static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
#endif
|
||||
} else if (ev == MG_EV_MQTT_OPEN) {
|
||||
MG_DEBUG(("MQTT CONNECT"));
|
||||
data->opened = true;
|
||||
struct mg_mqtt_opts sub_opts;
|
||||
memset(&sub_opts, 0, sizeof(sub_opts));
|
||||
sub_opts.topic = mg_str(mg_random_str(s_topic, sizeof(s_topic)));
|
||||
@@ -326,12 +338,30 @@ static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool mqtt_attempt(struct mg_mgr *mgr, struct mqtt_data *data,
|
||||
struct mg_mqtt_opts *opts) {
|
||||
int i;
|
||||
data->connected = data->opened = data->passed = false;
|
||||
mg_mgr_poll(mgr, 0); // update interface timing (*)
|
||||
mg_mgr_poll(mgr, 0);
|
||||
s_conn = mg_mqtt_connect(mgr, data->url, opts, mqtt_fn, data);
|
||||
ASSERT(s_conn != NULL);
|
||||
for (i = 0; i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
|
||||
i++) {
|
||||
mg_mgr_poll(mgr, 0);
|
||||
usleep(5000); // 5 ms (*) See fetch() above for reasons
|
||||
}
|
||||
mg_mgr_poll(mgr, 0);
|
||||
return data->connected && !data->opened && s_conn == NULL;
|
||||
}
|
||||
|
||||
static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
|
||||
struct mqtt_data data;
|
||||
struct mg_mqtt_opts opts;
|
||||
bool retry;
|
||||
int attempt;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
opts.clean = true, opts.version = 4;
|
||||
data.passed = false;
|
||||
#if defined(MQTT_HOST) && MG_TLS != MG_TLS_BUILTIN
|
||||
if (host_ip == NULL) {
|
||||
printf("\nMQTT_HOST defined but no HOST_IP provided, skipping MQTT tests\n");
|
||||
@@ -359,15 +389,10 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
|
||||
data.url = strdup(MQTT_URL);
|
||||
#endif
|
||||
#endif // MG_TLS
|
||||
mg_mgr_poll(mgr, 0); // update interface timing (*)
|
||||
mg_mgr_poll(mgr, 0);
|
||||
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data);
|
||||
ASSERT(s_conn != NULL);
|
||||
for (int i = 0;
|
||||
i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
|
||||
i++) {
|
||||
mg_mgr_poll(mgr, 0);
|
||||
usleep(5000); // 5 ms (*) See fetch() above for reasons
|
||||
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
|
||||
retry = mqtt_attempt(mgr, &data, &opts);
|
||||
if (!retry || attempt + 1 == RTX_ATTEMPTS(mgr)) break;
|
||||
MG_INFO(("MQTT retry %d", attempt + 2));
|
||||
}
|
||||
ASSERT(data.passed);
|
||||
mg_mgr_poll(mgr, 0);
|
||||
@@ -406,16 +431,10 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
|
||||
//data.url = strdup(MQTT_URL);
|
||||
#endif
|
||||
#endif // MG_TLS
|
||||
data.passed = false;
|
||||
mg_mgr_poll(mgr, 0); // update interface timing (*)
|
||||
mg_mgr_poll(mgr, 0);
|
||||
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data);
|
||||
ASSERT(s_conn != NULL);
|
||||
for (int i = 0;
|
||||
i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
|
||||
i++) {
|
||||
mg_mgr_poll(mgr, 0);
|
||||
usleep(5000); // 5 ms (*) See fetch() above for reasons
|
||||
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
|
||||
retry = mqtt_attempt(mgr, &data, &opts);
|
||||
if (!retry || attempt + 1 == RTX_ATTEMPTS(mgr)) break;
|
||||
MG_INFO(("MQTT retry %d", attempt + 2));
|
||||
}
|
||||
ASSERT(data.passed);
|
||||
mg_mgr_poll(mgr, 0);
|
||||
@@ -445,7 +464,7 @@ static void test_http_server(struct mg_mgr *mgr) {
|
||||
struct mg_connection *c;
|
||||
char *cmd;
|
||||
pthread_t thread_id = (pthread_t) 0;
|
||||
int rc;
|
||||
int attempt, rc;
|
||||
#if MG_TLS
|
||||
struct mg_tls_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
@@ -468,8 +487,11 @@ static void test_http_server(struct mg_mgr *mgr) {
|
||||
s_poll_done = false;
|
||||
pthread_create(&thread_id, NULL, poll_thread,
|
||||
mgr); // simpler this way, no concurrency anyway
|
||||
MG_DEBUG(("CURL"));
|
||||
rc = system(cmd); // wait for curl
|
||||
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
|
||||
MG_DEBUG(("CURL attempt %d", attempt + 1));
|
||||
rc = system(cmd); // wait for curl
|
||||
if (rc == 0) break;
|
||||
}
|
||||
MG_DEBUG(("MONGOOSE"));
|
||||
s_poll_done = true;
|
||||
pthread_join(thread_id, NULL); // wait for Mongoose
|
||||
|
||||
+119
-6
@@ -1475,6 +1475,11 @@ static void h4(struct mg_connection *c, int ev, void *ev_data) {
|
||||
opts.root_dir = "/a=./data";
|
||||
opts.page404 = "./data/404.html"; // existing 404 page
|
||||
mg_http_serve_dir(c, hm, &opts);
|
||||
} else if (mg_match(hm->uri, mg_str("/unmapped"), NULL)) {
|
||||
struct mg_http_serve_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
opts.root_dir = "/a=./data";
|
||||
mg_http_serve_dir(c, hm, &opts);
|
||||
} else if (mg_match(hm->uri, mg_str("/b/#"), NULL)) {
|
||||
struct mg_http_serve_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
@@ -1502,6 +1507,7 @@ static void test_http_404(void) {
|
||||
ASSERT(cmpbody(buf, "hello\n") == 0);
|
||||
ASSERT(fetch(&mgr, buf, url, "GET /a/a.txt HTTP/1.0\n\n") == 200);
|
||||
ASSERT(cmpbody(buf, "hello\n") == 0);
|
||||
ASSERT(fetch(&mgr, buf, url, "GET /unmapped HTTP/1.0\n\n") == 400);
|
||||
ASSERT(fetch(&mgr, buf, url, "GET /b/a.txt HTTP/1.0\n\n") == 200);
|
||||
ASSERT(cmpbody(buf, "hello\n") == 0);
|
||||
|
||||
@@ -2399,10 +2405,15 @@ static void test_str(void) {
|
||||
ASSERT(sn("%.*s", 1, "ab"));
|
||||
ASSERT(sn("%.1s", "ab"));
|
||||
ASSERT(sn("%.99s", "a"));
|
||||
ASSERT(sn("%10.8s", "abc"));
|
||||
ASSERT(sn("%-6.4s", "ab"));
|
||||
ASSERT(sn("%5.1s", ""));
|
||||
ASSERT(sn("%11s", "a"));
|
||||
ASSERT(sn("%s", "a\0b"));
|
||||
ASSERT(sn("%2s", "a"));
|
||||
ASSERT(sn("%.*s", 3, "a\0b"));
|
||||
ASSERT(mg_snprintf(NULL, 0, "%.8s", (char *) NULL) == 0);
|
||||
ASSERT(mg_snprintf(NULL, 0, "%8.4s", (char *) NULL) == 8);
|
||||
ASSERT(sn("%d", 7));
|
||||
ASSERT(sn("%d", 123));
|
||||
#if MG_ARCH == MG_ARCH_UNIX
|
||||
@@ -3158,8 +3169,6 @@ static bool su_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
|
||||
static void su(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_HDRS) {
|
||||
mg_http_stream_body(c, (struct mg_http_message *) ev_data, su_cb, NULL);
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // Only for requests that were not streamed
|
||||
mg_http_reply(c, 400, "", "not streamed\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3194,8 +3203,7 @@ static void cr(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_CONNECT) {
|
||||
mg_printf(c, "GET / HTTP/1.0\r\n\r\n");
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
ASSERT(mg_http_stream_body(c, (struct mg_http_message *) ev_data, cr_cb,
|
||||
NULL));
|
||||
mg_http_stream_body(c, (struct mg_http_message *) ev_data, cr_cb, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3208,12 +3216,103 @@ static void cs(struct mg_connection *c, int ev, void *ev_data) {
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
struct stream_test_state {
|
||||
int fail_at, starts, chunks, ends;
|
||||
};
|
||||
|
||||
static bool stream_test_cb(struct mg_http_message *hm, struct mg_str *data,
|
||||
void **p) {
|
||||
struct stream_test_state *s = (struct stream_test_state *) *p;
|
||||
ASSERT(s != NULL);
|
||||
if (hm != NULL) {
|
||||
s->starts++;
|
||||
return s->fail_at != 1;
|
||||
} else if (data != NULL) {
|
||||
s->chunks++;
|
||||
return s->fail_at != 2;
|
||||
}
|
||||
s->ends++;
|
||||
*p = NULL;
|
||||
return s->fail_at != 3;
|
||||
}
|
||||
|
||||
static void stream_test_original(struct mg_connection *c, int ev, void *data) {
|
||||
(void) c, (void) ev, (void) data;
|
||||
ASSERT(false); // Streaming must take ownership even when validation fails
|
||||
}
|
||||
|
||||
static void test_http_stream_errors(void) {
|
||||
static const struct {
|
||||
const char *request;
|
||||
int client, fail_at, body_size, code, starts, chunks;
|
||||
} cases[] = {
|
||||
{"GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 0, 0, 0, 405, 0, 0},
|
||||
{"POST / HTTP/1.1\r\n\r\n", 0, 0, 0, 411, 0, 0},
|
||||
{"POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n",
|
||||
0, 0, 0, 400, 0, 0},
|
||||
{"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", 0, 0, 0, 200, 1, 1},
|
||||
{"PUT / HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 0, 0, 0, 200, 1, 0},
|
||||
// Start failure must reply without waiting for the body or sending 100.
|
||||
{"POST / HTTP/1.1\r\nContent-Length: 1000000\r\nExpect: 100-continue\r\n\r\n",
|
||||
0, 1, 0, 500, 1, 0},
|
||||
// Write failure must also reply before the announced body is complete.
|
||||
{"POST / HTTP/1.1\r\nContent-Length: 1000000\r\n\r\n",
|
||||
0, 2, 512, 500, 1, 1},
|
||||
{"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", 0, 3, 0, 500, 1, 1},
|
||||
// Clients close on errors and never send an HTTP error response.
|
||||
{"HTTP/1.1 200 OK\r\n\r\n", 1, 0, 0, 411, 0, 0},
|
||||
{"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n",
|
||||
1, 0, 0, 400, 0, 0},
|
||||
{"HTTP/1.1 200 OK\r\nContent-Length: 1000000\r\n\r\n",
|
||||
1, 1, 0, 500, 1, 0},
|
||||
{"HTTP/1.1 200 OK\r\nContent-Length: 1000000\r\n\r\n",
|
||||
1, 2, 512, 500, 1, 1},
|
||||
{"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nabc", 1, 3, 0, 500, 1, 1},
|
||||
{"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nabc", 1, 0, 0, 200, 1, 1},
|
||||
{"HTTP/1.1 204 No Content\r\n\r\n", 1, 0, 0, 200, 1, 0},
|
||||
};
|
||||
size_t i;
|
||||
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
struct mg_connection c;
|
||||
struct mg_http_message hm;
|
||||
struct stream_test_state state = {0, 0, 0, 0};
|
||||
char body[512];
|
||||
memset(&c, 0, sizeof(c));
|
||||
memset(body, 'x', sizeof(body));
|
||||
state.fail_at = cases[i].fail_at;
|
||||
c.is_client = cases[i].client != 0;
|
||||
c.fn = c.pfn = stream_test_original;
|
||||
mg_iobuf_add(&c.recv, 0, cases[i].request, strlen(cases[i].request));
|
||||
mg_iobuf_add(&c.recv, c.recv.len, body, (size_t) cases[i].body_size);
|
||||
ASSERT(mg_http_parse((char *) c.recv.buf, c.recv.len, &hm) > 0);
|
||||
mg_http_stream_body(&c, &hm, stream_test_cb, &state);
|
||||
ASSERT(c.pfn == NULL && c.is_draining);
|
||||
ASSERT(state.starts == cases[i].starts && state.chunks == cases[i].chunks);
|
||||
ASSERT(state.ends == 1);
|
||||
if (c.is_client) {
|
||||
ASSERT(c.send.len == 0);
|
||||
ASSERT(c.is_closing == (cases[i].code != 200));
|
||||
} else {
|
||||
ASSERT(mg_http_parse((char *) c.send.buf, c.send.len, &hm) > 0);
|
||||
ASSERT(mg_http_status(&hm) == cases[i].code);
|
||||
ASSERT(hm.message.len == c.send.len); // Exactly one response
|
||||
}
|
||||
mg_call(&c, MG_EV_READ, NULL);
|
||||
mg_error(&c, "stream cleanup test");
|
||||
mg_call(&c, MG_EV_CLOSE, NULL);
|
||||
ASSERT(state.ends == 1); // Error followed by close must not clean up twice
|
||||
mg_iobuf_free(&c.recv);
|
||||
mg_iobuf_free(&c.send);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_http_upload(void) {
|
||||
struct mg_mgr mgr;
|
||||
const char *url = "http://127.0.0.1:12352";
|
||||
int i;
|
||||
const char *s = "ok (8 foo\nbar\n)\n";
|
||||
|
||||
test_http_stream_errors();
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, url, us, NULL);
|
||||
mg_http_connect(&mgr, url, uc, (void *) &s);
|
||||
@@ -3257,7 +3356,7 @@ static void test_http_upload(void) {
|
||||
ASSERT(mgr.conns == NULL);
|
||||
}
|
||||
|
||||
// mg_http_stream_body: chunked request is not streamed, caller replies "400"
|
||||
// mg_http_stream_body: chunked request rejected with "400", cleanup still runs
|
||||
{
|
||||
char buf[FETCH_BUF_SIZE];
|
||||
s_su_done = s_su_end = 0;
|
||||
@@ -3268,7 +3367,7 @@ static void test_http_upload(void) {
|
||||
"POST /su_chunked.txt HTTP/1.1\r\n"
|
||||
"Transfer-Encoding: chunked\r\n\r\n"
|
||||
"3\r\nfoo\r\n0\r\n\r\n") == 400);
|
||||
ASSERT(s_su_done == 0 && s_su_end == 0); // The callback was never called
|
||||
ASSERT(s_su_done == 1 && s_su_end == 1); // End called without start
|
||||
ASSERT(fopen("su_chunked.txt", "r") == NULL);
|
||||
mg_mgr_free(&mgr);
|
||||
ASSERT(mgr.conns == NULL);
|
||||
@@ -5010,6 +5109,20 @@ static void test_dash(void) {
|
||||
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200);
|
||||
ASSERT(cmpbody(buf, login_admin_expected) == 0);
|
||||
|
||||
// OTA uses streaming and enforces the dashboard's authentication.
|
||||
ASSERT(fetch(&mgr, buf, url,
|
||||
"POST /api/ota HTTP/1.0\nContent-Length: 1\n\nx") == 403);
|
||||
ASSERT(fetch(&mgr, buf, url,
|
||||
"GET /api/ota HTTP/1.0\nContent-Length: 0\n"
|
||||
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 405);
|
||||
ASSERT(fetch(&mgr, buf, url,
|
||||
"POST /ota HTTP/1.0\nContent-Length: 1\n"
|
||||
"Authorization: Basic YWRtaW46YWRtaW4=\n\nx") == 404);
|
||||
ASSERT(fetch(&mgr, buf, url,
|
||||
"POST /api/ota HTTP/1.0\nContent-Length: 1\n"
|
||||
"Authorization: Basic YWRtaW46YWRtaW4=\n\nx") == 200);
|
||||
ASSERT(cmpbody(buf, "1 ok\n") == 0);
|
||||
|
||||
// set3 is admin-only
|
||||
ASSERT(fetch(&mgr, buf, url,
|
||||
"GET /api/get/set3 HTTP/1.0\n"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
THISDIR = $(realpath $(CURDIR))
|
||||
ROOTDIR = $(realpath $(CURDIR)/../../..)
|
||||
DOCKER ?= docker run --rm $(DA) -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf
|
||||
CMD ?= idf.py build
|
||||
CMD ?= idf.py -DCFLAGS_EXTRA="$(CFLAGS_EXTRA)" build
|
||||
PORT ?= /dev/ttyUSB0
|
||||
|
||||
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
|
||||
|
||||
all: example
|
||||
|
||||
example:
|
||||
|
||||
@@ -12,3 +12,5 @@ idf_component_register(SRCS "main.c"
|
||||
espcoredump
|
||||
nvs_flash
|
||||
app_update)
|
||||
separate_arguments(CFLAGS_EXTRA)
|
||||
component_compile_options(${CFLAGS_EXTRA})
|
||||
@@ -44,6 +44,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
size_t total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
|
||||
@@ -65,8 +70,10 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
}
|
||||
|
||||
extern void wifi_init(const char *ssid, const char *pass);
|
||||
|
||||
void app_main(void) {
|
||||
void wifi_init(const char *ssid, const char *pass);
|
||||
MG_HEALTH_INIT();
|
||||
wifi_init(WIFI_SSID, WIFI_PASS);
|
||||
|
||||
struct mg_mgr mgr;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#define MG_ARCH MG_ARCH_ESP32
|
||||
|
||||
#define MG_TLS MG_TLS_BUILTIN
|
||||
//#define MG_TLS MG_TLS_MBED
|
||||
#define MG_ENABLE_HEALTH 1
|
||||
@@ -9,7 +9,12 @@ static void t_fn(void *arg) { // Tick periodically
|
||||
}
|
||||
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "hi\n");
|
||||
if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "hi\n");
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,12 @@ static void t_fn(void *arg) { // Tick periodically
|
||||
}
|
||||
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "hi\n");
|
||||
if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) mg_http_reply(c, 200, "", "hi\n");
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,24 +3,24 @@
|
||||
extern void mgmp_init(void);
|
||||
extern void mgmp_poll(int);
|
||||
|
||||
STATIC mp_obj_t mongoose_init(void) {
|
||||
static mp_obj_t mongoose_init(void) {
|
||||
mgmp_init();
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mongoose_init_obj, mongoose_init);
|
||||
static MP_DEFINE_CONST_FUN_OBJ_0(mongoose_init_obj, mongoose_init);
|
||||
|
||||
STATIC mp_obj_t mongoose_poll(mp_obj_t t_obj) {
|
||||
static mp_obj_t mongoose_poll(mp_obj_t t_obj) {
|
||||
mgmp_poll(mp_obj_get_int(t_obj));
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mongoose_poll_obj, mongoose_poll);
|
||||
static MP_DEFINE_CONST_FUN_OBJ_1(mongoose_poll_obj, mongoose_poll);
|
||||
|
||||
STATIC const mp_rom_map_elem_t mongoose_module_globals_table[] = {
|
||||
static const mp_rom_map_elem_t mongoose_module_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mongoose) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mongoose_init_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&mongoose_poll_obj) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(mongoose_module_globals, mongoose_module_globals_table);
|
||||
static MP_DEFINE_CONST_DICT(mongoose_module_globals, mongoose_module_globals_table);
|
||||
|
||||
const mp_obj_module_t mongoose_module = {
|
||||
.base = { &mp_type_module },
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
#ifndef MQTT_SERVER_URL
|
||||
#define MQTT_SERVER_URL "mqtt://broker.hivemq.com:1883"
|
||||
#endif
|
||||
#ifndef MQTT_CLIENT_ID
|
||||
#define MQTT_CLIENT_ID "d3"
|
||||
#endif
|
||||
#define MQTT_USER MQTT_CLIENT_ID
|
||||
#define MQTT_PASS ""
|
||||
#define MQTT_PUBLISH_TOPIC "mg/" MQTT_CLIENT_ID "/tx"
|
||||
@@ -120,7 +124,7 @@ void mg_mqtt_poll(struct mg_mgr *mgr) {
|
||||
|
||||
// Reconnect if connection is closed, and send MQTT PINGs to keep
|
||||
// the connection alive or to detect connection loss
|
||||
if (mg_timer_expired(&timer, MQTT_RECONNECT_PERIOD_MS, mg_now())) {
|
||||
if (mg_timer_expired(&timer, MQTT_RECONNECT_PERIOD_MS, mg_millis())) {
|
||||
if (s_mqtt_conn == NULL) {
|
||||
struct mg_mqtt_opts opts = {
|
||||
.clean = true,
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Environment setup: https://mongoose.ws/documentation/tutorials/tools/
|
||||
|
||||
MGDIR = ../../../..
|
||||
|
||||
# Flash over USB: the board's Adafruit UF2 bootloader does serial DFU. The
|
||||
# firmware resets into it on a 1200 baud touch, see usb.c. PORT: the board's
|
||||
# USB serial port. adafruit-nrfutil: pip install adafruit-nrfutil
|
||||
PORT ?= /dev/cu.usbmodem11301
|
||||
NRFUTIL ?= adafruit-nrfutil
|
||||
|
||||
CFLAGS = -W -Wall -Wextra -Werror -Wshadow -Wdouble-promotion
|
||||
CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion
|
||||
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
|
||||
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include
|
||||
CFLAGS += -Inrfx/mdk -Inrfx -Inrfx/hal -Inrfx/templates -Inrfx/drivers/src -Itinyusb/src
|
||||
CFLAGS += -Wno-cast-align -Wno-shadow -Wno-unused-parameter
|
||||
CFLAGS += -DNRF52840_XXAA -D__STARTUP_CLEAR_BSS -D__START=main -D__HEAP_SIZE=0
|
||||
CFLAGS += -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
|
||||
CFLAGS += $(CFLAGS_EXTRA)
|
||||
|
||||
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lm -lgcc -Wl,--gc-sections
|
||||
|
||||
SOURCES = main.c hal.c usb.c $(MGDIR)/mongoose.c
|
||||
SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c
|
||||
SOURCES += nrfx/mdk/system_nrf52840.c nrfx/mdk/gcc_startup_nrf52840.S
|
||||
|
||||
# TinyUSB, for the USB serial console: the board has no USB-UART chip
|
||||
SOURCES += tinyusb/src/tusb.c tinyusb/src/common/tusb_fifo.c
|
||||
SOURCES += tinyusb/src/device/usbd.c tinyusb/src/device/usbd_control.c
|
||||
SOURCES += tinyusb/src/class/cdc/cdc_device.c
|
||||
SOURCES += tinyusb/src/portable/nordic/nrf5x/dcd_nrf5x.c
|
||||
|
||||
all build: firmware.hex
|
||||
|
||||
firmware.hex: firmware.elf
|
||||
arm-none-eabi-objcopy -O ihex $< $@
|
||||
arm-none-eabi-size --format=berkeley $<
|
||||
|
||||
firmware.elf: cmsis_core nrfx tinyusb $(SOURCES) $(wildcard *.h) link.ld Makefile
|
||||
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
firmware.zip: firmware.hex
|
||||
$(NRFUTIL) dfu genpkg --dev-type 0x0052 --sd-req 0xFFFE --application $< $@
|
||||
|
||||
flash: firmware.zip
|
||||
$(NRFUTIL) dfu serial --package $< -p $(PORT) -b 115200 --singlebank --touch 1200
|
||||
|
||||
cmsis_core:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
|
||||
|
||||
# nRF52840 device headers and SystemInit(), nrf_* register helpers for TinyUSB
|
||||
nrfx:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b v2.11.0 https://github.com/NordicSemiconductor/nrfx $@
|
||||
|
||||
tinyusb:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b 0.14.0 https://github.com/hathach/tinyusb $@
|
||||
|
||||
clean:
|
||||
rm -rf firmware.* *.dSYM tinyusb nrfx cmsis_core
|
||||
|
||||
.PHONY: all build flash clean
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
// Added to minimise the bin size: avoid pulling printf and friends
|
||||
void __assert_func(const char *s, int line, const char *fn, const char *expr) {
|
||||
(void) s, (void) line, (void) fn, (void) expr;
|
||||
for (;;) (void) 0; // or trigger the fault handler
|
||||
}
|
||||
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
|
||||
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
|
||||
if (period == 0) return false; // Avoid division by zero
|
||||
if (diff < period) return false; // Period has not elapsed yet
|
||||
*t += (diff / period) * period; // Preserve cadence, skip missed periods
|
||||
return true;
|
||||
}
|
||||
|
||||
static volatile uint64_t s_ticks; // Milliseconds since boot
|
||||
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
|
||||
s_ticks++;
|
||||
}
|
||||
|
||||
uint64_t hal_get_tick(void) {
|
||||
return s_ticks;
|
||||
};
|
||||
|
||||
struct stat;
|
||||
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
|
||||
(void) fd, (void) st;
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
|
||||
static unsigned char *s_current_heap_end = _end;
|
||||
|
||||
size_t hal_ram_used(void) {
|
||||
return (size_t) (s_current_heap_end - _end);
|
||||
}
|
||||
|
||||
size_t hal_ram_free(void) {
|
||||
unsigned char endofstack;
|
||||
return (size_t) (&endofstack - s_current_heap_end);
|
||||
}
|
||||
|
||||
void *_sbrk(int incr) {
|
||||
unsigned char *prev_heap;
|
||||
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
|
||||
prev_heap = s_current_heap_end;
|
||||
// Check how much space we got from the heap end to the stack end
|
||||
if (s_current_heap_end + incr > heap_end) return (void *) -1;
|
||||
s_current_heap_end += incr;
|
||||
return prev_heap;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _open(const char *path) {
|
||||
(void) path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _close(int fd) {
|
||||
(void) fd;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _isatty(int fd) {
|
||||
(void) fd;
|
||||
return 1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
|
||||
(void) fd, (void) ptr, (void) dir;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _exit(int status) {
|
||||
(void) status;
|
||||
for (;;) asm volatile("BKPT #0");
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _kill(int pid, int sig) {
|
||||
(void) pid, (void) sig;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _getpid(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
|
||||
(void) fd, (void) ptr, (void) len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
|
||||
(void) fd, (void) ptr, (void) len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _link(const char *a, const char *b) {
|
||||
(void) a, (void) b;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _unlink(const char *a) {
|
||||
(void) a;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _stat(const char *path, struct stat *st) {
|
||||
(void) path, (void) st;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int mkdir(const char *path, int mode) {
|
||||
(void) path, (void) mode;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _init(void) {
|
||||
}
|
||||
|
||||
uint64_t mg_micros(void);
|
||||
// SysTick runs at the core clock and reloads every millisecond. Sample
|
||||
// the tick counter and the sub-millisecond counter in one critical section.
|
||||
uint64_t mg_micros(void) {
|
||||
uint32_t primask = __get_PRIMASK(), val, pending;
|
||||
uint64_t ms;
|
||||
__disable_irq();
|
||||
ms = hal_get_tick();
|
||||
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
|
||||
pending = SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
}
|
||||
__set_PRIMASK(primask);
|
||||
if (pending != 0 && val != 0) ms++;
|
||||
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
|
||||
(SysTick->LOAD + 1);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "nrf.h"
|
||||
uint64_t hal_get_tick(void);
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
|
||||
size_t hal_ram_used(void);
|
||||
size_t hal_ram_free(void);
|
||||
void usb_init(void);
|
||||
void usb_poll(void);
|
||||
void usb_log(char ch, void *param);
|
||||
|
||||
#define BIT(x) (1UL << (x))
|
||||
#define PIN(port, num) (((port) << 5) | (num))
|
||||
#define PINNO(pin) ((pin) & 31)
|
||||
#define PINPORT(pin) ((pin) >> 5)
|
||||
|
||||
static inline NRF_GPIO_Type *hal_gpio_bank(uint16_t pin) {
|
||||
return PINPORT(pin) == 0 ? NRF_P0 : NRF_P1;
|
||||
}
|
||||
|
||||
static inline void hal_gpio_write(uint16_t pin, bool val) {
|
||||
NRF_GPIO_Type *gpio = hal_gpio_bank(pin);
|
||||
if (val) {
|
||||
gpio->OUTSET = BIT(PINNO(pin));
|
||||
} else {
|
||||
gpio->OUTCLR = BIT(PINNO(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hal_gpio_toggle(uint16_t pin) {
|
||||
hal_gpio_write(pin, (hal_gpio_bank(pin)->OUT & BIT(PINNO(pin))) == 0);
|
||||
}
|
||||
|
||||
static inline void hal_gpio_output(uint16_t pin) {
|
||||
hal_gpio_bank(pin)->PIN_CNF[PINNO(pin)] =
|
||||
(GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
|
||||
(GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos);
|
||||
}
|
||||
|
||||
static inline void hal_system_init(void) {
|
||||
extern uint32_t __isr_vector[];
|
||||
SCB->VTOR = (uint32_t) __isr_vector; // Application vectors above the bootloader
|
||||
__DSB();
|
||||
__ISB();
|
||||
__enable_irq();
|
||||
NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
|
||||
NRF_CLOCK->TASKS_HFCLKSTART = 1;
|
||||
while (!NRF_CLOCK->EVENTS_HFCLKSTARTED) (void) 0;
|
||||
SystemCoreClockUpdate();
|
||||
SysTick_Config(SystemCoreClock / 1000);
|
||||
}
|
||||
|
||||
static inline void hal_rng_init(void) {
|
||||
NRF_RNG->CONFIG = 1;
|
||||
NRF_RNG->TASKS_START = 1;
|
||||
}
|
||||
|
||||
static inline uint32_t hal_rng_read(void) {
|
||||
uint32_t value = 0;
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
while (!NRF_RNG->EVENTS_VALRDY) (void) 0;
|
||||
value |= (uint32_t) NRF_RNG->VALUE << (i * 8);
|
||||
NRF_RNG->EVENTS_VALRDY = 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* nRF52840 on the XIAO nRF52840, with the Adafruit UF2 bootloader. Flash:
|
||||
* MBR and SoftDevice S140 below 0x27000, the bootloader from 0xED000 up: the
|
||||
* application in between. The SoftDevice is not enabled, so all RAM is ours,
|
||||
* except the first 24k it would take: kept free, as the Arduino core does */
|
||||
ENTRY(Reset_Handler);
|
||||
MEMORY {
|
||||
flash(rx) : ORIGIN = 0x00027000, LENGTH = 0xC6000
|
||||
sram(rwx) : ORIGIN = 0x20006000, LENGTH = 0x3A000
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.vectors : { KEEP(*(.isr_vector)) . = ALIGN(4); } > flash
|
||||
.text : { *(.text* .text.*) } > flash
|
||||
.rodata : { *(.rodata*) . = ALIGN(4); } > flash
|
||||
|
||||
.data : {
|
||||
_sdata = .;
|
||||
*(.first_data)
|
||||
*(.iram .iram.*) /* Mongoose MG_IRAM functions */
|
||||
*(.data SORT(.data.*))
|
||||
. = ALIGN(4);
|
||||
_edata = .;
|
||||
} > sram AT > flash
|
||||
__data_start = _sdata;
|
||||
__data_end = _edata;
|
||||
__data_load_start = LOADADDR(.data);
|
||||
/* Unused data regions expected by Nordic's standard startup. */
|
||||
__sdata_start = _edata;
|
||||
__sdata_end = _edata;
|
||||
__sdata_load_start = _edata;
|
||||
__tdata_start = _edata;
|
||||
__tdata_end = _edata;
|
||||
__tdata_load_start = _edata;
|
||||
__fast_start = _edata;
|
||||
__fast_end = _edata;
|
||||
__fast_load_start = _edata;
|
||||
|
||||
.bss : {
|
||||
_sbss = .;
|
||||
*(.bss SORT(.bss.*) COMMON)
|
||||
. = ALIGN(4);
|
||||
_ebss = .;
|
||||
} > sram
|
||||
__bss_start__ = _sbss;
|
||||
__bss_end__ = _ebss;
|
||||
__tbss_start__ = _ebss;
|
||||
__tbss_end__ = _ebss;
|
||||
__sbss_start__ = _ebss;
|
||||
__sbss_end__ = _ebss;
|
||||
|
||||
/* Health record, see MG_HEALTH_RAM: not cleared, survives a warm reset */
|
||||
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > sram
|
||||
|
||||
. = ALIGN(8);
|
||||
_end = .; /* Start of heap, see _sbrk() in hal.c */
|
||||
|
||||
/* Nordic startup reserves its default 16 KiB stack at the top of RAM. */
|
||||
.stack ORIGIN(sram) + LENGTH(sram) - 0x4000 (NOLOAD) : {
|
||||
KEEP(*(.stack))
|
||||
} > sram
|
||||
ASSERT(_end <= __StackLimit, "RAM sections overlap the stack")
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
#define LED1 PIN(0, 26) // P0.26, user LED, active low
|
||||
|
||||
static void log_fn(char ch, void *param) {
|
||||
usb_log(ch, param);
|
||||
}
|
||||
|
||||
static void blink_task(void) {
|
||||
static uint64_t blink_timer = 0;
|
||||
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
|
||||
hal_gpio_toggle(LED1);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return hal_get_tick();
|
||||
}
|
||||
|
||||
bool mg_random(void *buf, size_t len) {
|
||||
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
|
||||
uint32_t r = hal_rng_read();
|
||||
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Self-signed P-256 certificate and key for the HTTPS listener. Created by:
|
||||
// openssl ecparam -genkey -name prime256v1 -noout -out key.pem
|
||||
// openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg
|
||||
#define TLS_CERT \
|
||||
"-----BEGIN CERTIFICATE-----\n" \
|
||||
"MIIBgjCCASegAwIBAgIUIJOq77Qn7lHbH6WseMemeqorR50wCgYIKoZIzj0EAwIw\n" \
|
||||
"FjEUMBIGA1UEAwwLbnVjbGVvLWg3MjMwHhcNMjYxMDEyMTUwMjE5WhcNMzYxMDA5\n" \
|
||||
"MTUwMjE5WjAWMRQwEgYDVQQDDAtudWNsZW8taDcyMzBZMBMGByqGSM49AgEGCCqG\n" \
|
||||
"SM49AwEHA0IABB6IQJNmzrsdQL+2BnZYS7GYOaj7sz8f6cjBmz3n8D9Em19XodlU\n" \
|
||||
"uy4ybIvyhr766lL0ERoTmMUivb/lGmzx9C+jUzBRMB0GA1UdDgQWBBQqxMuc4gj7\n" \
|
||||
"MTMzreWlRMNI3QMqTzAfBgNVHSMEGDAWgBQqxMuc4gj7MTMzreWlRMNI3QMqTzAP\n" \
|
||||
"BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQDY4xaJpuxq6b3rHK3b\n" \
|
||||
"lOIQyfkQX+orCRqQUkseplqpqwIhAKswZFNn+2zUNRRNY8+cHmdIwmdpsmxtoWYP\n" \
|
||||
"BCjX++LI\n" \
|
||||
"-----END CERTIFICATE-----\n"
|
||||
|
||||
#define TLS_KEY \
|
||||
"-----BEGIN EC PRIVATE KEY-----\n" \
|
||||
"MHcCAQEEIJ/GBl0TlD52q9PMg1KWaNIdXqp8b/HyIsABmBpqC+O1oAoGCCqGSM49\n" \
|
||||
"AwEHoUQDQgAEHohAk2bOux1Av7YGdlhLsZg5qPuzPx/pyMGbPefwP0SbX1eh2VS7\n" \
|
||||
"LjJsi/KGvvrqUvQRGhOYxSK9v+UabPH0Lw==\n" \
|
||||
"-----END EC PRIVATE KEY-----\n"
|
||||
|
||||
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_ACCEPT && c->is_tls) { // Accepted on the https:// listener
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
|
||||
} else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) {
|
||||
__asm volatile("udf #0"); // Trigger HardFault
|
||||
} else {
|
||||
mg_http_reply(c, 200, "",
|
||||
"Hi from Mongoose!\n"
|
||||
"tick: %llu\n"
|
||||
"ram used: %zu\n"
|
||||
"ram free: %zu\n",
|
||||
hal_get_tick(), hal_ram_used(), hal_ram_free());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
MG_HEALTH_INIT();
|
||||
|
||||
hal_system_init();
|
||||
usb_init();
|
||||
mg_log_set_fn(log_fn, NULL);
|
||||
hal_rng_init();
|
||||
hal_gpio_write(LED1, true);
|
||||
hal_gpio_output(LED1);
|
||||
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_http_listen(&mgr, "https://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_mqtt_init(&mgr);
|
||||
|
||||
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
|
||||
MG_INFO(("RAM used: %zu, free: %zu", hal_ram_used(), hal_ram_free()));
|
||||
MG_INFO(("Crash record: %M", mg_print_crash_record));
|
||||
|
||||
for (;;) {
|
||||
usb_poll();
|
||||
mg_mgr_poll(&mgr, 0);
|
||||
mg_mqtt_poll(&mgr);
|
||||
blink_task();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "nrf.h"
|
||||
#define MG_ARCH MG_ARCH_ARMGCC
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
#define MG_ENABLE_DRIVER_NRF52 1
|
||||
#define MG_ENABLE_TCPIP_TCPRTX 1
|
||||
#define MG_ENABLE_CUSTOM_MILLIS 1
|
||||
#define MG_ENABLE_CUSTOM_RANDOM 1
|
||||
#define MG_TLS MG_TLS_BUILTIN
|
||||
#define MQTT_CLIENT_ID "xiao-nrf52840"
|
||||
|
||||
#ifndef MQTT_SERVER_URL
|
||||
#define MQTT_SERVER_URL "mqtt://broker.emqx.io:1883"
|
||||
#endif
|
||||
|
||||
// Stable, locally administered MAC derived from the factory device ID.
|
||||
#define MG_SET_MAC_ADDRESS(mac) \
|
||||
do { \
|
||||
uint32_t id_[2] = {NRF_FICR->DEVICEID[0], NRF_FICR->DEVICEID[1]}; \
|
||||
uint8_t hash_[32]; \
|
||||
mg_sha256(hash_, (uint8_t *) id_, sizeof(id_)); \
|
||||
memcpy(mac, hash_, 6); \
|
||||
(mac)[0] = ((mac)[0] & 0xfc) | 2; \
|
||||
} while (0)
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#define CFG_TUSB_MCU OPT_MCU_NRF5X
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE
|
||||
#define CFG_TUD_ENDPOINT0_SIZE 64
|
||||
#define CFG_TUD_CDC 1
|
||||
#define CFG_TUD_CDC_RX_BUFSIZE 256
|
||||
#define CFG_TUD_CDC_TX_BUFSIZE 4096
|
||||
#define CFG_TUD_CDC_EP_BUFSIZE 64
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "hal.h"
|
||||
#include "tusb.h"
|
||||
extern void tusb_hal_nrf_power_event(uint32_t event);
|
||||
void USBD_IRQHandler(void) {
|
||||
tud_int_handler(0);
|
||||
}
|
||||
void POWER_CLOCK_IRQHandler(void) {
|
||||
if (NRF_POWER->EVENTS_USBDETECTED) {
|
||||
NRF_POWER->EVENTS_USBDETECTED = 0;
|
||||
tusb_hal_nrf_power_event(0);
|
||||
}
|
||||
if (NRF_POWER->EVENTS_USBREMOVED) {
|
||||
NRF_POWER->EVENTS_USBREMOVED = 0;
|
||||
tusb_hal_nrf_power_event(1);
|
||||
}
|
||||
if (NRF_POWER->EVENTS_USBPWRRDY) {
|
||||
NRF_POWER->EVENTS_USBPWRRDY = 0;
|
||||
tusb_hal_nrf_power_event(2);
|
||||
}
|
||||
}
|
||||
void usb_init(void) {
|
||||
NRF_POWER->INTENSET = POWER_INTENSET_USBDETECTED_Msk |
|
||||
POWER_INTENSET_USBREMOVED_Msk |
|
||||
POWER_INTENSET_USBPWRRDY_Msk;
|
||||
NVIC_SetPriority(POWER_CLOCK_IRQn, 3);
|
||||
NVIC_SetPriority(USBD_IRQn, 3);
|
||||
if (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk)
|
||||
tusb_hal_nrf_power_event(0);
|
||||
if (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_OUTPUTRDY_Msk)
|
||||
tusb_hal_nrf_power_event(2);
|
||||
NRF_POWER->EVENTS_USBDETECTED = 0;
|
||||
NRF_POWER->EVENTS_USBPWRRDY = 0;
|
||||
NRF_POWER->EVENTS_USBREMOVED = 0;
|
||||
NVIC_ClearPendingIRQ(POWER_CLOCK_IRQn);
|
||||
tusb_init();
|
||||
NVIC_EnableIRQ(POWER_CLOCK_IRQn);
|
||||
}
|
||||
void usb_poll(void) {
|
||||
tud_task();
|
||||
tud_cdc_write_flush();
|
||||
}
|
||||
void usb_log(char ch, void *param) {
|
||||
(void) param;
|
||||
if (tud_cdc_connected()) tud_cdc_write_char(ch);
|
||||
}
|
||||
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
|
||||
cdc_line_coding_t lc;
|
||||
(void) rts;
|
||||
tud_cdc_n_get_line_coding(itf, &lc);
|
||||
if (!dtr && lc.bit_rate == 1200) {
|
||||
NRF_POWER->GPREGRET = 0x57;
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
}
|
||||
static const tusb_desc_device_t s_dev = {
|
||||
.bLength = sizeof(tusb_desc_device_t),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = TUSB_CLASS_MISC,
|
||||
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
||||
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
||||
.bMaxPacketSize0 = 64,
|
||||
.idVendor = 0xcafe,
|
||||
.idProduct = 0x4001,
|
||||
.bcdDevice = 0x0100,
|
||||
.iManufacturer = 1,
|
||||
.iProduct = 2,
|
||||
.iSerialNumber = 3,
|
||||
.bNumConfigurations = 1};
|
||||
uint8_t const *tud_descriptor_device_cb(void) {
|
||||
return (const uint8_t *) &s_dev;
|
||||
}
|
||||
static const uint8_t s_cfg[] = {
|
||||
TUD_CONFIG_DESCRIPTOR(1, 2, 0, TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN, 0,
|
||||
100),
|
||||
TUD_CDC_DESCRIPTOR(0, 0, 0x81, 8, 0x02, 0x82, 64)};
|
||||
uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
|
||||
(void) index;
|
||||
return s_cfg;
|
||||
}
|
||||
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
|
||||
static uint16_t buf[32];
|
||||
const char *strings[] = {"", "Cesanta", "XIAO nRF52840", "mongoose-nrf52840"};
|
||||
size_t n = 0;
|
||||
(void) langid;
|
||||
if (index == 0) {
|
||||
buf[1] = 0x0409;
|
||||
n = 1;
|
||||
} else if (index < 4) {
|
||||
const char *s = strings[index];
|
||||
while (s[n] && n < 31) {
|
||||
buf[n + 1] = s[n];
|
||||
n++;
|
||||
}
|
||||
} else
|
||||
return NULL;
|
||||
buf[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * n + 2));
|
||||
return buf;
|
||||
}
|
||||
@@ -65,6 +65,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
|
||||
@@ -89,6 +89,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
|
||||
@@ -97,6 +97,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
MGDIR = ../../../..
|
||||
|
||||
CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion
|
||||
CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow
|
||||
CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion
|
||||
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
|
||||
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include
|
||||
@@ -10,9 +10,12 @@ CFLAGS += -Icmsis_mcxw71/MCXW/MCXW716C -Icmsis_mcxw71/MCXW/periph2
|
||||
CFLAGS += -DCPU_MCXW716CMFTA
|
||||
CFLAGS += -D__START=main -D__STARTUP_CLEAR_BSS # For startup_MCXW716C.S
|
||||
CFLAGS += -mcpu=cortex-m33 -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CFLAGS_EXTRA)
|
||||
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections
|
||||
|
||||
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lm -lgcc -Wl,--gc-sections
|
||||
|
||||
SOURCES = main.c hal.c $(MGDIR)/mongoose.c
|
||||
|
||||
SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c
|
||||
SOURCES += cmsis_mcxw71/MCXW/MCXW716C/gcc/startup_MCXW716C.S
|
||||
|
||||
all build: firmware.bin
|
||||
@@ -21,9 +24,13 @@ firmware.bin: firmware.elf
|
||||
arm-none-eabi-objcopy -O binary $< $@
|
||||
arm-none-eabi-size --format=berkeley $<
|
||||
|
||||
firmware.elf: cmsis_core cmsis_mcxw71 $(SOURCES) $(wildcard *.h) link.ld Makefile
|
||||
firmware.elf: cmsis_core cmsis_mcxw71 $(SOURCES) $(wildcard *.h) $(MGDIR)/mongoose.h link.ld Makefile mesh-config
|
||||
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
# Rebuild when overriding the board address or timing.
|
||||
.PHONY: mesh-config
|
||||
mesh-config:
|
||||
|
||||
cmsis_core:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
|
||||
|
||||
|
||||
@@ -2,6 +2,63 @@
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
// Initialise the transceiver the way NXP XCVR_Init() does it: start the RF
|
||||
// oscillator, wait for the radio to wake up, enable its clocks and load the
|
||||
// 802.15.4 register setup. Then select the legacy 802.15.4 demodulator and
|
||||
// CCA from RX digital. See PhyHwInit() in NXP ieee_802_15_4 PHY sources,
|
||||
// PhyPacketProcessor.c
|
||||
void hal_radio_init(void) {
|
||||
uint32_t stat = RFMC_RF2P4GHZ_STAT_RST_STAT_MASK |
|
||||
RFMC_RF2P4GHZ_STAT_LP_ACK_STAT_MASK;
|
||||
size_t i, n = 1000000;
|
||||
RFMC->CTRL &= ~RFMC_CTRL_RFMC_RST_MASK; // RFMC out of reset
|
||||
RFMC->RF2P4GHZ_CTRL |= RFMC_RF2P4GHZ_CTRL_XO_EN_MASK; // Start RF oscillator
|
||||
RFMC->RF2P4GHZ_CTRL &=
|
||||
~(RFMC_RF2P4GHZ_CTRL_LP_ENTER_MASK | RFMC_RF2P4GHZ_CTRL_RF_POR_MASK |
|
||||
RFMC_RF2P4GHZ_CTRL_RST_MASK); // Release radio reset, no low power
|
||||
while ((RFMC->RF2P4GHZ_STAT & stat) != 0 ||
|
||||
(RFMC->XO_STAT & RFMC_XO_STAT_XTAL_RDY_MASK) == 0) {
|
||||
if (--n == 0) {
|
||||
MG_ERROR(("Radio init failed")); // Radio did not wake up
|
||||
return;
|
||||
}
|
||||
}
|
||||
RADIO_CTRL->LL_CTRL = RADIO_CTRL_LL_CTRL_ACTIVE_LL_MASK;
|
||||
RFMC->RF2P4GHZ_CTRL |=
|
||||
RFMC_RF2P4GHZ_CTRL_BLE_LP_EN_MASK | RFMC_RF2P4GHZ_CTRL_BLE_WKUP_MASK;
|
||||
for (i = 0; i < sizeof(s_radio_regs) / sizeof(s_radio_regs[0]); i++) {
|
||||
*s_radio_regs[i].reg = s_radio_regs[i].val;
|
||||
}
|
||||
XCVR_MISC->XCVR_CTRL &= ~XCVR_MISC_XCVR_CTRL_DEMOD_SEL_MASK;
|
||||
XCVR_MISC->XCVR_CTRL |= XCVR_MISC_XCVR_CTRL_DEMOD_SEL(2);
|
||||
XCVR_ZBDEMOD->CCA_LQI_SRC |= XCVR_ZBDEMOD_CCA_LQI_SRC_CCA1_FROM_RX_DIG(1);
|
||||
}
|
||||
|
||||
// Random 32 bits, for mg_random(). Entropy is radio noise: receiver RSSI,
|
||||
// sampled while the radio receives, mixed with SysTick by SHA-256. One hash
|
||||
// fills a pool of 8 words. Good enough for random addresses and nonces, not
|
||||
// a certified TRNG
|
||||
uint32_t hal_rng_read(void) {
|
||||
static uint32_t counter, pool[8];
|
||||
static size_t avail;
|
||||
size_t i, n;
|
||||
if (avail == 0) {
|
||||
mg_sha256_ctx ctx;
|
||||
mg_sha256_init(&ctx);
|
||||
counter++;
|
||||
mg_sha256_update(&ctx, (uint8_t *) &counter, sizeof(counter));
|
||||
for (i = 0; i < 64; i++) {
|
||||
uint32_t sample[2] = {XCVR_RX_DIG->NB_RSSI_RES0, SysTick->VAL};
|
||||
mg_sha256_update(&ctx, (uint8_t *) sample, sizeof(sample));
|
||||
for (n = 0; n < 50; n++) __NOP(); // Let the RSSI update
|
||||
}
|
||||
mg_sha256_final((uint8_t *) pool, &ctx);
|
||||
avail = sizeof(pool) / sizeof(pool[0]);
|
||||
}
|
||||
return pool[--avail];
|
||||
}
|
||||
|
||||
// Added to minimise the bin size: avoid pulling printf and friends
|
||||
void __assert_func(const char *s, int line, const char *fn, const char *expr) {
|
||||
@@ -26,6 +83,26 @@ uint64_t hal_get_tick(void) {
|
||||
return s_ticks;
|
||||
}
|
||||
|
||||
uint64_t mg_micros(void);
|
||||
// SysTick runs at the core clock and reloads every millisecond. Sample
|
||||
// the tick counter and the sub-millisecond counter in one critical section.
|
||||
uint64_t mg_micros(void) {
|
||||
uint32_t primask = __get_PRIMASK(), val, pending;
|
||||
uint64_t ms;
|
||||
__disable_irq();
|
||||
ms = hal_get_tick();
|
||||
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
|
||||
pending = SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
}
|
||||
__set_PRIMASK(primask);
|
||||
if (pending != 0 && val != 0) ms++;
|
||||
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
|
||||
(SysTick->LOAD + 1);
|
||||
}
|
||||
|
||||
uint32_t SystemCoreClock;
|
||||
|
||||
// Called by the startup code before .data and .bss are initialised, so it
|
||||
|
||||
@@ -13,13 +13,21 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define BIT(x) (1UL << (x))
|
||||
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
|
||||
|
||||
#define SIRC_FREQUENCY 6000000UL // Slow IRC (FRO6M), Hz
|
||||
|
||||
uint64_t hal_get_tick(void);
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
|
||||
size_t hal_ram_used(void);
|
||||
size_t hal_ram_free(void);
|
||||
void hal_radio_init(void); // Set up the 2.4 GHz transceiver for 802.15.4
|
||||
uint32_t hal_rng_read(void); // Random 32 bits, radio noise. See hal.c
|
||||
extern uint32_t SystemCoreClock; // CPU clock, Hz. Set by hal_clock_init()
|
||||
|
||||
#define BIT(x) (1UL << (x))
|
||||
#define PIN(port, num) ((((port) - 'A') << 8) | (num))
|
||||
#define PINNO(pin) (pin & 255)
|
||||
#define PINPORT(pin) (pin >> 8)
|
||||
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
|
||||
#define SIRC_FREQUENCY 6000000UL // Slow IRC (FRO6M), Hz
|
||||
|
||||
// MRCC peripheral clock source, MRCC_xx[MUX], see fsl_clock.h
|
||||
enum { MRCC_SRC_FRO6M = 2, MRCC_SRC_FRO192M = 3 };
|
||||
|
||||
@@ -30,10 +38,43 @@ static inline uint32_t hal_firc_freq(void) {
|
||||
return freqs[SCG0->FIRCCFG & SCG_FIRCCFG_RANGE_MASK];
|
||||
}
|
||||
|
||||
// Work out the CPU clock from the SCG clock source and core divider. The
|
||||
// clock is left as configured at reset
|
||||
// Switch the system clock: source (2 SIRC, 3 FIRC), core and slow dividers
|
||||
// as divider - 1. Wait until the switch is done. See CLOCK_SetRunModeSysClkConfig
|
||||
static inline void hal_set_sysclk(uint32_t src, uint32_t divcore,
|
||||
uint32_t divslow) {
|
||||
SCG0->RCCR = SCG_RCCR_SCS(src) | SCG_RCCR_DIVCORE(divcore) |
|
||||
SCG_RCCR_DIVSLOW(divslow);
|
||||
while (((SCG0->CSR & SCG_CSR_SCS_MASK) >> SCG_CSR_SCS_SHIFT) != src) (void) 0;
|
||||
}
|
||||
|
||||
// Run the core at 96 MHz from FIRC, slow clock 24 MHz. Order as in NXP
|
||||
// BOARD_BootClockRUN(), clock_config.c: raise the core voltage and memory
|
||||
// timing first, then the clock. FIRC can only be changed when not in use
|
||||
static inline void hal_clock_96mhz(void) {
|
||||
while (SPC0->SC & SPC_SC_BUSY_MASK) (void) 0;
|
||||
CLRSET(SPC0->ACTIVE_CFG, SPC_ACTIVE_CFG_CORELDO_VDD_LVL_MASK,
|
||||
SPC_ACTIVE_CFG_CORELDO_VDD_LVL(2)); // Normal voltage
|
||||
while (SPC0->SC & SPC_SC_BUSY_MASK) (void) 0;
|
||||
CLRSET(FMU0->FCTRL, FMU_FCTRL_RWSC_MASK, FMU_FCTRL_RWSC(2)); // Wait states
|
||||
CLRSET(SPC0->SRAMCTL, SPC_SRAMCTL_VSM_MASK, SPC_SRAMCTL_VSM(2)); // 1.1 V
|
||||
SPC0->SRAMCTL |= SPC_SRAMCTL_REQ_MASK;
|
||||
while ((SPC0->SRAMCTL & SPC_SRAMCTL_ACK_MASK) == 0) (void) 0;
|
||||
SPC0->SRAMCTL &= ~SPC_SRAMCTL_REQ_MASK;
|
||||
hal_set_sysclk(2, 0, 3); // Run from SIRC meanwhile
|
||||
SCG0->FIRCCSR &= ~SCG_FIRCCSR_LK_MASK; // Unlock
|
||||
SCG0->FIRCCSR = SCG_FIRCCSR_FIRCERR_MASK; // Disable, clear error
|
||||
SCG0->FIRCCFG = SCG_FIRCCFG_RANGE(2); // 96 MHz
|
||||
SCG0->FIRCCSR |= SCG_FIRCCSR_FIRCEN_MASK;
|
||||
while ((SCG0->FIRCCSR & SCG_FIRCCSR_FIRCVLD_MASK) == 0) (void) 0;
|
||||
hal_set_sysclk(3, 0, 3); // FIRC, core / 1, slow / 4
|
||||
}
|
||||
|
||||
// Set the CPU clock to 96 MHz, then work out SystemCoreClock from the SCG
|
||||
// clock source and core divider
|
||||
static inline void hal_clock_init(void) {
|
||||
uint32_t csr = SCG0->CSR, freq = 0;
|
||||
uint32_t csr, freq = 0;
|
||||
hal_clock_96mhz();
|
||||
csr = SCG0->CSR;
|
||||
uint32_t src = (csr & SCG_CSR_SCS_MASK) >> SCG_CSR_SCS_SHIFT;
|
||||
uint32_t div = ((csr & SCG_CSR_DIVCORE_MASK) >> SCG_CSR_DIVCORE_SHIFT) + 1;
|
||||
if (src == 2) freq = SIRC_FREQUENCY; // Slow IRC
|
||||
@@ -54,15 +95,53 @@ static inline void hal_periph_clock(volatile uint32_t *mrcc, uint32_t src) {
|
||||
__DSB();
|
||||
}
|
||||
|
||||
// LPUART1 is wired to the MCU-Link debug probe USB serial port.
|
||||
// TX = PTC3, RX = PTC2, both pin mux alternative 3. See board.h, pin_mux.c
|
||||
static inline void hal_uart_init(LPUART_Type *uart, unsigned long baud) {
|
||||
// PORT, the pin multiplexer, of a pin. Ports A..C
|
||||
static inline PORT_Type *hal_pin_port(uint16_t pin) {
|
||||
PORT_Type *ports[] = {PORTA, PORTB, PORTC};
|
||||
return ports[PINPORT(pin)];
|
||||
}
|
||||
|
||||
// GPIO of a pin. Ports A..C
|
||||
static inline GPIO_Type *hal_pin_gpio(uint16_t pin) {
|
||||
GPIO_Type *gpios[] = {GPIOA, GPIOB, GPIOC};
|
||||
return gpios[PINPORT(pin)];
|
||||
}
|
||||
|
||||
// Route a pin to function mux: 1 GPIO, others see the datasheet pinout.
|
||||
// Clocks its PORT: MRCC_PORTA..C are consecutive
|
||||
static inline void hal_pin_mux(uint16_t pin, uint32_t mux) {
|
||||
hal_periph_clock(&MRCC->MRCC_PORTA + PINPORT(pin), 0);
|
||||
hal_pin_port(pin)->PCR[PINNO(pin)] = PORT_PCR_MUX(mux);
|
||||
}
|
||||
|
||||
static inline void hal_gpio_output(uint16_t pin) {
|
||||
hal_pin_mux(pin, 1);
|
||||
hal_periph_clock(&MRCC->MRCC_GPIOA + PINPORT(pin), 0); // Consecutive, too
|
||||
hal_pin_gpio(pin)->PDDR |= BIT(PINNO(pin));
|
||||
}
|
||||
|
||||
static inline void hal_gpio_write(uint16_t pin, bool val) {
|
||||
if (val) {
|
||||
hal_pin_gpio(pin)->PSOR = BIT(PINNO(pin));
|
||||
} else {
|
||||
hal_pin_gpio(pin)->PCOR = BIT(PINNO(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hal_gpio_toggle(uint16_t pin) {
|
||||
hal_pin_gpio(pin)->PTOR = BIT(PINNO(pin));
|
||||
}
|
||||
|
||||
// LPUART1, on tx and rx pins, pin mux alternative 3. On FRDM-MCXW71 it is
|
||||
// wired to the MCU-Link debug probe USB serial port: TX PTC3, RX PTC2, see
|
||||
// board.h, pin_mux.c
|
||||
static inline void hal_uart_init(LPUART_Type *uart, uint16_t tx, uint16_t rx,
|
||||
unsigned long baud) {
|
||||
uint32_t src = MRCC_SRC_FRO192M, freq = hal_firc_freq();
|
||||
uint32_t best_osr = 16, best_sbr = 1, best_err = UINT32_MAX, osr;
|
||||
if (freq == 0) src = MRCC_SRC_FRO6M, freq = SIRC_FREQUENCY; // FIRC off
|
||||
hal_periph_clock(&MRCC->MRCC_PORTC, 0);
|
||||
PORTC->PCR[2] = PORT_PCR_MUX(3); // RX
|
||||
PORTC->PCR[3] = PORT_PCR_MUX(3); // TX
|
||||
hal_pin_mux(rx, 3);
|
||||
hal_pin_mux(tx, 3);
|
||||
hal_periph_clock(&MRCC->MRCC_LPUART1, src);
|
||||
// Find the oversampling ratio (4..32) that gives the lowest baud rate error
|
||||
for (osr = 4; osr <= 32; osr++) {
|
||||
@@ -103,12 +182,283 @@ static inline void hal_wdog_disable(void) {
|
||||
while ((WDOG0->CS & WDOG_CS_RCS_MASK) == 0) (void) 0;
|
||||
}
|
||||
|
||||
// Random numbers come from radio noise, see hal_rng_read(): nothing to set
|
||||
// up here. The radio must be running, see hal_radio_init()
|
||||
static inline void hal_rng_init(void) {
|
||||
}
|
||||
|
||||
static inline void hal_sys_init(void) {
|
||||
SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU
|
||||
hal_wdog_disable();
|
||||
}
|
||||
|
||||
uint64_t hal_get_tick(void);
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
|
||||
size_t hal_ram_used(void);
|
||||
size_t hal_ram_free(void);
|
||||
// 802.15.4 (O-QPSK 250 kbps) radio register setup for the MCXW71 transceiver.
|
||||
// Values are taken from the NXP XCVR driver, release/25.06.00, configs/gen45:
|
||||
// XCVR_RadioGenRegSetup() and XCVR_RadioGenRBMESetup() with
|
||||
// xcvr_oqpsk_802p15p4_250kbps_full_config and xcvr_ble_uncoded_config.
|
||||
// https://github.com/nxp-mcuxpresso/mcuxsdk-middleware-xcvr
|
||||
// Copyright 2016 Freescale Semiconductor, Inc.
|
||||
// Copyright 2018-2023 NXP
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// One radio register write
|
||||
struct w71_radio_reg {
|
||||
volatile uint32_t *reg; // Register address
|
||||
uint32_t val; // Value to write
|
||||
};
|
||||
|
||||
// Register writes, in the order the NXP driver performs them
|
||||
static const struct w71_radio_reg s_radio_regs[] = {
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[0].DMD_WAVE_REG0, 0x0420b3d3},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[0].DMD_WAVE_REG1, 0x31d78f00},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[0].DMD_WAVE_REG2, 0x00025a6d},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[1].DMD_WAVE_REG0, 0x0420b3d3},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[1].DMD_WAVE_REG1, 0x38d78f00},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[1].DMD_WAVE_REG2, 0x0000403c},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[2].DMD_WAVE_REG0, 0x3c00420b},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[2].DMD_WAVE_REG1, 0x082c8100},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[2].DMD_WAVE_REG2, 0x0003c004},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[3].DMD_WAVE_REG0, 0x3c00420b},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[3].DMD_WAVE_REG1, 0x0f2c8100},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[3].DMD_WAVE_REG2, 0x0001b5d3},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[4].DMD_WAVE_REG0, 0x0403ce35},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[4].DMD_WAVE_REG1, 0x31d78f00},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[4].DMD_WAVE_REG2, 0x00025a6d},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[5].DMD_WAVE_REG0, 0x0403ce35},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[5].DMD_WAVE_REG1, 0x38d78f00},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[5].DMD_WAVE_REG2, 0x0000403c},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[6].DMD_WAVE_REG0, 0x3ce35c6d},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[6].DMD_WAVE_REG1, 0x082c8100},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[6].DMD_WAVE_REG2, 0x0003c004},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[7].DMD_WAVE_REG0, 0x3ce35c6d},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[7].DMD_WAVE_REG1, 0x0f2c8100},
|
||||
{&XCVR_2P4GHZ_PHY->DEMOD_WAVE[7].DMD_WAVE_REG2, 0x0001b5d3},
|
||||
{&XCVR_2P4GHZ_PHY->DMD_CTRL0, 0x6d940101},
|
||||
{&XCVR_2P4GHZ_PHY->DMD_CTRL1, 0xa8049008},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_CFG0, 0x15001502},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_CFG1, 0x0f080020},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_CFG2, 0x40000000},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_FAD_CFG, 0x00000000},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_FAD_CTRL, 0x00000000},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_PD_CFG0, 0x0000ff0c},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_PD_CFG1, 0x00000055},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_PD_CFG2, 0x00f600f6},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_PD_PH[0], 0x00030503},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_PD_PH[1], 0x003d3b3d},
|
||||
{&XCVR_2P4GHZ_PHY->FSK_PT, 0x00000048},
|
||||
{&XCVR_2P4GHZ_PHY->LR_AA_CFG, 0x06020696},
|
||||
{&XCVR_2P4GHZ_PHY->LR_PD_CFG, 0x0001019a},
|
||||
{&XCVR_2P4GHZ_PHY->LR_PD_PH[0], 0x3d383028},
|
||||
{&XCVR_2P4GHZ_PHY->LR_PD_PH[1], 0x20283038},
|
||||
{&XCVR_2P4GHZ_PHY->LR_PD_PH[2], 0x03081018},
|
||||
{&XCVR_2P4GHZ_PHY->LR_PD_PH[3], 0x20181008},
|
||||
{&XCVR_2P4GHZ_PHY->MISC, 0x03ff00a0},
|
||||
{&XCVR_2P4GHZ_PHY->PREPHY_MISC, 0x00000c10},
|
||||
{&XCVR_2P4GHZ_PHY->RTT_CTRL, 0x00004166},
|
||||
{&XCVR_2P4GHZ_PHY->RTT_REF, 0x00806348},
|
||||
{&XCVR_2P4GHZ_PHY->SM_CFG, 0x00a02432},
|
||||
{&RADIO_CTRL->LL_CTRL, 0x00000001},
|
||||
{&XCVR_ANALOG->LDO_0, 0x04440400},
|
||||
{&XCVR_ANALOG->LDO_1, 0x00000005},
|
||||
{&XCVR_ANALOG->PLL, 0x00000040},
|
||||
{&XCVR_ANALOG->RX_0, 0x01000002},
|
||||
{&XCVR_ANALOG->RX_1, 0x00022008},
|
||||
{&XCVR_ANALOG->TX_DAC_PA, 0x01000000},
|
||||
{&XCVR_ANALOG->XO_DIST, 0x00000000},
|
||||
{&XCVR_MISC->IPS_FO_ADDR[0], 0x00000370},
|
||||
{&XCVR_MISC->IPS_FO_ADDR[1], 0x0000002c},
|
||||
{&XCVR_MISC->IPS_FO_DRS0_DATA[0], 0x00000000},
|
||||
{&XCVR_MISC->IPS_FO_DRS0_DATA[1], 0x00000000},
|
||||
{&XCVR_MISC->IPS_FO_DRS1_DATA[0], 0x00000000},
|
||||
{&XCVR_MISC->IPS_FO_DRS1_DATA[1], 0x00000000},
|
||||
{&XCVR_MISC->LDO_TRIM_0, 0x00020000},
|
||||
{&XCVR_MISC->LDO_TRIM_1, 0x00000000},
|
||||
{&XCVR_MISC->XCVR_CTRL, 0x00130180},
|
||||
{&XCVR_PLL_DIG->CHAN_MAP, 0x00040000},
|
||||
{&XCVR_PLL_DIG->CHAN_MAP_EXT, 0x00100000},
|
||||
{&XCVR_PLL_DIG->DATA_RATE_OVRD_CTRL1, 0x00000089},
|
||||
{&XCVR_PLL_DIG->DATA_RATE_OVRD_CTRL2, 0x00180000},
|
||||
{&XCVR_PLL_DIG->DELAY_MATCH, 0x00050102},
|
||||
{&XCVR_PLL_DIG->HPMCAL_CTRL, 0x00002000},
|
||||
{&XCVR_PLL_DIG->HPM_BUMP, 0x00440144},
|
||||
{&XCVR_PLL_DIG->HPM_CAL_TIMING, 0x00000000},
|
||||
{&XCVR_PLL_DIG->HPM_CTRL, 0x00840000},
|
||||
{&XCVR_PLL_DIG->HPM_SDM_RES, 0x00ff0000},
|
||||
{&XCVR_PLL_DIG->LOCK_DETECT, 0x0e1a1800},
|
||||
{&XCVR_PLL_DIG->LPM_CTRL, 0x88880a00},
|
||||
{&XCVR_PLL_DIG->LPM_SDM_CTRL1, 0x00260000},
|
||||
{&XCVR_PLL_DIG->MOD_CTRL, 0x80000000},
|
||||
{&XCVR_PLL_DIG->PLL_DATARATE_CTRL, 0x04126644},
|
||||
{&XCVR_PLL_DIG->TUNING_CAP_RX_CTRL, 0x00000000},
|
||||
{&XCVR_PLL_DIG->TUNING_CAP_TX_CTRL, 0x00ffffa3},
|
||||
{&XCVR_RX_DIG->ACQ_FILT_0_3, 0x737f0502},
|
||||
{&XCVR_RX_DIG->ACQ_FILT_0_3_DRS, 0x0f7f3a01},
|
||||
{&XCVR_RX_DIG->ACQ_FILT_10_11, 0x00ef0061},
|
||||
{&XCVR_RX_DIG->ACQ_FILT_10_11_DRS, 0x012b0021},
|
||||
{&XCVR_RX_DIG->ACQ_FILT_4_7, 0xf8221478},
|
||||
{&XCVR_RX_DIG->ACQ_FILT_4_7_DRS, 0x3c06617f},
|
||||
{&XCVR_RX_DIG->ACQ_FILT_8_9, 0x01d501bc},
|
||||
{&XCVR_RX_DIG->ACQ_FILT_8_9_DRS, 0x019401f3},
|
||||
{&XCVR_RX_DIG->AGC_CTRL, 0x587c8d19},
|
||||
{&XCVR_RX_DIG->AGC_CTRL_STAT, 0x0000872c},
|
||||
{&XCVR_RX_DIG->AGC_IDX0_GAIN_CFG, 0x000000f2},
|
||||
{&XCVR_RX_DIG->AGC_IDX0_GAIN_VAL, 0x000003fd},
|
||||
{&XCVR_RX_DIG->AGC_IDX0_THR, 0x0000001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX10_GAIN_CFG, 0x00004a03},
|
||||
{&XCVR_RX_DIG->AGC_IDX10_GAIN_VAL, 0x000000eb},
|
||||
{&XCVR_RX_DIG->AGC_IDX10_THR, 0x0096001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX11_GAIN_CFG, 0x00007e03},
|
||||
{&XCVR_RX_DIG->AGC_IDX11_GAIN_VAL, 0x000000fa},
|
||||
{&XCVR_RX_DIG->AGC_IDX11_THR, 0x00780000},
|
||||
{&XCVR_RX_DIG->AGC_IDX1_GAIN_CFG, 0x000001b2},
|
||||
{&XCVR_RX_DIG->AGC_IDX1_GAIN_VAL, 0x0000000b},
|
||||
{&XCVR_RX_DIG->AGC_IDX1_THR, 0x015e001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX2_GAIN_CFG, 0x00000123},
|
||||
{&XCVR_RX_DIG->AGC_IDX2_GAIN_VAL, 0x00000026},
|
||||
{&XCVR_RX_DIG->AGC_IDX2_THR, 0x00c8001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX3_GAIN_CFG, 0x00000113},
|
||||
{&XCVR_RX_DIG->AGC_IDX3_GAIN_VAL, 0x0000003c},
|
||||
{&XCVR_RX_DIG->AGC_IDX3_THR, 0x00c8001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX4_GAIN_CFG, 0x00000083},
|
||||
{&XCVR_RX_DIG->AGC_IDX4_GAIN_VAL, 0x00000057},
|
||||
{&XCVR_RX_DIG->AGC_IDX4_THR, 0x00c8001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX5_GAIN_CFG, 0x00000183},
|
||||
{&XCVR_RX_DIG->AGC_IDX5_GAIN_VAL, 0x0000006d},
|
||||
{&XCVR_RX_DIG->AGC_IDX5_THR, 0x00fa001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX6_GAIN_CFG, 0x00000203},
|
||||
{&XCVR_RX_DIG->AGC_IDX6_GAIN_VAL, 0x00000087},
|
||||
{&XCVR_RX_DIG->AGC_IDX6_THR, 0x012c001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX7_GAIN_CFG, 0x00000603},
|
||||
{&XCVR_RX_DIG->AGC_IDX7_GAIN_VAL, 0x000000a0},
|
||||
{&XCVR_RX_DIG->AGC_IDX7_THR, 0x012c001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX8_GAIN_CFG, 0x00000e03},
|
||||
{&XCVR_RX_DIG->AGC_IDX8_GAIN_VAL, 0x000000b7},
|
||||
{&XCVR_RX_DIG->AGC_IDX8_THR, 0x012c001e},
|
||||
{&XCVR_RX_DIG->AGC_IDX9_GAIN_CFG, 0x00002203},
|
||||
{&XCVR_RX_DIG->AGC_IDX9_GAIN_VAL, 0x000000d2},
|
||||
{&XCVR_RX_DIG->AGC_IDX9_THR, 0x0096001e},
|
||||
{&XCVR_RX_DIG->AGC_MIS_GAIN_CFG, 0x00000001},
|
||||
{&XCVR_RX_DIG->AGC_THR_FAST, 0x0190000f},
|
||||
{&XCVR_RX_DIG->AGC_THR_FAST_DRS, 0x0190001e},
|
||||
{&XCVR_RX_DIG->AGC_THR_MIS, 0x00960028},
|
||||
{&XCVR_RX_DIG->AGC_TIMING0, 0x04102398},
|
||||
{&XCVR_RX_DIG->AGC_TIMING0_DRS, 0x40002000},
|
||||
{&XCVR_RX_DIG->AGC_TIMING1, 0x190242a7},
|
||||
{&XCVR_RX_DIG->AGC_TIMING1_DRS, 0x000002a7},
|
||||
{&XCVR_RX_DIG->AGC_TIMING2, 0xe000007f},
|
||||
{&XCVR_RX_DIG->AGC_TIMING2_DRS, 0x0000007f},
|
||||
{&XCVR_RX_DIG->CTRL0, 0x68063083},
|
||||
{&XCVR_RX_DIG->CTRL0_DRS, 0x000030c0},
|
||||
{&XCVR_RX_DIG->CTRL1, 0x00000180},
|
||||
{&XCVR_RX_DIG->DCOC_CTRL0, 0x04332d28},
|
||||
{&XCVR_RX_DIG->DCOC_CTRL0_DRS, 0x00000114},
|
||||
{&XCVR_RX_DIG->DCOC_CTRL1, 0x00000000},
|
||||
{&XCVR_RX_DIG->DEMOD_FILT_0_1, 0x01fe01fd},
|
||||
{&XCVR_RX_DIG->DEMOD_FILT_0_1_DRS, 0x00ff0003},
|
||||
{&XCVR_RX_DIG->DEMOD_FILT_2_4, 0x0ba20824},
|
||||
{&XCVR_RX_DIG->DEMOD_FILT_2_4_DRS, 0x124221ff},
|
||||
{&XCVR_RX_DIG->NB_RSSI_CTRL0, 0xed472234},
|
||||
{&XCVR_RX_DIG->NB_RSSI_CTRL1, 0x00000000},
|
||||
{&XCVR_RX_DIG->RCCAL_CTRL0, 0x0000003a},
|
||||
{&XCVR_RX_DIG->RSSI_GLOBAL_CTRL, 0x80008330},
|
||||
{&XCVR_RX_DIG->WB_RSSI_CTRL, 0x00223342},
|
||||
{&XCVR_TSM->CTRL, 0xff000000},
|
||||
{&XCVR_TSM->END_OF_SEQ, 0x5c5a7270},
|
||||
{&XCVR_TSM->FAST_CTRL1, 0x24006f00},
|
||||
{&XCVR_TSM->FAST_CTRL2, 0x66284208},
|
||||
{&XCVR_TSM->FAST_CTRL3, 0x18001800},
|
||||
{&XCVR_TSM->RECYCLE_COUNT, 0x00001258},
|
||||
{&XCVR_TSM->TIMING00, 0xffffffff},
|
||||
{&XCVR_TSM->TIMING01, 0xffffffff},
|
||||
{&XCVR_TSM->TIMING02, 0xffffffff},
|
||||
{&XCVR_TSM->TIMING03, 0xffffffff},
|
||||
{&XCVR_TSM->TIMING04, 0xffffffff},
|
||||
{&XCVR_TSM->TIMING05, 0xffffffff},
|
||||
{&XCVR_TSM->TIMING06, 0xffffffff},
|
||||
{&XCVR_TSM->TIMING07, 0xffffffff},
|
||||
{&XCVR_TSM->TIMING08, 0xffffffff},
|
||||
{&XCVR_TSM->TIMING09, 0x5700ffff},
|
||||
{&XCVR_TSM->TIMING10, 0x11081108},
|
||||
{&XCVR_TSM->TIMING11, 0x5b137113},
|
||||
{&XCVR_TSM->TIMING12, 0x5b19714f},
|
||||
{&XCVR_TSM->TIMING13, 0x572dffff},
|
||||
{&XCVR_TSM->TIMING14, 0x0000716f},
|
||||
{&XCVR_TSM->TIMING15, 0x5b58716f},
|
||||
{&XCVR_TSM->TIMING16, 0x59580000},
|
||||
{&XCVR_TSM->TIMING17, 0x5b580000},
|
||||
{&XCVR_TSM->TIMING18, 0x5b580000},
|
||||
{&XCVR_TSM->TIMING19, 0x11061106},
|
||||
{&XCVR_TSM->TIMING20, 0x11001100},
|
||||
{&XCVR_TSM->TIMING21, 0x19001100},
|
||||
{&XCVR_TSM->TIMING22, 0x58006f00},
|
||||
{&XCVR_TSM->TIMING23, 0x58006f00},
|
||||
{&XCVR_TSM->TIMING24, 0x5b007100},
|
||||
{&XCVR_TSM->TIMING25, 0x5c007200},
|
||||
{&XCVR_TSM->TIMING26, 0x5c007200},
|
||||
{&XCVR_TSM->TIMING27, 0x5c007200},
|
||||
{&XCVR_TSM->TIMING28, 0x5c007200},
|
||||
{&XCVR_TSM->TIMING29, 0xffff7200},
|
||||
{&XCVR_TSM->TIMING30, 0x5c00ffff},
|
||||
{&XCVR_TSM->TIMING31, 0x5b087108},
|
||||
{&XCVR_TSM->TIMING32, 0x1911ffff},
|
||||
{&XCVR_TSM->TIMING33, 0x23115a11},
|
||||
{&XCVR_TSM->TIMING34, 0x5b117111},
|
||||
{&XCVR_TSM->TIMING35, 0x5b117111},
|
||||
{&XCVR_TSM->TIMING36, 0x5b117111},
|
||||
{&XCVR_TSM->TIMING37, 0x5b117111},
|
||||
{&XCVR_TSM->TIMING38, 0x5b117111},
|
||||
{&XCVR_TSM->TIMING39, 0x5b2b7111},
|
||||
{&XCVR_TSM->TIMING40, 0xffff7111},
|
||||
{&XCVR_TSM->TIMING41, 0xffff7111},
|
||||
{&XCVR_TSM->TIMING42, 0x5b127112},
|
||||
{&XCVR_TSM->TIMING43, 0x5b12ffff},
|
||||
{&XCVR_TSM->TIMING44, 0x5b12ffff},
|
||||
{&XCVR_TSM->TIMING45, 0xffff7112},
|
||||
{&XCVR_TSM->TIMING46, 0xffff716f},
|
||||
{&XCVR_TSM->TIMING47, 0x5b137157},
|
||||
{&XCVR_TSM->TIMING48, 0x5b23715a},
|
||||
{&XCVR_TSM->TIMING49, 0x5b23715a},
|
||||
{&XCVR_TSM->TIMING50, 0x5b2bffff},
|
||||
{&XCVR_TSM->TIMING51, 0x5b2bffff},
|
||||
{&XCVR_TSM->TIMING52, 0xffffffff},
|
||||
{&XCVR_TSM->WU_LATENCY, 0x00060006},
|
||||
{&XCVR_TX_DIG->DATARATE_CONFIG_FILTER_CTRL, 0x00010000},
|
||||
{&XCVR_TX_DIG->DATARATE_CONFIG_FSK_CTRL, 0x08001800},
|
||||
{&XCVR_TX_DIG->DATARATE_CONFIG_GFSK_CTRL, 0x00000804},
|
||||
{&XCVR_TX_DIG->DATA_PADDING_CTRL, 0x00001012},
|
||||
{&XCVR_TX_DIG->DATA_PADDING_CTRL_1, 0x00001600},
|
||||
{&XCVR_TX_DIG->DATA_PADDING_CTRL_2, 0x08001800},
|
||||
{&XCVR_TX_DIG->FSK_CTRL, 0x04001c00},
|
||||
{&XCVR_TX_DIG->GFSK_COEFF_0_1, 0x00040001},
|
||||
{&XCVR_TX_DIG->GFSK_COEFF_2_3, 0x0028000d},
|
||||
{&XCVR_TX_DIG->GFSK_COEFF_4_5, 0x00c00063},
|
||||
{&XCVR_TX_DIG->GFSK_COEFF_6_7, 0x0176012c},
|
||||
{&XCVR_TX_DIG->GFSK_CTRL, 0x000003fd},
|
||||
{&XCVR_TX_DIG->IMAGE_FILTER_CTRL, 0x00000000},
|
||||
{&XCVR_TX_DIG->PA_CTRL, 0x00020126},
|
||||
{&XCVR_TX_DIG->PA_RAMP_TBL0, 0x06040201},
|
||||
{&XCVR_TX_DIG->PA_RAMP_TBL1, 0x14100c09},
|
||||
{&XCVR_TX_DIG->PA_RAMP_TBL2, 0x26211c18},
|
||||
{&XCVR_TX_DIG->PA_RAMP_TBL3, 0x3c38322c},
|
||||
{&XCVR_TX_DIG->SWITCH_TX_CTRL, 0x00000000},
|
||||
{&XCVR_TX_DIG->TXDIG_CTRL, 0x00000007},
|
||||
{&XCVR_2P4GHZ_PHY->DMD_CTRL1, 0x8c049008},
|
||||
{&RBME->CRCW_CFG, 0x00000001},
|
||||
{&RBME->CRCW_CFG2, 0x00000000},
|
||||
{&RBME->CRCW_CFG3, 0x00000403},
|
||||
{&RBME->CRC_INIT, 0x55555500},
|
||||
{&RBME->CRC_PHR_SZ, 0x00000002},
|
||||
{&RBME->CRC_POLY, 0x00065b00},
|
||||
{&RBME->CRC_XOR_OUT, 0x00000000},
|
||||
{&RBME->FCP_CFG, 0x00000000},
|
||||
{&RBME->FEC_BSZ_OV_B4SP, 0x00000000},
|
||||
{&RBME->FEC_CFG1, 0x00000000},
|
||||
{&RBME->FEC_CFG2, 0x00000000},
|
||||
{&RBME->FRAME_OVER_SZ, 0x00000000},
|
||||
{&RBME->NPAYL_OVER_SZ, 0x00000000},
|
||||
{&RBME->PKT_SZ, 0x00244000},
|
||||
{&RBME->SPREAD_CFG, 0x00000000},
|
||||
{&RBME->WHITEN_CFG, 0x00410705},
|
||||
{&RBME->WHITEN_POLY, 0x00000004},
|
||||
{&RBME->WHITEN_SZ_THR, 0x00000000},
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
/* MCXW716C memory map, see MCUXpresso SDK gcc/mcxw716_flash.ld */
|
||||
ENTRY(Reset_Handler);
|
||||
MEMORY {
|
||||
flash(rx) : ORIGIN = 0x00000000, LENGTH = 1024k
|
||||
flash(rx) : ORIGIN = 0x00000000, LENGTH = 512k /* Upper half: OTA staging */
|
||||
sram(rwx) : ORIGIN = 0x20000000, LENGTH = 112k
|
||||
}
|
||||
__StackTop = ORIGIN(sram) + LENGTH(sram); /* Stack points here */
|
||||
@@ -16,6 +16,7 @@ SECTIONS {
|
||||
__data_start__ = .;
|
||||
_sdata = .;
|
||||
*(.first_data)
|
||||
*(.iram .iram.*) /* OTA flash operations, copied to RAM by startup */
|
||||
*(.data SORT(.data.*))
|
||||
. = ALIGN(4);
|
||||
__data_end__ = .;
|
||||
@@ -32,6 +33,9 @@ SECTIONS {
|
||||
_ebss = .;
|
||||
} > sram
|
||||
|
||||
/* Health record, see MG_HEALTH_RAM: not cleared, survives a warm reset */
|
||||
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > sram
|
||||
|
||||
. = ALIGN(8);
|
||||
_end = .; /* Start of heap, see _sbrk() in hal.c */
|
||||
}
|
||||
@@ -4,29 +4,115 @@
|
||||
#include "hal.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
#ifndef UART_DEBUG
|
||||
#define UART_DEBUG LPUART1 // MCU-Link USB serial port
|
||||
#define UART_DEBUG_TX_PIN PIN('C', 3)
|
||||
#define UART_DEBUG_RX_PIN PIN('C', 2)
|
||||
#endif
|
||||
|
||||
#define LED1 PIN('A', 19)
|
||||
#define LED2 PIN('A', 20)
|
||||
#define LED3 PIN('A', 21)
|
||||
|
||||
static void log_fn(char ch, void *param) {
|
||||
hal_uart_write_buf(param, &ch, 1);
|
||||
}
|
||||
|
||||
static void blink_task(void) {
|
||||
static uint64_t blink_timer = 0;
|
||||
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
|
||||
hal_gpio_toggle(LED2);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return hal_get_tick();
|
||||
}
|
||||
|
||||
bool mg_random(void *buf, size_t len) {
|
||||
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
|
||||
uint32_t r = hal_rng_read();
|
||||
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Self-signed P-256 certificate and key for the HTTPS listener. Created by:
|
||||
// openssl ecparam -genkey -name prime256v1 -noout -out key.pem
|
||||
// openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg
|
||||
#define TLS_CERT \
|
||||
"-----BEGIN CERTIFICATE-----\n" \
|
||||
"MIIBgjCCASegAwIBAgIUIJOq77Qn7lHbH6WseMemeqorR50wCgYIKoZIzj0EAwIw\n" \
|
||||
"FjEUMBIGA1UEAwwLbnVjbGVvLWg3MjMwHhcNMjYxMDEyMTUwMjE5WhcNMzYxMDA5\n" \
|
||||
"MTUwMjE5WjAWMRQwEgYDVQQDDAtudWNsZW8taDcyMzBZMBMGByqGSM49AgEGCCqG\n" \
|
||||
"SM49AwEHA0IABB6IQJNmzrsdQL+2BnZYS7GYOaj7sz8f6cjBmz3n8D9Em19XodlU\n" \
|
||||
"uy4ybIvyhr766lL0ERoTmMUivb/lGmzx9C+jUzBRMB0GA1UdDgQWBBQqxMuc4gj7\n" \
|
||||
"MTMzreWlRMNI3QMqTzAfBgNVHSMEGDAWgBQqxMuc4gj7MTMzreWlRMNI3QMqTzAP\n" \
|
||||
"BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQDY4xaJpuxq6b3rHK3b\n" \
|
||||
"lOIQyfkQX+orCRqQUkseplqpqwIhAKswZFNn+2zUNRRNY8+cHmdIwmdpsmxtoWYP\n" \
|
||||
"BCjX++LI\n" \
|
||||
"-----END CERTIFICATE-----\n"
|
||||
|
||||
#define TLS_KEY \
|
||||
"-----BEGIN EC PRIVATE KEY-----\n" \
|
||||
"MHcCAQEEIJ/GBl0TlD52q9PMg1KWaNIdXqp8b/HyIsABmBpqC+O1oAoGCCqGSM49\n" \
|
||||
"AwEHoUQDQgAEHohAk2bOux1Av7YGdlhLsZg5qPuzPx/pyMGbPefwP0SbX1eh2VS7\n" \
|
||||
"LjJsi/KGvvrqUvQRGhOYxSK9v+UabPH0Lw==\n" \
|
||||
"-----END EC PRIVATE KEY-----\n"
|
||||
|
||||
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_ACCEPT && c->is_tls) { // Accepted on the https:// listener
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
|
||||
} else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) {
|
||||
__asm volatile("udf #0"); // Trigger HardFault
|
||||
} else {
|
||||
mg_http_reply(c, 200, "",
|
||||
"Hi from Mongoose!\n"
|
||||
"tick: %llu\n"
|
||||
"ram used: %zu\n"
|
||||
"ram free: %zu\n",
|
||||
hal_get_tick(), hal_ram_used(), hal_ram_free());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
MG_HEALTH_INIT();
|
||||
|
||||
hal_clock_init();
|
||||
hal_uart_init(UART_DEBUG, 115200);
|
||||
hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200);
|
||||
mg_log_set_fn(log_fn, UART_DEBUG);
|
||||
hal_rng_init();
|
||||
hal_radio_init();
|
||||
hal_gpio_output(LED1), hal_gpio_write(LED1, true); // Active low
|
||||
hal_gpio_output(LED2), hal_gpio_write(LED2, true); // Active low
|
||||
hal_gpio_output(LED3), hal_gpio_write(LED3, true); // Active low
|
||||
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_http_listen(&mgr, "https://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_mqtt_init(&mgr);
|
||||
|
||||
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
|
||||
MG_INFO(("RAM used: %zu, free: %zu", hal_ram_used(), hal_ram_free()));
|
||||
MG_INFO(("Crash record: %M", mg_print_crash_record));
|
||||
|
||||
for (;;) {
|
||||
static uint64_t timer;
|
||||
if (hal_timer_expired(&timer, 1000, hal_get_tick())) {
|
||||
MG_INFO(("Hi from Mongoose, tick %llu", hal_get_tick()));
|
||||
}
|
||||
mg_mgr_poll(&mgr, 0);
|
||||
mg_mqtt_poll(&mgr);
|
||||
blink_task();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,7 +1,33 @@
|
||||
#pragma once
|
||||
#include "MCXW716C.h"
|
||||
|
||||
// See https://mongoose.ws/documentation/#build-options
|
||||
// See https://mongoose.ws/docs/getting-started/build-options/
|
||||
#define MG_ARCH MG_ARCH_ARMGCC
|
||||
#define MG_TLS MG_TLS_BUILTIN
|
||||
#define MQTT_CLIENT_ID "w71" // For tutorials/mqtt/mqtt-client
|
||||
#define MG_OTA MG_OTA_MCXW71
|
||||
|
||||
#define MG_ENABLE_TCPIP 1 // Built-in TCP/IP stack. No network driver yet
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
#define MG_ENABLE_DRIVER_MCXW71 1
|
||||
#define MG_ENABLE_CUSTOM_MILLIS 1
|
||||
#define MG_ENABLE_CUSTOM_RANDOM 1
|
||||
#define MG_ENABLE_TCPIP_TCPRTX 1 // Link can be lossy, enable retransmit
|
||||
|
||||
// Stable MAC, so the DHCP address stays the same across reboots: a hash of
|
||||
// the 128-bit chip unique ID, MSCM UID, locally administered
|
||||
#define MG_SET_MAC_ADDRESS(mac) \
|
||||
do { \
|
||||
uint8_t b_[32]; \
|
||||
mg_sha256(b_, (uint8_t *) MSCM->UID, sizeof(MSCM->UID)); \
|
||||
(mac)[0] = 2, (mac)[1] = b_[1], (mac)[2] = b_[2], (mac)[3] = b_[3]; \
|
||||
(mac)[4] = b_[4], (mac)[5] = b_[5]; \
|
||||
} while (0)
|
||||
|
||||
// Crash report support. Health record lives in the .mg_health region, see link.ld
|
||||
#define MG_ENABLE_HEALTH 1
|
||||
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
|
||||
|
||||
// mdash.net device management service support
|
||||
#define MG_ENABLE_MDASH 0
|
||||
#define MG_MDASH_KEY "..." // Set this - mDash device token
|
||||
#define MG_FIRMWARE_VERSION "w71.1.0.0"
|
||||
@@ -0,0 +1,68 @@
|
||||
# Environment setup: https://mongoose.ws/documentation/tutorials/tools/
|
||||
|
||||
MGDIR = ../../../..
|
||||
|
||||
CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion
|
||||
CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion
|
||||
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
|
||||
CFLAGS += -Wno-cast-function-type -Wno-old-style-declaration
|
||||
CFLAGS += -Wno-unused-parameter
|
||||
CFLAGS += -I. -I$(MGDIR) -Imongoose -Icmsis_core/CMSIS/Core/Include/
|
||||
CFLAGS += $(addprefix -Icmsis_rw612/devices/RW612/drivers/romapi/,bootloader nboot otp iap flexspi)
|
||||
CFLAGS += -Icmsis_rw612/devices/RW612/ -Icmsis_rw612/devices/RW612/periph/ -Icmsis_rw612/devices/RW612/drivers/
|
||||
CFLAGS += -DCPU_RW612ETA2I -DCPU_RW612ETA2I_cm33_no_dsp
|
||||
CFLAGS += -DBOOT_HEADER_ENABLE=1
|
||||
CFLAGS += -DRADIO_FW='"$(RADIO_FW)"'
|
||||
CFLAGS += -mcpu=cortex-m33+nodsp -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CFLAGS_EXTRA)
|
||||
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections
|
||||
|
||||
SOURCES = main.c hal.c radio.c startup.c flexspi.c flash_config.c $(MGDIR)/mongoose.c
|
||||
|
||||
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_clock.c
|
||||
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_trng.c
|
||||
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_reset.c
|
||||
SOURCES += $(addprefix cmsis_rw612/devices/RW612/drivers/,fsl_power.c fsl_ocotp.c \
|
||||
romapi/bootloader/fsl_romapi.c romapi/nboot/fsl_romapi_nboot.c romapi/otp/fsl_romapi_otp.c)
|
||||
|
||||
# NXP BLE/802.15.4 combo firmware for CPU2, linked into our image. NXP license:
|
||||
# https://github.com/NXP/wifi_nb_fw/blob/nxp-v4.4.3/LICENSE.txt
|
||||
RADIO_FW = rw61x_sb_ble_15d4_combo_a2.bin
|
||||
RADIO_FW_URL = https://github.com/NXP/wifi_nb_fw/raw/nxp-v4.4.3/rw61x/$(RADIO_FW)
|
||||
RADIO_FW_SHA256 = 2efac0d771e371377d57159c40f0909c7ecc6f6b950ce69ed2a3e02ac7eb686a
|
||||
|
||||
all build: firmware.bin
|
||||
|
||||
firmware.bin: firmware.elf
|
||||
arm-none-eabi-objcopy -O binary $< $@
|
||||
arm-none-eabi-size --format=berkeley $<
|
||||
|
||||
firmware.elf: cmsis_core cmsis_rw612 $(RADIO_FW) $(SOURCES) $(wildcard *.h) $(MGDIR)/mongoose.h link.ld Makefile mongoose_config.h mesh-config
|
||||
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
# Rebuild when overriding the board address or timing.
|
||||
.PHONY: mesh-config
|
||||
mesh-config:
|
||||
|
||||
cmsis_core:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
|
||||
|
||||
$(RADIO_FW):
|
||||
curl -sL $(RADIO_FW_URL) -o $@.tmp
|
||||
echo "$(RADIO_FW_SHA256) $@.tmp" | shasum -a 256 -c - >/dev/null
|
||||
mv $@.tmp $@
|
||||
|
||||
cmsis_rw612:
|
||||
curl -sL https://mcuxpresso.nxp.com/cmsis_pack/repo/NXP.RW612_DFP.25.12.00.pack -o NXP.RW612_DFP.25.12.00.pack
|
||||
unzip -q NXP.RW612_DFP.25.12.00.pack -d $@
|
||||
rm NXP.RW612_DFP.25.12.00.pack
|
||||
|
||||
# Show all symbols sorted by size
|
||||
show_symbols_sorted_by_size: firmware.bin
|
||||
arm-none-eabi-readelf -sW firmware.elf | sed 1,3d | sed 's,^.*: ,,' | tr -s ' ' | cut -d ' ' -f 2,7 | sort -nk1 | grep -v '^0 '
|
||||
|
||||
flash: firmware.bin
|
||||
(echo 'loadfile firmware.elf'; echo 'reset'; echo 'go'; echo 'exit';) | JLinkExe -device RW612 -if SWD -speed 4000 -autoconnect 1 -NoGui 1 -ExitOnError 1
|
||||
# JRun --nogui --device RW612 --if SWD firmware.elf
|
||||
|
||||
clean:
|
||||
rm -rf firmware.* cmsis_* mongoose/mongoose.* $(RADIO_FW)
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021-2024 NXP
|
||||
* All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "flash_config.h"
|
||||
|
||||
/* Component ID definition, used by tools. */
|
||||
#ifndef FSL_COMPONENT_ID
|
||||
#define FSL_COMPONENT_ID "platform.drivers.flash_config"
|
||||
#endif
|
||||
|
||||
/*******************************************************************************
|
||||
* Code
|
||||
******************************************************************************/
|
||||
#if defined(BOOT_HEADER_ENABLE) && (BOOT_HEADER_ENABLE == 1)
|
||||
#if defined(__ARMCC_VERSION) || defined(__GNUC__)
|
||||
__attribute__((section(".flash_conf"), used))
|
||||
#elif defined(__ICCARM__)
|
||||
#pragma location = ".flash_conf"
|
||||
#endif
|
||||
const fc_flexspi_nor_config_t flexspi_config = {
|
||||
.memConfig =
|
||||
{
|
||||
.tag = FC_BLOCK_TAG,
|
||||
.version = FC_BLOCK_VERSION,
|
||||
.readSampleClkSrc = 1,
|
||||
.csHoldTime = 3,
|
||||
.csSetupTime = 3,
|
||||
.deviceModeCfgEnable = 1,
|
||||
.deviceModeSeq = {.seqNum = 1, .seqId = 2},
|
||||
.deviceModeArg = 0x02,
|
||||
.configCmdEnable = 0,
|
||||
.deviceType = 0x1,
|
||||
.sflashPadType = kSerialFlash_4Pads,
|
||||
.serialClkFreq = 5,
|
||||
.sflashA1Size = 0x4000000U,
|
||||
.sflashA2Size = 0,
|
||||
.sflashB1Size = 0,
|
||||
.sflashB2Size = 0,
|
||||
.lookupTable =
|
||||
{
|
||||
/* Read */
|
||||
[0] = FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0xEC, FC_RADDR_SDR, FC_FLEXSPI_4PAD, 0x20),
|
||||
[1] = FC_FLEXSPI_LUT_SEQ(FC_MODE8_SDR, FC_FLEXSPI_4PAD, 0xF0, FC_DUMMY_SDR, FC_FLEXSPI_4PAD, 0x04),
|
||||
[2] = FC_FLEXSPI_LUT_SEQ(FC_READ_SDR, FC_FLEXSPI_4PAD, 0x04, FC_STOP_EXE, FC_FLEXSPI_1PAD, 0x00),
|
||||
|
||||
/* Read Status */
|
||||
[4 * 1 + 0] =
|
||||
FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x05, FC_READ_SDR, FC_FLEXSPI_1PAD, 0x04),
|
||||
|
||||
/* Write Status */
|
||||
[4 * 2 + 0] =
|
||||
FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x31, FC_WRITE_SDR, FC_FLEXSPI_1PAD, 0x01),
|
||||
|
||||
/* Write Enable */
|
||||
[4 * 3 + 0] =
|
||||
FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x06, FC_STOP_EXE, FC_FLEXSPI_1PAD, 0x00),
|
||||
|
||||
/* Sector erase */
|
||||
[4 * 5 + 0] =
|
||||
FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x21, FC_RADDR_SDR, FC_FLEXSPI_1PAD, 0x20),
|
||||
|
||||
/* Block erase */
|
||||
[4 * 8 + 0] =
|
||||
FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0xDC, FC_RADDR_SDR, FC_FLEXSPI_1PAD, 0x20),
|
||||
|
||||
/* Page program */
|
||||
[4 * 9 + 0] =
|
||||
FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x34, FC_RADDR_SDR, FC_FLEXSPI_1PAD, 0x20),
|
||||
[4 * 9 + 1] =
|
||||
FC_FLEXSPI_LUT_SEQ(FC_WRITE_SDR, FC_FLEXSPI_4PAD, 0x00, FC_STOP_EXE, FC_FLEXSPI_1PAD, 0x00),
|
||||
|
||||
/* chip erase */
|
||||
[4 * 11 + 0] =
|
||||
FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0xC7, FC_STOP_EXE, FC_FLEXSPI_1PAD, 0x00),
|
||||
},
|
||||
},
|
||||
.pageSize = 0x100,
|
||||
.sectorSize = 0x1000,
|
||||
.ipcmdSerialClkFreq = 0,
|
||||
.blockSize = 0x10000,
|
||||
.fcb_fill[0] = 0xFFFFFFFF,
|
||||
};
|
||||
#endif /* BOOT_HEADER_ENABLE */
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2021-2023 NXP
|
||||
* All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef __FLASH_CONFIG__
|
||||
#define __FLASH_CONFIG__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "fsl_common.h"
|
||||
|
||||
/*! @name Driver version */
|
||||
/*@{*/
|
||||
/*! @brief FLASH_CONFIG driver version 2.0.0. */
|
||||
#define FSL_FLASH_CONFIG_DRIVER_VERSION (MAKE_VERSION(2, 0, 0))
|
||||
/*@}*/
|
||||
|
||||
/*******************************************************************************
|
||||
* Definition
|
||||
******************************************************************************/
|
||||
|
||||
/* FLEXSPI memory config block related defintions */
|
||||
#define FC_BLOCK_TAG (0x42464346)
|
||||
#define FC_BLOCK_VERSION (0x00000000)
|
||||
|
||||
#define FC_CMD_SDR 0x01
|
||||
#define FC_CMD_DDR 0x21
|
||||
#define FC_RADDR_SDR 0x02
|
||||
#define FC_RADDR_DDR 0x22
|
||||
#define FC_CADDR_SDR 0x03
|
||||
#define FC_CADDR_DDR 0x23
|
||||
#define FC_MODE1_SDR 0x04
|
||||
#define FC_MODE1_DDR 0x24
|
||||
#define FC_MODE2_SDR 0x05
|
||||
#define FC_MODE2_DDR 0x25
|
||||
#define FC_MODE4_SDR 0x06
|
||||
#define FC_MODE4_DDR 0x26
|
||||
#define FC_MODE8_SDR 0x07
|
||||
#define FC_MODE8_DDR 0x27
|
||||
#define FC_WRITE_SDR 0x08
|
||||
#define FC_WRITE_DDR 0x28
|
||||
#define FC_READ_SDR 0x09
|
||||
#define FC_READ_DDR 0x29
|
||||
#define FC_LEARN_SDR 0x0A
|
||||
#define FC_LEARN_DDR 0x2A
|
||||
#define FC_DATSZ_SDR 0x0B
|
||||
#define FC_DATSZ_DDR 0x2B
|
||||
#define FC_DUMMY_SDR 0x0C
|
||||
#define FC_DUMMY_DDR 0x2C
|
||||
#define FC_DUMMY_RWDS_SDR 0x0D
|
||||
#define FC_DUMMY_RWDS_DDR 0x2D
|
||||
#define FC_JMP_ON_CS 0x1F
|
||||
#define FC_STOP_EXE 0
|
||||
|
||||
#define FC_FLEXSPI_1PAD 0
|
||||
#define FC_FLEXSPI_2PAD 1
|
||||
#define FC_FLEXSPI_4PAD 2
|
||||
#define FC_FLEXSPI_8PAD 3
|
||||
|
||||
#define FC_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \
|
||||
(FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \
|
||||
FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1))
|
||||
|
||||
/* !@brief Data pad used in Read command */
|
||||
enum
|
||||
{
|
||||
kSerialFlash_1Pads = 1,
|
||||
kSerialFlash_2Pads = 2,
|
||||
kSerialFlash_4Pads = 4,
|
||||
kSerialFlash_8Pads = 8,
|
||||
};
|
||||
|
||||
/* !@brief FLEXSPI clock configuration - In High speed boot mode mode */
|
||||
enum
|
||||
{
|
||||
kFlexSpiSerialClk_30MHz = 1,
|
||||
kFlexSpiSerialClk_50MHz = 2,
|
||||
kFlexSpiSerialClk_60MHz = 3,
|
||||
kFlexSpiSerialClk_80MHz = 4,
|
||||
kFlexSpiSerialClk_100MHz = 5,
|
||||
kFlexSpiSerialClk_120MHz = 6,
|
||||
kFlexSpiSerialClk_133MHz = 7,
|
||||
kFlexSpiSerialClk_166MHz = 8,
|
||||
kFlexSpiSerialClk_200MHz = 9,
|
||||
};
|
||||
|
||||
/* !@brief FLEXSPI clock configuration - In Normal boot SDR mode */
|
||||
enum
|
||||
{
|
||||
kFlexSpiSerialClk_SDR_24MHz = 1,
|
||||
kFlexSpiSerialClk_SDR_48MHz = 2,
|
||||
};
|
||||
|
||||
/* !@brief FLEXSPI clock configuration - In Normal boot DDR mode */
|
||||
enum
|
||||
{
|
||||
kFlexSpiSerialClk_DDR_48MHz = 1,
|
||||
};
|
||||
|
||||
/* !@brief Misc feature bit definitions */
|
||||
enum
|
||||
{
|
||||
kFlexSpiMiscOffset_DiffClkEnable = 0, /* !< Bit for Differential clock enable */
|
||||
kFlexSpiMiscOffset_WordAddressableEnable = 3, /* !< Bit for Word Addressable enable */
|
||||
kFlexSpiMiscOffset_SafeConfigFreqEnable = 4, /* !< Bit for Safe Configuration Frequency enable */
|
||||
kFlexSpiMiscOffset_DdrModeEnable = 6, /* !< Bit for DDR clock confiuration indication. */
|
||||
};
|
||||
|
||||
/* !@brief Flash Configuration Command Type */
|
||||
enum
|
||||
{
|
||||
kDeviceConfigCmdType_Generic, /* !< Generic command, for example: configure dummy cycles, drive strength, etc */
|
||||
kDeviceConfigCmdType_QuadEnable, /* !< Quad Enable command */
|
||||
kDeviceConfigCmdType_Spi2Xpi, /* !< Switch from SPI to DPI/QPI/OPI mode */
|
||||
kDeviceConfigCmdType_Xpi2Spi, /* !< Switch from DPI/QPI/OPI to SPI mode */
|
||||
kDeviceConfigCmdType_Spi2NoCmd, /* !< Switch to 0-4-4/0-8-8 mode */
|
||||
kDeviceConfigCmdType_Reset, /* !< Reset device command */
|
||||
};
|
||||
|
||||
typedef struct _fc_flexspi_dll_time
|
||||
{
|
||||
uint8_t time_100ps; /* !< Data valid time, in terms of 100ps */
|
||||
uint8_t delay_cells; /* !< Data valid time, in terms of delay cells */
|
||||
} fc_flexspi_dll_time_t;
|
||||
|
||||
/* !@brief FlexSPI LUT Sequence structure */
|
||||
typedef struct _fc_flexspi_lut_seq
|
||||
{
|
||||
uint8_t seqNum; /* !< Sequence Number, valid number: 1-16 */
|
||||
uint8_t seqId; /* !< Sequence Index, valid number: 0-15 */
|
||||
uint16_t reserved;
|
||||
} fc_flexspi_lut_seq_t;
|
||||
|
||||
/* !@brief FlexSPI Memory Configuration Block */
|
||||
typedef struct _fc_flexspi_mem_config
|
||||
{
|
||||
uint32_t tag; /* !< [0x000-0x003] Tag, fixed value 0x42464346UL */
|
||||
uint32_t version; /* !< [0x004-0x007] Version,[31:24] -'V', [23:16] - Major, [15:8] - Minor, [7:0] - bugfix */
|
||||
uint32_t reserved0; /* !< [0x008-0x00b] Reserved for future use */
|
||||
uint8_t readSampleClkSrc; /* !< [0x00c-0x00c] Read Sample Clock Source, valid value: 0/1/3 */
|
||||
uint8_t csHoldTime; /* !< [0x00d-0x00d] CS hold time, default value: 3 */
|
||||
uint8_t csSetupTime; /* !< [0x00e-0x00e] CS setup time, default value: 3 */
|
||||
uint8_t columnAddressWidth; /* !< [0x00f-0x00f] Column Address with, for HyperBus protocol, it is fixed to 3, For
|
||||
Serial NAND, need to refer to datasheet */
|
||||
uint8_t deviceModeCfgEnable; /* !< [0x010-0x010] Device Mode Configure enable flag, 1 - Enable, 0 - Disable */
|
||||
uint8_t deviceModeType; /* !< [0x011-0x011] Specify the configuration command type:Quad Enable, DPI/QPI/OPI switch,
|
||||
Generic configuration, etc. */
|
||||
uint16_t waitTimeCfgCommands; /* !< [0x012-0x013] Wait time for all configuration commands, unit: 100us, Used for
|
||||
DPI/QPI/OPI switch or reset command */
|
||||
fc_flexspi_lut_seq_t deviceModeSeq; /* !< [0x014-0x017] Device mode sequence info, [7:0] - LUT sequence id, [15:8] -
|
||||
LUt sequence number, [31:16] Reserved */
|
||||
uint32_t deviceModeArg; /* !< [0x018-0x01b] Argument/Parameter for device configuration */
|
||||
uint8_t configCmdEnable; /* !< [0x01c-0x01c] Configure command Enable Flag, 1 - Enable, 0 - Disable */
|
||||
uint8_t configModeType[3]; /* !< [0x01d-0x01f] Configure Mode Type, similar as deviceModeTpe */
|
||||
fc_flexspi_lut_seq_t configCmdSeqs[3]; /* !< [0x020-0x02b] Sequence info for Device Configuration command, similar
|
||||
as deviceModeSeq */
|
||||
uint32_t reserved1; /* !< [0x02c-0x02f] Reserved for future use */
|
||||
uint32_t configCmdArgs[3]; /* !< [0x030-0x03b] Arguments/Parameters for device Configuration commands */
|
||||
uint32_t reserved2; /* !< [0x03c-0x03f] Reserved for future use */
|
||||
uint32_t controllerMiscOption; /* !< [0x040-0x043] Controller Misc Options, see Misc feature bit definitions for
|
||||
more details */
|
||||
uint8_t deviceType; /* !< [0x044-0x044] Device Type: See Flash Type Definition for more details */
|
||||
uint8_t sflashPadType; /* !< [0x045-0x045] Serial Flash Pad Type: 1 - Single, 2 - Dual, 4 - Quad, 8 - Octal */
|
||||
uint8_t serialClkFreq; /* !< [0x046-0x046] Serial Flash Frequencey, device specific definitions, See System Boot
|
||||
Chapter for more details */
|
||||
uint8_t lutCustomSeqEnable; /* !< [0x047-0x047] LUT customization Enable, it is required if the program/erase cannot
|
||||
be done using 1 LUT sequence, currently, only applicable to HyperFLASH */
|
||||
uint32_t reserved3[2]; /* !< [0x048-0x04f] Reserved for future use */
|
||||
uint32_t sflashA1Size; /* !< [0x050-0x053] Size of Flash connected to A1 */
|
||||
uint32_t sflashA2Size; /* !< [0x054-0x057] Size of Flash connected to A2 */
|
||||
uint32_t sflashB1Size; /* !< [0x058-0x05b] Size of Flash connected to B1 */
|
||||
uint32_t sflashB2Size; /* !< [0x05c-0x05f] Size of Flash connected to B2 */
|
||||
uint32_t csPadSettingOverride; /* !< [0x060-0x063] CS pad setting override value */
|
||||
uint32_t sclkPadSettingOverride; /* !< [0x064-0x067] SCK pad setting override value */
|
||||
uint32_t dataPadSettingOverride; /* !< [0x068-0x06b] data pad setting override value */
|
||||
uint32_t dqsPadSettingOverride; /* !< [0x06c-0x06f] DQS pad setting override value */
|
||||
uint32_t timeoutInMs; /* !< [0x070-0x073] Timeout threshold for read status command */
|
||||
uint32_t commandInterval; /* !< [0x074-0x077] CS deselect interval between two commands */
|
||||
fc_flexspi_dll_time_t dataValidTime[2]; /* !< [0x078-0x07b] CLK edge to data valid time for PORT A and PORT B */
|
||||
uint16_t busyOffset; /* !< [0x07c-0x07d] Busy offset, valid value: 0-31 */
|
||||
uint16_t busyBitPolarity; /* !< [0x07e-0x07f] Busy flag polarity, 0 - busy flag is 1 when flash device is busy, 1 -
|
||||
busy flag is 0 when flash device is busy */
|
||||
uint32_t lookupTable[64]; /* !< [0x080-0x17f] Lookup table holds Flash command sequences */
|
||||
fc_flexspi_lut_seq_t lutCustomSeq[12]; /* !< [0x180-0x1af] Customizable LUT Sequences */
|
||||
uint32_t reserved4[4]; /* !< [0x1b0-0x1bf] Reserved for future use */
|
||||
} fc_flexspi_mem_config_t;
|
||||
/*
|
||||
* Serial NOR configuration block
|
||||
*/
|
||||
typedef struct _fc_flexspi_nor_config
|
||||
{
|
||||
#if defined(__ARMCC_VERSION) || defined(__ICCARM__)
|
||||
uint8_t padding[0x400]; /* !< Padding for MDK and IAR */
|
||||
#endif
|
||||
fc_flexspi_mem_config_t memConfig; /* !< Common memory configuration info via FlexSPI */
|
||||
uint32_t pageSize; /* !< Page size of Serial NOR */
|
||||
uint32_t sectorSize; /* !< Sector size of Serial NOR */
|
||||
uint8_t ipcmdSerialClkFreq; /* !< Clock frequency for IP command */
|
||||
uint8_t isUniformBlockSize; /* !< Sector/Block size is the same */
|
||||
uint8_t isDataOrderSwapped; /* !< Data order (D0, D1, D2, D3) is swapped (D1,D0, D3, D2) */
|
||||
uint8_t reserved0[1]; /* !< Reserved for future use */
|
||||
uint8_t serialNorType; /* !< Serial NOR Flash type: 0/1/2/3 */
|
||||
uint8_t needExitNoCmdMode; /* !< Need to exit NoCmd mode before other IP command */
|
||||
uint8_t halfClkForNonReadCmd; /* !< Half the Serial Clock for non-read command: true/false */
|
||||
uint8_t needRestoreNoCmdMode; /* !< Need to Restore NoCmd mode after IP commmand execution */
|
||||
uint32_t blockSize; /* !< Block size */
|
||||
uint32_t flashStateCtx; /* !< Flash State Context */
|
||||
uint32_t reserve2[10]; /* !< Reserved for future use */
|
||||
uint32_t fcb_fill[0x280]; /* !< Fill */
|
||||
} fc_flexspi_nor_config_t;
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* __FLASH_CONFIG__ */
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "RW612.h"
|
||||
#include "fsl_flexspi.h" // peripheral structures
|
||||
|
||||
__attribute__((section(".iram"))) static void deinit_flash(FLEXSPI_Type *base) {
|
||||
CLKCTL0->PSCCTL0_SET =
|
||||
CLKCTL0_PSCCTL0_SET_FLEXSPI0_MASK; // Enable FLEXSPI clock again
|
||||
base->MCR0 &= ~FLEXSPI_MCR0_MDIS_MASK; // Enable FLEXSPI module
|
||||
// Wait until FLEXSPI is not busy
|
||||
while (!((base->STS0 & FLEXSPI_STS0_ARBIDLE_MASK) &&
|
||||
(base->STS0 & FLEXSPI_STS0_SEQIDLE_MASK)))
|
||||
(void)0;
|
||||
base->MCR0 |=
|
||||
FLEXSPI_MCR0_MDIS_MASK; // Disable module during the reset procedure
|
||||
}
|
||||
|
||||
__attribute__((section(".iram"))) static void init_flash(FLEXSPI_Type *base) {
|
||||
uint32_t status;
|
||||
uint32_t lastStatus;
|
||||
uint32_t retry;
|
||||
|
||||
// Loopback from DQS pad can maximize RD board flash speed.
|
||||
if ((base->MCR0 & FLEXSPI_MCR0_RXCLKSRC_MASK) != FLEXSPI_MCR0_RXCLKSRC(1)) {
|
||||
base->MCR0 =
|
||||
(base->MCR0 & ~FLEXSPI_MCR0_RXCLKSRC_MASK) | FLEXSPI_MCR0_RXCLKSRC(1);
|
||||
}
|
||||
base->DLLCR[0] = 0x1U;
|
||||
base->MCR0 &= ~FLEXSPI_MCR0_MDIS_MASK; // Enable FLEXSPI module
|
||||
base->MCR0 |= FLEXSPI_MCR0_SWRESET_MASK;
|
||||
while (base->MCR0 & FLEXSPI_MCR0_SWRESET_MASK)
|
||||
(void)0;
|
||||
|
||||
// Need to wait DLL locked if DLL enabled
|
||||
if (0U != (base->DLLCR[0] & FLEXSPI_DLLCR_DLLEN_MASK)) {
|
||||
lastStatus = base->STS2;
|
||||
retry = 10;
|
||||
// Wait slave delay line locked and slave reference delay line locked.
|
||||
do {
|
||||
status = base->STS2;
|
||||
if ((status &
|
||||
(FLEXSPI_STS2_AREFLOCK_MASK | FLEXSPI_STS2_ASLVLOCK_MASK)) ==
|
||||
(FLEXSPI_STS2_AREFLOCK_MASK | FLEXSPI_STS2_ASLVLOCK_MASK)) {
|
||||
// Locked
|
||||
retry = 100;
|
||||
break;
|
||||
} else if (status == lastStatus) {
|
||||
retry--;
|
||||
} else {
|
||||
retry = 10;
|
||||
lastStatus = status;
|
||||
}
|
||||
} while (retry > 0);
|
||||
// According to ERR011377, need to delay at least 100 NOPs to ensure the DLL
|
||||
// is locked
|
||||
for (; retry > 0U; retry--)
|
||||
(void)0;
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((section(".iram"))) void
|
||||
set_flexspi_clock(FLEXSPI_Type *base, uint32_t src, uint32_t divider) {
|
||||
if ((CLKCTL0->FLEXSPIFCLKSEL != CLKCTL0_FLEXSPIFCLKSEL_SEL(src)) ||
|
||||
((CLKCTL0->FLEXSPIFCLKDIV & CLKCTL0_FLEXSPIFCLKDIV_DIV_MASK) !=
|
||||
(divider - 1))) {
|
||||
// Always deinit FLEXSPI and init FLEXSPI for the flash to make sure the
|
||||
// flash works correctly after the FLEXSPI root clock changed as the default
|
||||
// FLEXSPI configuration may does not work for the new root clock frequency.
|
||||
deinit_flash(base);
|
||||
|
||||
// Disable clock before changing clock source
|
||||
CLKCTL0->PSCCTL0_CLR = CLKCTL0_PSCCTL0_CLR_FLEXSPI0_MASK;
|
||||
// Update flexspi clock.
|
||||
CLKCTL0->FLEXSPIFCLKSEL = CLKCTL0_FLEXSPIFCLKSEL_SEL(src);
|
||||
CLKCTL0->FLEXSPIFCLKDIV |=
|
||||
CLKCTL0_FLEXSPIFCLKDIV_RESET_MASK; // Reset the divider counter
|
||||
CLKCTL0->FLEXSPIFCLKDIV = CLKCTL0_FLEXSPIFCLKDIV_DIV(divider - 1);
|
||||
while ((CLKCTL0->FLEXSPIFCLKDIV) & CLKCTL0_FLEXSPIFCLKDIV_REQFLAG_MASK)
|
||||
(void)0;
|
||||
// Enable FLEXSPI clock again
|
||||
CLKCTL0->PSCCTL0_SET = CLKCTL0_PSCCTL0_SET_FLEXSPI0_MASK;
|
||||
init_flash(base);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
// Added to minimise the bin size: avoid pulling printf and friends
|
||||
void __assert_func(const char *s, int line, const char *fn, const char *expr) {
|
||||
(void) s, (void) line, (void) fn, (void) expr;
|
||||
for (;;) (void) 0; // or trigger the fault handler
|
||||
}
|
||||
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
|
||||
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
|
||||
if (period == 0) return false; // Avoid division by zero
|
||||
if (diff < period) return false; // Period has not elapsed yet
|
||||
*t += (diff / period) * period; // Preserve cadence, skip missed periods
|
||||
return true;
|
||||
}
|
||||
|
||||
static volatile uint64_t s_ticks; // Milliseconds since boot
|
||||
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
|
||||
s_ticks++;
|
||||
}
|
||||
|
||||
uint64_t hal_get_tick(void) {
|
||||
return s_ticks;
|
||||
};
|
||||
|
||||
uint64_t mg_micros(void);
|
||||
// SysTick runs at the core clock and reloads every millisecond. Sample
|
||||
// the tick counter and the sub-millisecond counter in one critical section.
|
||||
uint64_t mg_micros(void) {
|
||||
uint32_t primask = __get_PRIMASK(), val, pending;
|
||||
uint64_t ms;
|
||||
__disable_irq();
|
||||
ms = hal_get_tick();
|
||||
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
|
||||
pending = SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
}
|
||||
__set_PRIMASK(primask);
|
||||
if (pending != 0 && val != 0) ms++;
|
||||
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
|
||||
(SysTick->LOAD + 1);
|
||||
}
|
||||
|
||||
uint32_t SystemCoreClock = SYS_FREQUENCY;
|
||||
int hal_radio_err; // Result of hal_radio_init()
|
||||
void SystemInit(void) { // Called automatically by startup code
|
||||
hal_system_init(); // Enable FPU
|
||||
hal_radio_err = hal_radio_init(); // At the boot clock, see radio.c
|
||||
hal_clock_init();
|
||||
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
|
||||
}
|
||||
|
||||
void ExitRun0Mode(void) {
|
||||
}
|
||||
|
||||
struct stat;
|
||||
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
|
||||
(void) fd, (void) st;
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
|
||||
static unsigned char *s_current_heap_end = _end;
|
||||
|
||||
size_t hal_ram_used(void) {
|
||||
return (size_t) (s_current_heap_end - _end);
|
||||
}
|
||||
|
||||
size_t hal_ram_free(void) {
|
||||
unsigned char endofstack;
|
||||
return (size_t) (&endofstack - s_current_heap_end);
|
||||
}
|
||||
|
||||
void *_sbrk(int incr) {
|
||||
unsigned char *prev_heap;
|
||||
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
|
||||
prev_heap = s_current_heap_end;
|
||||
// Check how much space we got from the heap end to the stack end
|
||||
if (s_current_heap_end + incr > heap_end) return (void *) -1;
|
||||
s_current_heap_end += incr;
|
||||
return prev_heap;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _open(const char *path) {
|
||||
(void) path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _close(int fd) {
|
||||
(void) fd;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _isatty(int fd) {
|
||||
(void) fd;
|
||||
return 1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
|
||||
(void) fd, (void) ptr, (void) dir;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _exit(int status) {
|
||||
(void) status;
|
||||
for (;;) asm volatile("BKPT #0");
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _kill(int pid, int sig) {
|
||||
(void) pid, (void) sig;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _getpid(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
|
||||
(void) fd, (void) ptr, (void) len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
|
||||
(void) fd, (void) ptr, (void) len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _link(const char *a, const char *b) {
|
||||
(void) a, (void) b;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _unlink(const char *a) {
|
||||
(void) a;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _stat(const char *path, struct stat *st) {
|
||||
(void) path, (void) st;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int mkdir(const char *path, int mode) {
|
||||
(void) path, (void) mode;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _init(void) {
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
// Copyright (c) 2023 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "RW612.h"
|
||||
#include "fsl_clock.h"
|
||||
#include "fsl_ocotp.h"
|
||||
#include "fsl_power.h"
|
||||
#include "fsl_io_mux.h"
|
||||
#include "fsl_reset.h"
|
||||
#include "fsl_trng.h"
|
||||
|
||||
int hal_radio_init(void); // Start the 802.15.4 radio firmware. 0: success
|
||||
extern int hal_radio_err; // hal_radio_init() result, called by SystemInit()
|
||||
|
||||
#define BIT(x) (1UL << (x))
|
||||
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
|
||||
#define PIN(bank, num) ((bank << 8) | (num))
|
||||
#define PINNO(pin) (pin & 255)
|
||||
#define PINBANK(pin) (pin >> 8)
|
||||
|
||||
void hal_init(void);
|
||||
size_t hal_ram_free(void);
|
||||
size_t hal_ram_used(void);
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
|
||||
uint64_t hal_get_tick(void);
|
||||
|
||||
static inline void spin(volatile uint32_t count) {
|
||||
while (count--)
|
||||
(void)0;
|
||||
}
|
||||
|
||||
#define SYS_FREQUENCY 260000000UL
|
||||
#define FRG_PLL_CLKDIV 13
|
||||
|
||||
enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT };
|
||||
enum {
|
||||
HAL_GPIO_TYPE_NOPULL_WEAKEST = 0x0,
|
||||
HAL_GPIO_TYPE_NOPULL_WEAK = 0x4,
|
||||
HAL_GPIO_TYPE_NOPULL_STRONG = 0x8,
|
||||
HAL_GPIO_TYPE_NOPULL_STRONGEST = 0xC,
|
||||
HAL_GPIO_TYPE_PULLUP_WEAKEST = 0x1,
|
||||
HAL_GPIO_TYPE_PULLUP_WEAK = 0x5,
|
||||
HAL_GPIO_TYPE_PULLUP_STRONG = 0x9,
|
||||
HAL_GPIO_TYPE_PULLUP_STRONGEST = 0xD,
|
||||
HAL_GPIO_TYPE_PULLDOWN_WEAKEST = 0x2,
|
||||
HAL_GPIO_TYPE_PULLDOWN_WEAK = 0x6,
|
||||
HAL_GPIO_TYPE_PULLDOWN_STRONG = 0xA,
|
||||
HAL_GPIO_TYPE_PULLDOWN_STRONGEST = 0xE,
|
||||
HAL_GPIO_TYPE_NOPULL = HAL_GPIO_TYPE_NOPULL_STRONGEST,
|
||||
HAL_GPIO_TYPE_PULLUP = HAL_GPIO_TYPE_PULLUP_STRONGEST,
|
||||
HAL_GPIO_TYPE_PULLDOWN = HAL_GPIO_TYPE_PULLDOWN_STRONGEST,
|
||||
};
|
||||
|
||||
// Wrapper structure over the IO_MUX_* macros in fsl_io_mux.h
|
||||
// To use those macros with our gpio functions,
|
||||
// wrap them inside this structure as shown in the example from the
|
||||
// ethernet_init function.
|
||||
typedef struct
|
||||
{
|
||||
uint32_t pinLowMask, pinHighMask, gpioFcSetMask,
|
||||
gpioFcClrMask, fselSetMask, fselClrMask, ctimerSetMask,
|
||||
ctimerClrMask, sctimerSetMask, sctimerClrMask;
|
||||
} io_mux_config_t;
|
||||
|
||||
static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type,
|
||||
io_mux_config_t *mux_config) {
|
||||
uint8_t bank = PINBANK(pin);
|
||||
uint8_t pin_no = PINNO(pin);
|
||||
if (mode == HAL_GPIO_MODE_INPUT) {
|
||||
GPIO->DIR[bank] &= ~(1 << pin_no);
|
||||
} else {
|
||||
GPIO->DIR[bank] |= (1 << pin_no);
|
||||
}
|
||||
|
||||
// configure pull
|
||||
pin_no += bank * 32;
|
||||
uint32_t shift = (pin_no % 16) * 2;
|
||||
uint32_t pull_val = (uint32_t)type & 3;
|
||||
uint32_t drive_val = ((uint32_t)type >> 2) & 3;
|
||||
volatile uint32_t *pull_reg, *drive_reg;
|
||||
if (pin_no < 22 || pin_no > 27) {
|
||||
pull_reg = &SOCCTRL->PAD_PU_PD_EN0 + pin_no / 16;
|
||||
drive_reg = &SOCCTRL->SR_CONFIG0 + pin_no / 16;
|
||||
} else {
|
||||
pull_reg = &AON_SOC_CIU->PAD_PU_PD_EN1;
|
||||
drive_reg = &AON_SOC_CIU->SR_CONFIG1;
|
||||
}
|
||||
*pull_reg = (*pull_reg & ~(3 << shift)) | (pull_val << shift);
|
||||
*drive_reg = (*drive_reg & ~(3 << shift)) | (drive_val << shift);
|
||||
|
||||
if (mux_config) {
|
||||
IO_MUX_SetPinMux(mux_config->pinLowMask, mux_config->pinHighMask,
|
||||
mux_config->gpioFcSetMask, mux_config->gpioFcClrMask,
|
||||
mux_config->fselSetMask, mux_config->fselClrMask,
|
||||
mux_config->ctimerSetMask, mux_config->ctimerClrMask,
|
||||
mux_config->sctimerSetMask, mux_config->sctimerClrMask
|
||||
);
|
||||
} else {
|
||||
// if no mux config is specified, assume default gpio settings and
|
||||
// no special function and just enable the IO
|
||||
IO_MUX_SetPinMux(bank ? 0 : BIT(PINNO(pin)), !bank ? 0 : BIT(PINNO(pin)),
|
||||
1 << 16, 0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hal_gpio_input(uint16_t pin) {
|
||||
hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_TYPE_PULLUP_STRONGEST, NULL);
|
||||
}
|
||||
static inline void hal_gpio_output(uint16_t pin) {
|
||||
hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_TYPE_NOPULL_STRONGEST, NULL);
|
||||
}
|
||||
|
||||
static inline bool hal_gpio_read(uint16_t pin) {
|
||||
return GPIO->PIN[PINBANK(pin)] & (1 << PINNO(pin));
|
||||
}
|
||||
|
||||
static inline void hal_gpio_write(uint16_t pin, bool value) {
|
||||
if (value) {
|
||||
GPIO->SET[PINBANK(pin)] |= (1 << PINNO(pin));
|
||||
} else {
|
||||
GPIO->CLR[PINBANK(pin)] |= (1 << PINNO(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hal_gpio_toggle(uint16_t pin) {
|
||||
hal_gpio_read(pin) ? hal_gpio_write(pin, false) : hal_gpio_write(pin, true);
|
||||
}
|
||||
|
||||
#if defined(__ZEPHYR__)
|
||||
#undef MG_HAL_DISABLE_SYSTEM_INIT
|
||||
#define MG_HAL_DISABLE_SYSTEM_INIT 1
|
||||
#undef MG_HAL_DISABLE_NEWLIB
|
||||
#define MG_HAL_DISABLE_NEWLIB 1
|
||||
#undef MG_HAL_DISABLE_CLOCK
|
||||
#define MG_HAL_DISABLE_CLOCK 1
|
||||
#undef MG_HAL_DISABLE_RANDOM
|
||||
#define MG_HAL_DISABLE_RANDOM 1
|
||||
#undef MG_HAL_DISABLE_MILLIS
|
||||
#define MG_HAL_DISABLE_MILLIS 1
|
||||
#undef MG_HAL_DISABLE_UART
|
||||
#define MG_HAL_DISABLE_UART 1
|
||||
#undef MG_HAL_DISABLE_ETHERNET
|
||||
#define MG_HAL_DISABLE_ETHERNET 1
|
||||
#undef MG_HAL_SYSTICK
|
||||
#define MG_HAL_SYSTICK MG_HAL_SYSTICK_NONE
|
||||
#endif
|
||||
|
||||
static inline void hal_uart_init(USART_Type *uart, unsigned long baud,
|
||||
uint16_t tx_pin, uint16_t rx_pin) {
|
||||
if (uart == USART0 && tx_pin == PIN(0, 3) &&
|
||||
rx_pin == PIN(0, 2)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC0_USART_DATA);
|
||||
} else if (uart == USART1 && tx_pin == PIN(0, 8) &&
|
||||
rx_pin == PIN(0, 9)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC1_USART_DATA);
|
||||
} else if (uart == USART2 && tx_pin == PIN(0, 14) &&
|
||||
rx_pin == PIN(0, 13)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC2_USART_DATA);
|
||||
} else if (uart == USART3 && tx_pin == PIN(0, 26) &&
|
||||
rx_pin == PIN(0, 24)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC3_USART_DATA);
|
||||
} else if (uart == USART14 && tx_pin == PIN(1, 24) &&
|
||||
rx_pin == PIN(1, 25)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC14_USART_DATA);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t uart_index = ((uint32_t)uart - USART0_BASE) / 0x1000;
|
||||
|
||||
// set FRG clk
|
||||
CLKCTL1->FLEXCOMM[uart_index].FRGCLKSEL = kCLOCK_FrgPllDiv;
|
||||
CLKCTL1->FLEXCOMM[uart_index].FRGCTL =
|
||||
(CLKCTL1_FRGCTL_MULT(0) | CLKCTL1_FRGCTL_DIV(255));
|
||||
CLOCK_AttachClk(CLKCTL1_TUPLE_MUXA(0x508 + uart_index * 0x20, 4));
|
||||
|
||||
// set FLEXCOMM to USART mode
|
||||
CLOCK_EnableClock(CLK_GATE_DEFINE(CLK_CTL1_PSCCTL0, uart_index + 8));
|
||||
uint32_t mask = BIT(uart_index + 8);
|
||||
RSTCTL1->PRSTCTL0_SET = mask;
|
||||
while ((RSTCTL1->PRSTCTL0 & mask) == 0)
|
||||
spin(1);
|
||||
RSTCTL1->PRSTCTL0_CLR = mask;
|
||||
while ((RSTCTL1->PRSTCTL0 & mask) == mask)
|
||||
spin(1);
|
||||
((FLEXCOMM_Type *)uart)->PSELID = 1;
|
||||
|
||||
uart->FIFOCFG |= USART_FIFOCFG_EMPTYTX_MASK |
|
||||
USART_FIFOCFG_ENABLETX_MASK; // empty and enable txFIFO
|
||||
uart->CFG = BIT(2) | BIT(0); // 8 bit data length and enable
|
||||
|
||||
// compute baud
|
||||
uint32_t uart_freq = SYS_FREQUENCY / FRG_PLL_CLKDIV;
|
||||
uint32_t best_diff = (uint32_t)-1, best_osr = 0xF, best_brg = (uint32_t)-1;
|
||||
uint32_t osr, brg, diff, current_baud;
|
||||
for (osr = 15; osr >= 4; osr--) {
|
||||
brg = (((uart_freq * 10) / ((osr + 1) * baud)) - 5) / 10;
|
||||
if (brg > 0xFFFF)
|
||||
continue;
|
||||
current_baud = uart_freq / ((osr + 1) * (brg + 1));
|
||||
diff =
|
||||
(baud < current_baud) ? (current_baud - baud) : (baud - current_baud);
|
||||
if (diff < best_diff) {
|
||||
best_diff = diff;
|
||||
best_osr = osr;
|
||||
best_brg = brg;
|
||||
}
|
||||
}
|
||||
uart->OSR = best_osr;
|
||||
uart->BRG = best_brg;
|
||||
}
|
||||
|
||||
static inline void hal_uart_write_byte(USART_Type *uart, uint8_t byte) {
|
||||
while ((uart->FIFOSTAT & USART_FIFOSTAT_TXNOTFULL_MASK) == 0)
|
||||
spin(1);
|
||||
uart->FIFOWR = byte;
|
||||
}
|
||||
static inline void hal_uart_write_buf(USART_Type *uart, char *buf, size_t len) {
|
||||
while (len-- > 0)
|
||||
hal_uart_write_byte(uart, *(uint8_t *)buf++);
|
||||
}
|
||||
|
||||
static inline void hal_rng_init(void) {
|
||||
trng_config_t config;
|
||||
TRNG_GetDefaultConfig(&config);
|
||||
TRNG_Init(TRNG, &config);
|
||||
}
|
||||
|
||||
static inline uint32_t hal_rng_read(void) {
|
||||
uint32_t value;
|
||||
TRNG_GetRandomData(TRNG, &value, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline void hal_ethernet_init(void) {
|
||||
CLOCK_EnableClock(kCLOCK_TddrMciEnetClk);
|
||||
hal_gpio_init(PIN(0, 25), HAL_GPIO_MODE_OUTPUT, IO_MUX_PinConfigPullUpDriveWeak,
|
||||
&(io_mux_config_t){ IO_MUX_ENET_CLK }); // ENET_CLK on GPIO_25
|
||||
IO_MUX_SetPinMux(
|
||||
IO_MUX_ENET_RX); // ENET_RX on pin GPIO_62, GPIO_63, GPIO_22, GPIO_23
|
||||
IO_MUX_SetPinMux(IO_MUX_ENET_TX); // ENET_TX on pin GPIO_58, GPIO_59, GPIO_60
|
||||
IO_MUX_SetPinMux(IO_MUX_ENET_MDIO); // ENET_MDIO on pin GPIO_56, GPIO_57
|
||||
|
||||
// reset ENET peripheral
|
||||
RSTCTL0->PRSTCTL1_SET = BIT(25);
|
||||
while ((RSTCTL0->PRSTCTL1 & BIT(25)) == 0)
|
||||
spin(1);
|
||||
RSTCTL0->PRSTCTL1_CLR = BIT(25);
|
||||
while ((RSTCTL0->PRSTCTL1 & BIT(25)) == BIT(25))
|
||||
spin(1);
|
||||
RSTCTL0->PRSTCTL1_SET = BIT(26);
|
||||
while ((RSTCTL0->PRSTCTL1 & BIT(26)) == 0)
|
||||
spin(1);
|
||||
RSTCTL0->PRSTCTL1_CLR = BIT(26);
|
||||
while ((RSTCTL0->PRSTCTL1 & BIT(26)) == BIT(26))
|
||||
spin(1);
|
||||
|
||||
// Enable clocks
|
||||
CLOCK_EnableClock(kCLOCK_EnetIpg);
|
||||
CLOCK_EnableClock(kCLOCK_EnetIpgS);
|
||||
|
||||
NVIC_EnableIRQ(ENET_IRQn);
|
||||
}
|
||||
extern void set_flexspi_clock(FLEXSPI_Type *base, uint32_t src,
|
||||
uint32_t divider);
|
||||
static inline void hal_clock_init(void) {
|
||||
const clock_avpll_config_t avpllConfig_BOARD_BootClockRUN = {
|
||||
.ch1Freq =
|
||||
kCLOCK_AvPllChFreq12p288m, // AVPLL channel frequency 12.288 MHz
|
||||
.ch2Freq = kCLOCK_AvPllChFreq64m, // AVPLL channel frequency 64 MHz
|
||||
.enableCali = true, // AVPLL calibration is enabled
|
||||
};
|
||||
// As NXP BOARD_BootClockRUN(): the ROM leaves the ELS glitch detector and
|
||||
// voltage sensors armed, trimmed for the boot clock. Disable them before
|
||||
// changing the clock, or the ROM resets the chip when it next uses ELS
|
||||
POWER_DisableGDetVSensors();
|
||||
if ((PMU->CAU_SLP_CTRL & PMU_CAU_SLP_CTRL_SOC_SLP_RDY_MASK) == 0) {
|
||||
CLOCK_EnableClock(
|
||||
kCLOCK_RefClkCauSlp); // Enable the CAU sleep clock for PMU
|
||||
}
|
||||
if ((SYSCTL2->SOURCE_CLK_GATE & SYSCTL2_SOURCE_CLK_GATE_REFCLK_SYS_CG_MASK) !=
|
||||
0) {
|
||||
CLOCK_EnableClock(kCLOCK_RefClkSys); // Enable the REFCLK_SYS clock
|
||||
}
|
||||
CLOCK_InitT3RefClk(kCLOCK_T3MciIrc48m); // Initialize T3 PLL and enable
|
||||
// outputs that are not clock gated
|
||||
CLOCK_EnableClock(
|
||||
kCLOCK_T3PllMciIrcClk); // Enable FFRO - T3 PLL 48/60 MHz IRC clock output
|
||||
CLOCK_EnableClock(
|
||||
kCLOCK_T3PllMci256mClk); // Enable T3 PLL 256 MHz clock output
|
||||
|
||||
// Move FLEXSPI clock source to T3 256M / 4 to avoid instruction/data fetch
|
||||
// issue in XIP when updating PLL and main clock.
|
||||
set_flexspi_clock(FLEXSPI, 6, 4);
|
||||
CLOCK_AttachClk(kSYSOSC_to_MAIN_CLK); // Set core clock to safe system
|
||||
// oscillator for initialization
|
||||
CLOCK_SetClkDiv(kCLOCK_DivSysCpuAhbClk,
|
||||
1); // Set system CPU AHB clock divider to 1
|
||||
CLOCK_InitTcpuRefClk(
|
||||
3120000000UL,
|
||||
kCLOCK_TcpuFlexspiDiv10); // Initialize TCPU PLL and enable outputs
|
||||
CLOCK_EnableClock(kCLOCK_TcpuMciClk); // Enable TCPU PLL MCI clock output
|
||||
CLOCK_InitTddrRefClk(
|
||||
kCLOCK_TddrFlexspiDiv10); // Initialize TDDR PLL and enable outputs
|
||||
CLOCK_EnableClock(
|
||||
kCLOCK_TddrMciFlexspiClk); // Enable TDDR PLL FlexSPI clock output
|
||||
CLOCK_InitAvPll(&avpllConfig_BOARD_BootClockRUN); // Initialize AVPLL and
|
||||
// enable both channels
|
||||
CLOCK_AttachClk(kRC32K_to_CLK32K); // Switch CLK32K to RC32K
|
||||
CLOCK_AttachClk(
|
||||
kSYSTICK_DIV_to_SYSTICK_CLK); // Switch SYSTICK_CLK to SYSTICK_DIV
|
||||
CLOCK_SetClkDiv(kCLOCK_DivAudioPllClk, 1U); // Set AUDIOPLLCLKDIV divider to 1
|
||||
CLOCK_SetClkDiv(kCLOCK_DivPllFrgClk,
|
||||
FRG_PLL_CLKDIV); // Set FRGPLLCLKDIV divider
|
||||
CLOCK_SetClkDiv(kCLOCK_DivMainPllClk, 1U); // Set MAINPLLCLKDIV divider to 1
|
||||
CLOCK_SetClkDiv(kCLOCK_DivAux0PllClk, 1U); // Set AUX0PLLCLKDIV divider to 1
|
||||
CLOCK_SetClkDiv(kCLOCK_DivSystickClk, 1U); // Set SYSTICKFCLKDIV divider to 1
|
||||
CLOCK_SetClkDiv(kCLOCK_DivPmuFclk, 5U); // Set PMUFCLKDIV divider to 5
|
||||
CLOCK_AttachClk(
|
||||
kMAIN_PLL_to_MAIN_CLK); // Select the main clock source for system clock
|
||||
set_flexspi_clock(FLEXSPI, 2U, 2U); // set_flexspi_clock
|
||||
|
||||
CLOCK_EnableClock(kCLOCK_HsGpio0);
|
||||
RSTCTL1->PRSTCTL1_SET = BIT(0); // Reset GPIO_0
|
||||
while ((RSTCTL1->PRSTCTL1 & BIT(0)) == 0)
|
||||
spin(1);
|
||||
RSTCTL1->PRSTCTL1_CLR = BIT(0);
|
||||
while ((RSTCTL1->PRSTCTL1 & BIT(0)) == BIT(0))
|
||||
spin(1);
|
||||
|
||||
CLOCK_EnableClock(kCLOCK_HsGpio1);
|
||||
RSTCTL1->PRSTCTL1_SET = BIT(1); // Reset GPIO_1
|
||||
while ((RSTCTL1->PRSTCTL1 & BIT(1)) == 0)
|
||||
spin(1);
|
||||
RSTCTL1->PRSTCTL1_CLR = BIT(1);
|
||||
while ((RSTCTL1->PRSTCTL1 & BIT(1)) == BIT(1))
|
||||
spin(1);
|
||||
}
|
||||
|
||||
#if defined(__MCUXPRESSO)
|
||||
#undef MG_HAL_DISABLE_SYSTEM_INIT
|
||||
#define MG_HAL_DISABLE_SYSTEM_INIT 1
|
||||
#undef MG_HAL_SBRK_NONE
|
||||
#define MG_HAL_SBRK_NONE 1
|
||||
#endif
|
||||
|
||||
static inline void hal_system_init(void) {
|
||||
SCB->CPACR |=
|
||||
(3UL << 0 * 2) | (3UL << 1 * 2) | // Enable PowerQuad (CPO/CP1)
|
||||
(3UL << 10 * 2) | (3UL << 11 * 2); // Enable FPU
|
||||
SCB->NSACR |= ((3UL << 0) | (3UL << 10));
|
||||
CACHE64_CTRL0->CCR = CACHE64_CTRL_CCR_INVW1_MASK | CACHE64_CTRL_CCR_INVW0_MASK;
|
||||
CACHE64_CTRL0->CCR |= CACHE64_CTRL_CCR_GO_MASK;
|
||||
while ((CACHE64_CTRL0->CCR & CACHE64_CTRL_CCR_GO_MASK) != 0) spin(1);
|
||||
// Enable cache, enable write buffer
|
||||
CACHE64_CTRL0->CCR = (CACHE64_CTRL_CCR_ENWRBUF_MASK | CACHE64_CTRL_CCR_ENCACHE_MASK);
|
||||
|
||||
// Set whole FlexSPI0 space to write through
|
||||
CACHE64_POLSEL0->REG0_TOP = 0x07FFFC00U;
|
||||
CACHE64_POLSEL0->REG1_TOP = 0x0U;
|
||||
CACHE64_POLSEL0->POLSEL = 0x1U;
|
||||
|
||||
__DSB();
|
||||
__ISB();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
ENTRY(Reset_Handler);
|
||||
MEMORY {
|
||||
flash(rx) : ORIGIN = 0x08000000, LENGTH = 0x4000000
|
||||
sram(rwx) : ORIGIN = 0x20000000, LENGTH = 0x123000 /* Top is reserved, as in NXP wireless linker scripts */
|
||||
}
|
||||
__StackTop = ORIGIN(sram) + LENGTH(sram);
|
||||
|
||||
SECTIONS {
|
||||
.boot_hdr : ALIGN(4) { FILL(0xFF); . = 0x400; __FLASH_BASE = .; KEEP(*(.flash_conf)) . = 0x1000; } > flash
|
||||
.vectors : { FILL(256) KEEP(*(.isr_vector)) } > flash
|
||||
.text : { *(.text.* *.text.*) } > flash
|
||||
.rodata : { *(.rodata*) } > flash
|
||||
|
||||
.iram : {
|
||||
_siram = .;
|
||||
*(.iram)
|
||||
*(CodeQuickAccess) /* NXP SDK drivers, e.g. fsl_power.c */
|
||||
*(DataQuickAccess)
|
||||
_eiram = .;
|
||||
} > sram AT > flash
|
||||
_siiram = LOADADDR(.iram);
|
||||
|
||||
.data : {
|
||||
_sdata = .;
|
||||
*(.first_data)
|
||||
*(.data SORT(.data.*))
|
||||
_edata = .;
|
||||
} > sram AT > flash
|
||||
_sidata = LOADADDR(.data);
|
||||
|
||||
.bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) _ebss = .; } > sram
|
||||
. = ALIGN(8);
|
||||
_end = .;
|
||||
|
||||
_image_start = LOADADDR(.text);
|
||||
_image_end = LOADADDR(.data) + SIZEOF(.data);
|
||||
_image_size = _image_end - _image_start;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
#define LED_RADIO PIN(0, 12) // Green, active low
|
||||
#define LED_LAN PIN(0, 0) // Blue, active low
|
||||
#define LED_RED PIN(0, 1) // Red, active low
|
||||
|
||||
static uint64_t s_radio_led_until;
|
||||
|
||||
// Cumulative totals. Up: radio -> LAN; down: LAN -> radio queue.
|
||||
static uint64_t s_frames_up, s_frames_down, s_bytes_up, s_bytes_down;
|
||||
static uint64_t s_radio_sent, s_radio_received;
|
||||
static size_t (*s_radio_tx)(struct mg_tcpip_if *, const void *, size_t);
|
||||
|
||||
static size_t radio_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len) {
|
||||
size_t sent = s_radio_tx(ifp, buf, len);
|
||||
if (sent != 0) s_radio_sent++;
|
||||
return sent;
|
||||
}
|
||||
|
||||
static size_t eth_up(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
size_t sent = mg_tcpip_driver_rw612.tx(buf, len, ifp);
|
||||
if (sent != 0) s_frames_up++, s_bytes_up += sent;
|
||||
return sent;
|
||||
}
|
||||
|
||||
static size_t eth_down(struct mg_mesh_node *node, const void *buf, size_t len) {
|
||||
size_t queued = mg_mesh_default.tx(node, buf, len);
|
||||
if (queued != 0) s_frames_down++, s_bytes_down += queued;
|
||||
return queued;
|
||||
}
|
||||
|
||||
static void radio_rx(struct mg_mesh_node *node, void *buf, size_t len) {
|
||||
s_radio_received++;
|
||||
s_radio_led_until = mg_millis() + 2;
|
||||
mg_mesh_default.rx(node, buf, len);
|
||||
}
|
||||
|
||||
static void log_fn(char ch, void *arg) {
|
||||
hal_uart_write_buf(arg, &ch, 1);
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return hal_get_tick();
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
static struct mg_tcpip_driver_rw612_data eth_data;
|
||||
static struct mg_tcpip_if eth;
|
||||
static struct mg_tcpip_driver eth_driver;
|
||||
static char eth_queue[8192];
|
||||
static struct mg_tcpip_driver_rw612w_data radio_data;
|
||||
static struct mg_tcpip_if radio;
|
||||
static struct mg_mesh mesh;
|
||||
uint32_t lan_received = 0;
|
||||
uint64_t lan_led_until = 0;
|
||||
uint64_t link_timer = 0, stats_timer = 0;
|
||||
|
||||
hal_clock_init();
|
||||
hal_rng_init();
|
||||
hal_gpio_output(LED_RADIO);
|
||||
hal_gpio_output(LED_LAN);
|
||||
hal_gpio_output(LED_RED);
|
||||
hal_gpio_write(LED_RADIO, true);
|
||||
hal_gpio_write(LED_LAN, true);
|
||||
hal_gpio_write(LED_RED, true);
|
||||
hal_uart_init(USART3, 115200, PIN(0, 26), PIN(0, 24));
|
||||
mg_log_set_fn(log_fn, USART3);
|
||||
hal_ethernet_init();
|
||||
|
||||
mesh = mg_mesh_default;
|
||||
mesh.tx = eth_down;
|
||||
mesh.rx = radio_rx; // Observe radio reception without modifying the driver.
|
||||
radio_data.strategy = &mesh;
|
||||
radio.driver = &mg_tcpip_driver_rw612w;
|
||||
radio.driver_data = &radio_data;
|
||||
if (!radio.driver->init(&radio)) return 1;
|
||||
s_radio_tx = radio_data.mesh->tx;
|
||||
radio_data.mesh->tx = radio_tx;
|
||||
|
||||
eth_data.mdc_cr = MG_DRIVER_MDC_CR;
|
||||
eth_data.mdc_holdtime = MG_DRIVER_MDC_HOLDTIME;
|
||||
eth_data.phy_addr = MG_TCPIP_PHY_ADDR;
|
||||
eth_driver = mg_tcpip_driver_rw612;
|
||||
eth_driver.tx = eth_up;
|
||||
eth.driver = ð_driver;
|
||||
eth.driver_data = ð_data;
|
||||
memcpy(eth.mac, radio.mac, 6);
|
||||
mg_queue_init(ð.recv_queue, eth_queue, sizeof(eth_queue));
|
||||
if (!eth.driver->init(ð)) return 1;
|
||||
|
||||
ENET->RCR |= ENET_RCR_PROM_MASK; // Receive frames addressed to radio nodes.
|
||||
radio_data.mesh->lan = ð
|
||||
radio_data.mesh->next_tick = 0;
|
||||
|
||||
MG_INFO(("Ethernet/radio bridge, channel %u", MG_802154_CHANNEL));
|
||||
stats_timer = mg_millis() + 5000;
|
||||
for (;;) {
|
||||
uint64_t now = mg_millis();
|
||||
if (mg_timer_expired(&link_timer, 100, mg_millis())) {
|
||||
eth.state = eth.driver->poll(ð, true) ? MG_TCPIP_STATE_UP
|
||||
: MG_TCPIP_STATE_DOWN;
|
||||
}
|
||||
radio.driver->poll(&radio, false);
|
||||
if (mg_timer_expired(&stats_timer, 5000, now)) {
|
||||
MG_INFO(
|
||||
("ETH frames up/down %llu/%llu, bytes up/down "
|
||||
"%llu/%llu, radio frames sent/rcvd %llu/%llu",
|
||||
s_frames_up, s_frames_down, s_bytes_up, s_bytes_down, s_radio_sent,
|
||||
s_radio_received));
|
||||
}
|
||||
if (eth.nrecv != lan_received) {
|
||||
lan_received = eth.nrecv;
|
||||
lan_led_until = now + 2;
|
||||
}
|
||||
hal_gpio_write(LED_RADIO, now >= s_radio_led_until);
|
||||
hal_gpio_write(LED_LAN, now >= lan_led_until);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
// See https://mongoose.ws/docs/getting-started/build-environment/
|
||||
#define MG_ARCH MG_ARCH_ARMGCC
|
||||
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
#define MG_ENABLE_DRIVER_RW612 1 // Ethernet
|
||||
#define MG_ENABLE_DRIVER_RW612W 1 // Mesh bridge over raw 802.15.4
|
||||
#define MG_ENABLE_CUSTOM_MILLIS 1
|
||||
|
||||
|
||||
// The bridge initializes and polls its two L2 interfaces directly.
|
||||
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
|
||||
@@ -0,0 +1,236 @@
|
||||
// Load NXP BLE/802.15.4 combo firmware to CPU2 and start it.
|
||||
// Port of NXP mcux-component conn_fwloader: fsl_loader_utils.c (SB3 loader,
|
||||
// load_service_monolithic) and nboot_hal.c (manifest parameters).
|
||||
// Copyright 2024 NXP
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#include "hal.h"
|
||||
#include "fsl_ocotp.h"
|
||||
#include "fsl_power.h"
|
||||
#include "fsl_romapi_nboot.h"
|
||||
#include "fsl_romapi_otp.h"
|
||||
#include "fsl_kb_api.h"
|
||||
#include "fsl_sbloader_v3.h"
|
||||
|
||||
// The firmware image, linked into our flash. Signed SB3 container, see
|
||||
// https://github.com/NXP/wifi_nb_fw, rw61x/rw61x_sb_ble_15d4_combo_a2.bin
|
||||
__asm__(
|
||||
".section .rodata.radio_fw,\"a\"\n"
|
||||
".balign 16\n"
|
||||
"radio_fw:\n"
|
||||
".incbin \"" RADIO_FW "\"\n"
|
||||
".balign 4\n"
|
||||
"radio_fw_end:\n"
|
||||
".previous\n");
|
||||
extern const uint8_t radio_fw[], radio_fw_end[];
|
||||
|
||||
#define CIU2_RST_SW3 (*(volatile uint32_t *) 0x4424011cU)
|
||||
#define CIU2_RST_SW3_CPU2_RST BIT(4)
|
||||
#define CPU2_MAGIC (*(volatile uint32_t *) 0x443c0000U) // Set by firmware
|
||||
#define CPU2_MAGIC_VALUE 0xabcdef89U
|
||||
#define OTP_LCS_FUSE_IDX 45U // Life cycle state, and its redundant copy
|
||||
#define OTP_PCK_BLOB_FUSE_IDX 116U // Part common key blob, 48 bytes
|
||||
|
||||
// SB3 loader state. See fsl_ldr_Context_v3_t
|
||||
struct sb3 {
|
||||
nboot_context_t nboot; // ROM nboot context
|
||||
uint8_t buf[MAX(NBOOT_SB3_MANIFEST_MAX_SIZE_IN_BYTES,
|
||||
NBOOT_SB3_BLOCK_MAX_SIZE_IN_BYTES)]; // Block being read
|
||||
uint32_t buf_size; // Bytes needed for the current block
|
||||
uint32_t buf_pos; // Bytes in buf so far
|
||||
bool header_done; // Manifest (block 0) processed
|
||||
uint32_t data_offset; // Data offset within a block
|
||||
uint32_t blocks; // Data blocks processed
|
||||
uint8_t *data; // Data of the current block
|
||||
uint32_t pos; // Position in data
|
||||
bool in_section; // Inside a data section
|
||||
uint32_t section_done; // Bytes of the section handled
|
||||
sb3_section_header_t section;
|
||||
bool in_range; // Inside a data range
|
||||
uint32_t range_gap; // Padding after the range, 16 byte alignment
|
||||
sb3_data_range_header_t range;
|
||||
bool has_expansion; // Range expansion read
|
||||
bool executed; // Execute command seen: firmware loaded
|
||||
};
|
||||
static struct sb3 s_sb3;
|
||||
|
||||
static uint32_t antipole(uint32_t v) {
|
||||
return (v & 0xffffU) | (~(v & 0xffffU) << 16);
|
||||
}
|
||||
|
||||
// Manifest parameters: root of trust for NXP signed images, rev A2 chips.
|
||||
// See nboot_hal_get_sb3_manifest_params(), nboot_hal.c
|
||||
static bool manifest_params(nboot_sb3_load_manifest_parms_t *p) {
|
||||
static const uint32_t rkh_a2[8] = {0xE7C7E9BB, 0x12C8C535, 0x37E61148,
|
||||
0x2BE7F18C, 0x8F0E3094, 0xB2BA7F32,
|
||||
0xEC9B4ECB, 0xAD9FC941};
|
||||
uint32_t i, lcs = OCOTP->OTP_SHADOW[OTP_LCS_FUSE_IDX];
|
||||
memset(p, 0, sizeof(*p));
|
||||
if ((lcs & 0xff) != ((lcs >> 8) & 0xff)) return false; // Corrupt LCS
|
||||
for (i = 0; i < NBOOT_ROOT_CERT_COUNT; i++) {
|
||||
p->soc_RoTNVM.soc_rootKeyRevocation[i] = kNBOOT_RootKey_Enabled;
|
||||
}
|
||||
memcpy(p->soc_RoTNVM.soc_rkh, rkh_a2, sizeof(rkh_a2));
|
||||
p->soc_RoTNVM.soc_numberOfRootKeys = 1;
|
||||
for (i = 1; i < NBOOT_ROOT_CERT_COUNT; i++) {
|
||||
p->soc_RoTNVM.soc_rootKeyUsage[i] = kNBOOT_RootKeyUsage_Unused;
|
||||
}
|
||||
p->soc_RoTNVM.soc_rootKeyTypeAndLength = kNBOOT_RootKey_Ecdsa_P256;
|
||||
p->soc_RoTNVM.soc_lifecycle = (nboot_soc_lifecycle_t) antipole(lcs & 0xff);
|
||||
for (i = 0; i < sizeof(p->pckBlob) / 4; i++) {
|
||||
if (OCOTP_OtpFuseRead(OTP_PCK_BLOB_FUSE_IDX + i,
|
||||
(uint32_t *) &p->pckBlob[4 * i]) != kStatus_Success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Manifest (block 0): verify, learn the block layout. See ldr_DoHeader_v3()
|
||||
static bool sb3_header(struct sb3 *s) {
|
||||
nboot_sb3_header_t *h = (nboot_sb3_header_t *) s->buf;
|
||||
nboot_sb3_load_manifest_parms_t parms;
|
||||
if (s->buf_size == sizeof(nboot_sb3_header_t)) { // Header only so far
|
||||
s->buf_size = h->imageTotalLength;
|
||||
s->buf_pos = sizeof(nboot_sb3_header_t);
|
||||
return s->buf_size <= sizeof(s->buf);
|
||||
}
|
||||
if (!manifest_params(&parms)) return false;
|
||||
// Protected status: the status code is in the low 32 bits
|
||||
if ((nboot_status_t) nboot_sb3_load_manifest(&s->nboot, (uint32_t *) h,
|
||||
&parms) != kStatus_NBOOT_Success) {
|
||||
return false;
|
||||
}
|
||||
s->data_offset = 4 + h->certificateBlockOffset - sizeof(nboot_sb3_header_t);
|
||||
s->buf_size = s->data_offset + NBOOT_SB3_CHUNK_SIZE_IN_BYTES;
|
||||
s->header_done = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copy (part of) the current load range to its destination
|
||||
static void sb3_load(struct sb3 *s) {
|
||||
uint32_t avail = NBOOT_SB3_CHUNK_SIZE_IN_BYTES - s->pos;
|
||||
uint32_t n = s->range.length < avail ? s->range.length : avail;
|
||||
memcpy((void *) s->range.startAddress, &s->data[s->pos], n);
|
||||
s->pos += n, s->section_done += n;
|
||||
s->range.startAddress += n, s->range.length -= n;
|
||||
if (s->range.length == 0) { // Range done, skip alignment padding
|
||||
s->in_range = false;
|
||||
s->pos += s->range_gap, s->section_done += s->range_gap;
|
||||
}
|
||||
}
|
||||
|
||||
// Process one decrypted data block. See ldr_DoBlock()
|
||||
static bool sb3_block(struct sb3 *s) {
|
||||
while (s->pos < NBOOT_SB3_CHUNK_SIZE_IN_BYTES) {
|
||||
if (!s->in_section) { // New section
|
||||
memcpy(&s->section, &s->data[s->pos], sizeof(s->section));
|
||||
if (s->section.sectionType == kSectionNone) break; // Rest is unused
|
||||
if (s->section.sectionType != kSectionDataRange) return false;
|
||||
s->in_section = true, s->section_done = 0, s->in_range = false;
|
||||
s->pos += sizeof(sb3_section_header_t);
|
||||
}
|
||||
if (!s->in_range) { // New range
|
||||
memcpy(&s->range, &s->data[s->pos], sizeof(s->range));
|
||||
if (s->range.tag != SB3_DATA_RANGE_HEADER_TAG) return false;
|
||||
s->in_range = true, s->has_expansion = false, s->range_gap = 0;
|
||||
s->pos += sizeof(sb3_data_range_header_t);
|
||||
s->section_done += sizeof(sb3_data_range_header_t);
|
||||
if (s->range.cmd == kSB3_CmdLoad) {
|
||||
s->range_gap = (SB3_DATA_ALIGNMENT_SIZE_IN_BYTE -
|
||||
(s->range.length & 15U)) & 15U;
|
||||
}
|
||||
}
|
||||
if (s->range.cmd == kSB3_CmdLoad) {
|
||||
if (!s->has_expansion) {
|
||||
if (s->pos == NBOOT_SB3_CHUNK_SIZE_IN_BYTES) break; // Next block
|
||||
s->has_expansion = true;
|
||||
s->pos += sizeof(sb3_data_range_expansion_t);
|
||||
s->section_done += sizeof(sb3_data_range_expansion_t);
|
||||
}
|
||||
if (s->pos < NBOOT_SB3_CHUNK_SIZE_IN_BYTES) sb3_load(s);
|
||||
} else if (s->range.cmd == kSB3_CmdExecute) {
|
||||
s->executed = true;
|
||||
return true;
|
||||
} else {
|
||||
s->in_range = false; // Other commands: nothing to do
|
||||
}
|
||||
if (s->section_done == s->section.length) s->in_section = false;
|
||||
if (s->section_done > s->section.length) return false;
|
||||
}
|
||||
s->pos = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Feed image bytes, process complete blocks. See fsl_sbloader_pump()
|
||||
static bool sb3_pump(struct sb3 *s, const uint8_t *p, uint32_t len) {
|
||||
while (len > 0 && !s->executed) {
|
||||
uint32_t n = s->buf_size - s->buf_pos;
|
||||
if (n > len) n = len;
|
||||
memcpy(&s->buf[s->buf_pos], p, n);
|
||||
s->buf_pos += n, p += n, len -= n;
|
||||
if (s->buf_pos < s->buf_size) break;
|
||||
s->buf_pos = 0;
|
||||
if (!s->header_done) {
|
||||
if (!sb3_header(s)) return false;
|
||||
} else {
|
||||
if (s->blocks >= s->nboot.totalBlocks) return false;
|
||||
if ((nboot_status_t) nboot_sb3_load_block(
|
||||
&s->nboot, (uint32_t *) s->buf) != kStatus_NBOOT_Success) {
|
||||
return false;
|
||||
}
|
||||
s->blocks++;
|
||||
s->data = &s->buf[s->data_offset];
|
||||
if (!sb3_block(s)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Authenticate the image with the ROM, copy it to CPU2 memory.
|
||||
// See loader_process_sb_file()
|
||||
static bool sb3_process(const uint8_t *img, size_t len) {
|
||||
struct sb3 *s = &s_sb3;
|
||||
bool ok = false;
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->buf_size = sizeof(nboot_sb3_header_t);
|
||||
// Start ELS only if it is off: the ROM keeps keys in it after boot
|
||||
if ((CLKCTL0->PSCCTL0 & CLKCTL0_PSCCTL0_ELS_MASK) == 0 ||
|
||||
(CLKCTL0->PSCCTL1 & CLKCTL0_PSCCTL1_ELS_APB_MASK) == 0 ||
|
||||
(RSTCTL0->PRSTCTL0 & RSTCTL0_PRSTCTL0_ELS_MASK) != 0) {
|
||||
CLOCK_EnableClock(kCLOCK_Els);
|
||||
CLOCK_EnableClock(kCLOCK_ElsApb);
|
||||
RESET_PeripheralReset(kELS_RST_SHIFT_RSTn);
|
||||
}
|
||||
if (nboot_context_init(&s->nboot) == kStatus_NBOOT_Success) {
|
||||
ok = sb3_pump(s, img, (uint32_t) len) && s->executed;
|
||||
(void) nboot_context_deinit(&s->nboot);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Load the radio firmware to CPU2 and start it. See sb3_fw_reset() and
|
||||
// load_service_monolithic(), fsl_loader_utils.c. Call before changing the
|
||||
// boot clock: the ROM trims the ELS glitch detector for it, and after the
|
||||
// clock change ELS reports glitch events. Interrupts may be off.
|
||||
// Return 0 on success, or the failed step: 1 chip revision, 2 OTP, 3 image
|
||||
// load, 4 firmware start
|
||||
int hal_radio_init(void) {
|
||||
uint32_t n = 100000000; // Firmware start timeout, loop iterations
|
||||
bool ok;
|
||||
if ((SOCCTRL->CHIP_INFO & 15U) != 2U) return 1; // Image is for rev A2
|
||||
POWER_PowerOffBle();
|
||||
spin(100000);
|
||||
if (otp_init(0) != kStatus_Success) return 2;
|
||||
POWER_PowerOnBle();
|
||||
spin(100000);
|
||||
CPU2_MAGIC = 0;
|
||||
ok = sb3_process(radio_fw, (size_t) (radio_fw_end - radio_fw));
|
||||
(void) otp_deinit();
|
||||
if (!ok) return 3;
|
||||
CIU2_RST_SW3 |= CIU2_RST_SW3_CPU2_RST;
|
||||
while (CPU2_MAGIC != CPU2_MAGIC_VALUE) {
|
||||
if (--n == 0) return 4; // Firmware did not start
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
#include "RW612.h"
|
||||
|
||||
void Reset_Handler(void); // Defined below
|
||||
void Dummy_Handler(void); // Defined below
|
||||
void SysTick_Handler(void); // Defined in main.c
|
||||
void SystemInit(void); // Defined in main.c, called by reset handler
|
||||
void __StackTop(void); // Defined in link.ld
|
||||
void _image_size(void);
|
||||
|
||||
#define WEAK __attribute__((weak, alias("Default_Handler")))
|
||||
|
||||
void Default_Handler(void);
|
||||
WEAK void NMI_Handler(void);
|
||||
WEAK void HardFault_Handler(void);
|
||||
WEAK void MemManage_Handler(void);
|
||||
WEAK void BusFault_Handler(void);
|
||||
WEAK void UsageFault_Handler(void);
|
||||
WEAK void SecureFault_Handler(void);
|
||||
WEAK void SVC_Handler(void);
|
||||
WEAK void DebugMon_Handler(void);
|
||||
WEAK void PendSV_Handler(void);
|
||||
WEAK void SysTick_Handler(void);
|
||||
WEAK void IntDefaultHandler(void);
|
||||
WEAK void WDT0_IRQHandler(void);
|
||||
WEAK void DMA0_IRQHandler(void);
|
||||
WEAK void GPIO_INTA_IRQHandler(void);
|
||||
WEAK void GPIO_INTB_IRQHandler(void);
|
||||
WEAK void PIN_INT0_IRQHandler(void);
|
||||
WEAK void PIN_INT1_IRQHandler(void);
|
||||
WEAK void PIN_INT2_IRQHandler(void);
|
||||
WEAK void PIN_INT3_IRQHandler(void);
|
||||
WEAK void UTICK_IRQHandler(void);
|
||||
WEAK void MRT_IRQHandler(void);
|
||||
WEAK void CTIMER0_IRQHandler(void);
|
||||
WEAK void CTIMER1_IRQHandler(void);
|
||||
WEAK void SCT0_IRQHandler(void);
|
||||
WEAK void CTIMER3_IRQHandler(void);
|
||||
WEAK void FLEXCOMM0_IRQHandler(void);
|
||||
WEAK void FLEXCOMM1_IRQHandler(void);
|
||||
WEAK void FLEXCOMM2_IRQHandler(void);
|
||||
WEAK void FLEXCOMM3_IRQHandler(void);
|
||||
WEAK void Reserved34_IRQHandler(void);
|
||||
WEAK void Reserved35_IRQHandler(void);
|
||||
WEAK void FLEXCOMM14_IRQHandler(void);
|
||||
WEAK void Reserved37_IRQHandler(void);
|
||||
WEAK void Reserved38_IRQHandler(void);
|
||||
WEAK void GFMRT_IRQHandler(void);
|
||||
WEAK void Reserved40_IRQHandler(void);
|
||||
WEAK void DMIC_IRQHandler(void);
|
||||
WEAK void WKDEEPSLEEP_IRQHandler(void);
|
||||
WEAK void HYPERVISOR_IRQHandler(void);
|
||||
WEAK void SECUREVIOLATION_IRQHandler(void);
|
||||
WEAK void HWVAD_IRQHandler(void);
|
||||
WEAK void Reserved46_IRQHandler(void);
|
||||
WEAK void Reserved47_IRQHandler(void);
|
||||
WEAK void RTC_IRQHandler(void);
|
||||
WEAK void Reserved49_IRQHandler(void);
|
||||
WEAK void Reserved50_IRQHandler(void);
|
||||
WEAK void PIN_INT4_IRQHandler(void);
|
||||
WEAK void PIN_INT5_IRQHandler(void);
|
||||
WEAK void PIN_INT6_IRQHandler(void);
|
||||
WEAK void PIN_INT7_IRQHandler(void);
|
||||
WEAK void CTIMER2_IRQHandler(void);
|
||||
WEAK void CTIMER4_IRQHandler(void);
|
||||
WEAK void OS_EVENT_TIMER_IRQHandler(void);
|
||||
WEAK void FLEXSPI_IRQHandler(void);
|
||||
WEAK void Reserved59_IRQHandler(void);
|
||||
WEAK void Reserved60_IRQHandler(void);
|
||||
WEAK void Reserved61_IRQHandler(void);
|
||||
WEAK void SDIO_IRQHandler(void);
|
||||
WEAK void SGPIO_INTA_IRQHandler(void);
|
||||
WEAK void SGPIO_INTB_IRQHandler(void);
|
||||
WEAK void Reserved65_IRQHandler(void);
|
||||
WEAK void USB_IRQHandler(void);
|
||||
WEAK void Reserved67_IRQHandler(void);
|
||||
WEAK void Reserved68_IRQHandler(void);
|
||||
WEAK void Reserved69_IRQHandler(void);
|
||||
WEAK void DMA1_IRQHandler(void);
|
||||
WEAK void PUF_IRQHandler(void);
|
||||
WEAK void POWERQUAD_IRQHandler(void);
|
||||
WEAK void Reserved73_IRQHandler(void);
|
||||
WEAK void Reserved74_IRQHandler(void);
|
||||
WEAK void Reserved75_IRQHandler(void);
|
||||
WEAK void Reserved76_IRQHandler(void);
|
||||
WEAK void LCD_IRQHandler(void);
|
||||
WEAK void CAPTIMER_IRQHandler(void);
|
||||
WEAK void Reserved79_IRQHandler(void);
|
||||
WEAK void W2MWKUP_DONE0_IRQHandler(void);
|
||||
WEAK void W2MWKUP_DONE1_IRQHandler(void);
|
||||
WEAK void W2MWKUP_DONE2_IRQHandler(void);
|
||||
WEAK void W2MWKUP_DONE3_IRQHandler(void);
|
||||
WEAK void W2MWKUP_DONE4_IRQHandler(void);
|
||||
WEAK void W2MWKUP_DONE5_IRQHandler(void);
|
||||
WEAK void W2MWKUP_DONE6_IRQHandler(void);
|
||||
WEAK void W2MWKUP_DONE7_IRQHandler(void);
|
||||
WEAK void W2MWKUP0_IRQHandler(void);
|
||||
WEAK void W2MWKUP1_IRQHandler(void);
|
||||
WEAK void WL_MCI_INT0_IRQHandler(void);
|
||||
WEAK void WL_MCI_INT1_IRQHandler(void);
|
||||
WEAK void WL_MCI_INT2_IRQHandler(void);
|
||||
WEAK void WL_MCI_INT3_IRQHandler(void);
|
||||
WEAK void WL_MCI_INT4_IRQHandler(void);
|
||||
WEAK void WL_MCI_INT5_IRQHandler(void);
|
||||
WEAK void WL_MCI_INT6_IRQHandler(void);
|
||||
WEAK void WL_MCI_INT7_IRQHandler(void);
|
||||
WEAK void B2MWKUP_DONE0_IRQHandler(void);
|
||||
WEAK void B2MWKUP_DONE1_IRQHandler(void);
|
||||
WEAK void B2MWKUP_DONE2_IRQHandler(void);
|
||||
WEAK void B2MWKUP_DONE3_IRQHandler(void);
|
||||
WEAK void B2MWKUP_DONE4_IRQHandler(void);
|
||||
WEAK void B2MWKUP_DONE5_IRQHandler(void);
|
||||
WEAK void B2MWKUP_DONE6_IRQHandler(void);
|
||||
WEAK void B2MWKUP_DONE7_IRQHandler(void);
|
||||
WEAK void B2MWKUP0_IRQHandler(void);
|
||||
WEAK void B2MWKUP1_IRQHandler(void);
|
||||
WEAK void BLE_MCI_INT0_IRQHandler(void);
|
||||
WEAK void BLE_MCI_INT1_IRQHandler(void);
|
||||
WEAK void BLE_MCI_INT2_IRQHandler(void);
|
||||
WEAK void BLE_MCI_INT3_IRQHandler(void);
|
||||
WEAK void BLE_MCI_INT4_IRQHandler(void);
|
||||
WEAK void BLE_MCI_INT5_IRQHandler(void);
|
||||
WEAK void BLE_MCI_INT6_IRQHandler(void);
|
||||
WEAK void BLE_MCI_INT7_IRQHandler(void);
|
||||
WEAK void PIN0_INT_IRQHandler(void);
|
||||
WEAK void PIN1_INT_IRQHandler(void);
|
||||
WEAK void ELS_IRQHandler(void);
|
||||
WEAK void ELS_GDET_IRQHandler(void);
|
||||
WEAK void ELS_GDET_UM_IRQHandler(void);
|
||||
WEAK void PKC_INT_IRQHandler(void);
|
||||
WEAK void PKC_ERR_IRQHandler(void);
|
||||
WEAK void CDOG_INT_IRQHandler(void);
|
||||
WEAK void GAU_DAC_IRQHandler(void);
|
||||
WEAK void GAU_ACOMP_WKUP_IRQHandler(void);
|
||||
WEAK void GAU_ACOMP_IRQHandler(void);
|
||||
WEAK void GAU_ADC1_IRQHandler(void);
|
||||
WEAK void GAU_ADC0_IRQHandler(void);
|
||||
WEAK void USIM_IRQHandler(void);
|
||||
WEAK void OTP_IRQHandler(void);
|
||||
WEAK void ENET_IRQHandler(void);
|
||||
WEAK void ENET_TIMER_IRQHandler(void);
|
||||
WEAK void PMIP_IRQHandler(void);
|
||||
WEAK void PMIP_CHANGE_IRQHandler(void);
|
||||
WEAK void ITRC_IRQHandler(void);
|
||||
WEAK void Reserved136_IRQHandler(void);
|
||||
WEAK void Reserved137_IRQHandler(void);
|
||||
WEAK void Reserved138_IRQHandler(void);
|
||||
WEAK void TRNG_IRQHandler(void);
|
||||
WEAK void ACC_C_INT_IRQHandler(void);
|
||||
WEAK void ACC_S_INT_IRQHandler(void);
|
||||
WEAK void WACC_IRQHandler(void);
|
||||
WEAK void BACC_IRQHandler(void);
|
||||
WEAK void GDMA_IRQHandler(void);
|
||||
|
||||
|
||||
#define WEAK_ALIAS __attribute__((weak, alias("Default_Handler")))
|
||||
|
||||
|
||||
__attribute__((section(".vectors"))) void (*const tab[16 + 156])(void) = {
|
||||
&__StackTop, // The initial stack pointer
|
||||
Reset_Handler, // The reset handler
|
||||
NMI_Handler, // NMI Handler
|
||||
HardFault_Handler, // Hard Fault Handler
|
||||
MemManage_Handler, // MPU Fault Handler
|
||||
BusFault_Handler, // Bus Fault Handler
|
||||
UsageFault_Handler, // Usage Fault Handler
|
||||
SecureFault_Handler, // Secure Fault Handler
|
||||
&_image_size, // Image size
|
||||
0, // Image type
|
||||
0, // Reserved
|
||||
SVC_Handler, // SVCall Handler
|
||||
DebugMon_Handler, // Debug Monitor Handler
|
||||
(void (*)())tab, // Image load address
|
||||
PendSV_Handler, // PendSV Handler
|
||||
SysTick_Handler, // SysTick Handler
|
||||
|
||||
// Chip Level - RW612
|
||||
WDT0_IRQHandler, // 16 : Windowed watchdog timer 0 (WDT0)(Cortex-M33 watchdog)
|
||||
DMA0_IRQHandler, // 17 : Direct memory access (DMA) controller 0 (secure or Cortex-M33 DMA)
|
||||
GPIO_INTA_IRQHandler, // 18 : GPIO interrupt A
|
||||
GPIO_INTB_IRQHandler, // 19 : GPIO interrupt B
|
||||
PIN_INT0_IRQHandler, // 20 : Pin interrupt 0 or pattern match engine slice 0
|
||||
PIN_INT1_IRQHandler, // 21 : Pin interrupt 1 or pattern match engine slice 1
|
||||
PIN_INT2_IRQHandler, // 22 : Pin interrupt 2 or pattern match engine slice 2
|
||||
PIN_INT3_IRQHandler, // 23 : Pin interrupt 3 or pattern match engine slice 3
|
||||
UTICK_IRQHandler, // 24 : Micro-tick Timer (UTICK)
|
||||
MRT_IRQHandler, // 25 : Multi-Rate Timer (MRT). Global MRT interrupts
|
||||
CTIMER0_IRQHandler, // 26 : Standard counter/timer CTIMER0
|
||||
CTIMER1_IRQHandler, // 27 : Standard counter/timer CTIMER1
|
||||
SCT0_IRQHandler, // 28 : SCTimer/PWM
|
||||
CTIMER3_IRQHandler, // 29 : Standard counter/timer CTIMER3
|
||||
FLEXCOMM0_IRQHandler, // 30 : Flexcomm Interface 0 (USART, SPI, I2C, I2S)
|
||||
FLEXCOMM1_IRQHandler, // 31 : Flexcomm Interface 1 (USART, SPI, I2C, I2S)
|
||||
FLEXCOMM2_IRQHandler, // 32 : Flexcomm Interface 2 (USART, SPI, I2C, I2S)
|
||||
FLEXCOMM3_IRQHandler, // 33 : Flexcomm Interface 3 (USART, SPI, I2C, I2S)
|
||||
Reserved34_IRQHandler, // 34 : Reserved interrupt
|
||||
Reserved35_IRQHandler, // 35 : Reserved interrupt
|
||||
FLEXCOMM14_IRQHandler, // 36 : Flexcomm Interface 14 (USART, SPI, I2C, I2S)
|
||||
Reserved37_IRQHandler, // 37 : Reserved interrupt
|
||||
Reserved38_IRQHandler, // 38 : Reserved interrupt
|
||||
GFMRT_IRQHandler, // 39 : Free Multi-rate timer (GFMRT). Global MRT interrupts
|
||||
Reserved40_IRQHandler, // 40 : Reserved interrupt
|
||||
DMIC_IRQHandler, // 41 : Digital microphone (DMIC) and DMIC subsystem
|
||||
WKDEEPSLEEP_IRQHandler, // 42 : Wake-up from deep sleep
|
||||
HYPERVISOR_IRQHandler, // 43 : Hypervisor service software interrupt
|
||||
SECUREVIOLATION_IRQHandler, // 44 : Secure violation
|
||||
HWVAD_IRQHandler, // 45 : Hardware Voice Activity Detector
|
||||
Reserved46_IRQHandler, // 46 : Reserved interrupt
|
||||
Reserved47_IRQHandler, // 47 : Reserved interrupt
|
||||
RTC_IRQHandler, // 48 : RTC alarm and wake-up
|
||||
Reserved49_IRQHandler, // 49 : Reserved interrupt
|
||||
Reserved50_IRQHandler, // 50 : Reserved interrupt
|
||||
PIN_INT4_IRQHandler, // 51 : Pin interrupt 4 or pattern match engine slice 4
|
||||
PIN_INT5_IRQHandler, // 52 : Pin interrupt 5 or pattern match engine slice 5
|
||||
PIN_INT6_IRQHandler, // 53 : Pin interrupt 6 or pattern match engine slice 6
|
||||
PIN_INT7_IRQHandler, // 54 : Pin interrupt 7 or pattern match engine slice 7
|
||||
CTIMER2_IRQHandler, // 55 : Standard counter/timer CTIMER2
|
||||
CTIMER4_IRQHandler, // 56 : Standard counter/timer CTIMER4
|
||||
OS_EVENT_TIMER_IRQHandler, // 57 : OS event timer 0
|
||||
FLEXSPI_IRQHandler, // 58 : FLEXSPI interface
|
||||
Reserved59_IRQHandler, // 59 : Reserved interrupt
|
||||
Reserved60_IRQHandler, // 60 : Reserved interrupt
|
||||
Reserved61_IRQHandler, // 61 : Reserved interrupt
|
||||
SDIO_IRQHandler, // 62 : The secure digital interface
|
||||
SGPIO_INTA_IRQHandler, // 63 : Secure GPIO interrupt A
|
||||
SGPIO_INTB_IRQHandler, // 64 : Secure GPIO interrupt B
|
||||
Reserved65_IRQHandler, // 65 : Reserved interrupt
|
||||
USB_IRQHandler, // 66 : High-speed USB device/host
|
||||
Reserved67_IRQHandler, // 67 : Reserved interrupt
|
||||
Reserved68_IRQHandler, // 68 : Reserved interrupt
|
||||
Reserved69_IRQHandler, // 69 : Reserved interrupt
|
||||
DMA1_IRQHandler, // 70 : DMA controller 1 (non-secure or HiFi 4 DMA)
|
||||
PUF_IRQHandler, // 71 : Physical Unclonable Function
|
||||
POWERQUAD_IRQHandler, // 72 : PowerQuad math coprocessor
|
||||
Reserved73_IRQHandler, // 73 : Reserved interrupt
|
||||
Reserved74_IRQHandler, // 74 : Reserved interrupt
|
||||
Reserved75_IRQHandler, // 75 : Reserved interrupt
|
||||
Reserved76_IRQHandler, // 76 : Reserved interrupt
|
||||
LCD_IRQHandler, // 77 : LCDIC
|
||||
CAPTIMER_IRQHandler, // 78 : Capture timer
|
||||
Reserved79_IRQHandler, // 79 : Reserved interrupt
|
||||
W2MWKUP_DONE0_IRQHandler, // 80 : Wi-Fi to MCU, wakeup done 0
|
||||
W2MWKUP_DONE1_IRQHandler, // 81 : Wi-Fi to MCU, wakeup done 1
|
||||
W2MWKUP_DONE2_IRQHandler, // 82 : Wi-Fi to MCU, wakeup done 2
|
||||
W2MWKUP_DONE3_IRQHandler, // 83 : Wi-Fi to MCU, wakeup done 3
|
||||
W2MWKUP_DONE4_IRQHandler, // 84 : Wi-Fi to MCU, wakeup done 4
|
||||
W2MWKUP_DONE5_IRQHandler, // 85 : Wi-Fi to MCU, wakeup done 5
|
||||
W2MWKUP_DONE6_IRQHandler, // 86 : Wi-Fi to MCU, wakeup done 6
|
||||
W2MWKUP_DONE7_IRQHandler, // 87 : Wi-Fi to MCU, wakeup done 7
|
||||
W2MWKUP0_IRQHandler, // 88 : Wi-Fi to MCU, wakeup signal 0
|
||||
W2MWKUP1_IRQHandler, // 89 : Wi-Fi to MCU, wakueup signal 1
|
||||
WL_MCI_INT0_IRQHandler, // 90 : Wi-Fi to MCU interrupt 0
|
||||
WL_MCI_INT1_IRQHandler, // 91 : Reserved for Wi-Fi to MCU
|
||||
WL_MCI_INT2_IRQHandler, // 92 : Reserved for Wi-Fi to MCU
|
||||
WL_MCI_INT3_IRQHandler, // 93 : Reserved for Wi-Fi to MCU
|
||||
WL_MCI_INT4_IRQHandler, // 94 : Reserved for Wi-Fi to MCU
|
||||
WL_MCI_INT5_IRQHandler, // 95 : Reserved for Wi-Fi to MCU
|
||||
WL_MCI_INT6_IRQHandler, // 96 : Reserved for Wi-Fi to MCU
|
||||
WL_MCI_INT7_IRQHandler, // 97 : Reserved for Wi-Fi to MCU
|
||||
B2MWKUP_DONE0_IRQHandler, // 98 : Bluetooth LE/802.15.4 radio to MCU, wakeup done 0
|
||||
B2MWKUP_DONE1_IRQHandler, // 99 : Bluetooth LE/802.15.4 radio to MCU, wakeup done 1
|
||||
B2MWKUP_DONE2_IRQHandler, // 100: Bluetooth LE/802.15.4 radio to MCU, wakeup done 2
|
||||
B2MWKUP_DONE3_IRQHandler, // 101: Bluetooth LE/802.15.4 radio to MCU, wakeup done 3
|
||||
B2MWKUP_DONE4_IRQHandler, // 102: Bluetooth LE/802.15.4 radio to MCU, wakeup done 4
|
||||
B2MWKUP_DONE5_IRQHandler, // 103: Bluetooth LE/802.15.4 radio to MCU, wakeup done 5
|
||||
B2MWKUP_DONE6_IRQHandler, // 104: Bluetooth LE/802.15.4 radio to MCU, wakeup done 6
|
||||
B2MWKUP_DONE7_IRQHandler, // 105: Bluetooth LE/802.15.4 radio to MCU, wakeup done 7
|
||||
B2MWKUP0_IRQHandler, // 106: Bluetooth LE/802.15.4 radio to MCU, wakeup signal 0
|
||||
B2MWKUP1_IRQHandler, // 107: Bluetooth LE/802.15.4 radio to MCU, wakeup signal 1
|
||||
BLE_MCI_INT0_IRQHandler, // 108: Bluetooth LE/802.15.4 radio to MCU interrupt 0
|
||||
BLE_MCI_INT1_IRQHandler, // 109: Reserved for Bluetooth LE/802.15.4 radio to MCU
|
||||
BLE_MCI_INT2_IRQHandler, // 110: Reserved for Bluetooth LE/802.15.4 radio to MCU
|
||||
BLE_MCI_INT3_IRQHandler, // 111: Reserved for Bluetooth LE/802.15.4 radio to MCU
|
||||
BLE_MCI_INT4_IRQHandler, // 112: Reserved for Bluetooth LE/802.15.4 radio to MCU
|
||||
BLE_MCI_INT5_IRQHandler, // 113: Reserved for Bluetooth LE/802.15.4 radio to MCU
|
||||
BLE_MCI_INT6_IRQHandler, // 114: Reserved for Bluetooth LE/802.15.4 radio to MCU
|
||||
BLE_MCI_INT7_IRQHandler, // 115: Reserved for Bluetooth LE/802.15.4 radio to MCU
|
||||
PIN0_INT_IRQHandler, // 116: From AON GPIO
|
||||
PIN1_INT_IRQHandler, // 117: From AON GPIO
|
||||
ELS_IRQHandler, // 118: EdgeLock subsystem (ELS)
|
||||
ELS_GDET_IRQHandler, // 119: ELS IRQ line for GDET error
|
||||
ELS_GDET_UM_IRQHandler, // 120: ELS un-gated latched error
|
||||
PKC_INT_IRQHandler, // 121: Public key crypto-processor (PKC) interrupt
|
||||
PKC_ERR_IRQHandler, // 122: PKC error
|
||||
CDOG_INT_IRQHandler, // 123: Code watch dog timmer interrupt
|
||||
GAU_DAC_IRQHandler, // 124: General analog unit (GAU) digital to analog converter (DAC)
|
||||
GAU_ACOMP_WKUP_IRQHandler, // 125: GAU analog comparator (ACOMP) wake-up
|
||||
GAU_ACOMP_IRQHandler, // 126: GAU analog comparator
|
||||
GAU_ADC1_IRQHandler, // 127: GAU analog to digital converter 1 (ADC1)
|
||||
GAU_ADC0_IRQHandler, // 128: GAU analog to digital converter 0 (ADC0)
|
||||
USIM_IRQHandler, // 129: Universal subscriber identity module (USIM) interface
|
||||
OTP_IRQHandler, // 130: One time programmable (OTP) memory interrupt
|
||||
ENET_IRQHandler, // 131: Etheret interrupt
|
||||
ENET_TIMER_IRQHandler, // 132: Ethernet timer interrupt
|
||||
PMIP_IRQHandler, // 133: Power management IP (PMIP)
|
||||
PMIP_CHANGE_IRQHandler, // 134: PMIP change from 1 to 0
|
||||
ITRC_IRQHandler, // 135: Intrusion and tamper response controller (ITRC) interrupt request
|
||||
Reserved136_IRQHandler, // 136: Reserved interrupt
|
||||
Reserved137_IRQHandler, // 137: Reserved interrupt
|
||||
Reserved138_IRQHandler, // 138: Reserved interrupt
|
||||
TRNG_IRQHandler, // 139: TRNG interrupt request
|
||||
ACC_C_INT_IRQHandler, // 140: AHB memory access checker - Cortex-M33 code bus
|
||||
ACC_S_INT_IRQHandler, // 141: AHB memory access checker - Cortex-M33 sys bus
|
||||
WACC_IRQHandler, // 142: Wi-Fi accessed during power off
|
||||
BACC_IRQHandler, // 143: Bluetooth LE/802.15.4 radio accessed during power off
|
||||
GDMA_IRQHandler, // 144: General purpose direct memory access (GDMA) interrupt
|
||||
};
|
||||
|
||||
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
|
||||
__attribute__((naked, noreturn)) void Reset_Handler(void) {
|
||||
__asm("cpsid i"); // Disable interrupts
|
||||
// Config VTOR & MSPLIM register
|
||||
__asm("LDR R0, =0xE000ED08 \n"
|
||||
"STR %0, [R0] \n"
|
||||
"LDR R1, [%0] \n"
|
||||
"MSR MSP, R1 \n"
|
||||
"MSR MSPLIM, %1 \n"
|
||||
:
|
||||
: "r"(tab), "r"(_end)
|
||||
: "r0", "r1");
|
||||
extern long _sbss, _ebss, _sdata, _edata, _sidata, _siram, _siiram, _eiram;
|
||||
for (long *dst = &_sbss; dst < &_ebss; dst++) *dst = 0;
|
||||
for (long *dst = &_sdata, *src = &_sidata; dst < &_edata;) *dst++ = *src++;
|
||||
for (long *dst = &_siram, *src = &_siiram; dst < &_eiram;) *dst++ = *src++;
|
||||
SystemInit();
|
||||
__asm("cpsie i"); // Reenable interrupts
|
||||
// Call main()
|
||||
extern void main(void);
|
||||
main();
|
||||
for (;;) (void) 0; // Infinite loop
|
||||
}
|
||||
|
||||
void SystemCoreClockUpdate (void) {
|
||||
|
||||
}
|
||||
|
||||
void Default_Handler(void) {
|
||||
for (;;) (void) 0;
|
||||
}
|
||||
@@ -147,16 +147,22 @@ static inline void hal_gpio_toggle(uint16_t pin) {
|
||||
#define MG_HAL_SYSTICK MG_HAL_SYSTICK_NONE
|
||||
#endif
|
||||
|
||||
static inline void hal_uart_init(USART_Type *uart, unsigned long baud) {
|
||||
if (uart == USART0) {
|
||||
static inline void hal_uart_init(USART_Type *uart, unsigned long baud,
|
||||
uint16_t tx_pin, uint16_t rx_pin) {
|
||||
if (uart == USART0 && tx_pin == PIN(0, 3) &&
|
||||
rx_pin == PIN(0, 2)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC0_USART_DATA);
|
||||
} else if (uart == USART1) {
|
||||
} else if (uart == USART1 && tx_pin == PIN(0, 8) &&
|
||||
rx_pin == PIN(0, 9)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC1_USART_DATA);
|
||||
} else if (uart == USART2) {
|
||||
} else if (uart == USART2 && tx_pin == PIN(0, 14) &&
|
||||
rx_pin == PIN(0, 13)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC2_USART_DATA);
|
||||
} else if (uart == USART3) {
|
||||
} else if (uart == USART3 && tx_pin == PIN(0, 26) &&
|
||||
rx_pin == PIN(0, 24)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC3_USART_DATA);
|
||||
} else if (uart == USART14) {
|
||||
} else if (uart == USART14 && tx_pin == PIN(1, 24) &&
|
||||
rx_pin == PIN(1, 25)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC14_USART_DATA);
|
||||
} else {
|
||||
return;
|
||||
|
||||
@@ -65,6 +65,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
@@ -91,7 +96,7 @@ int main(void) {
|
||||
hal_gpio_write(LED2, true);
|
||||
hal_gpio_output(LED3);
|
||||
hal_gpio_write(LED3, true);
|
||||
hal_uart_init(UART_DEBUG, 115200);
|
||||
hal_uart_init(UART_DEBUG, 115200, PIN(0, 26), PIN(0, 24));
|
||||
mg_log_set_fn(log_fn, UART_DEBUG);
|
||||
|
||||
hal_ethernet_init();
|
||||
|
||||
@@ -61,6 +61,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
|
||||
@@ -65,6 +65,11 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
|
||||
@@ -5,7 +5,12 @@
|
||||
#include "mongoose.h"
|
||||
|
||||
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
uint64_t tick = to_ms_since_boot(get_absolute_time());
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
|
||||
@@ -33,7 +33,12 @@ static void driver_init(struct mg_mgr *mgr) {
|
||||
}
|
||||
|
||||
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
uint64_t tick = to_ms_since_boot(get_absolute_time());
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
|
||||
@@ -58,7 +58,12 @@ static void driver_init(struct mg_mgr *mgr) {
|
||||
}
|
||||
|
||||
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
if (ev == MG_EV_HTTP_HDRS) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/ota"), NULL)) {
|
||||
mg_http_stream_body(c, hm, mg_http_ota_cb, NULL);
|
||||
}
|
||||
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
uint64_t tick = to_ms_since_boot(get_absolute_time());
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
|
||||
Loaded 100 of 139 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user