Compare commits

...
47 Commits
Author SHA1 Message Date
Sergio R. Caprile f59a3f71ed improve RTX tests for coverage and resiliency 2026-11-06 11:35:45 -03:00
Sergio R. Caprile aaf202ba2f Merge pull request #3759 from cesanta/tlstight
misc tightening and cleanup
2026-11-06 09:54:17 -03:00
Sergio R. Caprile 5c7b7b68bc misc tightening and cleanup 2026-11-06 09:51:41 -03:00
Sergio R. Caprile 28f88f016c Merge pull request #3758 from cesanta/4861
be more RFC4861 6.1.2 friendly
2026-11-06 09:47:15 -03:00
Sergio R. Caprile e8bb01b547 per family close on IP change 2026-11-06 09:37:02 -03:00
Sergio R. Caprile 324f59abc5 Merge pull request #3744 from cesanta/hcd
Add coredump RPC method
2026-11-06 07:34:38 +00:00
Sergey Lyubka 3eb33ab349 Merge pull request #3757 from cesanta/m3
Add radio simulation
2026-11-06 07:33:27 +00:00
Sergio R. Caprile 66eb0384be be more RFC4861 6.1.2 friendly 2026-11-05 19:54:31 -03:00
Sergio R. Caprile 1f276ba70a add coredump RPC 2026-11-05 19:49:07 -03:00
Sergio R. Caprile 1a9bc9d3f3 Merge pull request #3742 from cesanta/hesp
Integrate ESP32 health support
2026-11-05 22:34:20 +00:00
cpq d39519b92f Add radio simulation 2026-11-05 22:23:06 +00:00
Sergio R. Caprile 3e11429cac workaround Pico-SDK 2.1.1 vs GCC15 2026-11-05 16:18:45 -03:00
Sergio R. Caprile 9337a78cd0 unbreak BSD on some systems 2026-11-05 11:44:27 -03:00
Sergio R. Caprile d99cc0959b workaround Pico-SDK 2.1.1 vs GCC15 2026-11-05 11:30:23 -03:00
Sergio R. Caprile 02b5504e60 Merge pull request #3756 from cesanta/trasher
fix Docker breaking Win32
2026-11-04 17:15:50 -03:00
Sergio R. Caprile 822c6a9489 fix GCC15 2026-11-04 16:41:04 -03:00
Sergio R. Caprile 03c022080f fix Docker breaking Win32 2026-11-04 16:02:10 -03:00
Sergey Lyubka 84198c7e70 Merge pull request #3753 from cesanta/mesh
Add skeleton for the radio mesh simulator
2026-11-04 18:09:10 +00:00
Sergio R. Caprile f3d531938c Merge pull request #3754 from cesanta/bsdmin
Add missing BSD functions
2026-11-04 14:56:58 -03:00
Sergio R. Caprile b6975ab08f pin Ubuntu runners during migration 2026-11-04 14:56:11 -03:00
Sergio R. Caprile 66afa5e551 Add missing BSD functions 2026-11-04 13:35:49 -03:00
Sergey Lyubka 7886a73807 Merge pull request #3749 from cesanta/defuri
harden root_dir
2026-11-02 12:42:29 +00:00
Sergey Lyubka ccc953318a Merge pull request #3750 from cesanta/strpres
survive to width.precision screw ups in %s
2026-11-02 12:41:05 +00:00
cpq 684ae27dc2 Add udp echo server to tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal 2026-11-02 12:30:23 +00:00
cpq fc71bc0588 Add skeleton for the radio mesh simulator 2026-11-01 11:05:59 +00:00
Sergey Lyubka 08305b9000 Merge pull request #3751 from cesanta/dhcp
Fix: DHCP client stuck in REQUESTING state forever if the REQUEST or ACK is lost
2026-10-30 12:43:26 +00:00
cpq 69e8c0a92f Fix: DHCP client stuck in REQUESTING state forever if the REQUEST or ACK is lost 2026-10-30 09:42:12 +00:00
Sergio R. Caprile 8eceacc0d3 survive to width.precision screw ups in %s 2026-10-27 17:30:26 -03:00
Sergio R. Caprile b6df410570 Integrate ESP32 health support 2026-10-27 11:32:36 -03:00
Sergio R. Caprile 26b971b4a8 harden root_dir 2026-10-27 11:09:08 -03:00
Sergio R. Caprile b766c65615 avoid 802.15.4 using a standard BSD function name, enable BSD test after mg_error() fix 2026-10-23 14:00:24 -03:00
cpq 8296b88fb6 Update readme 2026-10-23 15:21:10 +01:00
Sergio R. Caprile a5efbf0592 misc cleanups and standardizations 2026-10-22 16:53:56 -03:00
Sergio R. Caprile 37c98dd893 mg_delay_ms() survives mg_millis() restarting on some 32-bit platforms 2026-10-22 13:49:21 -03:00
cpq 770a4ff27e Sync description 2026-10-22 17:21:05 +01:00
Sergio R. Caprile b6560306ba avoid reconnect timer being disrupted by possible SNTP updates 2026-10-22 12:12:27 -03:00
Sergio R. Caprile 5b39c93478 Merge pull request #3746 from cesanta/ota
Make mg_http_ota_cb public, add /ota endpoints to MCU examples
2026-10-22 11:30:35 -03:00
cpq 086008b716 Nit 2026-10-22 14:55:19 +01:00
cpq 35eb50a1e6 Nit 2026-10-22 14:46:19 +01:00
cpq 431e35ae8c Update README 2026-10-22 14:44:50 +01:00
cpq 25e2a99d8e Make mg_http_ota_cb public, add /ota endpoints to MCU examples 2026-10-22 14:30:10 +01:00
cpq 88b01ef6f0 Add security.md 2026-10-21 11:48:41 +01:00
cpq bd8ee86f9e Cube pack 7.23.3 2026-10-21 11:33:05 +01:00
Sergey Lyubka ef96f7a28c Merge pull request #3741 from cesanta/w71_2
Ethernet + IPv4 over 802.15.4 + bridge, mcxw71 and rw612
2026-10-21 10:22:43 +01:00
Sergey Lyubka 03f240a244 Merge pull request #3743 from cesanta/c5
Add nucleo-c5a3zg
2026-10-21 10:21:25 +01:00
cpq c775f31ca7 Add nucleo-c5a3zg 2026-10-21 10:16:02 +01:00
cpq 1ca4fd1fef Add frdm-mcxw71 support 2026-10-20 21:48:34 +01:00
139 changed files with 15830 additions and 632 deletions

No files matched your search

+16 -14
View File
@@ -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
+2 -2
View File
@@ -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:
+10 -6
View File
@@ -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:
+8 -4
View File
@@ -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
+51 -27
View File
@@ -1,4 +1,4 @@
# Mongoose - Embedded Web Server / Embedded Network Library
# Mongoose: a full-stack embedded networking library
[![License: GPLv2/Commercial](https://img.shields.io/badge/License-GPLv2%20or%20Commercial-green.svg)](https://opensource.org/licenses/gpl-2.0.php)
[![Build Status]( https://github.com/cesanta/mongoose/actions/workflows/quicktest.yml/badge.svg)](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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+369 -85
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+240 -15
View File
@@ -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
+11
View File
@@ -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
View File
@@ -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') {
+2
View File
@@ -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);
};
+236
View File
@@ -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
+35
View File
@@ -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
+183
View File
@@ -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
+34
View File
@@ -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
+179
View File
@@ -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
+34
View File
@@ -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
+2
View File
@@ -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
+513
View File
@@ -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
+35
View File
@@ -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
+1 -1
View File
@@ -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
+6 -3
View File
@@ -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
View File
@@ -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()
+6 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+80
View File
@@ -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
View File
@@ -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
View File
@@ -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.
}
+5
View File
@@ -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 {
+16 -3
View File
@@ -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
+15
View File
@@ -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.).
+181
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+35
View File
@@ -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
+324
View File
@@ -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.
+261
View File
@@ -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,
};
+776
View File
@@ -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;
}
+109
View File
@@ -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
View File
@@ -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
View File
@@ -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));
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+3 -1
View File
@@ -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})
+8 -1
View File
@@ -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
+6 -1
View File
@@ -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;
}
+6 -1
View File
@@ -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;
}
+6 -6
View File
@@ -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 },
+5 -1
View File
@@ -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
+141
View File
@@ -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);
}
+68
View File
@@ -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")
}
+105
View File
@@ -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
+99
View File
@@ -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)) {
+10 -3
View File
@@ -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 $@
+77
View File
@@ -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
+368 -18
View File
@@ -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},
};
+5 -1
View File
@@ -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 */
}
+91 -5
View File
@@ -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"
+68
View File
@@ -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__ */
+82
View File
@@ -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);
}
}
+153
View File
@@ -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) {
}
+372
View File
@@ -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();
}
+38
View File
@@ -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;
}
+123
View File
@@ -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 = &eth_driver;
eth.driver_data = &eth_data;
memcpy(eth.mac, radio.mac, 6);
mg_queue_init(&eth.recv_queue, eth_queue, sizeof(eth_queue));
if (!eth.driver->init(&eth)) return 1;
ENET->RCR |= ENET_RCR_PROM_MASK; // Receive frames addressed to radio nodes.
radio_data.mesh->lan = &eth;
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(&eth, 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
+236
View File
@@ -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;
}
+338
View File
@@ -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;
}
+12 -6
View File
@@ -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;
+6 -1
View File
@@ -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();
+5
View File
@@ -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)) {
+5
View File
@@ -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)) {
+6 -1
View File
@@ -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)) {
+6 -1
View File
@@ -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)) {
+6 -1
View File
@@ -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