Compare commits

..
62 Commits
Author SHA1 Message Date
Sergio R. Caprile 9e9693db6f make padding strict 2026-11-10 12:51:25 -03:00
Sergio R. Caprile c1f8dafc72 silence some compilers 2026-11-10 11:27:10 -03:00
Sergio R. Caprile 7d77d8f3d7 silence some compilers 2026-11-10 11:26:36 -03:00
Sergey Lyubka c01a1b613f Merge pull request #3761 from cesanta/ms
Flatten mem stats
2026-11-10 13:25:39 +00:00
Sergio R. Caprile 73950e863a Merge pull request #3765 from cesanta/del
Enforce DELETE request
2026-11-10 09:16:02 -03:00
robert b4ca50b3c6 Enforce DELETE request 2026-11-10 13:52:12 +02:00
cpq 069024b41c Flatten mem stats 2026-11-10 11:21:36 +00:00
Sergio R. Caprile 706f4787e2 fix unbalanced allocator set 2026-11-09 17:25:56 -03:00
Sergio R. Caprile 4d8831f2b6 unbreak VC98 weird 64-bit handling 2026-11-09 14:31:17 -03:00
Sergio R. Caprile 5b6c08acfb unbreak VC98 weird 64-bit handling 2026-11-09 12:00:04 -03:00
Sergio R. Caprile 528be76a23 unbreak tests against cesanta.com 2026-11-09 11:40:26 -03:00
Sergio R. Caprile b6ba6a56d9 Merge commit from fork
avoid cross IP version feeds
2026-11-09 11:18:41 -03:00
Sergio R. Caprile 1efc211a51 Merge commit from fork
change mg_tls_verify_cert_signature() to boolean and enforce consistent return values
2026-11-09 11:16:01 -03:00
Sergio R. Caprile ca5ca3403c change mg_tls_verify_cert_signature() to boolean and enforce consistent return values 2026-11-09 11:12:30 -03:00
Sergio R. Caprile 7a86c401b9 Merge commit from fork
fix checks
2026-11-09 11:04:19 -03:00
Sergio R. Caprile 52cc8ef1d3 Merge commit from fork
honor WS mask
2026-11-09 10:58:29 -03:00
Sergio R. Caprile b975a8e761 Merge commit from fork
honor parse and next return values
2026-11-09 10:55:39 -03:00
Sergio R. Caprile 2d7e44201d honor parse and next return values 2026-11-09 10:53:05 -03:00
Sergio R. Caprile b415c23bc5 Merge commit from fork
fix ws data length handling
2026-11-09 10:12:25 -03:00
Sergio R. Caprile cba6c4f8df Merge commit from fork
add safety checks for month
2026-11-09 10:06:41 -03:00
Sergio R. Caprile 9d6d064b39 Merge commit from fork
Check header table bounds
2026-11-09 09:56:57 -03:00
cpq de037d6ef9 calloc -> mg_calloc 2026-11-06 19:58:46 +00:00
Sergey Lyubka e7c8aa017d Merge pull request #3747 from cesanta/memory-report
Add heap/stack reporting for mdash
2026-11-06 19:42:11 +00:00
Sergio R. Caprile fe8ebced03 more tightness and nitpicks 2026-11-06 15:52:03 -03:00
Sergio R. Caprile 62cd82df81 Merge pull request #3760 from cesanta/rtx
improve RTX tests for coverage and resiliency
2026-11-06 14:58:29 -03:00
Sergio R. Caprile e2408a2cfc improve RTX tests for coverage and resiliency 2026-11-06 14:28:23 -03:00
robert da33868409 Add memory stats reporting for mdash with FreeRTOS example for H723 2026-11-06 17:33:28 +02: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
Sergio R. Caprile fc79fd0be5 avoid cross IP version feeds 2026-11-03 10:59:34 -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 22a94c0906 fix checks 2026-10-19 11:48:51 -03:00
Sergio R. Caprile f347dbf2b5 honor WS mask 2026-10-09 11:58:25 -03:00
Sergio R. Caprile 39a3478429 fix ws data length handling 2026-09-02 16:28:35 -03:00
Sergio R. Caprile 7fccc2b75f add safety checks for month 2026-09-02 12:51:31 -03:00
robert 07e580d956 Check header table bounds 2026-08-21 15:07:11 +03:00
272 changed files with 255671 additions and 2378 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" 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: c89:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -137,7 +137,7 @@ jobs:
make -C test mip_tap_test make -C test mip_tap_test
mip89: mip89:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -149,7 +149,7 @@ jobs:
with: { fetch-depth: 2 } 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 }} - 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: mipbe:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -168,7 +168,7 @@ jobs:
make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }} make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }}
miprtxbe: miprtxbe:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
name: MIP RTX be s390 port name: MIP RTX be s390 port
env: env:
IPV6: 0 IPV6: 0
@@ -204,7 +204,7 @@ jobs:
test/health.awk < log > json 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" 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: 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -216,7 +216,7 @@ jobs:
with: { fetch-depth: 2 } with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} - run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
l2be: l2be:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -272,7 +272,7 @@ jobs:
test/health.awk < log > json 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" scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/ATcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
atcmd89: 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -285,7 +285,7 @@ jobs:
with: { fetch-depth: 2 } with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }} - run: make -C test ${{ matrix.target }}
atcmdbe: atcmdbe:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: 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" scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/bsd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
bsdbe: bsdbe:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -343,7 +343,7 @@ jobs:
make -C test/bsd ${{ matrix.target }} make -C test/bsd ${{ matrix.target }}
s390: s390:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -356,7 +356,7 @@ jobs:
with: { fetch-depth: 2 } 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 }} - 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: armhf:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: 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" 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: 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: strategy:
fail-fast: false fail-fast: false
max-parallel: 1 # no parallel runs, to minimize MQTT errors max-parallel: 1 # no parallel runs, to minimize MQTT errors
@@ -479,7 +479,7 @@ jobs:
- run: make -C test riscv SSL=${{ matrix.ssl }} - run: make -C test riscv SSL=${{ matrix.ssl }}
tutorials: tutorials:
runs-on: ubuntu-latest runs-on: ubuntu-24.04 # MicroPython v1.22.2 fails with Ubuntu 26.04 GCC 15
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -560,7 +560,8 @@ jobs:
# - run: make -C tutorials/${{ matrix.example.path }} build # - run: make -C tutorials/${{ matrix.example.path }} build
wizard_examples_arm: 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -637,6 +638,7 @@ jobs:
example: example:
- path: micropython/esp32 - path: micropython/esp32
- path: esp32/uart-bridge - path: esp32/uart-bridge
- path: esp32/minimal
name: ${{ matrix.example.path }} name: ${{ matrix.example.path }}
env: env:
GO: 0 GO: 0
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
test/health.awk < log > json 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" scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/atcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
atcmd89: 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -35,7 +35,7 @@ jobs:
with: { fetch-depth: 2 } with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }} - run: make -C test ${{ matrix.target }}
atcmdbe: atcmdbe:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
+10 -6
View File
@@ -35,7 +35,7 @@ jobs:
- if: ${{ env.GO == 1 }} - if: ${{ env.GO == 1 }}
run: if [ "${{ matrix.target }}" == "test" ]; then ./test/setup_mqtt_server.sh ; fi && make -C test ${{ matrix.target }} run: if [ "${{ matrix.target }}" == "test" ]; then ./test/setup_mqtt_server.sh ; fi && make -C test ${{ matrix.target }}
s390: s390:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
env: env:
TFLAGS: -DMQTT_LOCALHOST TFLAGS: -DMQTT_LOCALHOST
steps: steps:
@@ -45,7 +45,7 @@ jobs:
- if: ${{ env.GO == 1 }} - 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 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: armhf:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
env: env:
TFLAGS: -DMQTT_LOCALHOST TFLAGS: -DMQTT_LOCALHOST
steps: steps:
@@ -83,7 +83,7 @@ jobs:
/opt/homebrew/opt/mosquitto/sbin/mosquitto -c /Users/runner/work/mongoose/mongoose/test/mosquitto.conf.macos & /opt/homebrew/opt/mosquitto/sbin/mosquitto -c /Users/runner/work/mongoose/mongoose/test/mosquitto.conf.macos &
make -C test test ASAN_OPTIONS= make -C test test ASAN_OPTIONS=
windows: 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -186,6 +186,7 @@ jobs:
matrix: matrix:
example: example:
- path: esp32/uart-bridge - path: esp32/uart-bridge
- path: esp32/minimal
name: ${{ matrix.example.path }} name: ${{ matrix.example.path }}
env: env:
GO: 0 GO: 0
@@ -200,7 +201,8 @@ jobs:
run: make -C tutorials/${{ matrix.example.path }} build run: make -C tutorials/${{ matrix.example.path }} build
generic_examples_arm: 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -243,7 +245,8 @@ jobs:
run: make -C tutorials/${{ matrix.example.path }} build run: make -C tutorials/${{ matrix.example.path }} build
device_dashboard_examples_arm: 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -269,7 +272,8 @@ jobs:
run: make -C tutorials/${{ matrix.example.path }} build run: make -C tutorials/${{ matrix.example.path }} build
mqtt_dashboard_examples_arm: 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
+1947 -548
View File
File diff suppressed because it is too large. Load diff
+329 -114
View File
@@ -253,8 +253,11 @@ extern "C" {
#include <esp_system.h> #include <esp_system.h>
#include <esp_mac.h> #include <esp_mac.h>
#include <esp_idf_version.h> #include <esp_idf_version.h>
// #include <esp_memory_utils.h> // For health record coredumps #if MG_ENABLE_HEALTH
// #include <esp_core_dump.h> // For health record coredumps #include <esp_memory_utils.h>
#include <esp_core_dump.h>
#include <esp_partition.h>
#endif
#define MG_PATH_MAX 128 #define MG_PATH_MAX 128
@@ -262,10 +265,6 @@ extern "C" {
#define MG_ENABLE_POSIX_FS 1 #define MG_ENABLE_POSIX_FS 1
#endif #endif
#ifndef MG_HEALTH_DATA_SIZE
#define MG_HEALTH_DATA_SIZE 20
#endif
#endif #endif
@@ -1139,6 +1138,10 @@ struct timeval {
#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG #define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG
#endif #endif
#ifndef MG_ENABLE_CUSTOM_USAGE
#define MG_ENABLE_CUSTOM_USAGE 0 // Let user define mg_usage
#endif
#ifndef MG_ENABLE_FREERTOS #ifndef MG_ENABLE_FREERTOS
#define MG_ENABLE_FREERTOS 0 // FreeRTOS RTOS #define MG_ENABLE_FREERTOS 0 // FreeRTOS RTOS
#endif #endif
@@ -4109,6 +4112,8 @@ struct mg_dash {
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix 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 // Resolves the built-in file manager's root dir for user u, e.g. per-user
// upload directories. NULL: file manager disabled // 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); bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
}; };
@@ -4373,6 +4378,8 @@ void mg_ota_poll(struct mg_mgr *);
#if MG_ENABLE_HEALTH
#ifndef MG_HEALTH_MAGIC #ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version #define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif #endif
@@ -4381,13 +4388,17 @@ void mg_ota_poll(struct mg_mgr *);
#define MG_HEALTH_RAM __attribute__((section(".mg_health"))) #define MG_HEALTH_RAM __attribute__((section(".mg_health")))
#endif #endif
#if !defined(MG_HEALTH_DATA_SIZE) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
#define MG_HEALTH_DATA_SIZE 250
#endif
#ifndef MG_HEALTH_DATA_SIZE #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 && CONFIG_IDF_TARGET_ARCH_XTENSA
#define MG_HEALTH_DATA_SIZE 23 // 7 header words + 16 backtrace PCs
#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_RISCV
#define MG_HEALTH_DATA_SIZE 7 // PC, task name, RA, SP
#else
#define MG_HEALTH_DATA_SIZE 10 #define MG_HEALTH_DATA_SIZE 10
#endif #endif
#endif // MG_HEALTH_DATA_SIZE
// Places the record in RAM that survives a reset. Define in mongoose_config.h // 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"))) // to match the linker script, e.g. __attribute__((section(".mg_health")))
@@ -4395,6 +4406,13 @@ void mg_ota_poll(struct mg_mgr *);
#define MG_HEALTH_RAM #define MG_HEALTH_RAM
#endif #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, // 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 // so these are ordered by how much they tell you: the most specific cause a
// device reports wins // device reports wins
@@ -4409,9 +4427,10 @@ enum mg_health_reason {
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
}; };
struct mg_health { struct mg_health { // NOTE: use 32-bit fields to avoid write issues
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot uint32_t reset_reason; // enum mg_health_reason that started this boot
uint32_t arch; // enum mg_health_arch, core architecture (ARM, etc)
uint32_t cpuid; // ARM CPU ID uint32_t cpuid; // ARM CPU ID
uint32_t data[MG_HEALTH_DATA_SIZE]; uint32_t data[MG_HEALTH_DATA_SIZE];
}; };
@@ -4444,9 +4463,99 @@ 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
enum mg_usage_metric {
MG_USAGE_NUM_TASKS,
MG_USAGE_TASK_NAME,
MG_USAGE_STACK_TOTAL,
MG_USAGE_STACK_USED,
MG_USAGE_STACK_PEAK,
MG_USAGE_HEAP_TOTAL,
MG_USAGE_HEAP_USED,
MG_USAGE_HEAP_PEAK
};
// Returns a task or heap metric. task is a zero-based index; NUM_TASKS and heap
// metrics ignore it. Sizes are in bytes, TASK_NAME returns const char * cast to
// size_t
// Unsupported metrics return 0. PEAK is the maximum used since boot
// Set MG_ENABLE_CUSTOM_USAGE=1 and define mg_usage() in the application to
// provide custom measurements
size_t mg_usage(int task, int metric);
#define MG_HEALTH_INIT() mg_health_init() #define MG_HEALTH_INIT() mg_health_init()
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *); extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
// JSON memory statistics via mg_usage(), suitable for %M: an array of
// {name, total, used, peak} entries, starting with "heap", then task stacks
// Unavailable metrics are reported as 0
extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *);
extern void mg_health_init(void); 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 size_t mg_print_mem_stats(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 // Cloud connector API to https://mdash.net - device management cloud
@@ -4620,17 +4729,6 @@ struct mg_tcpip_driver {
typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev, typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev,
void *ev_data); void *ev_data);
#if MG_ENABLE_BSD_SOCKETS
#ifndef MG_TCPIP_SENDTO_CACHE_SIZE
#define MG_TCPIP_SENDTO_CACHE_SIZE 4
#endif
struct mg_tcpip_sendto_entry {
uint32_t ip;
struct mg_l2addr addr;
};
#endif
// clang-format off // clang-format off
// Events delivered to mg_tcpip_if.fn (user) and mg_tcpip_if.pfn (driver). // Events delivered to mg_tcpip_if.fn (user) and mg_tcpip_if.pfn (driver).
// Each entry shows the ev_data type to cast inside the handler. // Each entry shows the ev_data type to cast inside the handler.
@@ -4693,9 +4791,6 @@ struct mg_tcpip_if {
enum mg_l2type l2type; // Layer-2 type: Ethernet, PPP, etc. (see l2.h) enum mg_l2type l2type; // Layer-2 type: Ethernet, PPP, etc. (see l2.h)
union mg_l2data l2data; // Layer-2 config and state (see l2.h) union mg_l2data l2data; // Layer-2 config and state (see l2.h)
char *dns4_url; // Active DNS server URL (set from DHCP or config) char *dns4_url; // Active DNS server URL (set from DHCP or config)
#if MG_ENABLE_BSD_SOCKETS
struct mg_tcpip_sendto_entry sendto[MG_TCPIP_SENDTO_CACHE_SIZE]; // Local IPv4 neighbors
#endif
uint64_t now; // Current time in ms (updated each mg_mgr_poll) uint64_t now; // Current time in ms (updated each mg_mgr_poll)
uint64_t timer_1000ms; // Timestamp of last 1-second tick uint64_t timer_1000ms; // Timestamp of last 1-second tick
uint64_t lease_expire; // DHCP lease expiry time in ms uint64_t lease_expire; // DHCP lease expiry time in ms
@@ -4762,41 +4857,11 @@ extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi;
extern struct mg_tcpip_driver mg_tcpip_driver_st67w6; 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_atcmd;
extern struct mg_tcpip_driver mg_tcpip_driver_netc; extern struct mg_tcpip_driver mg_tcpip_driver_netc;
extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
extern struct mg_tcpip_driver mg_tcpip_driver_rw612_154;
// IEEE 802.15.4 radio channel and PAN ID. Settable in mongoose_config.h
#ifndef MG_L2_802154_CHANNEL
#define MG_L2_802154_CHANNEL 15 // 11..26
#endif
#ifndef MG_L2_802154_PAN_ID
#define MG_L2_802154_PAN_ID 0xabcd
#endif
// driver_data for IEEE 802.15.4 radio drivers. The interface is Ethernet:
// each Ethernet frame is split into broadcast radio frames, and reassembled
// on the other side, see mg_l2_802154_tx() and mg_l2_802154_rx()
struct mg_l2_802154_data {
uint8_t channel; // User-set channel, 11..26
uint16_t pan_id; // User-set PAN ID
uint8_t seq, tag; // MAC sequence number, tag of the last sent frame
uint8_t rxtag, rxnext; // Frame being reassembled: tag, next fragment
uint8_t rxsrc[8]; // Its sender
uint16_t rxlen; // Its bytes received so far
uint8_t rxbuf[1514]; // Reassembly buffer, one Ethernet frame
};
// Splits an Ethernet frame into radio frames, sends each by calling send().
// Called by radio drivers' tx(). Return len, or 0 on error
size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len,
size_t (*send)(const uint8_t *, size_t,
struct mg_tcpip_if *));
// Adds a received radio frame f of n bytes to the frame being reassembled.
// Called by radio drivers' rx(). Return the Ethernet frame length, copied to
// buf, when it is complete, otherwise 0
size_t mg_l2_802154_rx(struct mg_tcpip_if *ifp, const uint8_t *f, size_t n,
void *buf, size_t len);
// SPI bus abstraction for SPI-attached network chips (e.g. W5500). // SPI bus abstraction for SPI-attached network chips (e.g. W5500).
// Populate and assign to the driver's driver_data field. // Populate and assign to the driver's driver_data field.
@@ -4850,6 +4915,86 @@ struct mg_tcpip_spi {
#endif #endif
#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
@@ -4907,37 +5052,19 @@ void mg_bsd_poll(struct mg_mgr *);
#ifndef EPIPE #ifndef EPIPE
#define EPIPE 32 #define EPIPE 32
#endif #endif
#ifndef EOPNOTSUPP #ifndef EAFNOSUPPORT
#define EOPNOTSUPP 95 #define EAFNOSUPPORT 97
#endif #endif
#ifndef EISCONN #ifndef ENOPROTOOPT
#define EISCONN 106 #define ENOPROTOOPT 92
#endif #endif
#ifndef EDESTADDRREQ #ifndef ENOSPC
#define EDESTADDRREQ 121 #define ENOSPC 28
#endif
#ifndef EMSGSIZE
#define EMSGSIZE 90
#endif #endif
#ifndef EPROTONOSUPPORT #ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT 93 #define EPROTONOSUPPORT 93
#endif #endif
#if defined(__DCC__)
#pragma pack(1)
#else
#pragma pack(push, 1)
#endif
struct mg_bsd_udp_frame {
uint32_t ip;
uint16_t port, len;
};
#if defined(__DCC__)
#pragma pack(0)
#else
#pragma pack(pop)
#endif
#if MG_ENABLE_BSD_PROTOTYPES #if MG_ENABLE_BSD_PROTOTYPES
typedef unsigned int socklen_t; typedef unsigned int socklen_t;
typedef unsigned long nfds_t; typedef unsigned long nfds_t;
@@ -5015,7 +5142,9 @@ typedef struct fd_set {
#define IPPROTO_UDP 17 #define IPPROTO_UDP 17
#define INADDR_ANY 0 #define INADDR_ANY 0
#define INADDR_LOOPBACK 0x7f000001 #define INADDR_LOOPBACK 0x7f000001
#define INADDR_NONE ((in_addr_t) -1)
#define INET_ADDRSTRLEN 16 #define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
#define SOL_SOCKET 0xffff #define SOL_SOCKET 0xffff
#define SO_REUSEADDR 2 #define SO_REUSEADDR 2
#define SO_REUSEPORT 15 #define SO_REUSEPORT 15
@@ -5209,30 +5338,74 @@ struct mg_tcpip_driver_imxrt_data {
} while (0) } while (0)
#endif #endif
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \ #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
MG_ENABLE_DRIVER_MCXW71 MG_ENABLE_DRIVER_MCXW71
// NXP MCXW71 IEEE 802.15.4 radio, ZLL link layer, as an Ethernet interface: struct mg_tcpip_driver_mcxw71_data {
// Ethernet frames are split into radio frames, see mg_l2_802154_tx(). struct mg_mesh_node *mesh; // Driver-owned runtime state
// The transceiver (XCVR) must be set up for 802.15.4 before mg_tcpip_init(), const struct mg_mesh *strategy; // NULL selects mg_mesh_default
// see hal_radio_init() in the FRDM-MCXW71 tutorial. };
// driver_data: struct mg_l2_802154_data, channel and PAN ID set
#define MG_TCPIP_DRIVER_INIT(mgr) \ extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
#define MG_TCPIP_DRIVER_MCXW71_INIT(manager) \
do { \ do { \
static struct mg_l2_802154_data driver_data_; \ static struct mg_tcpip_driver_mcxw71_data driver_data_; \
static struct mg_tcpip_if mif_; \ static struct mg_tcpip_if mif_; \
driver_data_.channel = MG_L2_802154_CHANNEL; \ MG_SET_MAC_ADDRESS(mif_.mac); \
driver_data_.pan_id = MG_L2_802154_PAN_ID; \ 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 = &mg_tcpip_driver_mcxw71; \
mif_.driver_data = &driver_data_; \ mif_.driver_data = &driver_data_; \
MG_SET_MAC_ADDRESS(mif_.mac); \ mg_tcpip_init((manager), &mif_); \
mg_tcpip_init(mgr, &mif_); \ MG_INFO(("Driver: mcxw71, MAC: %M", mg_print_mac, mif_.mac)); \
MG_INFO(("Driver: mcxw71, channel %d", MG_L2_802154_CHANNEL)); \
} while (0) } 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 #endif
@@ -5283,6 +5456,40 @@ extern struct mg_tcpip_driver mg_tcpip_driver_netc;
} while (0) } while (0)
#endif #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 && \ #if MG_ENABLE_TCPIP && \
@@ -5435,6 +5642,7 @@ struct mg_tcpip_driver_rw612_data {
#define MG_DRIVER_MDC_HOLDTIME 3 #define MG_DRIVER_MDC_HOLDTIME 3
#endif #endif
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(mgr) \ #define MG_TCPIP_DRIVER_INIT(mgr) \
do { \ do { \
static struct mg_tcpip_driver_rw612_data driver_data_; \ static struct mg_tcpip_driver_rw612_data driver_data_; \
@@ -5450,34 +5658,41 @@ struct mg_tcpip_driver_rw612_data {
mg_tcpip_init(mgr, &mif_); \ mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \ MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0) } while (0)
#endif #endif
#endif
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \
MG_ENABLE_DRIVER_RW612_154
// NXP RW612 IEEE 802.15.4 radio, as an Ethernet interface: Ethernet frames
// are split into radio frames, see mg_l2_802154_tx(). The radio runs on #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612W) && \
// CPU2: NXP BLE/802.15.4 combo firmware, an OpenThread radio co-processor MG_ENABLE_DRIVER_RW612W
// (RCP). The firmware must be loaded and running before mg_tcpip_init(), see
// the FRDM-RW612 eth_radio_bridge tutorial. The driver talks to it over the struct mg_tcpip_driver_rw612w_data {
// CPU2 <-> CPU3 IMU mailbox: IMUMC messages carrying HDLC framed Spinel. struct mg_mesh_node *mesh; // Driver-owned runtime state
// driver_data: struct mg_l2_802154_data, channel and PAN ID set 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 #ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(mgr) \ #define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_RW612W_INIT(x)
do { \
static struct mg_l2_802154_data driver_data_; \
static struct mg_tcpip_if mif_; \
driver_data_.channel = MG_L2_802154_CHANNEL; \
driver_data_.pan_id = MG_L2_802154_PAN_ID; \
mif_.driver = &mg_tcpip_driver_rw612_154; \
mif_.driver_data = &driver_data_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: rw612_154, channel %d", MG_L2_802154_CHANNEL)); \
} while (0)
#endif #endif
#endif #endif
+5 -6
View File
@@ -22,8 +22,11 @@
#include <esp_system.h> #include <esp_system.h>
#include <esp_mac.h> #include <esp_mac.h>
#include <esp_idf_version.h> #include <esp_idf_version.h>
// #include <esp_memory_utils.h> // For health record coredumps #if MG_ENABLE_HEALTH
// #include <esp_core_dump.h> // For health record coredumps #include <esp_memory_utils.h>
#include <esp_core_dump.h>
#include <esp_partition.h>
#endif
#define MG_PATH_MAX 128 #define MG_PATH_MAX 128
@@ -31,8 +34,4 @@
#define MG_ENABLE_POSIX_FS 1 #define MG_ENABLE_POSIX_FS 1
#endif #endif
#ifndef MG_HEALTH_DATA_SIZE
#define MG_HEALTH_DATA_SIZE 20
#endif
#endif #endif
+270 -101
View File
@@ -1,6 +1,5 @@
#include "bsd.h" #include "bsd.h"
#include "dns.h" #include "dns.h"
#include "util.h"
#include <stdarg.h> #include <stdarg.h>
#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS #if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS
@@ -33,8 +32,9 @@ static struct mg_mgr *s_mgr;
enum bsd_op { enum bsd_op {
BSD_OP_NONE, BSD_OP_SOCKET, BSD_OP_BIND, BSD_OP_LISTEN, BSD_OP_FCNTL, 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_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_RECVFROM, BSD_OP_CLOSE, BSD_OP_SHUTDOWN, BSD_OP_SETSOCKOPT,
BSD_OP_GETHOSTBYNAME, 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 // A BSD API request from a user task. Gets sent via the RTOS queue, and
@@ -51,6 +51,7 @@ struct mg_bsd_cmd {
struct { int fd, backlog; } op_listen; struct { int fd, backlog; } op_listen;
struct { int fd, cmd, arg; } op_fcntl; struct { int fd, cmd, arg; } op_fcntl;
struct { int fd; } op_close; 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_bind;
struct { int fd; const struct sockaddr *addr; socklen_t len; } op_connect; struct { int fd; const struct sockaddr *addr; socklen_t len; } op_connect;
struct { int fd; struct sockaddr *addr; socklen_t *addrlen; } op_accept; struct { int fd; struct sockaddr *addr; socklen_t *addrlen; } op_accept;
@@ -58,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; } 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; 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; 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 { 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 { int nfds; fd_set *r, *w, *e; uint64_t deadline; } op_select;
struct { const char *name; struct hostent *result; } op_gethostbyname; struct { const char *name; struct hostent *result; } op_gethostbyname;
@@ -74,6 +78,7 @@ struct mg_bsd_sock {
struct mg_connection *c; struct mg_connection *c;
struct mg_bsd_sock *listener; struct mg_bsd_sock *listener;
bool used, bound, closed, nonblock, is_udp, orphan; // closed: c was closed 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 ? static struct mg_bsd_sock s_socks[MG_BSD_MAX_SOCKETS > 0 ?
@@ -94,6 +99,21 @@ static struct mg_bsd_sock *bsd_get(int fd) {
return s; 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) { static void bsd_attach(struct mg_bsd_sock *s, struct mg_connection *c) {
s->c = c; s->c = c;
c->fn = bsd_ev; c->fn = bsd_ev;
@@ -111,57 +131,6 @@ static struct mg_connection *bsd_new_conn(struct mg_bsd_sock *s) {
return c; return c;
} }
// Mirror net_builtin.c::trim_len() logic
static bool bsd_udp_too_big(struct mg_connection *c, size_t len) {
struct mg_tcpip_if *ifp = c->mgr->ifp;
size_t hlen = c->rem.is_ip6 ? 48 : 32; // IP plus UDP headers
size_t cap = ifp->mtu - hlen;
size_t room = ifp->tx.len - (ifp->framesize - ifp->l2mtu) - hlen;
return len > cap || len > room;
}
static bool bsd_udp_send(struct mg_connection *c, uint32_t ip, uint16_t port,
const void *buf, size_t len) {
struct mg_bsd_udp_frame *h;
size_t off = c->send.len, total = sizeof(*h) + len;
if (mg_iobuf_add(&c->send, off, NULL, total) != total) return false;
h = (struct mg_bsd_udp_frame *) (c->send.buf + off);
h->ip = ip;
h->port = port;
h->len = (uint16_t) len;
memcpy(h + 1, buf, len);
return true;
}
static size_t bsd_recv(struct mg_bsd_sock *s, void *buf, size_t len,
int flags, struct sockaddr *src, socklen_t *addrlen) {
struct mg_connection *c = s->c;
size_t n;
if (!s->is_udp) {
n = c->recv.len < len ? c->recv.len : len;
memcpy(buf, c->recv.buf, n);
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
} else {
struct mg_bsd_udp_frame *h = (struct mg_bsd_udp_frame *) c->recv.buf;
assert(c->recv.len >= sizeof(*h));
assert(h->len <= c->recv.len - sizeof(*h));
n = h->len < len ? h->len : len;
memcpy(buf, h + 1, n);
if (src != NULL && addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) src;
sa->sin_family = AF_INET;
sa->sin_port = h->port;
sa->sin_addr.s_addr = h->ip;
*addrlen = sizeof(*sa);
}
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, sizeof(*h) + h->len);
}
return n;
}
#if MG_ENABLE_FREERTOS #if MG_ENABLE_FREERTOS
// Complete operations blocked on c before Mongoose destroys it. // Complete operations blocked on c before Mongoose destroys it.
static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) { static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) {
@@ -230,8 +199,10 @@ static void bsd_ev(struct mg_connection *c, int ev, void *ev_data) {
c->is_bsd = 1; c->is_bsd = 1;
c->is_udp = child->is_udp; 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) { } 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 #if MG_ENABLE_FREERTOS
bsd_finish_waiters(s, true); bsd_finish_waiters(s, true);
#endif #endif
@@ -274,11 +245,11 @@ static void wufn(struct mg_connection *c, int ev, void *ev_data) {
break; break;
} }
} }
free(m); mg_free(m);
} }
} else if (ev == MG_EV_CLOSE) { } else if (ev == MG_EV_CLOSE) {
struct wumsg *m; struct wumsg *m;
while (xQueueReceive(q, &m, 0) == pdTRUE) free(m); while (xQueueReceive(q, &m, 0) == pdTRUE) mg_free(m);
vQueueDelete(q); vQueueDelete(q);
c->mgr->pipe.q = NULL; c->mgr->pipe.q = NULL;
} }
@@ -311,7 +282,7 @@ bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf,
size_t len) { size_t len) {
struct wumsg *m; struct wumsg *m;
if (mgr->pipe.q == NULL || conn_id == 0) return false; if (mgr->pipe.q == NULL || conn_id == 0) return false;
m = (struct wumsg *) calloc(1, sizeof(*m) + len); m = (struct wumsg *) mg_calloc(1, sizeof(*m) + len);
if (m == NULL) { if (m == NULL) {
MG_ERROR(("OOM")); MG_ERROR(("OOM"));
return false; return false;
@@ -320,7 +291,7 @@ bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf,
m->len = len; m->len = len;
memcpy(m->data, buf, len); memcpy(m->data, buf, len);
if (!bsd_qsend((QueueHandle_t) mgr->pipe.q, &m, 0)) { if (!bsd_qsend((QueueHandle_t) mgr->pipe.q, &m, 0)) {
free(m); mg_free(m);
return false; return false;
} }
return true; return true;
@@ -341,10 +312,12 @@ static int bsd_ready(struct mg_bsd_sock *s) {
} }
return 0; return 0;
} }
if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ; if (s->is_shut_rd || s->c->recv.len > 0 || s->c->is_draining)
if ((s->is_udp || !s->c->is_connecting) && !s->c->is_closing && ready |= BSD_READ;
!s->c->is_draining) if (s->is_shut_wr) ready |= BSD_ERROR;
ready |= BSD_WRITE; // UDP socket or established TCP socket 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; return ready;
} }
@@ -358,7 +331,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
if (t->orphan && t->listener == s) { if (t->orphan && t->listener == s) {
if (t->c != NULL) { if (t->c != NULL) {
t->c->fn_data = NULL; t->c->fn_data = NULL;
t->c->is_draining = 1; mg_close_conn(t->c);
t->c = NULL; t->c = NULL;
} }
memset(t, 0, sizeof(*t)); // Release orphan memset(t, 0, sizeof(*t)); // Release orphan
@@ -366,7 +339,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
} }
if (s->c != NULL) { if (s->c != NULL) {
s->c->fn_data = NULL; s->c->fn_data = NULL;
s->c->is_draining = 1; mg_close_conn(s->c);
s->c = NULL; s->c = NULL;
} }
memset(s, 0, sizeof(*s)); // Release socket memset(s, 0, sizeof(*s)); // Release socket
@@ -420,9 +393,13 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
} }
} }
return true; return true;
case BSD_OP_BIND: case BSD_OP_BIND: {
int err;
if ((s = bsd_get(cmd->op_bind.fd)) == NULL) { if ((s = bsd_get(cmd->op_bind.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; 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 { } else {
const struct sockaddr_in *sa = const struct sockaddr_in *sa =
(const struct sockaddr_in *) cmd->op_bind.addr; (const struct sockaddr_in *) cmd->op_bind.addr;
@@ -441,6 +418,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
} }
} }
return true; return true;
}
case BSD_OP_LISTEN: { case BSD_OP_LISTEN: {
char url[32]; char url[32];
@@ -516,11 +494,17 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true; return true;
#endif #endif
} }
case BSD_OP_CONNECT: case BSD_OP_CONNECT: {
int err;
if ((s = bsd_get(cmd->op_connect.fd)) == NULL) { if ((s = bsd_get(cmd->op_connect.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
return true; 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 (!cmd->started && s->c == NULL) {
if (s->closed || (c = bsd_new_conn(s)) == NULL) { if (s->closed || (c = bsd_new_conn(s)) == NULL) {
cmd->result = -1, cmd->err = s->closed ? EIO : ENOMEM; cmd->result = -1, cmd->err = s->closed ? EIO : ENOMEM;
@@ -545,8 +529,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = -1, cmd->err = ECONNREFUSED; cmd->result = -1, cmd->err = ECONNREFUSED;
return true; return true;
} }
if (s->is_udp || !c->is_connecting) { if (!c->is_connecting) {
cmd->result = 0; // UDP may still resolve ARP; TCP waits for SYN/SYN-ACK cmd->result = 0;
return true; return true;
} }
if (s->nonblock) { if (s->nonblock) {
@@ -559,6 +543,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = -1, cmd->err = EINPROGRESS; cmd->result = -1, cmd->err = EINPROGRESS;
return true; return true;
#endif #endif
}
case BSD_OP_RECV: case BSD_OP_RECV:
if ((s = bsd_get(cmd->op_recv.fd)) == NULL) { if ((s = bsd_get(cmd->op_recv.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
@@ -570,15 +555,18 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true; return true;
} }
if (c->recv.len > 0) { if (c->recv.len > 0) {
cmd->result = (int) bsd_recv(s, cmd->op_recv.buf, cmd->op_recv.len, size_t n = cmd->op_recv.len;
cmd->op_recv.flags, NULL, NULL); if (c->recv.len < n) n = c->recv.len;
memcpy(cmd->op_recv.buf, c->recv.buf, n);
if (!(cmd->op_recv.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
cmd->result = (int) n;
return true; return true;
} }
if (s->err != 0) { if (s->err != 0) {
cmd->result = -1, cmd->err = s->err; cmd->result = -1, cmd->err = s->err;
return true; 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; cmd->result = 0;
return true; return true;
} }
@@ -592,17 +580,10 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SEND: case BSD_OP_SEND:
if ((s = bsd_get(cmd->op_send.fd)) == NULL) { if ((s = bsd_get(cmd->op_send.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
} else if (s->err != 0 || s->closed || } else if (s->is_shut_wr || (c = s->c) == NULL || s->err != 0 ||
((c = s->c) != NULL && (c->is_closing || c->is_draining))) { c->is_closing || c->is_draining) {
cmd->result = -1, cmd->err = EPIPE; cmd->result = -1, cmd->err = EPIPE;
} else if (c == NULL || (s->is_udp && !c->is_client)) { } else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) {
cmd->result = -1, cmd->err = s->is_udp ? EDESTADDRREQ : EPIPE;
} else if (s->is_udp && bsd_udp_too_big(c, cmd->op_send.len)) {
cmd->result = -1, cmd->err = EMSGSIZE;
} else if ((s->is_udp ? bsd_udp_send(c, c->rem.addr.ip4, c->rem.port,
cmd->op_send.buf, cmd->op_send.len)
: mg_send(c, cmd->op_send.buf,
cmd->op_send.len))) {
cmd->result = (int) cmd->op_send.len; cmd->result = (int) cmd->op_send.len;
} else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) { } else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN; cmd->result = -1, cmd->err = EAGAIN;
@@ -614,23 +595,24 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
#endif #endif
} }
return true; return true;
case BSD_OP_SENDTO: case BSD_OP_SENDTO: {
int err;
if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) { if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
} else if (!s->is_udp) { } else if ((err = bsd_addr4(cmd->op_sendto.addr,
cmd->result = -1, cmd->err = EOPNOTSUPP; cmd->op_sendto.addrlen)) != 0) {
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing || cmd->result = -1, cmd->err = err;
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; cmd->result = -1, cmd->err = EPIPE;
} else if (c->is_client) {
cmd->result = -1, cmd->err = EISCONN;
} else { } else {
const struct sockaddr_in *sa = const struct sockaddr_in *sa =
(const struct sockaddr_in *) cmd->op_sendto.addr; (const struct sockaddr_in *) cmd->op_sendto.addr;
if (bsd_udp_too_big(c, cmd->op_sendto.len)) { c->rem.port = sa->sin_port;
cmd->result = -1, cmd->err = EMSGSIZE; c->rem.addr.ip4 = sa->sin_addr.s_addr;
} else if (bsd_udp_send(c, sa->sin_addr.s_addr, sa->sin_port, c->rem.is_ip6 = false;
cmd->op_sendto.buf, cmd->op_sendto.len)) { if (mg_send(c, cmd->op_sendto.buf, cmd->op_sendto.len)) {
cmd->result = (int) cmd->op_sendto.len; cmd->result = (int) cmd->op_sendto.len;
} else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) { } else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN; cmd->result = -1, cmd->err = EAGAIN;
@@ -643,12 +625,14 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
} }
} }
return true; return true;
}
case BSD_OP_RECVFROM: case BSD_OP_RECVFROM:
if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) { if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
return true; return true;
} else if (!s->is_udp) { }
cmd->result = -1, cmd->err = EOPNOTSUPP; if (s->is_shut_rd) {
cmd->result = 0;
return true; return true;
} }
if ((c = s->c) == NULL) { if ((c = s->c) == NULL) {
@@ -657,11 +641,25 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true; return true;
} }
if (c->recv.len > 0) { if (c->recv.len > 0) {
cmd->result = (int) bsd_recv(s, cmd->op_recvfrom.buf, size_t n = cmd->op_recvfrom.len;
cmd->op_recvfrom.len,
cmd->op_recvfrom.flags, if (cmd->op_recvfrom.src != NULL &&
cmd->op_recvfrom.src, (cmd->op_recvfrom.addrlen == NULL ||
cmd->op_recvfrom.addrlen); *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);
if (cmd->op_recvfrom.src != NULL && cmd->op_recvfrom.addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_recvfrom.src;
sa->sin_family = AF_INET;
sa->sin_port = c->rem.port;
sa->sin_addr.s_addr = c->rem.addr.ip4;
*cmd->op_recvfrom.addrlen = sizeof(*sa);
}
cmd->result = (int) n;
return true; return true;
} }
if (s->err != 0) { if (s->err != 0) {
@@ -683,6 +681,98 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = 0; cmd->result = 0;
} }
return true; 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: { case BSD_OP_POLL: {
struct pollfd *p; struct pollfd *p;
int i, n = 0, ready; int i, n = 0, ready;
@@ -699,7 +789,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
p->revents |= POLLIN; p->revents |= POLLIN;
if ((p->events & POLLOUT) && (ready & BSD_WRITE)) if ((p->events & POLLOUT) && (ready & BSD_WRITE))
p->revents |= POLLOUT; 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 (s->closed) p->revents |= POLLHUP;
} }
if (p->revents != 0) n++; if (p->revents != 0) n++;
@@ -951,6 +1041,55 @@ int close(int fd) {
return cmd.result; 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) { int poll(struct pollfd *fds, nfds_t nfds, int timeout) {
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
uint64_t deadline = timeout < 0 ? (uint64_t) ~0 uint64_t deadline = timeout < 0 ? (uint64_t) ~0
@@ -982,6 +1121,36 @@ struct hostent *gethostbyname(const char *name) {
return cmd.op_gethostbyname.result; 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 #endif // MG_ENABLE_BSD_SOCKETS
#if MG_ENABLE_BSD_SOCKETS #if MG_ENABLE_BSD_SOCKETS
+8 -24
View File
@@ -55,37 +55,19 @@ void mg_bsd_poll(struct mg_mgr *);
#ifndef EPIPE #ifndef EPIPE
#define EPIPE 32 #define EPIPE 32
#endif #endif
#ifndef EOPNOTSUPP #ifndef EAFNOSUPPORT
#define EOPNOTSUPP 95 #define EAFNOSUPPORT 97
#endif #endif
#ifndef EISCONN #ifndef ENOPROTOOPT
#define EISCONN 106 #define ENOPROTOOPT 92
#endif #endif
#ifndef EDESTADDRREQ #ifndef ENOSPC
#define EDESTADDRREQ 121 #define ENOSPC 28
#endif
#ifndef EMSGSIZE
#define EMSGSIZE 90
#endif #endif
#ifndef EPROTONOSUPPORT #ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT 93 #define EPROTONOSUPPORT 93
#endif #endif
#if defined(__DCC__)
#pragma pack(1)
#else
#pragma pack(push, 1)
#endif
struct mg_bsd_udp_frame {
uint32_t ip;
uint16_t port, len;
};
#if defined(__DCC__)
#pragma pack(0)
#else
#pragma pack(pop)
#endif
#if MG_ENABLE_BSD_PROTOTYPES #if MG_ENABLE_BSD_PROTOTYPES
typedef unsigned int socklen_t; typedef unsigned int socklen_t;
typedef unsigned long nfds_t; typedef unsigned long nfds_t;
@@ -163,7 +145,9 @@ typedef struct fd_set {
#define IPPROTO_UDP 17 #define IPPROTO_UDP 17
#define INADDR_ANY 0 #define INADDR_ANY 0
#define INADDR_LOOPBACK 0x7f000001 #define INADDR_LOOPBACK 0x7f000001
#define INADDR_NONE ((in_addr_t) -1)
#define INET_ADDRSTRLEN 16 #define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
#define SOL_SOCKET 0xffff #define SOL_SOCKET 0xffff
#define SO_REUSEADDR 2 #define SO_REUSEADDR 2
#define SO_REUSEPORT 15 #define SO_REUSEPORT 15
+4
View File
@@ -12,6 +12,10 @@
#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG #define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG
#endif #endif
#ifndef MG_ENABLE_CUSTOM_USAGE
#define MG_ENABLE_CUSTOM_USAGE 0 // Let user define mg_usage
#endif
#ifndef MG_ENABLE_FREERTOS #ifndef MG_ENABLE_FREERTOS
#define MG_ENABLE_FREERTOS 0 // FreeRTOS RTOS #define MG_ENABLE_FREERTOS 0 // FreeRTOS RTOS
#endif #endif
+4
View File
@@ -609,7 +609,11 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
mg_serve_fs(c, hm, u, false); mg_serve_fs(c, hm, u, false);
} else if (mg_match(hm->uri, mg_str("/api/del/*/*/*"), parts) || } else if (mg_match(hm->uri, mg_str("/api/del/*/*/*"), parts) ||
mg_match(hm->uri, mg_str("/api/del/*/*"), parts)) { mg_match(hm->uri, mg_str("/api/del/*/*"), parts)) {
if (mg_strcmp(hm->method, mg_str("DELETE")) == 0) {
mg_dash_handle_del(c, dash, u, parts); mg_dash_handle_del(c, dash, u, parts);
} else {
mg_http_reply(c, 405, "Allow: DELETE\r\n", "Method Not Allowed\n");
}
} else if (mg_match(hm->uri, mg_str("/api/add/*"), parts)) { } else if (mg_match(hm->uri, mg_str("/api/add/*"), parts)) {
mg_dash_handle_add(c, dash, u, parts, hm->body); mg_dash_handle_add(c, dash, u, parts, hm->body);
} else if (mg_match(hm->uri, mg_str("/api/get/*/*"), parts) && } else if (mg_match(hm->uri, mg_str("/api/get/*/*"), parts) &&
+2
View File
@@ -96,6 +96,8 @@ struct mg_dash {
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix 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 // Resolves the built-in file manager's root dir for user u, e.g. per-user
// upload directories. NULL: file manager disabled // 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); bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
}; };
+169 -75
View File
@@ -1,3 +1,5 @@
#include "l2_mesh.h"
#include "log.h"
#include "net_builtin.h" #include "net_builtin.h"
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \ #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
@@ -9,12 +11,12 @@ struct mg_zll {
volatile uint32_t IRQSTS, PHY_CTRL, EVENT_TMR, TIMESTAMP, T1CMP, T2CMP, volatile uint32_t IRQSTS, PHY_CTRL, EVENT_TMR, TIMESTAMP, T1CMP, T2CMP,
T2PRIMECMP, T3CMP, T4CMP, PA_PWR, CHANNEL_NUM0, LQI_AND_RSSI, T2PRIMECMP, T3CMP, T4CMP, PA_PWR, CHANNEL_NUM0, LQI_AND_RSSI,
MACSHORTADDRS0, MACLONGADDRS0_LSB, MACLONGADDRS0_MSB, RX_FRAME_FILTER, MACSHORTADDRS0, MACLONGADDRS0_LSB, MACLONGADDRS0_MSB, RX_FRAME_FILTER,
CCA_LQI_CTRL, CCA2_CTRL, RESERVED_0, DSM_CTRL, RESERVED_1, CCA_LQI_CTRL, CCA2_CTRL, RESERVED_0, DSM_CTRL, RESERVED_1, MACSHORTADDRS1,
MACSHORTADDRS1, MACLONGADDRS1_LSB, MACLONGADDRS1_MSB, DUAL_PAN_CTRL, MACLONGADDRS1_LSB, MACLONGADDRS1_MSB, DUAL_PAN_CTRL, CHANNEL_NUM1,
CHANNEL_NUM1, SAM_CTRL, SAM_TABLE, SAM_MATCH, SAM_FREE_IDX, SAM_CTRL, SAM_TABLE, SAM_MATCH, SAM_FREE_IDX, SEQ_CTRL_STS, ACKDELAY,
SEQ_CTRL_STS, ACKDELAY, FILTERFAIL_CODE, RX_WTR_MARK, RESERVED_2, FILTERFAIL_CODE, RX_WTR_MARK, RESERVED_2, SLOT_PRELOAD, SEQ_STATE,
SLOT_PRELOAD, SEQ_STATE, TMR_PRESCALE, LENIENCY_LSB, LENIENCY_MSB, TMR_PRESCALE, LENIENCY_LSB, LENIENCY_MSB, PART_ID, COEX_CTRL,
PART_ID, COEX_CTRL, COEX_PRIORITY, ENHACK_CTRL0; COEX_PRIORITY, ENHACK_CTRL0;
}; };
// TrustZone: code built for the secure state (-mcmse) uses the secure alias // TrustZone: code built for the secure state (-mcmse) uses the secure alias
@@ -32,111 +34,203 @@ struct mg_zll {
#define MGI_IRQSTS_SEQIRQ MG_BIT(0) #define MGI_IRQSTS_SEQIRQ MG_BIT(0)
#define MGI_IRQSTS_RXIRQ MG_BIT(2) #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_IRQSTS_RX_LEN(x) (((x) >> 24) & 0x7f) // Frame length incl. FCS
#define MGI_PHY_CTRL_XCVSEQ 7U #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_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_MASK 0x1fU
#define MGI_SEQ_STATE_RX_WU 0x10U // RX warm up #define MGI_SEQ_STATE_RX_WU 0x10U // RX warm up
#define MGI_SEQ_CTRL_NO_RX_RECYCLE MG_BIT(5) #define MGI_SEQ_CTRL_NO_RX_RECYCLE MG_BIT(5)
#define MGI_FRAME_FILTER 0x70bU // Beacon, data, command; versions 0..2 #define MGI_FRAME_FILTER 0x70bU // Beacon, data, command; versions 0..2
enum { MGI_SEQ_IDLE = 0, MGI_SEQ_RX = 1, MGI_SEQ_TX = 2 }; // PHY_CTRL[XCVSEQ] // PHY_CTRL[XCVSEQ]
#define MGI_TX_TIMEOUT 400U // Symbols, 16 us: 127 bytes enum { MGI_SEQ_IDLE = 0, MGI_SEQ_RX = 1, MGI_SEQ_TX = 2 };
#define MGI_SEND_TIMEOUT_MS 60
// Symbol timer, 16 us, 24 bits static struct mg_tcpip_if *s_mcxw71_ifp;
static uint32_t mg_mcxw71_symbols(void) { static struct mg_queue s_mcxw71_rx;
return MGI_ZLL->EVENT_TMR >> 8; static char s_mcxw71_queue[2048];
} static volatile bool s_mcxw71_txing, s_mcxw71_done;
static uint64_t s_mcxw71_sendstart;
// Wait for the sequence to end, up to MGI_TX_TIMEOUT symbols
static bool mg_mcxw71_wait(void) {
uint32_t start = mg_mcxw71_symbols();
while (((mg_mcxw71_symbols() - start) & 0xffffffU) < MGI_TX_TIMEOUT) {
if (MGI_ZLL->IRQSTS & MGI_IRQSTS_SEQIRQ) return true;
}
return false;
}
// Stop the current sequence, wait for the sequencer to go idle. See PhyAbort() // Stop the current sequence, wait for the sequencer to go idle. See PhyAbort()
static void mg_mcxw71_idle(void) { static void mg_mcxw71_abort(void) {
if (MGI_ZLL->PHY_CTRL & MGI_PHY_CTRL_XCVSEQ) { if (MGI_ZLL->PHY_CTRL & MGI_PHY_CTRL_XCVSEQ) {
while ((MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) == MGI_SEQ_STATE_RX_WU) while ((MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) == MGI_SEQ_STATE_RX_WU)
(void) 0; (void) 0;
MGI_ZLL->PHY_CTRL &= ~MGI_PHY_CTRL_XCVSEQ; MGI_ZLL->PHY_CTRL &= ~MGI_PHY_CTRL_XCVSEQ;
while (MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) (void) 0; while (MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) (void) 0;
} }
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; // Clear all flags, write 1 to clear
}
// Start a new sequence: MGI_SEQ_RX or MGI_SEQ_TX
static void mg_mcxw71_start(uint32_t seq) {
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
MGI_ZLL->PHY_CTRL |= seq;
} }
static bool mg_mcxw71_init(struct mg_tcpip_if *ifp) { // Start receiving, with the sequence end IRQ on, see RF_802_15_4_IRQHandler()
struct mg_l2_802154_data *d = static void mg_mcxw71_listen(void) {
(struct mg_l2_802154_data *) ifp->driver_data; MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
if (d == NULL || d->channel < 11 || d->channel > 26) { MGI_ZLL->PHY_CTRL |= MGI_SEQ_RX;
MG_ERROR(("Set driver_data, channel 11..26")); MGI_ZLL->PHY_CTRL &= ~(MGI_PHY_CTRL_SEQMSK | MGI_PHY_CTRL_TRCV_MSK);
return false; 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->SEQ_CTRL_STS |= MGI_SEQ_CTRL_NO_RX_RECYCLE;
MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS; // Mask all IRQs, we poll MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS;
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
MGI_ZLL->SAM_CTRL = 0; MGI_ZLL->SAM_CTRL = 0;
MGI_ZLL->RX_FRAME_FILTER = MGI_FRAME_FILTER; MGI_ZLL->RX_FRAME_FILTER = MGI_FRAME_FILTER;
MGI_ZLL->TMR_PRESCALE = 5; // 16 us timebase, one symbol MGI_ZLL->CHANNEL_NUM0 = MG_802154_CHANNEL;
MGI_ZLL->CHANNEL_NUM0 = d->channel; MGI_ZLL->MACSHORTADDRS0 =
MGI_ZLL->MACSHORTADDRS0 = (0xfffeU << 16) | d->pan_id; // Filter by PAN ID ((uint32_t) node->radio_address << 16) | MG_802154_PAN_ID;
MGI_ZLL->PA_PWR = 0x3f; // PA slices, max output power MGI_ZLL->PA_PWR = 0x3f;
mg_mcxw71_start(MGI_SEQ_RX); mg_mcxw71_listen();
return true; return true;
} }
// Send one radio frame: MAC header and payload, no FCS static size_t mgi_mcxw71_tx(const void *buf, size_t len,
static size_t mg_mcxw71_send(const uint8_t *p, size_t len,
struct mg_tcpip_if *ifp) { struct mg_tcpip_if *ifp) {
size_t i; struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
bool ok; return data->mesh->strategy->tx(data->mesh, buf, len);
mg_mcxw71_idle();
MGI_TX_PB[0] = (uint8_t) (len + 2); // FCS is added by hardware
for (i = 0; i < len; i++) MGI_TX_PB[i + 1] = p[i]; // 8-bit access only
mg_mcxw71_start(MGI_SEQ_TX);
ok = mg_mcxw71_wait();
mg_mcxw71_idle();
mg_mcxw71_start(MGI_SEQ_RX);
(void) ifp;
return ok ? len : 0;
} }
static size_t mg_mcxw71_tx(const void *buf, size_t len, static size_t mgi_mcxw71_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
return mg_l2_802154_tx(ifp, buf, len, mg_mcxw71_send); 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 size_t mg_mcxw71_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) { static bool mgi_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) {
uint32_t irqsts = MGI_ZLL->IRQSTS; struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
uint8_t f[125]; struct mg_mesh_node *node = data->mesh;
size_t i, n = 0; uint64_t now = mg_micros();
if ((irqsts & MGI_IRQSTS_SEQIRQ) == 0) return 0; // Still receiving char *buf;
if (irqsts & MGI_IRQSTS_RXIRQ) { size_t len;
n = MGI_IRQSTS_RX_LEN(irqsts); (void) s1;
n = n >= 2 ? n - 2 : 0; // Strip FCS if (s_mcxw71_txing &&
if (n > sizeof(f)) n = sizeof(f); mg_millis() > s_mcxw71_sendstart + MGI_SEND_TIMEOUT_MS) {
for (i = 0; i < n; i++) f[i] = MGI_RX_PB[i]; 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);
} }
mg_mcxw71_idle(); }
mg_mcxw71_start(MGI_SEQ_RX); if (s_mcxw71_done) {
return mg_l2_802154_rx(ifp, f, n, buf, len); 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 mg_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) { static bool mgi_mcxw71_init(struct mg_tcpip_if *ifp) {
(void) ifp, (void) s1; struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
return true; // Radio link is always up 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 = {mg_mcxw71_init, mg_mcxw71_tx, struct mg_tcpip_driver mg_tcpip_driver_mcxw71 = {
mg_mcxw71_rx, mg_mcxw71_poll}; mgi_mcxw71_init, mgi_mcxw71_tx, mgi_mcxw71_rx, mgi_mcxw71_poll};
#endif #endif
+22 -12
View File
@@ -1,25 +1,35 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#pragma once #pragma once
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \ #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
MG_ENABLE_DRIVER_MCXW71 MG_ENABLE_DRIVER_MCXW71
// NXP MCXW71 IEEE 802.15.4 radio, ZLL link layer, as an Ethernet interface: struct mg_tcpip_driver_mcxw71_data {
// Ethernet frames are split into radio frames, see mg_l2_802154_tx(). struct mg_mesh_node *mesh; // Driver-owned runtime state
// The transceiver (XCVR) must be set up for 802.15.4 before mg_tcpip_init(), const struct mg_mesh *strategy; // NULL selects mg_mesh_default
// see hal_radio_init() in the FRDM-MCXW71 tutorial. };
// driver_data: struct mg_l2_802154_data, channel and PAN ID set
#define MG_TCPIP_DRIVER_INIT(mgr) \ extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
#define MG_TCPIP_DRIVER_MCXW71_INIT(manager) \
do { \ do { \
static struct mg_l2_802154_data driver_data_; \ static struct mg_tcpip_driver_mcxw71_data driver_data_; \
static struct mg_tcpip_if mif_; \ static struct mg_tcpip_if mif_; \
driver_data_.channel = MG_L2_802154_CHANNEL; \ MG_SET_MAC_ADDRESS(mif_.mac); \
driver_data_.pan_id = MG_L2_802154_PAN_ID; \ 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 = &mg_tcpip_driver_mcxw71; \
mif_.driver_data = &driver_data_; \ mif_.driver_data = &driver_data_; \
MG_SET_MAC_ADDRESS(mif_.mac); \ mg_tcpip_init((manager), &mif_); \
mg_tcpip_init(mgr, &mif_); \ MG_INFO(("Driver: mcxw71, MAC: %M", mg_print_mac, mif_.mac)); \
MG_INFO(("Driver: mcxw71, channel %d", MG_L2_802154_CHANNEL)); \
} while (0) } while (0)
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MCXW71_INIT(x)
#endif
#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 #define MG_DRIVER_MDC_HOLDTIME 3
#endif #endif
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(mgr) \ #define MG_TCPIP_DRIVER_INIT(mgr) \
do { \ do { \
static struct mg_tcpip_driver_rw612_data driver_data_; \ 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_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \ MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0) } while (0)
#endif
#endif #endif
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \
MG_ENABLE_DRIVER_RW612_154
// NXP RW612 IEEE 802.15.4 radio, as an Ethernet interface: Ethernet frames
// are split into radio frames, see mg_l2_802154_tx(). The radio runs on
// CPU2: NXP BLE/802.15.4 combo firmware, an OpenThread radio co-processor
// (RCP). The firmware must be loaded and running before mg_tcpip_init(), see
// the FRDM-RW612 eth_radio_bridge tutorial. The driver talks to it over the
// CPU2 <-> CPU3 IMU mailbox: IMUMC messages carrying HDLC framed Spinel.
// driver_data: struct mg_l2_802154_data, channel and PAN ID set
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(mgr) \
do { \
static struct mg_l2_802154_data driver_data_; \
static struct mg_tcpip_if mif_; \
driver_data_.channel = MG_L2_802154_CHANNEL; \
driver_data_.pan_id = MG_L2_802154_PAN_ID; \
mif_.driver = &mg_tcpip_driver_rw612_154; \
mif_.driver_data = &driver_data_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: rw612_154, channel %d", MG_L2_802154_CHANNEL)); \
} while (0)
#endif
#endif
+138 -75
View File
@@ -1,7 +1,16 @@
#include "net_builtin.h" #include "net_builtin.h"
#include "l2_mesh.h"
#include "log.h"
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \ #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612W) && \
MG_ENABLE_DRIVER_RW612_154 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 // 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 // are CPU3. Messages go through the IMU mailbox: IMU messages carry IMUMC
@@ -73,8 +82,10 @@ enum {
MGI_PROP_NCP_VERSION = 2, MGI_PROP_NCP_VERSION = 2,
MGI_PROP_PHY_ENABLED = 0x20, MGI_PROP_PHY_ENABLED = 0x20,
MGI_PROP_PHY_CHAN = 0x21, MGI_PROP_PHY_CHAN = 0x21,
MGI_PROP_SADDR = 0x35, // Short address, little endian
MGI_PROP_PANID = 0x36, MGI_PROP_PANID = 0x36,
MGI_PROP_RAW_STREAM = 0x37, MGI_PROP_RAW_STREAM = 0x37,
MGI_PROP_PROMISCUOUS = 0x38,
MGI_PROP_STREAM_RAW = 0x71 MGI_PROP_STREAM_RAW = 0x71
}; };
#define MGI_RESET_STACK 2 // RESET argument: reset the RCP stack only #define MGI_RESET_STACK 2 // RESET argument: reset the RCP stack only
@@ -82,7 +93,6 @@ enum {
#define MGI_STATUS_RESET_MAX 127 #define MGI_STATUS_RESET_MAX 127
#define MGI_TX_TIMEOUT_MS 200 // Wait for transmit done, incl. retries #define MGI_TX_TIMEOUT_MS 200 // Wait for transmit done, incl. retries
#define MGI_RSP_TIMEOUT_MS 500 #define MGI_RSP_TIMEOUT_MS 500
#define MGI_RXQ_CNT 16 // A whole Ethernet frame, 15 radio frames
// Driver state, one radio // Driver state, one radio
struct mg_rw612 { struct mg_rw612 {
@@ -98,11 +108,13 @@ struct mg_rw612 {
bool rsp; // Response to the pending request received bool rsp; // Response to the pending request received
uint32_t rsp_status; // Its status, if LAST_STATUS, else 0 uint32_t rsp_status; // Its status, if LAST_STATUS, else 0
char version[64]; // RCP version string char version[64]; // RCP version string
uint8_t rxq[MGI_RXQ_CNT][127]; // Received frames, no FCS bool txing; // A send is on, see mg_rw612_start()
uint8_t rxlen[MGI_RXQ_CNT]; // Their lengths uint64_t txstart; // Its start, ms
uint8_t rxhead, rxtail; // Queue indexes struct mg_tcpip_if *ifp; // Our interface, for the L2 receive pool
}; };
static struct mg_rw612 s_rw612; 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, static void mg_rw612_imu_send(uint8_t type, uint8_t subtype,
const uint32_t *payload, uint8_t len) { const uint32_t *payload, uint8_t len) {
@@ -126,12 +138,15 @@ static void mg_rw612_control(uint8_t subtype, uint32_t a, uint32_t b,
} }
static void mg_rw612_queue_rx(const uint8_t *frame, size_t len) { static void mg_rw612_queue_rx(const uint8_t *frame, size_t len) {
struct mg_rw612 *s = &s_rw612; {
uint8_t next = (uint8_t) ((s->rxhead + 1) % MGI_RXQ_CNT); char *dst;
if (next == s->rxtail || len > sizeof(s->rxq[0])) return; // Full: drop if (mg_queue_book(&s_rw612w_rx, &dst, len) >= len) {
memcpy(s->rxq[s->rxhead], frame, len); memcpy(dst, frame, len);
s->rxlen[s->rxhead] = (uint8_t) len; mg_queue_add(&s_rw612w_rx, len);
s->rxhead = next; } else {
s_rw612.ifp->ndrop++;
}
}
} }
// Spinel packed unsigned integer: 7 bits per byte, LSB first // Spinel packed unsigned integer: 7 bits per byte, LSB first
@@ -293,14 +308,13 @@ static bool mg_rw612_imumc_send(const uint8_t *p, size_t len) {
return true; return true;
} }
// Send a Spinel frame, HDLC encoded, and wait for the response with the // Send a Spinel frame, HDLC encoded, with a new transaction ID. Its
// same transaction ID. Return false on timeout // response sets rsp, see mg_rw612_spinel()
static bool mg_rw612_request(uint8_t *f, size_t len, uint32_t timeout_ms) { static bool mg_rw612_post(uint8_t *f, size_t len) {
struct mg_rw612 *s = &s_rw612; struct mg_rw612 *s = &s_rw612;
uint8_t buf[2 * 160 + 6]; uint8_t buf[2 * 160 + 6];
uint16_t fcs = 0xffff; uint16_t fcs = 0xffff;
size_t i, n = 0; size_t i, n = 0;
uint64_t expire = mg_millis() + timeout_ms;
s->tid = (uint8_t) (s->tid % 15 + 1); s->tid = (uint8_t) (s->tid % 15 + 1);
f[0] = (uint8_t) (0x80 | s->tid); // Header: flag, interface 0, TID f[0] = (uint8_t) (0x80 | s->tid); // Header: flag, interface 0, TID
s->rsp = false; s->rsp = false;
@@ -318,9 +332,15 @@ static bool mg_rw612_request(uint8_t *f, size_t len, uint32_t timeout_ms) {
buf[n++] = c; buf[n++] = c;
} }
buf[n++] = 0x7e; buf[n++] = 0x7e;
if (!mg_rw612_imumc_send(buf, n)) return false; return mg_rw612_imumc_send(buf, n);
while (!s->rsp && mg_millis() < expire) mg_rw612_poll_imu(); }
return s->rsp;
// 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) { static bool mg_rw612_set(uint32_t prop, const uint8_t *val, size_t len) {
@@ -338,27 +358,43 @@ static bool mg_rw612_fail(const char *msg) {
return false; return false;
} }
static bool mg_rw612_154_init(struct mg_tcpip_if *ifp) { // 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_rw612 *s = &s_rw612;
struct mg_l2_802154_data *d = struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
(struct mg_l2_802154_data *) ifp->driver_data; struct mg_mesh_node *node = data->mesh;
uint8_t f[8], one = 1, pan[2]; uint8_t f[8], one = 1, zero = 0, pan[2], addr[2], channel = MG_802154_CHANNEL;
uint64_t expire; uint64_t expire;
if (d == NULL || d->channel < 11 || d->channel > 26) {
MG_ERROR(("Set driver_data, channel 11..26"));
return false;
}
if (MGI_MBOX->magic[0] != MGI_SYNC_MAGIC) { if (MGI_MBOX->magic[0] != MGI_SYNC_MAGIC) {
MG_ERROR(("Radio firmware is not running on CPU2")); MG_ERROR(("Radio firmware is not running on CPU2"));
return false; return false;
} }
memset(s, 0, sizeof(*s)); memset(s, 0, sizeof(*s));
s->free_bufs = 0xffff; s->free_bufs = 0xffff, s->ifp = ifp;
// Reset our send FIFO, drop stale messages. See IMU_Init() // Reset our send FIFO, drop stale messages. See IMU_Init()
MGI_IMU->CNTL |= MGI_IMU_FLUSH; MGI_IMU->CNTL |= MGI_IMU_FLUSH;
MGI_IMU->CNTL &= ~(MGI_IMU_FLUSH | MGI_IMU_WAIT_FOR_ACK); MGI_IMU->CNTL &= ~(MGI_IMU_FLUSH | MGI_IMU_WAIT_FOR_ACK);
MGI_IMU->CNTL = (MGI_IMU->CNTL & ~MGI_IMU_WATERMARK_MASK) | MGI_IMU->CNTL =
MGI_IMU_WATERMARK(15); (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; while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) (void) MGI_IMU->RD_MSG;
// Synchronise the IMU link, then connect the IMUMC endpoints // Synchronise the IMU link, then connect the IMUMC endpoints
MGI_MBOX->magic[1] = MGI_SYNC_MAGIC; MGI_MBOX->magic[1] = MGI_SYNC_MAGIC;
@@ -385,10 +421,15 @@ static bool mg_rw612_154_init(struct mg_tcpip_if *ifp) {
if (!mg_rw612_set(MGI_PROP_PHY_ENABLED, &one, 1)) { if (!mg_rw612_set(MGI_PROP_PHY_ENABLED, &one, 1)) {
return mg_rw612_fail("PHY enable failed"); return mg_rw612_fail("PHY enable failed");
} }
pan[0] = (uint8_t) d->pan_id, pan[1] = (uint8_t) (d->pan_id >> 8); pan[0] = (uint8_t) MG_802154_PAN_ID;
if (!mg_rw612_set(MGI_PROP_PHY_CHAN, &d->channel, 1) || pan[1] = (uint8_t) (MG_802154_PAN_ID >> 8);
!mg_rw612_set(MGI_PROP_PANID, pan, 2)) { addr[0] = (uint8_t) node->radio_address;
return mg_rw612_fail("Channel or PAN ID set failed"); 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)) { if (!mg_rw612_set(MGI_PROP_RAW_STREAM, &one, 1)) {
return mg_rw612_fail("Raw stream enable failed"); return mg_rw612_fail("Raw stream enable failed");
@@ -396,55 +437,77 @@ static bool mg_rw612_154_init(struct mg_tcpip_if *ifp) {
return true; return true;
} }
// Send one radio frame: MAC header and payload, no FCS static size_t mgi_rw612w_tx(const void *buf, size_t len,
static size_t mg_rw612_send(const uint8_t *p, size_t len,
struct mg_tcpip_if *ifp) { struct mg_tcpip_if *ifp) {
struct mg_l2_802154_data *d = struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
(struct mg_l2_802154_data *) ifp->driver_data; return data->mesh->strategy->tx(data->mesh, buf, len);
uint8_t f[3 + 2 + 127 + 4]; }
size_t n = 3;
f[1] = MGI_CMD_SET, f[2] = MGI_PROP_STREAM_RAW; static size_t mgi_rw612w_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
f[n++] = (uint8_t) (len + 2), f[n++] = 0; // PSDU length, incl. FCS struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
memcpy(f + n, p, len), n += len; size_t len = data->mesh->rx_len;
f[n++] = 0, f[n++] = 0; // FCS, filled in by the radio if (len > cap) return 0;
f[n++] = d->channel; if (len != 0 && buf != ifp->recv_queue.buf)
f[n++] = 4; // Max CSMA backoffs memcpy(buf, ifp->recv_queue.buf, len);
f[n++] = 0; // Max frame retries: broadcast frames are not acknowledged data->mesh->rx_len = 0;
f[n++] = 1; // CSMA-CA enabled
if (!mg_rw612_request(f, n, MGI_TX_TIMEOUT_MS)) {
MG_ERROR(("TX timeout"));
return 0;
}
if (s_rw612.rsp_status != 0) {
MG_VERBOSE(("TX failed, status %lu", (unsigned long) s_rw612.rsp_status));
return 0;
}
return len; return len;
} }
static size_t mg_rw612_154_tx(const void *buf, size_t len, static bool mgi_rw612w_poll(struct mg_tcpip_if *ifp, bool s1) {
struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
return mg_l2_802154_tx(ifp, buf, len, mg_rw612_send); struct mg_mesh_node *node = data->mesh;
} uint64_t now = mg_micros();
char *buf;
static size_t mg_rw612_154_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) { size_t len;
struct mg_rw612 *s = &s_rw612; (void) s1;
size_t res = 0; bool was_sending = s_rw612.txing;
mg_rw612_poll_imu(); mg_rw612_poll_imu();
while (s->rxtail != s->rxhead && res == 0) { if (s_rw612.txing &&
res = mg_l2_802154_rx(ifp, s->rxq[s->rxtail], s->rxlen[s->rxtail], buf, (s_rw612.rsp || mg_millis() > s_rw612.txstart + MGI_TX_TIMEOUT_MS)) {
len); if (!s_rw612.rsp) MG_ERROR(("TX timeout"));
s->rxtail = (uint8_t) ((s->rxtail + 1) % MGI_RXQ_CNT); s_rw612.txing = false;
} }
return res; 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 mg_rw612_154_poll(struct mg_tcpip_if *ifp, bool s1) { static bool mgi_rw612w_init(struct mg_tcpip_if *ifp) {
(void) ifp, (void) s1; struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
return s_rw612.up; 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_rw612_154 = { struct mg_tcpip_driver mg_tcpip_driver_rw612w = {
mg_rw612_154_init, mg_rw612_154_tx, mg_rw612_154_rx, mg_rw612_154_poll}; mgi_rw612w_init, mgi_rw612w_tx, mgi_rw612w_rx, mgi_rw612w_poll};
#endif #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
+6 -1
View File
@@ -214,7 +214,12 @@ size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt,
n++; n++;
} else if (c == 's') { } else if (c == 's') {
char *p = va_arg(*ap, char *); 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++) for (j = 0; !minus && pr < w && j + pr < w; j++)
n += scpy(out, param, &pad, 1); n += scpy(out, param, &pad, 1);
n += scpy(out, param, p, pr); n += scpy(out, param, p, pr);
+207 -35
View File
@@ -1,12 +1,177 @@
#include "arch.h"
#include "config.h" #include "config.h"
#include "health.h" #include "health.h"
#if MG_ENABLE_HEALTH
#if !MG_ENABLE_CUSTOM_USAGE
#if MG_ARCH == MG_ARCH_ESP32
#include <esp_heap_caps.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#elif MG_ENABLE_FREERTOS && defined(__GNUC__) && defined(__ELF__)
// Some FreeRTOS allocators provide current free bytes but no low watermark.
extern size_t xPortGetMinimumEverFreeHeapSize(void) __attribute__((weak));
#endif
#if MG_ENABLE_FREERTOS && MG_ARCH == MG_ARCH_CUBE && \
configUSE_TRACE_FACILITY == 1 && configRECORD_STACK_HIGH_ADDRESS != 1
#warning \
"Set configRECORD_STACK_HIGH_ADDRESS to 1 in FreeRTOSConfig.h to report stack total and peak"
#endif
size_t mg_usage(int task, int metric) {
#if MG_ARCH == MG_ARCH_ESP32 || \
(MG_ENABLE_FREERTOS && defined(configTOTAL_HEAP_SIZE))
if (metric == MG_USAGE_HEAP_TOTAL || metric == MG_USAGE_HEAP_USED ||
metric == MG_USAGE_HEAP_PEAK) {
size_t total, available;
#if MG_ARCH == MG_ARCH_ESP32
total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
if (metric == MG_USAGE_HEAP_TOTAL) return total;
available = metric == MG_USAGE_HEAP_USED
? heap_caps_get_free_size(MALLOC_CAP_DEFAULT)
: heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
#else
total = configTOTAL_HEAP_SIZE;
if (metric == MG_USAGE_HEAP_TOTAL) return total;
if (metric == MG_USAGE_HEAP_USED) {
available = xPortGetFreeHeapSize();
} else {
#if defined(__GNUC__) && defined(__ELF__)
if (xPortGetMinimumEverFreeHeapSize == NULL) return 0;
available = xPortGetMinimumEverFreeHeapSize();
#else
return 0;
#endif
}
#endif
return available <= total ? total - available : 0;
}
#endif
#if MG_ENABLE_FREERTOS || MG_ARCH == MG_ARCH_ESP32
#if configUSE_TRACE_FACILITY == 1
UBaseType_t count = uxTaskGetNumberOfTasks();
if (metric == MG_USAGE_NUM_TASKS) return (size_t) count;
if (task >= 0 && (size_t) task < count) {
TaskStatus_t tasks[count];
if ((size_t) task >= uxTaskGetSystemState(tasks, count, NULL)) return 0;
TaskStatus_t *t = &tasks[task];
if (metric == MG_USAGE_TASK_NAME) return (size_t) t->pcTaskName;
#if (MG_ARCH == MG_ARCH_CUBE || MG_ARCH == MG_ARCH_ESP32) && \
configRECORD_STACK_HIGH_ADDRESS == 1
size_t total = 0;
uintptr_t tcb = (uintptr_t) t->xHandle;
uintptr_t base = (uintptr_t) t->pxStackBase;
if (tcb != 0 &&
t->pcTaskName == (const char *) (tcb + offsetof(StaticTask_t, ucDummy7))) {
StackType_t *end;
uintptr_t field = tcb + offsetof(StaticTask_t, pxDummy8);
memcpy(&end, (const void *) field, sizeof(end));
if (base != 0 && (uintptr_t) end >= base &&
(uintptr_t) end - base <= ((size_t) -1) - sizeof(*end))
total = (size_t) ((uintptr_t) end - base) + sizeof(*end);
}
if (metric == MG_USAGE_STACK_TOTAL) return total;
if (metric == MG_USAGE_STACK_PEAK) {
size_t available =
(size_t) t->usStackHighWaterMark * sizeof(StackType_t);
return available <= total ? total - available : 0;
}
#endif
}
#endif
#endif
(void) task, (void) metric;
return 0;
}
#endif
static size_t print_mem_stat(mg_pfn_t fn, void *arg, const char *name,
size_t total, size_t used, size_t peak) {
return mg_xprintf(fn, arg, "{%m:%m,%m:%zu,%m:%zu,%m:%zu}", MG_ESC("name"),
MG_ESC(name == NULL ? "" : name), MG_ESC("total"), total,
MG_ESC("used"), used, MG_ESC("peak"), peak);
}
size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
int i, count;
size_t heap_total = mg_usage(0, MG_USAGE_HEAP_TOTAL);
size_t heap_used = mg_usage(0, MG_USAGE_HEAP_USED);
size_t heap_peak = mg_usage(0, MG_USAGE_HEAP_PEAK);
size_t len = mg_xprintf(fn, arg, "[");
len += print_mem_stat(fn, arg, "heap", heap_total, heap_used, heap_peak);
count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS);
for (i = 0; i < count; i++) {
const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME);
len += mg_xprintf(fn, arg, ",");
len += print_mem_stat(fn, arg, name, mg_usage(i, MG_USAGE_STACK_TOTAL),
mg_usage(i, MG_USAGE_STACK_USED),
mg_usage(i, MG_USAGE_STACK_PEAK));
}
(void) ap;
return len + mg_xprintf(fn, arg, "]");
}
#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 // 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 // MG_HEALTH_RAM and the .mg_health region in the linker script
struct mg_health mg_health_record MG_HEALTH_RAM; struct mg_health mg_health_record MG_HEALTH_RAM;
// STM32 H723 support. Needs the _estack symbol from the linker script // STM32 H7, H5, C5 support. Needs the _estack symbol from the linker script
#if MG_ENABLE_HEALTH && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5)) #if defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5)
// Fault handler body. Runs in exception context: no printf, no malloc, no // Fault handler body. Runs in exception context: no printf, no malloc, no
// blocking calls. Copies the unwind registers and raw stack into the health // blocking calls. Copies the unwind registers and raw stack into the health
// record, then resets. // record, then resets.
@@ -26,6 +191,7 @@ __attribute__((used, noinline)) static void fault_c(uint32_t *frame,
r[3 + i] = stack + i < &_estack ? stack[i] : 0; r[3 + i] = stack + i < &_estack ? stack[i] : 0;
} }
mg_health_record.cpuid = SCB->CPUID; mg_health_record.cpuid = SCB->CPUID;
mg_health_record.arch = MG_HEALTH_ARCH_ARM;
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT; mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
NVIC_SystemReset(); NVIC_SystemReset();
} }
@@ -48,52 +214,54 @@ void BusFault_Handler(void) __attribute__((alias("HardFault_Handler")));
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler"))); void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
#endif #endif
// Start a new boot: validate the record, count the boot, store the reset // Start a new boot: validate the record. Called by MG_HEALTH_INIT()
// reason. The current boot moves to prev_* slots first, so the previous
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
void mg_health_init(void) { void mg_health_init(void) {
char magic[] = MG_HEALTH_MAGIC; char magic[] = MG_HEALTH_MAGIC;
// struct mg_health *h = mg_health_get();
if (!mg_health_valid()) { // First boot, or RAM lost its contents if (!mg_health_valid()) { // First boot, or RAM lost its contents
memset(&mg_health_record, 0, sizeof(mg_health_record)); memset(&mg_health_record, 0, sizeof(mg_health_record));
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic)); memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
} }
#if MG_ARCH == MG_ARCH_ESP32
#if 0 && MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH #if CONFIG_IDF_TARGET_ARCH_XTENSA
{ mg_health_record.arch = MG_HEALTH_ARCH_XTENSA;
#elif CONFIG_IDF_TARGET_ARCH_RISCV
mg_health_record.arch = 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 // 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 ...". // addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
// The core dump stays in flash, so check the reset reason, otherwise an // The core dump stays in flash, so check the reset reason, otherwise an
// old crash is reported on every boot // old crash is reported on every boot
static esp_core_dump_summary_t s; // Big on RISC-V, keep it off the stack esp_core_dump_summary_t s;
esp_reset_reason_t rr = esp_reset_reason();
uint32_t *out = mg_health_record.data; uint32_t *out = mg_health_record.data;
size_t n = 0, max = MG_HEALTH_DATA_SIZE; size_t n = 0;
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;
memset(out, 0, sizeof(mg_health_record.data)); memset(out, 0, sizeof(mg_health_record.data));
out[n++] = s.exc_pc; if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
rr != ESP_RST_TASK_WDT) ||
esp_core_dump_get_summary(&s) != ESP_OK) {
return; // If not panic, and if cannot get crash info, give up
}
out[n++] = s.exc_pc; // Crash PC
memcpy(&out[n], s.exc_task, 16), n += 4; // Crashed task name
#if CONFIG_IDF_TARGET_ARCH_XTENSA #if CONFIG_IDF_TARGET_ARCH_XTENSA
for (uint32_t i = 0; i < s.exc_bt_info.depth && n < max; i++) { out[n++] = s.ex_info.exc_a[0];
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[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);
#elif CONFIG_IDF_TARGET_ARCH_RISCV #elif CONFIG_IDF_TARGET_ARCH_RISCV
// No unwinder: take the return address, then words on the stack that out[n++] = s.ex_info.ra;
// point into executable memory (a heuristic, may include stale values) out[n++] = s.ex_info.sp;
if (n < max) out[n++] = s.ex_info.ra; size_t len = s.exc_bt_info.dump_size / 4;
for (uint32_t i = 0; i + 4 <= s.exc_bt_info.dump_size && n < max; i += 4) { if (len > MG_HEALTH_DATA_SIZE - n) len = MG_HEALTH_DATA_SIZE - n;
uint32_t w; memcpy(&out[n], s.exc_bt_info.stackdump, 4 * len);
memcpy(&w, &s.exc_bt_info.stackdump[i], sizeof(w)); // Byte array
if (esp_ptr_executable((void *) (uintptr_t) w)) out[n++] = w;
}
#endif
}
#endif #endif
#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) { static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
@@ -107,10 +275,13 @@ 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 mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t len = 0; size_t len = 0;
if (mg_health_valid() && if (mg_health_valid() &&
mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT) { (mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT ||
mg_health_record.reset_reason == MG_HEALTH_RESET_WATCHDOG)) {
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]); 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]}", // len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%lu,%m:%lu,%m:%u,%m:[%M]}", //
MG_ESC("version"), mg_health_record.magic[3], // 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("cpuid"), mg_health_record.cpuid, //
MG_ESC("data"), print_uint32, n, mg_health_record.data); MG_ESC("data"), print_uint32, n, mg_health_record.data);
} else { } else {
@@ -119,3 +290,4 @@ size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
(void) ap; (void) ap;
return len; return len;
} }
#endif
+109 -5
View File
@@ -5,6 +5,8 @@
#include "arch.h" #include "arch.h"
#include "printf.h" #include "printf.h"
#if MG_ENABLE_HEALTH
#ifndef MG_HEALTH_MAGIC #ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version #define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif #endif
@@ -13,13 +15,17 @@
#define MG_HEALTH_RAM __attribute__((section(".mg_health"))) #define MG_HEALTH_RAM __attribute__((section(".mg_health")))
#endif #endif
#if !defined(MG_HEALTH_DATA_SIZE) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
#define MG_HEALTH_DATA_SIZE 250
#endif
#ifndef MG_HEALTH_DATA_SIZE #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 && CONFIG_IDF_TARGET_ARCH_XTENSA
#define MG_HEALTH_DATA_SIZE 23 // 7 header words + 16 backtrace PCs
#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_RISCV
#define MG_HEALTH_DATA_SIZE 7 // PC, task name, RA, SP
#else
#define MG_HEALTH_DATA_SIZE 10 #define MG_HEALTH_DATA_SIZE 10
#endif #endif
#endif // MG_HEALTH_DATA_SIZE
// Places the record in RAM that survives a reset. Define in mongoose_config.h // 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"))) // to match the linker script, e.g. __attribute__((section(".mg_health")))
@@ -27,6 +33,13 @@
#define MG_HEALTH_RAM #define MG_HEALTH_RAM
#endif #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, // 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 // so these are ordered by how much they tell you: the most specific cause a
// device reports wins // device reports wins
@@ -41,9 +54,10 @@ enum mg_health_reason {
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
}; };
struct mg_health { struct mg_health { // NOTE: use 32-bit fields to avoid write issues
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot uint32_t reset_reason; // enum mg_health_reason that started this boot
uint32_t arch; // enum mg_health_arch, core architecture (ARM, etc)
uint32_t cpuid; // ARM CPU ID uint32_t cpuid; // ARM CPU ID
uint32_t data[MG_HEALTH_DATA_SIZE]; uint32_t data[MG_HEALTH_DATA_SIZE];
}; };
@@ -76,6 +90,96 @@ 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
enum mg_usage_metric {
MG_USAGE_NUM_TASKS,
MG_USAGE_TASK_NAME,
MG_USAGE_STACK_TOTAL,
MG_USAGE_STACK_USED,
MG_USAGE_STACK_PEAK,
MG_USAGE_HEAP_TOTAL,
MG_USAGE_HEAP_USED,
MG_USAGE_HEAP_PEAK
};
// Returns a task or heap metric. task is a zero-based index; NUM_TASKS and heap
// metrics ignore it. Sizes are in bytes, TASK_NAME returns const char * cast to
// size_t
// Unsupported metrics return 0. PEAK is the maximum used since boot
// Set MG_ENABLE_CUSTOM_USAGE=1 and define mg_usage() in the application to
// provide custom measurements
size_t mg_usage(int task, int metric);
#define MG_HEALTH_INIT() mg_health_init() #define MG_HEALTH_INIT() mg_health_init()
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *); extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
// JSON memory statistics via mg_usage(), suitable for %M: an array of
// {name, total, used, peak} entries, starting with "heap", then task stacks
// Unavailable metrics are reported as 0
extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *);
extern void mg_health_init(void); 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 size_t mg_print_mem_stats(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
+9 -1
View File
@@ -265,6 +265,12 @@ static bool mg_http_parse_headers(const char *s, const char *end,
// MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, v.buf)); // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, v.buf));
h[i].name = k, h[i].value = v; // Success. Assign values h[i].name = k, h[i].value = v; // Success. Assign values
} }
if (i == max_hdrs &&
!(s < end &&
(s[0] == '\n' ||
(s[0] == '\r' && s + 1 < end && s[1] == '\n')))) {
return false;
}
return true; return true;
} }
@@ -734,13 +740,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) { 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; struct mg_str k, v, part, s = mg_str(r), u = {NULL, 0}, d = u;
size_t n; size_t n;
bool matched = false;
while (mg_span(s, &part, &s, ',')) { while (mg_span(s, &part, &s, ',')) {
if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0); 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 (v.len == 0) v = k, k = mg_str("/"), u = k, d = v;
if (uri.len < k.len) continue; if (uri.len < k.len) continue;
if (mg_strcmp(k, mg_str_n(uri.buf, k.len)) != 0) 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); n = mg_snprintf(buf, len, "%.*s", (int) d.len, d.buf);
if (len == 0 || n + 2 >= len) return false; // Path overflow if (len == 0 || n + 2 >= len) return false; // Path overflow
if (n > 0 && buf[n - 1] != '/') buf[n++] = '/', buf[n] = '\0'; // Add slash if (n > 0 && buf[n - 1] != '/') buf[n++] = '/', buf[n] = '\0'; // Add slash
-60
View File
@@ -1,60 +0,0 @@
#include "l2.h"
#include "net_builtin.h"
#if MG_ENABLE_TCPIP
// Radio frame: 802.15.4 MAC header, 15 bytes: data frame, PAN ID compression,
// broadcast destination, our EUI-64 made from MAC as source. Then 2 bytes:
// tag, fragment index << 4 | last fragment index. Then data. 125 bytes max,
// 127 minus FCS
#define MGI_802154_HDR 17
#define MGI_802154_DATA (125 - MGI_802154_HDR)
size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len,
size_t (*send_fn)(const uint8_t *, size_t,
struct mg_tcpip_if *)) {
struct mg_l2_802154_data *d =
(struct mg_l2_802154_data *) ifp->driver_data;
const uint8_t *p = (const uint8_t *) buf, *m = ifp->mac;
uint8_t f[MGI_802154_HDR + MGI_802154_DATA];
size_t i, n, last = (len - 1) / MGI_802154_DATA;
if (len == 0 || last > 15) return 0;
d->tag++;
for (i = 0; i <= last; i++) {
n = i == last ? len - i * MGI_802154_DATA : MGI_802154_DATA;
f[0] = 0x41, f[1] = 0xd8, f[2] = d->seq++; // 2006 frame, see above
f[3] = (uint8_t) d->pan_id, f[4] = (uint8_t) (d->pan_id >> 8);
f[5] = f[6] = 0xff;
f[7] = m[5], f[8] = m[4], f[9] = m[3], f[10] = 0xfe, f[11] = 0xff;
f[12] = m[2], f[13] = m[1], f[14] = m[0]; // Little endian EUI-64
f[15] = d->tag, f[16] = (uint8_t) ((i << 4) | last);
memcpy(f + MGI_802154_HDR, p + i * MGI_802154_DATA, n);
if (send_fn(f, MGI_802154_HDR + n, ifp) == 0) return 0;
}
return len;
}
size_t mg_l2_802154_rx(struct mg_tcpip_if *ifp, const uint8_t *f, size_t n,
void *buf, size_t len) {
struct mg_l2_802154_data *d =
(struct mg_l2_802154_data *) ifp->driver_data;
size_t idx, last;
if (n <= MGI_802154_HDR || f[0] != 0x41 || f[1] != 0xd8 ||
f[3] != (uint8_t) d->pan_id || f[4] != (uint8_t) (d->pan_id >> 8))
return 0; // Not ours
idx = f[16] >> 4, last = f[16] & 15;
if (idx == 0) d->rxtag = f[15], d->rxnext = 0, d->rxlen = 0;
if (idx == 0) memcpy(d->rxsrc, f + 7, sizeof(d->rxsrc));
n -= MGI_802154_HDR;
if (idx != d->rxnext || idx > last || f[15] != d->rxtag ||
memcmp(d->rxsrc, f + 7, sizeof(d->rxsrc)) != 0 ||
d->rxlen + n > sizeof(d->rxbuf))
return 0; // Missed a fragment, or from another sender
memcpy(d->rxbuf + d->rxlen, f + MGI_802154_HDR, n);
d->rxlen = (uint16_t) (d->rxlen + n), d->rxnext++;
if (idx < last || d->rxlen > len) return 0;
memcpy(buf, d->rxbuf, d->rxlen);
return d->rxlen;
}
#endif
+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 *) mg_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);
mg_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;
mg_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 *) mg_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);
mg_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
+38
View File
@@ -89,6 +89,7 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
} }
mg_iobuf_free(&io); mg_iobuf_free(&io);
} else if (ev == MG_EV_CLOSE) { } else if (ev == MG_EV_CLOSE) {
mg_health_coredump_end();
mg_mdash_conn = NULL; mg_mdash_conn = NULL;
MG_INFO(("disconnected, ram %lu", 0)); MG_INFO(("disconnected, ram %lu", 0));
} }
@@ -105,6 +106,10 @@ static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
MG_ESC("crash"), mg_print_crash_record); MG_ESC("crash"), mg_print_crash_record);
} }
static void mg_mdash_rpc_mem_stats(struct mg_rpc_req *r) {
mg_rpc_ok(r, "%M", mg_print_mem_stats);
}
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) { static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
size_t total = (size_t) mg_json_get_long(r->frame, "$.params.size", -1); size_t total = (size_t) mg_json_get_long(r->frame, "$.params.size", -1);
if (mg_ota_begin(total)) { if (mg_ota_begin(total)) {
@@ -146,13 +151,46 @@ static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
// MG_ESC("percent")); // 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) { void mg_mdash_init(struct mg_mgr *mgr) {
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY)); MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL); mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("Sys.GetInfo"), mg_mdash_rpc_get_info, NULL); mg_rpc_add(&mgr->rpcs, mg_str("Sys.GetInfo"), mg_mdash_rpc_get_info, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("Sys.MemStats"), mg_mdash_rpc_mem_stats, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, NULL); 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.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("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) { void mg_mdash_poll(struct mg_mgr *mgr) {
+38 -128
View File
@@ -10,8 +10,14 @@
#define MG_TCPIP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms #define MG_TCPIP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
#endif #endif
#ifndef MG_TCPIP_ACK_MS
#define MG_TCPIP_ACK_MS 150 // Timeout for ACKing #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 #define MG_TCPIP_ARP_MS 100 // Timeout for ARP response
#endif
#define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment #define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment
#define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection #define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection
#define MG_TCPIP_RTX_MS 1000 // Initial retransmission timeout (RFC-6298, 2.1) #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}; {{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 = { 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}; {{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]) #define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1])
#endif #endif
@@ -418,7 +425,7 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
if (ifp->is_ip_changed) { if (ifp->is_ip_changed) {
struct mg_connection *c; struct mg_connection *c;
for (c = ifp->mgr->conns; c != NULL; c = c->next) { 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; ifp->is_ip_changed = false;
} }
@@ -595,6 +602,7 @@ static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt,
if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport && if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
((lsn && c->is_listening && !(c->loc.is_ip6 ^ (pkt->ip6 != NULL))) || ((lsn && c->is_listening && !(c->loc.is_ip6 ^ (pkt->ip6 != NULL))) ||
(!lsn && !c->is_listening && c->rem.port == pkt->tcp->sport && (!lsn && !c->is_listening && c->rem.port == pkt->tcp->sport &&
!(c->loc.is_ip6 ^ (pkt->ip6 != NULL)) && // IP or IPv6 to same dest
((!c->loc.is_ip6 && c->rem.addr.ip4 == pkt->ip->src) ((!c->loc.is_ip6 && c->rem.addr.ip4 == pkt->ip->src)
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
|| (c->loc.is_ip6 && MG_IP6MATCH(c->rem.addr.ip6, pkt->ip6->src)) || (c->loc.is_ip6 && MG_IP6MATCH(c->rem.addr.ip6, pkt->ip6->src))
@@ -609,36 +617,6 @@ static void l2addr_resolved(struct mg_connection *c);
static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem, static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem,
bool is_udp, struct pkt *pkt); bool is_udp, struct pkt *pkt);
#if MG_ENABLE_BSD_SOCKETS
static uint8_t *sendto_cache_find(struct mg_tcpip_if *ifp, uint32_t ip) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return (uint8_t *) &a->addr;
}
return NULL;
}
static void sendto_cache_add(struct mg_tcpip_if *ifp, uint32_t ip,
const uint8_t *addr) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return; // already cached, skip
if (a->ip == 0) break; // not present, first empty slot
}
if (a == ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE) { // no room
memmove(ifp->sendto, ifp->sendto + 1,
sizeof(ifp->sendto[0]) * (MG_TCPIP_SENDTO_CACHE_SIZE - 1));
a--; // make room and point to last entry (just made)
}
a->ip = ip; // populate this entry
memcpy(&a->addr, addr, sizeof(a->addr));
}
static void sendto_cache_clear(struct mg_tcpip_if *ifp) {
memset(ifp->sendto, 0, sizeof(ifp->sendto));
}
#endif
// RFC826, ARP assumes Ethernet MAC addresses // RFC826, ARP assumes Ethernet MAC addresses
static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
@@ -764,6 +742,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
: pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->dhcp->siaddr ? pkt->dhcp->siaddr
: pkt->ip->src); : pkt->ip->src);
ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state 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 } else if (msgtype == 5) { // DHCPACK
if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP
uint64_t rand; uint64_t rand;
@@ -1044,12 +1023,13 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (pkt->pay.len < sizeof(*ra)) return; if (pkt->pay.len < sizeof(*ra)) return;
if (ifp->state6 == MG_TCPIP_STATE_UP) { 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 // parse options
while (opt_left >= 2) { while (opt_left >= 2) {
uint8_t type = opts[0], len = opts[1]; uint8_t type = opts[0], len = opts[1];
size_t length = (size_t) len * 8; 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) { if (type == 1 && length >= 8) {
// Received router's L2 address // Received router's L2 address
if (!mg_l2_ip6get(ifp->l2type, l2, opts + 2, len)) break; if (!mg_l2_ip6get(ifp->l2type, l2, opts + 2, len)) break;
@@ -1130,7 +1110,7 @@ static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
} }
} break; } break;
case 134: // Router Advertisement 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; break;
case 135: // Neighbor Solicitation case 135: // Neighbor Solicitation
rx_ndp_ns(ifp, pkt); rx_ndp_ns(ifp, pkt);
@@ -1167,7 +1147,7 @@ static void onstate6change(struct mg_tcpip_if *ifp) {
if (ifp->is_ip6_changed) { if (ifp->is_ip6_changed) {
struct mg_connection *c; struct mg_connection *c;
for (c = ifp->mgr->conns; c != NULL; c = c->next) { 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; ifp->is_ip6_changed = false;
} }
@@ -1244,57 +1224,30 @@ static void proto_error(struct mg_connection *c, bool r, const char *f, ...) {
static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct mg_connection *c = getpeer(ifp->mgr, pkt, true); struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
struct connstate *s; struct connstate *s;
struct mg_addr rem;
uint8_t *l2addr; uint8_t *l2addr;
size_t len = pkt->pay.len;
if (c == NULL) return false; // No UDP listener on this port if (c == NULL) return false; // No UDP listener on this port
s = (struct connstate *) (c + 1); s = (struct connstate *) (c + 1);
c->rem.port = pkt->udp->sport;
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer() if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer()
if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false; if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false;
if (c->is_bsd && c->is_client && c->rem.addr.ip6[0] = pkt->ip6->src[0],
(c->rem.port != pkt->udp->sport || c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true;
!MG_IP6MATCH(c->rem.addr.ip6, pkt->ip6->src)))
return true;
rem.addr.ip6[0] = pkt->ip6->src[0],
rem.addr.ip6[1] = pkt->ip6->src[1], rem.is_ip6 = true;
} else } else
#endif #endif
{ {
if (!udpcsum_ok(pkt->ip, pkt->udp)) return false; if (!udpcsum_ok(pkt->ip, pkt->udp)) return false;
if (c->is_bsd && c->is_client && c->rem.addr.ip4 = pkt->ip->src;
(c->rem.port != pkt->udp->sport || c->rem.addr.ip4 != pkt->ip->src))
return true;
rem.addr.ip4 = pkt->ip->src, rem.is_ip6 = false;
} }
rem.port = pkt->udp->sport; if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL)
if (!c->is_bsd || c->is_client) c->rem = rem;
if ((l2addr = get_return_l2addr(ifp, &rem, true, pkt)) == NULL)
return false; // safety net for lousy networks return false; // safety net for lousy networks
if (!c->is_bsd || c->is_client) memcpy(s->mac, l2addr, sizeof(s->mac)); memcpy(s->mac, l2addr, sizeof(s->mac));
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && !rem.is_ip6 && MG_IPISLOCAL(ifp, rem.addr.ip4))
sendto_cache_add(ifp, rem.addr.ip4, l2addr);
#endif
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) len += sizeof(struct mg_bsd_udp_frame);
#endif
if (c->recv.len >= MG_MAX_RECV_SIZE) { if (c->recv.len >= MG_MAX_RECV_SIZE) {
proto_error(c, false, "max_recv_buf_size reached"); proto_error(c, false, "max_recv_buf_size reached");
} else if (c->recv.size - c->recv.len < len && } else if (c->recv.size - c->recv.len < pkt->pay.len &&
!mg_iobuf_resize(&c->recv, c->recv.len + len)) { !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
proto_error(c, false, "oom"); proto_error(c, false, "oom");
} else { } else {
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) {
struct mg_bsd_udp_frame *h =
(struct mg_bsd_udp_frame *) &c->recv.buf[c->recv.len];
h->ip = rem.addr.ip4;
h->port = rem.port;
h->len = (uint16_t) pkt->pay.len;
c->recv.len += sizeof(*h);
}
#endif
memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
c->recv.len += pkt->pay.len; c->recv.len += pkt->pay.len;
mg_call(c, MG_EV_READ, &pkt->pay.len); mg_call(c, MG_EV_READ, &pkt->pay.len);
@@ -1430,7 +1383,7 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn,
c->loc.port = lsn->loc.port; c->loc.port = lsn->loc.port;
if ((l2addr = get_return_l2addr(lsn->mgr->ifp, &c->rem, false, pkt)) == if ((l2addr = get_return_l2addr(lsn->mgr->ifp, &c->rem, false, pkt)) ==
NULL) { NULL) {
free(c); // safety net for lousy networks, not actually needed mg_free(c); // safety net for lousy networks, not actually needed
return NULL; // as path has already been checked at SYN (sending SYN+ACK) return NULL; // as path has already been checked at SYN (sending SYN+ACK)
} }
memcpy(s->mac, l2addr, sizeof(s->mac)); memcpy(s->mac, l2addr, sizeof(s->mac));
@@ -2098,6 +2051,9 @@ static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) {
if (ifp->enable_dhcp_client && s1) { if (ifp->enable_dhcp_client && s1) {
if (ifp->state == MG_TCPIP_STATE_UP) { if (ifp->state == MG_TCPIP_STATE_UP) {
tx_dhcp_discover(ifp); // INIT (4.4.1) 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 && } else if (ifp->state == MG_TCPIP_STATE_READY &&
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
if (ifp->now >= ifp->lease_expire) { if (ifp->now >= ifp->lease_expire) {
@@ -2139,12 +2095,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) { 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_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN);
bool cur_l2 = (ifp->state6 >= MG_TCPIP_STATE_UP); 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 (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 && if (ifp->l2type == MG_TCPIP_L2_ETH && l2_up && ifp->ip6ll[0] == 0 &&
ifp->ip6ll[1] == 0) { // gen ll address ifp->ip6ll[1] == 0) { // gen ll address
mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac); 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. } // just got our link local address if we didn't have one.
// If static configuration is used, global addresses, // If static configuration is used, global addresses,
// prefix length, and gw are already filled at this point. // prefix length, and gw are already filled at this point.
@@ -2164,7 +2119,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) && if ((ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) &&
ifp->state6 == MG_TCPIP_STATE_UP && ifp->ip6ll[0] == 0 && ifp->state6 == MG_TCPIP_STATE_UP && ifp->ip6ll[0] == 0 &&
ifp->ip6ll[1] != 0) { // IPV6CP has got an IFCID, gen ll address 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); onstate6change(ifp);
} // just got our link local address if we didn't have one. } // just got our link local address if we didn't have one.
} }
@@ -2347,9 +2302,6 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
static void close_conn(struct mg_connection *c) { static void close_conn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && c->is_udp) sendto_cache_clear(c->mgr->ifp);
#endif
mg_iobuf_free(&s->txq); mg_iobuf_free(&s->txq);
mg_close_conn(c); mg_close_conn(c);
} }
@@ -2371,11 +2323,6 @@ static void send_syn(struct mg_connection *c) {
static void l2addr_resolved(struct mg_connection *c) { static void l2addr_resolved(struct mg_connection *c) {
if (c->is_udp) { if (c->is_udp) {
c->is_connecting = 0; c->is_connecting = 0;
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd)
sendto_cache_add(c->mgr->ifp, c->rem.addr.ip4,
((struct connstate *) (c + 1))->mac);
#endif
mg_call(c, MG_EV_CONNECT, NULL); mg_call(c, MG_EV_CONNECT, NULL);
} else { } else {
send_syn(c); send_syn(c);
@@ -2390,25 +2337,20 @@ void mg_connect_resolved(struct mg_connection *c) {
s->retransmit = ifp->enable_tcp_retransmit; s->retransmit = ifp->enable_tcp_retransmit;
s->txq.align = MG_TCPIP_TXQ_ALIGN; s->txq.align = MG_TCPIP_TXQ_ALIGN;
c->is_resolving = 0; c->is_resolving = 0;
if (c->loc.port == 0) { if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
if (ifp->eport < MG_EPHEMERAL_PORT_BASE)
ifp->eport = MG_EPHEMERAL_PORT_BASE;
c->loc.port = mg_htons(ifp->eport++); c->loc.port = mg_htons(ifp->eport++);
} // otherwise retain an existing bound port
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
if (c->rem.is_ip6) { if (c->rem.is_ip6) {
if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0 && if (c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll
c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll
c->loc.addr.ip6[0] = ifp->ip6ll[0], c->loc.addr.ip6[1] = ifp->ip6ll[1]; c->loc.addr.ip6[0] = ifp->ip6ll[0], c->loc.addr.ip6[1] = ifp->ip6ll[1];
} else if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0) { } else { // use global address
// Use global address
c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1]; c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1];
} // otherwise retain an existing bound address }
c->loc.is_ip6 = true; c->loc.is_ip6 = true;
} else } else
#endif #endif
{ {
if (c->loc.addr.ip4 == 0) c->loc.addr.ip4 = ifp->ip; c->loc.addr.ip4 = ifp->ip;
} }
MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port,
&c->rem)); &c->rem));
@@ -2439,20 +2381,11 @@ void mg_connect_resolved(struct mg_connection *c) {
uint32_t rem_ip = c->rem.addr.ip4; uint32_t rem_ip = c->rem.addr.ip4;
if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) && if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) &&
rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP) rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP)
// We're in the same LAN // If we're in the same LAN, fire an ARP lookup.
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && c->is_udp && // see if we already have L2 data
(l2addr = sendto_cache_find(ifp, rem_ip)) != NULL) {
memcpy(s->mac, l2addr, sizeof(s->mac));
l2addr_resolved(c);
} else
#endif
{ // fire an ARP lookup
MG_DEBUG(("%lu ARP lookup...", c->id)); MG_DEBUG(("%lu ARP lookup...", c->id));
mg_tcpip_arp_request(ifp, rem_ip, NULL); mg_tcpip_arp_request(ifp, rem_ip, NULL);
settmout(c, MIP_TTYPE_ARP); settmout(c, MIP_TTYPE_ARP);
c->is_arplooking = 1; c->is_arplooking = 1;
}
} else if (ifp->gw_ready) { } else if (ifp->gw_ready) {
memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); memcpy(s->mac, ifp->gwmac, sizeof(s->mac));
l2addr_resolved(c); l2addr_resolved(c);
@@ -2472,36 +2405,13 @@ bool mg_open_listener(struct mg_connection *c, const char *url) {
} }
static void write_conn(struct mg_connection *c) { static void write_conn(struct mg_connection *c) {
long len; long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
size_t off = 0;
bool empty = false;
#if MG_ENABLE_BSD_SOCKETS
struct mg_bsd_udp_frame *h;
if (c->is_bsd && c->is_udp) {
if (c->is_connecting) return;
h = (struct mg_bsd_udp_frame *) c->send.buf;
assert(c->send.len >= sizeof(*h));
assert(h->len <= c->send.len - sizeof(*h));
c->rem.port = h->port;
c->rem.is_ip6 = false;
if (!c->is_client && c->rem.addr.ip4 != h->ip) {
c->rem.addr.ip4 = h->ip;
mg_connect_resolved(c);
if (c->is_connecting) return;
}
len = mg_io_send(c, h + 1, h->len);
off = sizeof(*h), empty = h->len == 0;
} else
#endif
{
len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
: mg_io_send(c, c->send.buf, c->send.len); : mg_io_send(c, c->send.buf, c->send.len);
}
// TODO(): mg_tls_send() may return 0 forever on steady OOM // TODO(): mg_tls_send() may return 0 forever on steady OOM
if (len == MG_IO_ERR) { if (len == MG_IO_ERR) {
proto_error(c, false, "tx err"); proto_error(c, false, "tx err");
} else if (len > 0 || empty) { } else if (len > 0) {
mg_iobuf_del(&c->send, 0, off + (size_t) len); mg_iobuf_del(&c->send, 0, (size_t) len);
mg_call(c, MG_EV_WRITE, &len); mg_call(c, MG_EV_WRITE, &len);
} }
} }
+4 -48
View File
@@ -26,17 +26,6 @@ struct mg_tcpip_driver {
typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev, typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev,
void *ev_data); void *ev_data);
#if MG_ENABLE_BSD_SOCKETS
#ifndef MG_TCPIP_SENDTO_CACHE_SIZE
#define MG_TCPIP_SENDTO_CACHE_SIZE 4
#endif
struct mg_tcpip_sendto_entry {
uint32_t ip;
struct mg_l2addr addr;
};
#endif
// clang-format off // clang-format off
// Events delivered to mg_tcpip_if.fn (user) and mg_tcpip_if.pfn (driver). // Events delivered to mg_tcpip_if.fn (user) and mg_tcpip_if.pfn (driver).
// Each entry shows the ev_data type to cast inside the handler. // Each entry shows the ev_data type to cast inside the handler.
@@ -99,9 +88,6 @@ struct mg_tcpip_if {
enum mg_l2type l2type; // Layer-2 type: Ethernet, PPP, etc. (see l2.h) enum mg_l2type l2type; // Layer-2 type: Ethernet, PPP, etc. (see l2.h)
union mg_l2data l2data; // Layer-2 config and state (see l2.h) union mg_l2data l2data; // Layer-2 config and state (see l2.h)
char *dns4_url; // Active DNS server URL (set from DHCP or config) char *dns4_url; // Active DNS server URL (set from DHCP or config)
#if MG_ENABLE_BSD_SOCKETS
struct mg_tcpip_sendto_entry sendto[MG_TCPIP_SENDTO_CACHE_SIZE]; // Local IPv4 neighbors
#endif
uint64_t now; // Current time in ms (updated each mg_mgr_poll) uint64_t now; // Current time in ms (updated each mg_mgr_poll)
uint64_t timer_1000ms; // Timestamp of last 1-second tick uint64_t timer_1000ms; // Timestamp of last 1-second tick
uint64_t lease_expire; // DHCP lease expiry time in ms uint64_t lease_expire; // DHCP lease expiry time in ms
@@ -168,41 +154,11 @@ extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi;
extern struct mg_tcpip_driver mg_tcpip_driver_st67w6; 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_atcmd;
extern struct mg_tcpip_driver mg_tcpip_driver_netc; extern struct mg_tcpip_driver mg_tcpip_driver_netc;
extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
extern struct mg_tcpip_driver mg_tcpip_driver_rw612_154;
// IEEE 802.15.4 radio channel and PAN ID. Settable in mongoose_config.h #include "drivers/mg24.h"
#ifndef MG_L2_802154_CHANNEL #include "drivers/mcxw71.h"
#define MG_L2_802154_CHANNEL 15 // 11..26 #include "drivers/nrf52.h"
#endif #include "drivers/rw612w.h"
#ifndef MG_L2_802154_PAN_ID
#define MG_L2_802154_PAN_ID 0xabcd
#endif
// driver_data for IEEE 802.15.4 radio drivers. The interface is Ethernet:
// each Ethernet frame is split into broadcast radio frames, and reassembled
// on the other side, see mg_l2_802154_tx() and mg_l2_802154_rx()
struct mg_l2_802154_data {
uint8_t channel; // User-set channel, 11..26
uint16_t pan_id; // User-set PAN ID
uint8_t seq, tag; // MAC sequence number, tag of the last sent frame
uint8_t rxtag, rxnext; // Frame being reassembled: tag, next fragment
uint8_t rxsrc[8]; // Its sender
uint16_t rxlen; // Its bytes received so far
uint8_t rxbuf[1514]; // Reassembly buffer, one Ethernet frame
};
// Splits an Ethernet frame into radio frames, sends each by calling send().
// Called by radio drivers' tx(). Return len, or 0 on error
size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len,
size_t (*send)(const uint8_t *, size_t,
struct mg_tcpip_if *));
// Adds a received radio frame f of n bytes to the frame being reassembled.
// Called by radio drivers' rx(). Return the Ethernet frame length, copied to
// buf, when it is complete, otherwise 0
size_t mg_l2_802154_rx(struct mg_tcpip_if *ifp, const uint8_t *f, size_t n,
void *buf, size_t len);
// SPI bus abstraction for SPI-attached network chips (e.g. W5500). // SPI bus abstraction for SPI-attached network chips (e.g. W5500).
// Populate and assign to the driver's driver_data field. // Populate and assign to the driver's driver_data field.
+2 -3
View File
@@ -102,12 +102,12 @@ static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) {
} else if (ev == MG_EV_HTTP_MSG) { } else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
char version[MG_OTA_MAX_VERSION_LEN]; char version[MG_OTA_MAX_VERSION_LEN];
double result;
MG_DEBUG(("Got metadata: %.*s", hm->body.len, hm->body.buf)); MG_DEBUG(("Got metadata: %.*s", hm->body.len, hm->body.buf));
if (mg_http_status(hm) != 200 || mg_json_get(hm->body, "$", NULL) != 0 || if (mg_http_status(hm) != 200 || mg_json_get(hm->body, "$", NULL) != 0 ||
!mg_json_unescape(hm->body, "$.version", version, sizeof(version)) || !mg_json_unescape(hm->body, "$.version", version, sizeof(version)) ||
!mg_json_unescape(hm->body, "$.url", s_ota->url, sizeof(s_ota->url)) || !mg_json_unescape(hm->body, "$.url", s_ota->url, sizeof(s_ota->url)) ||
!mg_json_get_num(hm->body, "$.size", &result) || result <= 0) { !mg_str_to_num(mg_json_get_tok(hm->body, "$.size"), 10, &s_ota->size,
sizeof(s_ota->size)) || s_ota->size == 0) {
char buf[100]; char buf[100];
mg_snprintf(buf, sizeof(buf), "Bad metadata: %.*s", hm->body.len, mg_snprintf(buf, sizeof(buf), "Bad metadata: %.*s", hm->body.len,
hm->body.buf); hm->body.buf);
@@ -121,7 +121,6 @@ static void s_version_fn(struct mg_connection *c, int ev, void *ev_data) {
s_ota = NULL; s_ota = NULL;
} else { } else {
struct mg_connection *fc; struct mg_connection *fc;
s_ota->size = (size_t) result;
// TODO (robertc2000): parse and validate sha256 // TODO (robertc2000): parse and validate sha256
MG_DEBUG(("Firmware version: %s, url: %s, size: %ld", version, s_ota->url, MG_DEBUG(("Firmware version: %s, url: %s, size: %ld", version, s_ota->url,
s_ota->size)); s_ota->size));
+1 -5
View File
@@ -299,11 +299,7 @@ static long recv_raw(struct mg_connection *c, void *buf, size_t len) {
union usa usa; union usa usa;
socklen_t slen = tousa(&c->rem, &usa); socklen_t slen = tousa(&c->rem, &usa);
n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
if (n > 0) { if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
struct mg_addr rem;
tomgaddr(&usa, &rem, slen != sizeof(usa.sin));
c->rem = rem;
}
} else { } else {
n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
} }
+53 -48
View File
@@ -681,7 +681,7 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) {
if (!is_retry || rio->buf[0] != MG_TLS_CHANGE_CIPHER) break; if (!is_retry || rio->buf[0] != MG_TLS_CHANGE_CIPHER) break;
mg_tls_drop_record(c); // compatibility mode CCS before 2nd ClientHello mg_tls_drop_record(c); // compatibility mode CCS before 2nd ClientHello
} }
if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_CLIENT_HELLO) { if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->len < 6 || rio->buf[5] != MG_TLS_CLIENT_HELLO) {
mg_error(c, "not a client hello packet"); mg_error(c, "not a client hello packet");
return -1; return -1;
} }
@@ -711,19 +711,24 @@ static int mg_tls_server_recv_hello(struct mg_connection *c) {
uint16_t k; uint16_t k;
uint16_t key_exchange_len; uint16_t key_exchange_len;
uint8_t *key_exchange; uint8_t *key_exchange;
uint16_t n = MG_LOAD_BE16(ext + j + 2); uint16_t n;
if ((ext_len - j) < 4) goto fail;
n = MG_LOAD_BE16(ext + j + 2);
if (((uint32_t) n + j + 4) > ext_len) goto fail; if (((uint32_t) n + j + 4) > ext_len) goto fail;
if (MG_LOAD_BE16(ext + j) != 0x0033) { // not a key share extension, ignore if (MG_LOAD_BE16(ext + j) != 0x0033) { // not a key share extension, ignore
j += (uint16_t) (n + 4); j += (uint16_t) (n + 4);
continue; continue;
} }
if (n < 2) goto fail;
key_exchange_len = MG_LOAD_BE16(ext + j + 4); key_exchange_len = MG_LOAD_BE16(ext + j + 4);
key_exchange = ext + j + 6; key_exchange = ext + j + 6;
if ((key_exchange + key_exchange_len) > ((uint8_t *) rio->buf + rio->len)) if (key_exchange_len > n - 2)
goto fail; goto fail;
for (k = 0; k < key_exchange_len;) { for (k = 0; k < key_exchange_len;) {
uint16_t m = MG_LOAD_BE16(key_exchange + k + 2); uint16_t m, group;
uint16_t group = MG_LOAD_BE16(key_exchange + k); if ((key_exchange_len - k) < 4) goto fail;
m = MG_LOAD_BE16(key_exchange + k + 2);
group = MG_LOAD_BE16(key_exchange + k);
if (((uint32_t) m + k + 4) > key_exchange_len) goto fail; if (((uint32_t) m + k + 4) > key_exchange_len) goto fail;
// secp256r1, uncompressed point 0x04 || X || Y // secp256r1, uncompressed point 0x04 || X || Y
if (group == 0x0017 && m == 65 && key_exchange[k + 4] == 0x04) { if (group == 0x0017 && m == 65 && key_exchange[k + 4] == 0x04) {
@@ -1170,25 +1175,25 @@ static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
// EC certificate verify packet // EC certificate verify packet
uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00}; uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00};
size_t sigsz, verifysz = 0; size_t sigsz, verifysz = 0;
int neg1, neg2; int adj1, adj2;
uint8_t sig[64] = {0}; uint8_t sig[64] = {0};
if (!mg_p256_sign(tls->ec_key, hash, sizeof(hash), sig)) { if (!mg_p256_sign(tls->ec_key, hash, sizeof(hash), sig)) {
mg_error(c, "P-256 sign"); mg_error(c, "P-256 sign");
return false; return false;
} }
adj1 = sig[0] == 0 && !(sig[1] & 0x80) ? -1 : !!(sig[0] & 0x80);
neg1 = !!(sig[0] & 0x80); adj2 = sig[32] == 0 && !(sig[33] & 0x80) ? -1 : !!(sig[32] & 0x80);
neg2 = !!(sig[32] & 0x80);
verify[8] = 0x30; // ASN.1 SEQUENCE verify[8] = 0x30; // ASN.1 SEQUENCE
verify[9] = (uint8_t) (68 + neg1 + neg2); verify[9] = (uint8_t) (68 + adj1 + adj2);
verify[10] = 0x02; // ASN.1 INTEGER verify[10] = 0x02; // ASN.1 INTEGER
verify[11] = (uint8_t) (32 + neg1); verify[11] = (uint8_t) (32 + adj1);
memmove(verify + 12 + neg1, sig, 32); memmove(verify + (size_t) (12 + (adj1 > 0)), sig + (size_t) (adj1 < 0),
verify[12 + 32 + neg1] = 0x02; // ASN.1 INTEGER (size_t) (32 + (adj1 < 0)));
verify[13 + 32 + neg1] = (uint8_t) (32 + neg2); verify[12 + 32 + adj1] = 0x02; // ASN.1 INTEGER
memmove(verify + 14 + 32 + neg1 + neg2, sig + 32, 32); verify[13 + 32 + adj1] = (uint8_t) (32 + adj2);
memmove(verify + (size_t) (14 + 32 + adj1 + (adj2 > 0)),
sigsz = (size_t) (70 + neg1 + neg2); sig + (size_t) (32 + (adj2 < 0)), (size_t) (32 + (adj2 < 0)));
sigsz = (size_t) (70 + adj1 + adj2);
verifysz = 8U + sigsz; verifysz = 8U + sigsz;
verify[3] = (uint8_t) (sigsz + 4); verify[3] = (uint8_t) (sigsz + 4);
verify[7] = (uint8_t) sigsz; verify[7] = (uint8_t) sigsz;
@@ -1552,8 +1557,8 @@ static int mg_tls_parse_cert_der(void *buf, size_t dersz,
return -1; return -1;
} }
if (mg_der_next(&field, &after) <= 0 || ((after.type != 0x17 || after.len != 13) && (after.type != 0x18 || after.len != 15))) 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 if (after.len == 15 && memcmp(after.value, "99991231235959Z", 15) == 0) {
MG_ERROR(("No well-defined expiration date")); MG_ERROR(("No well-defined expiration date")); // RFC-5280 4.1.2.5
return -1; return -1;
} }
if (now > (t = asnt2t(after.value, after.type))) { if (now > (t = asnt2t(after.value, after.type))) {
@@ -1641,7 +1646,7 @@ static int mg_tls_parse_cert_der(void *buf, size_t dersz,
} }
if (i.type != 0x04) return -1; if (i.type != 0x04) return -1;
if (mg_der_next(&i, &b) <= 0 || b.type != 0x03) 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; if (ca && certsign) cert->is_ca = true;
@@ -1712,41 +1717,39 @@ static int mg_tls_verify_cert_san(const uint8_t *der, size_t dersz,
return -1; return -1;
} }
static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert, static void unpadlen(uint8_t *sig, struct mg_der_tlv *a, struct mg_der_tlv *b,
uint32_t n) {
if (a->len > n) a->value += (a->len - n), a->len = n;
if (b->len > n) b->value += (b->len - n), b->len = n;
memset(sig, 0, n - a->len); // short encoding
memmove(sig + (n - a->len), a->value, a->len);
memset(sig + n, 0, n - b->len);
memmove(sig + n + (n - b->len), b->value, b->len);
}
static bool mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
const struct mg_tls_cert *issuer) { const struct mg_tls_cert *issuer) {
if (issuer->is_ec_pubkey) { if (issuer->is_ec_pubkey) {
uint8_t sig[128]; uint8_t sig[128];
struct mg_der_tlv seq = {0, 0, 0}, a = {0, 0, 0}, b = {0, 0, 0}; struct mg_der_tlv seq = {0, 0, 0}, a = {0, 0, 0}, b = {0, 0, 0};
mg_der_parse((uint8_t *) cert->sig.buf, cert->sig.len, &seq); if (mg_der_parse((uint8_t *) cert->sig.buf, cert->sig.len, &seq) <= 0 ||
mg_der_next(&seq, &a); seq.type != 0x30 || mg_der_next(&seq, &a) <= 0 || a.type != 0x02 ||
mg_der_next(&seq, &b); mg_der_next(&seq, &b) <= 0 || b.type != 0x02 || a.len == 0 ||
if (a.len == 0 || b.len == 0) { b.len == 0 || seq.len != 0) {
MG_ERROR(("cert verification error")); MG_ERROR(("cert verification error"));
return 0; return false;
} }
if (issuer->pubkey.len == 64) { if (issuer->pubkey.len == 64) {
const uint32_t N = 32; unpadlen(sig, &a, &b, 32);
if (a.len > N) a.value += (a.len - N), a.len = N; // padding
if (b.len > N) b.value += (b.len - N), b.len = N;
memset(sig, 0, N - a.len); // short encoding
memmove(sig + (N - a.len), a.value, a.len);
memset(sig + N, 0, N - b.len);
memmove(sig + N + (N - b.len), b.value, b.len);
return mg_p256_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash, return mg_p256_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
cert->tbshashsz, sig); cert->tbshashsz, sig) == 1;
} else if (issuer->pubkey.len == 96) { } else if (issuer->pubkey.len == 96) {
const uint32_t N = 48; unpadlen(sig, &a, &b, 48);
if (a.len > N) a.value += (a.len - N), a.len = N; // padding
if (b.len > N) b.value += (b.len - N), b.len = N;
memset(sig, 0, N - a.len); // short encoding
memmove(sig + (N - a.len), a.value, a.len);
memset(sig + N, 0, N - b.len);
memmove(sig + N + (N - b.len), b.value, b.len);
return mg_p384_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash, return mg_p384_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
cert->tbshashsz, sig); cert->tbshashsz, sig) == 1;
} else { } else {
MG_ERROR(("unsupported public key length: %d", issuer->pubkey.len)); MG_ERROR(("unsupported public key length: %d", issuer->pubkey.len));
return 0; return false;
} }
} else { } else {
const uint8_t *n; const uint8_t *n;
@@ -1758,14 +1761,16 @@ static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
mg_der_next(&seq, &modulus) <= 0 || modulus.type != 2 || mg_der_next(&seq, &modulus) <= 0 || modulus.type != 2 ||
modulus.len == 0 || mg_der_next(&seq, &exponent) <= 0 || modulus.len == 0 || mg_der_next(&seq, &exponent) <= 0 ||
exponent.type != 2 || exponent.len == 0) { exponent.type != 2 || exponent.len == 0) {
return -1; MG_ERROR(("failed to parse RSA issuer public key"));
return false;
} }
n = modulus.value, nlen = mg_rsa_trim_len(&n, modulus.len); n = modulus.value, nlen = mg_rsa_trim_len(&n, modulus.len);
if (nlen > sizeof(sig2) || cert->tbshashsz > nlen || if (nlen > sizeof(sig2) || cert->tbshashsz > nlen ||
mg_rsa_mod_pow(modulus.value, modulus.len, exponent.value, mg_rsa_mod_pow(modulus.value, modulus.len, exponent.value,
exponent.len, (uint8_t *) cert->sig.buf, cert->sig.len, exponent.len, (uint8_t *) cert->sig.buf, cert->sig.len,
sig2, nlen) != 0) { sig2, nlen) != 0) {
return 0; MG_ERROR(("failed to process RSA cert sig"));
return false;
} }
return mg_rsa_pkcs_verify(sig2, nlen, cert->tbshash, cert->tbshashsz); return mg_rsa_pkcs_verify(sig2, nlen, cert->tbshash, cert->tbshashsz);
@@ -2055,15 +2060,15 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
mg_error(c, "certverify scheme %04x does not match the key", sigalg); mg_error(c, "certverify scheme %04x does not match the key", sigalg);
return -1; 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"); mg_error(c, "verification message is not an ASN.1 DER sequence");
return -1; 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"); mg_error(c, "missing first part of the signature");
return -1; 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"); mg_error(c, "missing second part of the signature");
return -1; return -1;
} }
+1 -1
View File
@@ -17,7 +17,7 @@ uint64_t mg_timegm(unsigned int year, unsigned int month, unsigned int day,
const uint64_t day_secs = 86400; const uint64_t day_secs = 86400;
const uint64_t year_secs = 31536000; const uint64_t year_secs = 31536000;
unsigned int y, ly; unsigned int y, ly;
if (year < 1970) return 0; if (year < 1970 || month == 0 || month > 12) return 0;
y = year - 1900; y = year - 1900;
ly = month > 2 ? y + 1 : y; ly = month > 2 ? y + 1 : y;
return (uint64_t) sec + 60 * min + 3600 * hour + return (uint64_t) sec + 60 * min + 3600 * hour +
+21 -11
View File
@@ -63,12 +63,14 @@ static uint32_t be32(const uint8_t *p) {
(((uint32_t) p[1]) << 16) | (((uint32_t) p[0]) << 24); (((uint32_t) p[1]) << 16) | (((uint32_t) p[0]) << 24);
} }
static size_t ws_process(uint8_t *buf, size_t len, struct ws_msg *msg) { static size_t ws_process(uint8_t *buf, size_t len, struct ws_msg *msg,
bool *masked) {
size_t i, n = 0, mask_len = 0; size_t i, n = 0, mask_len = 0;
memset(msg, 0, sizeof(*msg)); memset(msg, 0, sizeof(*msg));
if (len >= 2) { if (len >= 2) {
n = buf[1] & 0x7f; // Frame length *masked = (buf[1] & 0x80) != 0; // b7 Mask, b6:0 Frame length
mask_len = buf[1] & 128 ? 4 : 0; // last bit is a mask bit n = buf[1] & 0x7f;
mask_len = *masked ? 4 : 0;
msg->flags = buf[0]; msg->flags = buf[0];
if (n < 126 && len >= mask_len) { if (n < 126 && len >= mask_len) {
msg->data_len = n; msg->data_len = n;
@@ -77,14 +79,14 @@ static size_t ws_process(uint8_t *buf, size_t len, struct ws_msg *msg) {
msg->header_len = 4 + mask_len; msg->header_len = 4 + mask_len;
msg->data_len = (((size_t) buf[2]) << 8) | buf[3]; msg->data_len = (((size_t) buf[2]) << 8) | buf[3];
} else if (len >= 10 + mask_len) { } else if (len >= 10 + mask_len) {
// Sanity check and protect the boundary check below: data_len should not
// be larger than 1 GB, so it fits in 32-bits
uint32_t hi = be32(buf + 2), lo = be32(buf + 6);
if (hi != 0 || lo > 1024U * 1024U * 1024U) return 0;
msg->data_len = (size_t) lo;
msg->header_len = 10 + mask_len; msg->header_len = 10 + mask_len;
msg->data_len =
(size_t) (((uint64_t) be32(buf + 2) << 32) + be32(buf + 6));
} }
} }
// Sanity check, and integer overflow protection for the boundary check below
// data_len should not be larger than 1 Gb
if (msg->data_len > 1024 * 1024 * 1024) return 0;
if (msg->header_len + msg->data_len > len) return 0; if (msg->header_len + msg->data_len > len) return 0;
if (mask_len > 0) { if (mask_len > 0) {
uint8_t *p = buf + msg->header_len, *m = p - mask_len; uint8_t *p = buf + msg->header_len, *m = p - mask_len;
@@ -107,9 +109,11 @@ static size_t mkhdr(size_t len, int op, bool is_client, uint8_t *buf) {
} else { } else {
uint32_t tmp; uint32_t tmp;
buf[1] = 127; buf[1] = 127;
tmp = mg_htonl((uint32_t) (((uint64_t) len) >> 32)); tmp = (sizeof(len) <= sizeof(uint32_t) || len <= 0xffffffffU)
? 0 // avoid some 32-bit platforms breaking it (VC98)
: mg_htonl((uint32_t) ((uint64_t) len >> 32));
memcpy(&buf[2], &tmp, sizeof(tmp)); memcpy(&buf[2], &tmp, sizeof(tmp));
tmp = mg_htonl((uint32_t) (len & 0xffffffffU)); tmp = mg_htonl((uint32_t) len);
memcpy(&buf[6], &tmp, sizeof(tmp)); memcpy(&buf[6], &tmp, sizeof(tmp));
n = 10; n = 10;
} }
@@ -165,17 +169,23 @@ static bool mg_ws_client_handshake(struct mg_connection *c) {
static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) { static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) {
struct ws_msg msg; struct ws_msg msg;
bool msk = false; // is this frame masked ?
size_t ofs = (size_t) c->pfn_data; size_t ofs = (size_t) c->pfn_data;
// assert(ofs < c->recv.len); // assert(ofs < c->recv.len);
if (ev == MG_EV_READ) { if (ev == MG_EV_READ) {
if (c->is_client && !c->is_websocket && mg_ws_client_handshake(c)) return; if (c->is_client && !c->is_websocket && mg_ws_client_handshake(c)) return;
while (ws_process(c->recv.buf + ofs, c->recv.len - ofs, &msg) > 0) { while (ws_process(c->recv.buf + ofs, c->recv.len - ofs, &msg, &msk) > 0) {
char *s = (char *) c->recv.buf + ofs + msg.header_len; char *s = (char *) c->recv.buf + ofs + msg.header_len;
struct mg_ws_message m; struct mg_ws_message m;
size_t len; size_t len;
uint8_t final, op; uint8_t final, op;
if (msk == c->is_client) {
mg_error(c, c->is_client ? "masked WS server frame"
: "unmasked WS client frame");
return;
}
m.data.buf = s, m.data.len = msg.data_len, m.flags = msg.flags; m.data.buf = s, m.data.len = msg.data_len, m.flags = msg.flags;
len = msg.header_len + msg.data_len; len = msg.header_len + msg.data_len;
final = msg.flags & 128; final = msg.flags & 128;
+3 -3
View File
@@ -1,6 +1,6 @@
SRCS = mongoose.c unit_test.c packed_fs.c SRCS = mongoose.c unit_test.c packed_fs.c
HDRS = $(wildcard ../src/*.h) $(wildcard ../src/drivers/*.h) HDRS = $(wildcard ../src/*.h) $(wildcard ../src/drivers/*.h)
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_SSI=1 -DMG_ENABLE_ASSERT=1 -DMG_ENABLE_IPV6=$(IPV6) -DMG_ENABLE_MDASH=$(MDASH) DEFS ?= -DMG_ENABLE_LINES -DMG_ENABLE_SSI=1 -DMG_ENABLE_ASSERT=1 -DMG_ENABLE_IPV6=$(IPV6) -DMG_ENABLE_MDASH=$(MDASH)
WARN ?= -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion -Wundef WARN ?= -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion -Wundef
OPTS ?= -O3 -g3 OPTS ?= -O3 -g3
INCS ?= -Isrc -I. INCS ?= -Isrc -I.
@@ -16,7 +16,7 @@ IPV6 ?= 1
MDASH ?= 1 MDASH ?= 1
ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common
ASAN_OPTIONS ?= detect_leaks=1 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_ZEPHYR := $(dir $(wildcard ../tutorials/zephyr/*/Makefile))
TUTORIALS := ../tutorials/micropython/ $(filter-out $(TUTORIALS_EMBEDDED) $(TUTORIALS_ZEPHYR), $(dir $(wildcard ../tutorials/*/*/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)) 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))> $@ 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 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 # Check that all external (exported) symbols have "mg_" prefix
mg_prefix: mongoose.c mongoose.h mg_prefix: mongoose.c mongoose.h
+6 -9
View File
@@ -13,7 +13,7 @@ else
C_WARN ?= -Wno-deprecated C_WARN ?= -Wno-deprecated
endif endif
.PHONY: all bsd bsd_test bsd_client bsd_udp bsd_nortos bsd_nortos_test shm_tap_bridge \ .PHONY: all bsd bsd_test bsd_client bsd_nortos bsd_nortos_test shm_tap_bridge \
shm_port_bridge bsd_s390 bsd_nortos_s390 clean shm_port_bridge bsd_s390 bsd_nortos_s390 clean
TEST_SOURCES = bsd_shm_test.c TEST_SOURCES = bsd_shm_test.c
@@ -47,11 +47,11 @@ DOCKER ?= $(DOCKER_BIN) run --platform linux/amd64 --net=host --rm $(ENV) -v $(R
CFLAGS_MONGOOSE += -DMG_ARCH=MG_ARCH_CUSTOM CFLAGS_MONGOOSE += -DMG_ARCH=MG_ARCH_CUSTOM
all bsd: shm_tap_bridge bsd_client bsd_udp FreeRTOS-Kernel bsd_test all bsd: shm_tap_bridge bsd_client FreeRTOS-Kernel bsd_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS) ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS)
# also # also
bsd_nortos: shm_tap_bridge bsd_client bsd_udp bsd_nortos_test bsd_nortos: shm_tap_bridge bsd_client bsd_nortos_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_nortos_test $(ARGS) ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_nortos_test $(ARGS)
# requires shm_tap_bridge to be running # requires shm_tap_bridge to be running
@@ -70,9 +70,6 @@ bsd_nortos_test:
bsd_client: bsd_client.c bsd_client: bsd_client.c
$(CC) bsd_client.c -pthread -o $@ $(CC) bsd_client.c -pthread -o $@
bsd_udp: bsd_udp.c
$(CC) bsd_udp.c -o $@
# bridge shm queue <--> TAP interface # bridge shm queue <--> TAP interface
shm_tap_bridge: shm_tap_bridge.c shm_queue.h shm_tap_bridge: shm_tap_bridge.c shm_queue.h
$(CC) shm_tap_bridge.c -pthread -o $@ $(CC) shm_tap_bridge.c -pthread -o $@
@@ -82,7 +79,7 @@ bsd_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0
bsd_s390: ASAN= bsd_s390: ASAN=
bsd_s390: CC = $(DOCKER) mdashnet/s390 cc bsd_s390: CC = $(DOCKER) mdashnet/s390 cc
bsd_s390: RUN = $(DOCKER) --init mdashnet/s390 bsd_s390: RUN = $(DOCKER) --init mdashnet/s390
bsd_s390: shm_port_bridge bsd_client bsd_udp FreeRTOS-Kernel bsd_test bsd_s390: shm_port_bridge bsd_client FreeRTOS-Kernel bsd_test
$(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc' $(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc'
#also #also
@@ -90,14 +87,14 @@ bsd_nortos_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0
bsd_nortos_s390: ASAN= bsd_nortos_s390: ASAN=
bsd_nortos_s390: CC = $(DOCKER) mdashnet/s390 cc bsd_nortos_s390: CC = $(DOCKER) mdashnet/s390 cc
bsd_nortos_s390: RUN = $(DOCKER) --init mdashnet/s390 bsd_nortos_s390: RUN = $(DOCKER) --init mdashnet/s390
bsd_nortos_s390: shm_port_bridge bsd_client bsd_udp bsd_nortos_test bsd_nortos_s390: shm_port_bridge bsd_client bsd_nortos_test
$(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_nortos_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc' $(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_nortos_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc'
shm_port_bridge: shm_port_bridge.c shm_queue.h shm_port_bridge: shm_port_bridge.c shm_queue.h
$(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@ $(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@
clean: # Cleanup. Delete built program and all build artifacts clean: # Cleanup. Delete built program and all build artifacts
rm -rf bsd_test bsd_client bsd_udp shm_port_bridge *.o *.obj FreeRTOS-Kernel rm -rf bsd_test bsd_client shm_port_bridge *.o *.obj FreeRTOS-Kernel
FreeRTOS-Kernel: # FreeRTOS sources FreeRTOS-Kernel: # FreeRTOS sources
git clone -q -c advice.detachedHead=false --depth 1 -b V11.2.0 https://github.com/FreeRTOS/FreeRTOS-Kernel $@ git clone -q -c advice.detachedHead=false --depth 1 -b V11.2.0 https://github.com/FreeRTOS/FreeRTOS-Kernel $@
+11 -2
View File
@@ -92,7 +92,7 @@ fail:
exit(1); 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. // Wait for output, optionally draining through the final marker.
static int wait_data(const char *host, enum wait_mode mode) { 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); close(fd);
return 0; 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) { if (mode == WAIT_CLOSE) {
n = recv(fd, buf, sizeof(buf), 0); n = recv(fd, buf, sizeof(buf), 0);
close(fd); close(fd);
@@ -151,10 +158,12 @@ int main(int argc, char *argv[]) {
return wait_data(argv[1], WAIT_HOLD); return wait_data(argv[1], WAIT_HOLD);
if (argc == 3 && strcmp(argv[2], "expectclose") == 0) if (argc == 3 && strcmp(argv[2], "expectclose") == 0)
return wait_data(argv[1], WAIT_CLOSE); 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 == 3) connections = atoi(argv[2]);
if ((argc != 2 && argc != 3) || connections < 1 || if ((argc != 2 && argc != 3) || connections < 1 ||
connections > NUM_CONNECTIONS) { 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]); argv[0]);
return 1; return 1;
} }
-55
View File
@@ -1,55 +0,0 @@
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#define PORT 1235
int main(int argc, char *argv[]) {
struct sockaddr_in sa;
struct timeval timeout;
char buf[8];
ssize_t n;
int fd, rogue;
if (argc != 2) return 1;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(PORT);
if (inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1) return 1;
timeout.tv_sec = 5, timeout.tv_usec = 0;
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0 ||
connect(fd, (struct sockaddr *) &sa, sizeof(sa)) != 0 ||
send(fd, "hello", 5, 0) != 5 || recv(fd, buf, sizeof(buf), 0) != 5 ||
memcmp(buf, "ready", 5) != 0)
goto fail;
if ((rogue = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ||
setsockopt(rogue, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) !=
0 ||
send(fd, "one", 3, 0) != 3 ||
sendto(rogue, "two2", 4, 0, (struct sockaddr *) &sa, sizeof(sa)) != 4 ||
recv(fd, buf, sizeof(buf), 0) != 4 || memcmp(buf, "peer", 4) != 0 ||
recv(rogue, buf, sizeof(buf), 0) != 5 || memcmp(buf, "rogue", 5) != 0 ||
send(fd, "truncate", 8, 0) != 8 || send(fd, "peek", 4, 0) != 4 ||
recv(fd, buf, sizeof(buf), 0) != 4 || memcmp(buf, "more", 4) != 0 ||
recv(fd, buf, sizeof(buf), 0) != 3 || memcmp(buf, "one", 3) != 0 ||
recv(fd, buf, sizeof(buf), 0) != 4 || memcmp(buf, "two2", 4) != 0 ||
recv(fd, buf, sizeof(buf), 0) != 0 ||
sendto(rogue, "rogue", 5, 0, (struct sockaddr *) &sa, sizeof(sa)) != 5 ||
recv(fd, buf, sizeof(buf), 0) != 7 || memcmp(buf, "checked", 7) != 0 ||
send(fd, "echo", 4, 0) != 4 || recv(fd, buf, sizeof(buf), 0) != 4 ||
memcmp(buf, "echo", 4) != 0)
goto fail_close_rogue;
close(rogue);
close(fd);
return 0;
fail_close_rogue:
close(rogue);
fail:
close(fd);
return 1;
}
+141 -237
View File
@@ -33,175 +33,6 @@ static int s_abort = 0;
#define DASHBOARD(x) \ #define DASHBOARD(x) \
printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true") printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true")
#define UDP_PORT 1235
#if MG_ENABLE_FREERTOS
static volatile bool s_tx_held, s_tx_waits;
#endif
static int open_udp(void) {
struct sockaddr_in sa;
char buf;
int fd;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(UDP_PORT);
sa.sin_addr.s_addr = INADDR_ANY;
ASSERT((fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0);
ASSERT(bind(fd, (struct sockaddr *) &sa, sizeof(sa)) == 0);
ASSERT(send(fd, &buf, sizeof(buf), 0) < 0 && errno == EDESTADDRREQ);
ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0);
return fd;
}
static void check_udp_oversize(struct mg_mgr *mgr, int fd,
const struct sockaddr *dst, socklen_t dstlen) {
static char buf[1600];
size_t len = mgr->ifp->mtu + 1;
ASSERT(len <= sizeof(buf));
if (dst == NULL) {
ASSERT(send(fd, buf, len, 0) < 0 && errno == EMSGSIZE);
} else {
ASSERT(sendto(fd, buf, len, 0, dst, dstlen) < 0 && errno == EMSGSIZE);
}
}
static void udp_exchange(struct mg_mgr *mgr, int fd,
void (*progress)(struct mg_mgr *)) {
struct sockaddr_in peer, other, rogue = {0};
socklen_t peerlen, otherlen;
uint64_t deadline;
struct timeval zero = {0, 0};
struct pollfd p;
fd_set r, w, e;
bool one;
char buf[8];
ssize_t n;
for (;;) {
peerlen = sizeof(peer);
n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &peer, &peerlen);
if (n >= 0) break;
ASSERT(errno == EAGAIN);
progress(mgr);
}
ASSERT(n == 5 && memcmp(buf, "hello", 5) == 0);
ASSERT(sendto(fd, "ready", 5, 0, (struct sockaddr *) &peer, peerlen) ==
5);
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr); // Queue both UDP datagrams
do {
otherlen = sizeof(other);
n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &other,
&otherlen);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
one = n == 3;
if (one) {
ASSERT(memcmp(buf, "one", 3) == 0 && other.sin_port == peer.sin_port);
} else {
ASSERT(n == 4 && memcmp(buf, "two2", 4) == 0 &&
other.sin_port != peer.sin_port);
}
if (other.sin_port != peer.sin_port) rogue = other;
do {
otherlen = sizeof(other);
n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &other,
&otherlen);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
if (one) {
ASSERT(n == 4 && memcmp(buf, "two2", 4) == 0 &&
other.sin_port != peer.sin_port);
} else {
ASSERT(n == 3 && memcmp(buf, "one", 3) == 0 &&
other.sin_port == peer.sin_port);
}
if (other.sin_port != peer.sin_port) rogue = other;
check_udp_oversize(mgr, fd, (struct sockaddr *) &peer, peerlen);
ASSERT(sendto(fd, "peer", 4, 0, (struct sockaddr *) &peer, peerlen) == 4);
ASSERT(sendto(fd, "rogue", 5, 0, (struct sockaddr *) &rogue,
sizeof(rogue)) == 5);
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr);
ASSERT(connect(fd, (struct sockaddr *) &peer, peerlen) == 0);
ASSERT(sendto(fd, "no", 2, 0, (struct sockaddr *) &peer, peerlen) < 0 &&
errno == EISCONN);
check_udp_oversize(mgr, fd, NULL, 0);
ASSERT(send(fd, "more", 4, 0) == 4);
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr); // Queue both UDP datagrams
do {
p.fd = fd, p.events = POLLIN | POLLOUT, p.revents = 0;
ASSERT(poll(&p, 1, 0) == 1);
if (!(p.revents & POLLIN)) progress(mgr);
} while (!(p.revents & POLLIN));
ASSERT(p.revents == (POLLIN | POLLOUT));
FD_ZERO(&r);
FD_ZERO(&w);
FD_ZERO(&e);
FD_SET(fd, &r);
FD_SET(fd, &w);
FD_SET(fd, &e);
ASSERT(select(fd + 1, &r, &w, &e, &zero) == 1);
ASSERT(FD_ISSET(fd, &r) && FD_ISSET(fd, &w) && !FD_ISSET(fd, &e));
do {
n = recv(fd, buf, 3, 0);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
ASSERT(n == 3 && memcmp(buf, "tru", 3) == 0);
do {
n = recv(fd, buf, sizeof(buf), MSG_PEEK);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
ASSERT(n == 4 && memcmp(buf, "peek", 4) == 0);
ASSERT(recv(fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "peek", 4) == 0);
p.events = POLLIN | POLLOUT, p.revents = 0;
ASSERT(poll(&p, 1, 0) == 1 && p.revents == POLLOUT);
#if MG_ENABLE_FREERTOS
s_tx_waits = false, s_tx_held = true;
#endif
ASSERT(send(fd, "one", 3, 0) == 3);
ASSERT(send(fd, "two2", 4, 0) == 4);
ASSERT(send(fd, "", 0, 0) == 0);
#if MG_ENABLE_FREERTOS
while (!s_tx_waits) progress(mgr); // MG_IO_WAIT keeps all records queued
s_tx_held = false;
#endif
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr); // Let rogue arrive
ASSERT(recv(fd, buf, sizeof(buf), MSG_DONTWAIT) < 0 && errno == EAGAIN);
while ((n = send(fd, "checked", 7, 0)) < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
ASSERT(n == 7);
for (;;) {
n = recv(fd, buf, sizeof(buf), 0);
if (n >= 0) break;
ASSERT(errno == EAGAIN);
progress(mgr);
}
ASSERT(n == 4 && memcmp(buf, "echo", 4) == 0);
ASSERT(send(fd, "echo", 4, 0) == 4);
progress(mgr); // Drain the last queued datagram before close
ASSERT(close(fd) == 0);
}
static pid_t start_client(const char *ip, const char *connections) { static pid_t start_client(const char *ip, const char *connections) {
char *cmd[4]; char *cmd[4];
pid_t pid; pid_t pid;
@@ -215,18 +46,6 @@ static pid_t start_client(const char *ip, const char *connections) {
return pid; return pid;
} }
static pid_t start_udp(const char *ip) {
char *cmd[3];
pid_t pid;
extern char **environ;
cmd[0] = (char *) "./bsd_udp";
cmd[1] = (char *) ip;
cmd[2] = NULL;
ASSERT(posix_spawn(&pid, cmd[0], NULL, NULL, cmd, environ) == 0);
return pid;
}
static bool client_done(pid_t pid, int *status) { static bool client_done(pid_t pid, int *status) {
pid_t r = waitpid(pid, status, WNOHANG); pid_t r = waitpid(pid, status, WNOHANG);
ASSERT(r >= 0); ASSERT(r >= 0);
@@ -241,17 +60,12 @@ static void check_client(int status) {
static int open_listener(int backlog, bool nonblock) { static int open_listener(int backlog, bool nonblock) {
int lfd = socket(AF_INET, SOCK_STREAM, 0); int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sa; struct sockaddr_in sa;
char buf;
memset(&sa, 0, sizeof(sa)); memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET; sa.sin_family = AF_INET;
sa.sin_port = htons(1234); sa.sin_port = htons(1234);
sa.sin_addr.s_addr = INADDR_ANY; sa.sin_addr.s_addr = INADDR_ANY;
ASSERT(lfd >= 0); ASSERT(lfd >= 0);
ASSERT(sendto(lfd, &buf, sizeof(buf), 0, (struct sockaddr *) &sa,
sizeof(sa)) < 0 && errno == EOPNOTSUPP);
ASSERT(recvfrom(lfd, &buf, sizeof(buf), 0, NULL, NULL) < 0 &&
errno == EOPNOTSUPP);
ASSERT(bind(lfd, (struct sockaddr *) &sa, sizeof(sa)) == 0); ASSERT(bind(lfd, (struct sockaddr *) &sa, sizeof(sa)) == 0);
ASSERT(listen(lfd, backlog) == 0); ASSERT(listen(lfd, backlog) == 0);
if (nonblock) ASSERT(fcntl(lfd, F_SETFL, O_NONBLOCK) == 0); if (nonblock) ASSERT(fcntl(lfd, F_SETFL, O_NONBLOCK) == 0);
@@ -277,6 +91,22 @@ static void wait_ready(struct mg_mgr *mgr) {
MG_INFO(("Interface started, starting main loop")); 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 #if !MG_ENABLE_FREERTOS
static bool s_fail_alloc; static bool s_fail_alloc;
@@ -316,12 +146,12 @@ static volatile bool s_select_waiting, s_select_done;
static volatile bool s_poll_waiting, s_poll_done; static volatile bool s_poll_waiting, s_poll_done;
static volatile bool s_ready_waiting, s_ready_done; 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_backlog_ready, s_backlog_full, s_backlog_accept;
static volatile bool s_tx_fail, s_tx_error; 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_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_start, s_recv_waiting, s_recv_done;
static volatile bool s_recv_expect_error; static volatile bool s_recv_expect_error;
static volatile bool s_udp_ready, s_udp_done; static volatile bool s_shutdown_waiting, s_shutdown_done;
static size_t (*s_tx)(const void *, size_t, struct mg_tcpip_if *); static size_t (*s_tx)(const void *, size_t, struct mg_tcpip_if *);
struct client_data { struct client_data {
@@ -339,7 +169,7 @@ static void select_task(void *args);
static void readiness_task(void *args); static void readiness_task(void *args);
static void backlog_task(void *args); static void backlog_task(void *args);
static void tx_gate_task(void *args); static void tx_gate_task(void *args);
static void udp_task(void *args); static void shutdown_task(void *args);
struct atask_args { struct atask_args {
bool listener_nonblock; bool listener_nonblock;
@@ -488,27 +318,17 @@ static void tx_gate_task(void *args) {
// receiver/sender > network task > gate: final send is waiting // receiver/sender > network task > gate: final send is waiting
while (!s_send_waiting) vTaskDelay(1); while (!s_send_waiting) vTaskDelay(1);
ASSERT(!s_send_done); ASSERT(!s_send_done);
if (s_send_expect_error) s_tx_error = s_tx_fail = true; if (s_send_expect_shutdown) {
else s_tx_held = false; 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); vTaskDelete(NULL);
(void) args; (void) args;
} }
static void udp_delay(struct mg_mgr *mgr) {
(void) mgr;
vTaskDelay(1);
}
static void udp_task(void *args) {
struct mg_mgr *mgr = (struct mg_mgr *) args;
int fd = open_udp();
s_udp_ready = true;
udp_exchange(mgr, fd, udp_delay);
s_udp_done = true;
vTaskDelete(NULL);
}
static void recv_wait_task(void *args) { static void recv_wait_task(void *args) {
char buf; char buf;
int fd = (int) (uintptr_t) args; int fd = (int) (uintptr_t) args;
@@ -545,15 +365,19 @@ static void send_task(void *args) {
n = send(fd, buf, sizeof(buf), MSG_DONTWAIT); n = send(fd, buf, sizeof(buf), MSG_DONTWAIT);
ASSERT(n < 0 && errno == EAGAIN); // Same full c->send: 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 s_recv_start = true; // O_NONBLOCK is per socket; now start the receiver
while (!s_recv_waiting) vTaskDelay(1); while (!s_recv_waiting) vTaskDelay(1);
} }
s_send_waiting = true; // Gate task releases TX after this send blocks s_send_waiting = true; // Gate task releases TX after this send blocks
n = send(fd, &marker, sizeof(marker), 0); n = send(fd, &marker, sizeof(marker), 0);
if (s_send_expect_error) ASSERT(n < 0 && errno == EPIPE); if (s_send_expect_error || s_send_expect_shutdown) {
else ASSERT(n == (ssize_t) sizeof(marker)); ASSERT(n < 0 && errno == EPIPE);
} else {
ASSERT(n == (ssize_t) sizeof(marker));
}
s_send_done = true; s_send_done = true;
if (s_send_expect_shutdown) close(fd);
vTaskDelete(NULL); vTaskDelete(NULL);
} }
@@ -591,7 +415,7 @@ static void atask(void *args) {
ASSERT(result == pdPASS); ASSERT(result == pdPASS);
} }
if (cfg->gate != NULL) { 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); configMAX_PRIORITIES - 3, NULL);
ASSERT(result == pdPASS); ASSERT(result == pdPASS);
} }
@@ -717,27 +541,70 @@ static void backlog_task(void *args) {
vTaskDelete(NULL); 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. // task2: tests owner.
static void task2(void *args) { static void task2(void *args) {
struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager
char *ip; char *ip;
int status; int status;
pid_t first, second, udp; pid_t first, second;
vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms
s_error = false; s_error = false;
s_wait = false; s_wait = false;
ip = mg_mprintf("%M", mg_print_ip4, &mgr->ifp->ip); ip = mg_mprintf("%M", mg_print_ip4, &mgr->ifp->ip);
s_udp_ready = s_udp_done = false;
ASSERT(xTaskCreate(udp_task, "udp", 512, mgr, configMAX_PRIORITIES - 1,
NULL) == pdPASS);
while (!s_udp_ready) vTaskDelay(1);
udp = start_udp(ip);
while (!client_done(udp, &status)) vTaskDelay(1);
check_client(status);
while (!s_udp_done) vTaskDelay(1);
DASHBOARD("udp");
s_poll_waiting = s_poll_done = false; s_poll_waiting = s_poll_done = false;
ASSERT(xTaskCreate(poll_task, "poll", 512, NULL, ASSERT(xTaskCreate(poll_task, "poll", 512, NULL,
configMAX_PRIORITIES - 1, NULL) == pdPASS); configMAX_PRIORITIES - 1, NULL) == pdPASS);
@@ -768,6 +635,16 @@ static void task2(void *args) {
DASHBOARD("select_child_write"); DASHBOARD("select_child_write");
DASHBOARD("poll_child_write"); DASHBOARD("poll_child_write");
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_error = false;
s_backlog_ready = s_backlog_full = s_backlog_accept = false; s_backlog_ready = s_backlog_full = s_backlog_accept = false;
ASSERT(xTaskCreate(backlog_task, "backlog", 512, NULL, ASSERT(xTaskCreate(backlog_task, "backlog", 512, NULL,
@@ -882,12 +759,27 @@ static void task2(void *args) {
while (s_wait) vTaskDelay(pdMS_TO_TICKS(10)); while (s_wait) vTaskDelay(pdMS_TO_TICKS(10));
s_tx_held = s_tx_fail = s_tx_error = s_send_expect_error = false; 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); mg_free(ip);
s_error = false; s_error = false;
{ {
fd_set e; fd_set e;
struct pollfd p; struct pollfd p;
socklen_t len;
int err;
int fd = socket(AF_INET, SOCK_STREAM, 0); int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sa; struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa)); memset(&sa, 0, sizeof(sa));
@@ -907,6 +799,12 @@ static void task2(void *args) {
ASSERT(poll(&p, 1, 0) == 1); ASSERT(poll(&p, 1, 0) == 1);
ASSERT((p.revents & POLLERR) != 0); ASSERT((p.revents & POLLERR) != 0);
ASSERT((p.revents & POLLOUT) == 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); ASSERT(close(fd) == 0);
} }
DASHBOARD("select_error"); DASHBOARD("select_error");
@@ -1065,20 +963,6 @@ static void nortos_echo(struct mg_mgr *mgr, struct nortos_echo_client *clients,
nortos_wait_client(mgr, pid); nortos_wait_client(mgr, pid);
} }
static void udp_poll(struct mg_mgr *mgr) {
mg_mgr_poll(mgr, 0);
}
static void nortos_udp(struct mg_mgr *mgr, const char *ip) {
pid_t pid;
int fd = open_udp();
pid = start_udp(ip);
udp_exchange(mgr, fd, udp_poll);
nortos_wait_client(mgr, pid);
DASHBOARD("nortos_udp");
}
static void nortos_test(struct mg_mgr *mgr) { static void nortos_test(struct mg_mgr *mgr) {
static const char bootstrap = '\0'; static const char bootstrap = '\0';
static const char grow[512] = {0}; static const char grow[512] = {0};
@@ -1170,6 +1054,25 @@ static void nortos_test(struct mg_mgr *mgr) {
DASHBOARD("nortos_poll_children"); DASHBOARD("nortos_poll_children");
DASHBOARD("nortos_echo"); 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); fd = socket(AF_INET, SOCK_STREAM, 0);
memset(&sa, 0, sizeof(sa)); memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET; sa.sin_family = AF_INET;
@@ -1180,7 +1083,6 @@ static void nortos_test(struct mg_mgr *mgr) {
ASSERT(close(fd) == 0); ASSERT(close(fd) == 0);
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
DASHBOARD("nortos_nonblocking_connect"); DASHBOARD("nortos_nonblocking_connect");
nortos_udp(mgr, ip);
mg_free(ip); mg_free(ip);
} }
@@ -1211,6 +1113,8 @@ static bool bsd_x_test(struct mg_tcpip_driver *driver, void *driver_data, const
mg_tcpip_init(&mgr, &mif); mg_tcpip_init(&mgr, &mif);
ASSERT(mgr.ifp != NULL); // driver init succeeded ASSERT(mgr.ifp != NULL); // driver init succeeded
test_inet();
DASHBOARD("inet");
MG_INFO(("Init done, starting interface")); MG_INFO(("Init done, starting interface"));
usleep(200000); // 200 ms 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
};
+17 -7
View File
@@ -5,6 +5,11 @@
#include "mip_x_test.c" #include "mip_x_test.c"
// drop TCP in data phase, testing retransmit queue (RTX). It also validates
// duplicate-peer-data handling and “re-ACK the current sequence”.
static bool drop_tcp_segment(const void *buf, size_t len,
struct mg_tcpip_if *ifp, const char *dir);
struct port_driver_data { struct port_driver_data {
int sockfd; int sockfd;
struct sockaddr_in port_addr; struct sockaddr_in port_addr;
@@ -16,6 +21,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); ssize_t received = recv(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
usleep(1); // This is to avoid 100% CPU usleep(1); // This is to avoid 100% CPU
if (received < 0) return 0; if (received < 0) return 0;
if (drop_tcp_segment(buf, (size_t) received, ifp, "RX")) return 0;
return (size_t) received; return (size_t) received;
} }
@@ -37,15 +43,19 @@ static bool is_tcp_segment(const void *buf, size_t len) {
return (tcp->flags & (TH_SYN | TH_FIN)) == 0; 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, const char *dir) {
uint32_t random; uint32_t random;
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss if (!ifp->enable_tcp_retransmit || !is_tcp_segment(buf, len)) return false;
// Introduce a 10% segment loss
mg_random(&random, sizeof(random)); mg_random(&random, sizeof(random));
if (random % 10 < 3) { if (random % 10 != 0) return false;
MG_DEBUG(("dropping TCP segment")); MG_DEBUG(("dropping TCP %s segment", dir));
return len; 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, "TX")) return len;
ssize_t res = send(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0); ssize_t res = send(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
if (res < 0) { if (res < 0) {
MG_ERROR(("sock_tx failed: %d", errno)); MG_ERROR(("sock_tx failed: %d", errno));
+16 -7
View File
@@ -13,12 +13,17 @@
#include "mip_x_test.c" #include "mip_x_test.c"
// drop TCP in data phase, testing retransmit queue (RTX). It also validates
// duplicate-peer-data handling and “re-ACK the current sequence”.
static bool drop_tcp_segment(const void *buf, size_t len,
struct mg_tcpip_if *ifp, const char *dir);
// MIP TUNTAP driver // MIP TUNTAP driver
static size_t tap_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) { 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); ssize_t received = read(*(int *) ifp->driver_data, buf, len);
usleep(1); // This is to avoid 100% CPU usleep(1); // This is to avoid 100% CPU
if (received < 0) return 0; if (received < 0) return 0;
if (drop_tcp_segment(buf, (size_t) received, ifp, "RX")) return 0;
return (size_t) received; return (size_t) received;
} }
@@ -40,15 +45,19 @@ static bool is_tcp_segment(const void *buf, size_t len) {
return (tcp->flags & (TH_SYN | TH_FIN)) == 0; 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, const char *dir) {
uint32_t random; uint32_t random;
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss if (!ifp->enable_tcp_retransmit || !is_tcp_segment(buf, len)) return false;
// Introduce a 10% segment loss
mg_random(&random, sizeof(random)); mg_random(&random, sizeof(random));
if (random % 10 < 3) { if (random % 10 != 0) return false;
MG_DEBUG(("dropping TCP segment")); MG_DEBUG(("dropping TCP %s segment", dir));
return len; 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, "TX")) return len;
ssize_t res = write(*(int *) ifp->driver_data, buf, len); ssize_t res = write(*(int *) ifp->driver_data, buf, len);
if (res < 0) { if (res < 0) {
MG_ERROR(("tap_tx failed: %d", errno)); MG_ERROR(("tap_tx failed: %d", errno));
+4
View File
@@ -80,6 +80,10 @@ static void test_statechange(void) {
onstatechange(&iface); onstatechange(&iface);
ASSERT(executed == true); ASSERT(executed == true);
executed = false; 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 #if MG_ENABLE_IPV6
static void mif6_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) { static void mif6_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
+55 -30
View File
@@ -76,6 +76,9 @@ static int s_num_tests = 0;
static bool s_error = false; static bool s_error = false;
#define POLL_LIMIT(n) (mgr->ifp->enable_tcp_retransmit ? (n) * 10 : (n)) #define POLL_LIMIT(n) (mgr->ifp->enable_tcp_retransmit ? (n) * 10 : (n))
#define RTX_ATTEMPTS(mgr) ((mgr)->ifp->enable_tcp_retransmit ? 3 : 1)
#define SERVER_POLL_LIMIT(mgr) \
((mgr)->ifp->enable_tcp_retransmit ? 18000 : 300)
#ifdef NO_ABORT #ifdef NO_ABORT
static int s_abort = 0; static int s_abort = 0;
@@ -151,13 +154,15 @@ static void eh1(struct mg_connection *c, int ev, void *ev_data) {
struct fetch_data { struct fetch_data {
char *buf; char *buf;
const char *url; const char *url;
int code, closed; int code;
bool connected, received, closed;
}; };
static void fcb(struct mg_connection *c, int ev, void *ev_data) { static void fcb(struct mg_connection *c, int ev, void *ev_data) {
struct fetch_data *fd = (struct fetch_data *) c->fn_data; struct fetch_data *fd = (struct fetch_data *) c->fn_data;
if (ev == MG_EV_CONNECT) { if (ev == MG_EV_CONNECT) {
MG_DEBUG(("CONNECT")); MG_DEBUG(("CONNECT"));
fd->connected = true;
if (mg_url_is_ssl(fd->url)) { if (mg_url_is_ssl(fd->url)) {
struct mg_tls_opts opts; struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts)); // read CA from packed_fs memset(&opts, 0, sizeof(opts)); // read CA from packed_fs
@@ -173,6 +178,7 @@ static void fcb(struct mg_connection *c, int ev, void *ev_data) {
} }
} else if (ev == MG_EV_HTTP_MSG) { } else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data; 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, snprintf(fd->buf, FETCH_BUF_SIZE, "%.*s", (int) hm->message.len,
hm->message.buf); hm->message.buf);
fd->code = atoi(hm->uri.buf); fd->code = atoi(hm->uri.buf);
@@ -191,10 +197,11 @@ static void fcb(struct mg_connection *c, int ev, void *ev_data) {
static int fetch(struct mg_mgr *mgr, char *buf, const char *url, static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
const char *fmt, ...) { const char *fmt, ...) {
struct fetch_data fd = {buf, url, 0, 0}; int i, attempt;
int i;
struct mg_connection *c = NULL; struct mg_connection *c = NULL;
va_list ap; va_list ap;
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 mg_mgr_poll(mgr, 0); // update ifp->now to avoid ARP lookup using an old
// timestamp (from an ancient call in other test) // timestamp (from an ancient call in other test)
mg_mgr_poll(mgr, 0); // (prior idling collects lots of frames) mg_mgr_poll(mgr, 0); // (prior idling collects lots of frames)
@@ -216,7 +223,12 @@ static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
if (!fd.closed) // failed, force closure and conn removal, and loop end if (!fd.closed) // failed, force closure and conn removal, and loop end
((struct connstate *) (c + 1))->is_closing = 1; ((struct connstate *) (c + 1))->is_closing = 1;
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
if (!(fd.connected && fd.closed && !fd.received) ||
attempt + 1 == RTX_ATTEMPTS(mgr))
return fd.code; return fd.code;
MG_INFO(("HTTP retry %d", attempt + 2));
}
return 0;
} }
static void test_http_client(struct mg_mgr *mgr) { static void test_http_client(struct mg_mgr *mgr) {
@@ -256,13 +268,14 @@ static char s_topic[16];
struct mqtt_data { struct mqtt_data {
char *url; char *url;
bool passed; bool connected, opened, passed;
}; };
static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) { static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
struct mqtt_data *data = (struct mqtt_data *) c->fn_data; struct mqtt_data *data = (struct mqtt_data *) c->fn_data;
if (ev == MG_EV_CONNECT) { if (ev == MG_EV_CONNECT) {
MG_DEBUG(("CONNECT")); MG_DEBUG(("CONNECT"));
data->connected = true;
#if MG_TLS #if MG_TLS
struct mg_tls_opts opts; struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts)); memset(&opts, 0, sizeof(opts));
@@ -277,6 +290,7 @@ static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
#endif #endif
} else if (ev == MG_EV_MQTT_OPEN) { } else if (ev == MG_EV_MQTT_OPEN) {
MG_DEBUG(("MQTT CONNECT")); MG_DEBUG(("MQTT CONNECT"));
data->opened = true;
struct mg_mqtt_opts sub_opts; struct mg_mqtt_opts sub_opts;
memset(&sub_opts, 0, sizeof(sub_opts)); memset(&sub_opts, 0, sizeof(sub_opts));
sub_opts.topic = mg_str(mg_random_str(s_topic, sizeof(s_topic))); sub_opts.topic = mg_str(mg_random_str(s_topic, sizeof(s_topic)));
@@ -326,12 +340,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) { static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
struct mqtt_data data; struct mqtt_data data;
struct mg_mqtt_opts opts; struct mg_mqtt_opts opts;
bool retry;
int attempt;
memset(&opts, 0, sizeof(opts)); memset(&opts, 0, sizeof(opts));
opts.clean = true, opts.version = 4; opts.clean = true, opts.version = 4;
data.passed = false;
#if defined(MQTT_HOST) && MG_TLS != MG_TLS_BUILTIN #if defined(MQTT_HOST) && MG_TLS != MG_TLS_BUILTIN
if (host_ip == NULL) { if (host_ip == NULL) {
printf("\nMQTT_HOST defined but no HOST_IP provided, skipping MQTT tests\n"); printf("\nMQTT_HOST defined but no HOST_IP provided, skipping MQTT tests\n");
@@ -359,15 +391,10 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
data.url = strdup(MQTT_URL); data.url = strdup(MQTT_URL);
#endif #endif
#endif // MG_TLS #endif // MG_TLS
mg_mgr_poll(mgr, 0); // update interface timing (*) for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
mg_mgr_poll(mgr, 0); retry = mqtt_attempt(mgr, &data, &opts);
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data); if (!retry || attempt + 1 == RTX_ATTEMPTS(mgr)) break;
ASSERT(s_conn != NULL); MG_INFO(("MQTT retry %d", attempt + 2));
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
} }
ASSERT(data.passed); ASSERT(data.passed);
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
@@ -406,16 +433,10 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
//data.url = strdup(MQTT_URL); //data.url = strdup(MQTT_URL);
#endif #endif
#endif // MG_TLS #endif // MG_TLS
data.passed = false; for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
mg_mgr_poll(mgr, 0); // update interface timing (*) retry = mqtt_attempt(mgr, &data, &opts);
mg_mgr_poll(mgr, 0); if (!retry || attempt + 1 == RTX_ATTEMPTS(mgr)) break;
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data); MG_INFO(("MQTT retry %d", attempt + 2));
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
} }
ASSERT(data.passed); ASSERT(data.passed);
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
@@ -430,7 +451,7 @@ static volatile bool s_poll_done;
static void *poll_thread(void *p) { static void *poll_thread(void *p) {
struct mg_mgr *mgr = (struct mg_mgr *) p; struct mg_mgr *mgr = (struct mg_mgr *) p;
int i; int i;
for (i = 0; i < POLL_LIMIT(300) && !s_poll_done; i++) { for (i = 0; i < SERVER_POLL_LIMIT(mgr) && !s_poll_done; i++) {
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit
} }
@@ -445,7 +466,7 @@ static void test_http_server(struct mg_mgr *mgr) {
struct mg_connection *c; struct mg_connection *c;
char *cmd; char *cmd;
pthread_t thread_id = (pthread_t) 0; pthread_t thread_id = (pthread_t) 0;
int rc; int attempt, rc;
#if MG_TLS #if MG_TLS
struct mg_tls_opts opts; struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts)); memset(&opts, 0, sizeof(opts));
@@ -456,20 +477,24 @@ static void test_http_server(struct mg_mgr *mgr) {
mg_hexdump(opts.cert.buf, opts.cert.len); mg_hexdump(opts.cert.buf, opts.cert.len);
mg_hexdump(opts.key.buf, opts.key.len); mg_hexdump(opts.key.buf, opts.key.len);
c = mg_http_listen(mgr, "https://0.0.0.0:12347", eh1, &opts); c = mg_http_listen(mgr, "https://0.0.0.0:12347", eh1, &opts);
cmd = mg_mprintf("./mip_curl.sh --insecure https://%M:12347", mg_print_ip4, cmd = mg_mprintf("./mip_curl.sh --max-time 120 --insecure https://%M:12347",
&mgr->ifp->ip); mg_print_ip4, &mgr->ifp->ip);
#else #else
c = mg_http_listen(mgr, "http://0.0.0.0:12347", eh1, NULL); c = mg_http_listen(mgr, "http://0.0.0.0:12347", eh1, NULL);
cmd = cmd =
mg_mprintf("./mip_curl.sh http://%M:12347", mg_print_ip4, &mgr->ifp->ip); mg_mprintf("./mip_curl.sh --max-time 120 http://%M:12347", mg_print_ip4,
&mgr->ifp->ip);
#endif #endif
ASSERT(c != NULL); ASSERT(c != NULL);
ASSERT (mg_send(c, "NADA", 0)); // check mg_send allows len=0 ASSERT (mg_send(c, "NADA", 0)); // check mg_send allows len=0
s_poll_done = false; s_poll_done = false;
pthread_create(&thread_id, NULL, poll_thread, pthread_create(&thread_id, NULL, poll_thread,
mgr); // simpler this way, no concurrency anyway mgr); // simpler this way, no concurrency anyway
MG_DEBUG(("CURL")); for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
MG_DEBUG(("CURL attempt %d", attempt + 1));
rc = system(cmd); // wait for curl rc = system(cmd); // wait for curl
if (rc == 0) break;
}
MG_DEBUG(("MONGOOSE")); MG_DEBUG(("MONGOOSE"));
s_poll_done = true; s_poll_done = true;
pthread_join(thread_id, NULL); // wait for Mongoose pthread_join(thread_id, NULL); // wait for Mongoose
+56 -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.root_dir = "/a=./data";
opts.page404 = "./data/404.html"; // existing 404 page opts.page404 = "./data/404.html"; // existing 404 page
mg_http_serve_dir(c, hm, &opts); 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)) { } else if (mg_match(hm->uri, mg_str("/b/#"), NULL)) {
struct mg_http_serve_opts opts; struct mg_http_serve_opts opts;
memset(&opts, 0, sizeof(opts)); memset(&opts, 0, sizeof(opts));
@@ -1502,6 +1507,7 @@ static void test_http_404(void) {
ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(cmpbody(buf, "hello\n") == 0);
ASSERT(fetch(&mgr, buf, url, "GET /a/a.txt HTTP/1.0\n\n") == 200); ASSERT(fetch(&mgr, buf, url, "GET /a/a.txt HTTP/1.0\n\n") == 200);
ASSERT(cmpbody(buf, "hello\n") == 0); 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(fetch(&mgr, buf, url, "GET /b/a.txt HTTP/1.0\n\n") == 200);
ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(cmpbody(buf, "hello\n") == 0);
@@ -1981,13 +1987,41 @@ static void test_http_parse(void) {
} }
{ {
const char *s = "a b HTTP/1.0\na:1\nb:2\nc:3\nd:4\ne:5\nf:6\ng:7\nh:8\n\n"; const char *s = "a b HTTP/1.0\na:1\nb:2\nc:3\nd:4\ne:5\nf:6\ng:7\n\n";
ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s));
ASSERT((v = mg_http_get_header(&req, "e")) != NULL); ASSERT((v = mg_http_get_header(&req, "e")) != NULL);
ASSERT(vcmp(*v, "5")); ASSERT(vcmp(*v, "5"));
ASSERT((v = mg_http_get_header(&req, "g")) != NULL); ASSERT((v = mg_http_get_header(&req, "g")) != NULL);
ASSERT(vcmp(*v, "7")); ASSERT(vcmp(*v, "7"));
ASSERT((v = mg_http_get_header(&req, "h")) == NULL); // MG_MAX_HTTP_HEADERS
{
char headers[MG_MAX_HTTP_HEADERS * 10 + 32];
size_t i, len = 0;
len += (size_t) mg_snprintf(headers + len, sizeof(headers) - len,
"a b HTTP/1.0\n");
for (i = 0; i <= MG_MAX_HTTP_HEADERS; i++) {
len += (size_t) mg_snprintf(headers + len, sizeof(headers) - len,
"h%u:1\n", (unsigned) i);
}
len += (size_t) mg_snprintf(headers + len, sizeof(headers) - len, "\n");
ASSERT(mg_http_parse(headers, len, &req) == -1); // MG_MAX_HTTP_HEADERS
}
}
{
char headers[MG_MAX_HTTP_HEADERS * 10 + 128];
size_t i, len = 0;
len += (size_t) mg_snprintf(headers + len, sizeof(headers) - len,
"GET /public HTTP/1.1\r\n");
for (i = 0; i < MG_MAX_HTTP_HEADERS; i++) {
len += (size_t) mg_snprintf(headers + len, sizeof(headers) - len,
"h%u:1\r\n", (unsigned) i);
}
len += (size_t) mg_snprintf(headers + len, sizeof(headers) - len,
"Content-Length: 43\r\n\r\n"
"POST /admin HTTP/1.1\r\n"
"Content-Length: 0\r\n\r\n");
ASSERT(mg_http_parse(headers, len, &req) == -1);
} }
{ {
@@ -2399,10 +2433,15 @@ static void test_str(void) {
ASSERT(sn("%.*s", 1, "ab")); ASSERT(sn("%.*s", 1, "ab"));
ASSERT(sn("%.1s", "ab")); ASSERT(sn("%.1s", "ab"));
ASSERT(sn("%.99s", "a")); ASSERT(sn("%.99s", "a"));
ASSERT(sn("%10.8s", "abc"));
ASSERT(sn("%-6.4s", "ab"));
ASSERT(sn("%5.1s", ""));
ASSERT(sn("%11s", "a")); ASSERT(sn("%11s", "a"));
ASSERT(sn("%s", "a\0b")); ASSERT(sn("%s", "a\0b"));
ASSERT(sn("%2s", "a")); ASSERT(sn("%2s", "a"));
ASSERT(sn("%.*s", 3, "a\0b")); 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", 7));
ASSERT(sn("%d", 123)); ASSERT(sn("%d", 123));
#if MG_ARCH == MG_ARCH_UNIX #if MG_ARCH == MG_ARCH_UNIX
@@ -5207,26 +5246,37 @@ static void test_dash(void) {
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200);
ASSERT(cmpbody(buf, arr0_2_expected) == 0); ASSERT(cmpbody(buf, arr0_2_expected) == 0);
// Array deletion: single element s_arr_index = 7;
ASSERT(fetch(&mgr, buf, url, ASSERT(fetch(&mgr, buf, url,
"GET /api/del/arr/3 HTTP/1.0\n" "GET /api/del/arr/3 HTTP/1.0\n"
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 405);
ASSERT(strstr(buf, "Allow: DELETE\r\n") != NULL);
ASSERT(s_arr_index == 7);
ASSERT(fetch(&mgr, buf, url,
"GET /api/del/arr/1/3 HTTP/1.0\n"
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 405);
ASSERT(s_arr_index == 7);
// Array deletion: single element
ASSERT(fetch(&mgr, buf, url,
"DELETE /api/del/arr/3 HTTP/1.0\n"
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200);
ASSERT(cmpbody(buf, "1\n") == 0); ASSERT(cmpbody(buf, "1\n") == 0);
// Array deletion: range // Array deletion: range
ASSERT(fetch(&mgr, buf, url, ASSERT(fetch(&mgr, buf, url,
"GET /api/del/arr/1/3 HTTP/1.0\n" "DELETE /api/del/arr/1/3 HTTP/1.0\n"
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200);
ASSERT(cmpbody(buf, "3\n") == 0); ASSERT(cmpbody(buf, "3\n") == 0);
// Array deletion: out-of-range returns 403 // Array deletion: out-of-range returns 403
ASSERT(fetch(&mgr, buf, url, ASSERT(fetch(&mgr, buf, url,
"GET /api/del/arr/100 HTTP/1.0\n" "DELETE /api/del/arr/100 HTTP/1.0\n"
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 403); "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 403);
// Non-array set returns 404 // Non-array set returns 404
ASSERT(fetch(&mgr, buf, url, ASSERT(fetch(&mgr, buf, url,
"GET /api/del/set1/0 HTTP/1.0\n" "DELETE /api/del/set1/0 HTTP/1.0\n"
"Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 404); "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 404);
// Test that array change notification uses "arr/INDEX" key format // Test that array change notification uses "arr/INDEX" key format
+5 -8
View File
@@ -1,25 +1,22 @@
THISDIR = $(realpath $(CURDIR)) THISDIR = $(realpath $(CURDIR))
ROOTDIR = $(realpath $(CURDIR)/../../..) ROOTDIR = $(realpath $(CURDIR)/../../..)
DOCKER ?= docker run --rm $(DA) -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf DOCKER ?= docker run --rm $(DA) -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf
CMD ?= idf.py -DCFLAGS_EXTRA="$(CFLAGS_EXTRA)" build ARCH ?= esp32
PORT ?= /dev/ttyUSB0 PORT ?= /dev/ttyUSB0
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
all: example all: example
example: example:
true true
build: Makefile $(wildcard main/*) build: Makefile $(wildcard main/*)
$(DOCKER) $(CMD) test -d build || $(DOCKER) idf.py set-target $(ARCH)
$(DOCKER) idf.py build
flash: flash:
flash: CMD = idf.py flash cd build && esptool --chip $(ARCH) --baud 460800 --port $(PORT) write-flash @flash_args
flash: DA = --device $(PORT)
flash: build
clean: clean:
test -d build && $(DOCKER) rm -rf build sdkconfig || true test -d build && (rm -rf build sdkconfig || true)
.PHONY: build .PHONY: build
+3 -1
View File
@@ -70,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 app_main(void) {
void wifi_init(const char *ssid, const char *pass); MG_HEALTH_INIT();
wifi_init(WIFI_SSID, WIFI_PASS); wifi_init(WIFI_SSID, WIFI_PASS);
struct mg_mgr mgr; struct mg_mgr mgr;
@@ -1 +1,16 @@
#define MG_ARCH MG_ARCH_ESP32 #define MG_ARCH MG_ARCH_ESP32
// Crash reports and mem usage stats
// Make sure to add to sdkconfig.defaults the following:
// CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y
// CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y
// CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16
// CONFIG_ESP_COREDUMP_STACK_SIZE=2048
// CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
#define MG_ENABLE_HEALTH 1
// mdash.net device management support, https://mongoose.ws/docs/guides/mdash/
#define MG_ENABLE_MDASH 0
#define MG_MDASH_KEY "..."
#define MG_FIRMWARE_VERSION "esp32_min.1.0.0"
#define MG_TLS MG_TLS_BUILTIN
@@ -5,3 +5,5 @@ CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y
CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y
CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16 CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16
CONFIG_ESP_COREDUMP_STACK_SIZE=2048 CONFIG_ESP_COREDUMP_STACK_SIZE=2048
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
@@ -3,8 +3,12 @@
#include "mongoose.h" #include "mongoose.h"
#ifndef MQTT_SERVER_URL
#define MQTT_SERVER_URL "mqtt://broker.hivemq.com:1883" #define MQTT_SERVER_URL "mqtt://broker.hivemq.com:1883"
#endif
#ifndef MQTT_CLIENT_ID
#define MQTT_CLIENT_ID "d3" #define MQTT_CLIENT_ID "d3"
#endif
#define MQTT_USER MQTT_CLIENT_ID #define MQTT_USER MQTT_CLIENT_ID
#define MQTT_PASS "" #define MQTT_PASS ""
#define MQTT_PUBLISH_TOPIC "mg/" MQTT_CLIENT_ID "/tx" #define MQTT_PUBLISH_TOPIC "mg/" MQTT_CLIENT_ID "/tx"
@@ -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
@@ -3,6 +3,12 @@
#include "hal.h" #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) { 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 uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
if (period == 0) return false; // Avoid division by zero if (period == 0) return false; // Avoid division by zero
@@ -20,16 +26,6 @@ uint64_t hal_get_tick(void) {
return s_ticks; return s_ticks;
}; };
uint32_t SystemCoreClock = 160000000;
void SystemInit(void) { // Called automatically by startup code
hal_system_init(); // Enable FPU
hal_clock_init();
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
void ExitRun0Mode(void) {
}
struct stat; struct stat;
__attribute__((weak)) int _fstat(int fd, struct stat *st) { __attribute__((weak)) int _fstat(int fd, struct stat *st) {
(void) fd, (void) st; (void) fd, (void) st;
@@ -123,3 +119,23 @@ __attribute__((weak)) int mkdir(const char *path, int mode) {
__attribute__((weak)) void _init(void) { __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;
}
+6 -1
View File
@@ -14,6 +14,7 @@ CFLAGS += -mcpu=cortex-m33 -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CFLAGS_E
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lm -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 = main.c hal.c $(MGDIR)/mongoose.c
SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c
SOURCES += cmsis_mcxw71/MCXW/MCXW716C/gcc/startup_MCXW716C.S SOURCES += cmsis_mcxw71/MCXW/MCXW716C/gcc/startup_MCXW716C.S
@@ -23,9 +24,13 @@ firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@ arm-none-eabi-objcopy -O binary $< $@
arm-none-eabi-size --format=berkeley $< 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 $@ arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
# Rebuild when overriding the board address or timing.
.PHONY: mesh-config
mesh-config:
cmsis_core: cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
+36 -14
View File
@@ -9,7 +9,7 @@
// 802.15.4 register setup. Then select the legacy 802.15.4 demodulator and // 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, // CCA from RX digital. See PhyHwInit() in NXP ieee_802_15_4 PHY sources,
// PhyPacketProcessor.c // PhyPacketProcessor.c
bool hal_radio_init(void) { void hal_radio_init(void) {
uint32_t stat = RFMC_RF2P4GHZ_STAT_RST_STAT_MASK | uint32_t stat = RFMC_RF2P4GHZ_STAT_RST_STAT_MASK |
RFMC_RF2P4GHZ_STAT_LP_ACK_STAT_MASK; RFMC_RF2P4GHZ_STAT_LP_ACK_STAT_MASK;
size_t i, n = 1000000; size_t i, n = 1000000;
@@ -20,7 +20,10 @@ bool hal_radio_init(void) {
RFMC_RF2P4GHZ_CTRL_RST_MASK); // Release radio reset, no low power RFMC_RF2P4GHZ_CTRL_RST_MASK); // Release radio reset, no low power
while ((RFMC->RF2P4GHZ_STAT & stat) != 0 || while ((RFMC->RF2P4GHZ_STAT & stat) != 0 ||
(RFMC->XO_STAT & RFMC_XO_STAT_XTAL_RDY_MASK) == 0) { (RFMC->XO_STAT & RFMC_XO_STAT_XTAL_RDY_MASK) == 0) {
if (--n == 0) return false; // Radio did not wake up 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; RADIO_CTRL->LL_CTRL = RADIO_CTRL_LL_CTRL_ACTIVE_LL_MASK;
RFMC->RF2P4GHZ_CTRL |= RFMC->RF2P4GHZ_CTRL |=
@@ -31,17 +34,17 @@ bool hal_radio_init(void) {
XCVR_MISC->XCVR_CTRL &= ~XCVR_MISC_XCVR_CTRL_DEMOD_SEL_MASK; XCVR_MISC->XCVR_CTRL &= ~XCVR_MISC_XCVR_CTRL_DEMOD_SEL_MASK;
XCVR_MISC->XCVR_CTRL |= XCVR_MISC_XCVR_CTRL_DEMOD_SEL(2); 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); XCVR_ZBDEMOD->CCA_LQI_SRC |= XCVR_ZBDEMOD_CCA_LQI_SRC_CCA1_FROM_RX_DIG(1);
return true;
} }
// Random bytes for Mongoose, MG_ENABLE_CUSTOM_RANDOM. Entropy is radio noise: // Random 32 bits, for mg_random(). Entropy is radio noise: receiver RSSI,
// receiver RSSI, sampled while the radio receives, mixed with SysTick by // sampled while the radio receives, mixed with SysTick by SHA-256. One hash
// SHA-256. Good enough for random addresses and nonces, not a certified TRNG // fills a pool of 8 words. Good enough for random addresses and nonces, not
bool mg_random(void *buf, size_t len) { // a certified TRNG
static uint32_t counter; uint32_t hal_rng_read(void) {
uint8_t digest[32], *p = (uint8_t *) buf; static uint32_t counter, pool[8];
static size_t avail;
size_t i, n; size_t i, n;
while (len > 0) { if (avail == 0) {
mg_sha256_ctx ctx; mg_sha256_ctx ctx;
mg_sha256_init(&ctx); mg_sha256_init(&ctx);
counter++; counter++;
@@ -51,11 +54,10 @@ bool mg_random(void *buf, size_t len) {
mg_sha256_update(&ctx, (uint8_t *) sample, sizeof(sample)); mg_sha256_update(&ctx, (uint8_t *) sample, sizeof(sample));
for (n = 0; n < 50; n++) __NOP(); // Let the RSSI update for (n = 0; n < 50; n++) __NOP(); // Let the RSSI update
} }
mg_sha256_final(digest, &ctx); mg_sha256_final((uint8_t *) pool, &ctx);
n = len < sizeof(digest) ? len : sizeof(digest); avail = sizeof(pool) / sizeof(pool[0]);
memcpy(p, digest, n), p += n, len -= n;
} }
return true; return pool[--avail];
} }
// Added to minimise the bin size: avoid pulling printf and friends // Added to minimise the bin size: avoid pulling printf and friends
@@ -81,6 +83,26 @@ uint64_t hal_get_tick(void) {
return s_ticks; 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; uint32_t SystemCoreClock;
// Called by the startup code before .data and .bss are initialised, so it // Called by the startup code before .data and .bss are initialised, so it
+54 -8
View File
@@ -17,11 +17,14 @@ uint64_t hal_get_tick(void);
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now); bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
size_t hal_ram_used(void); size_t hal_ram_used(void);
size_t hal_ram_free(void); size_t hal_ram_free(void);
bool hal_radio_init(void); // Set up the 2.4 GHz transceiver for 802.15.4 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() extern uint32_t SystemCoreClock; // CPU clock, Hz. Set by hal_clock_init()
#define BIT(x) (1UL << (x)) #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 CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
#define SIRC_FREQUENCY 6000000UL // Slow IRC (FRO6M), Hz #define SIRC_FREQUENCY 6000000UL // Slow IRC (FRO6M), Hz
@@ -92,15 +95,53 @@ static inline void hal_periph_clock(volatile uint32_t *mrcc, uint32_t src) {
__DSB(); __DSB();
} }
// LPUART1 is wired to the MCU-Link debug probe USB serial port. // PORT, the pin multiplexer, of a pin. Ports A..C
// TX = PTC3, RX = PTC2, both pin mux alternative 3. See board.h, pin_mux.c static inline PORT_Type *hal_pin_port(uint16_t pin) {
static inline void hal_uart_init(LPUART_Type *uart, unsigned long baud) { 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 src = MRCC_SRC_FRO192M, freq = hal_firc_freq();
uint32_t best_osr = 16, best_sbr = 1, best_err = UINT32_MAX, osr; 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 if (freq == 0) src = MRCC_SRC_FRO6M, freq = SIRC_FREQUENCY; // FIRC off
hal_periph_clock(&MRCC->MRCC_PORTC, 0); hal_pin_mux(rx, 3);
PORTC->PCR[2] = PORT_PCR_MUX(3); // RX hal_pin_mux(tx, 3);
PORTC->PCR[3] = PORT_PCR_MUX(3); // TX
hal_periph_clock(&MRCC->MRCC_LPUART1, src); hal_periph_clock(&MRCC->MRCC_LPUART1, src);
// Find the oversampling ratio (4..32) that gives the lowest baud rate error // Find the oversampling ratio (4..32) that gives the lowest baud rate error
for (osr = 4; osr <= 32; osr++) { for (osr = 4; osr <= 32; osr++) {
@@ -141,6 +182,11 @@ static inline void hal_wdog_disable(void) {
while ((WDOG0->CS & WDOG_CS_RCS_MASK) == 0) (void) 0; 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) { static inline void hal_sys_init(void) {
SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU
hal_wdog_disable(); hal_wdog_disable();
@@ -33,6 +33,9 @@ SECTIONS {
_ebss = .; _ebss = .;
} > sram } > sram
/* Health record, see MG_HEALTH_RAM: not cleared, survives a warm reset */
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > sram
. = ALIGN(8); . = ALIGN(8);
_end = .; /* Start of heap, see _sbrk() in hal.c */ _end = .; /* Start of heap, see _sbrk() in hal.c */
} }
+35 -11
View File
@@ -1,24 +1,42 @@
// Copyright (c) 2026 Cesanta Software Limited // Copyright (c) 2026 Cesanta Software Limited
// All rights reserved // All rights reserved
//
// IPv4 over IEEE 802.15.4 on FRDM-MCXW71, Mongoose built-in TCP/IP stack.
// The radio carries Ethernet frames, channel 15, PAN ID 0xabcd. With the
// FRDM-RW612 eth_radio_bridge tutorial, gets an address from the LAN's DHCP
// server and is reachable from the LAN. Serves pages on ports 80 and 443
#include "hal.h" #include "hal.h"
#include "mongoose.h" #include "mongoose.h"
#ifndef UART_DEBUG
#define UART_DEBUG LPUART1 // MCU-Link USB serial port #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) { static void log_fn(char ch, void *param) {
hal_uart_write_buf(param, &ch, 1); 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) { uint64_t mg_millis(void) {
return hal_get_tick(); 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: // Self-signed P-256 certificate and key for the HTTPS listener. Created by:
// openssl ecparam -genkey -name prime256v1 -noout -out key.pem // openssl ecparam -genkey -name prime256v1 -noout -out key.pem
// openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg // openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg
@@ -70,16 +88,21 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
} }
int main(void) { int main(void) {
hal_clock_init(); MG_HEALTH_INIT();
hal_uart_init(UART_DEBUG, 115200);
mg_log_set_fn(log_fn, UART_DEBUG);
if (!hal_radio_init()) MG_ERROR(("Radio init failed")); hal_clock_init();
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; struct mg_mgr mgr;
mg_mgr_init(&mgr); mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0:80", http_ev_handler, NULL); mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
mg_http_listen(&mgr, "https://0.0.0.0:443", http_ev_handler, NULL); mg_http_listen(&mgr, "https://0.0.0.0", http_ev_handler, NULL);
mg_mqtt_init(&mgr); mg_mqtt_init(&mgr);
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000)); MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
@@ -89,6 +112,7 @@ int main(void) {
for (;;) { for (;;) {
mg_mgr_poll(&mgr, 0); mg_mgr_poll(&mgr, 0);
mg_mqtt_poll(&mgr); mg_mqtt_poll(&mgr);
blink_task();
} }
return 0; return 0;
@@ -1,9 +1,10 @@
#pragma once #pragma once
#include "MCXW716C.h" #include "MCXW716C.h"
// See https://mongoose.ws/docs/getting-started/build-environment/ // See https://mongoose.ws/docs/getting-started/build-options/
#define MG_ARCH MG_ARCH_ARMGCC #define MG_ARCH MG_ARCH_ARMGCC
#define MG_TLS MG_TLS_BUILTIN #define MG_TLS MG_TLS_BUILTIN
#define MQTT_CLIENT_ID "w71" // For tutorials/mqtt/mqtt-client
#define MG_OTA MG_OTA_MCXW71 #define MG_OTA MG_OTA_MCXW71
#define MG_ENABLE_TCPIP 1 #define MG_ENABLE_TCPIP 1
@@ -21,3 +22,12 @@
(mac)[0] = 2, (mac)[1] = b_[1], (mac)[2] = b_[2], (mac)[3] = b_[3]; \ (mac)[0] = 2, (mac)[1] = b_[1], (mac)[2] = b_[2], (mac)[3] = b_[3]; \
(mac)[4] = b_[4], (mac)[5] = b_[5]; \ (mac)[4] = b_[4], (mac)[5] = b_[5]; \
} while (0) } 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"
@@ -17,6 +17,7 @@ CFLAGS += -mcpu=cortex-m33+nodsp -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CF
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections 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 = 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_clock.c
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_trng.c SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_trng.c
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_reset.c SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_reset.c
@@ -35,9 +36,13 @@ firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@ arm-none-eabi-objcopy -O binary $< $@
arm-none-eabi-size --format=berkeley $< arm-none-eabi-size --format=berkeley $<
firmware.elf: cmsis_core cmsis_rw612 $(RADIO_FW) $(SOURCES) $(wildcard *.h) link.ld Makefile mongoose_config.h 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 $@ arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
# Rebuild when overriding the board address or timing.
.PHONY: mesh-config
mesh-config:
cmsis_core: cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
@@ -56,7 +61,7 @@ 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 ' 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 flash: firmware.bin
(echo 'loadfile firmware.elf'; echo 'reset'; echo 'exit';) | JLinkExe -device RW612 -if SWD -speed 4000 -autoconnect 1 -NoGui 1 -ExitOnError 1 (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 # JRun --nogui --device RW612 --if SWD firmware.elf
clean: clean:
@@ -26,6 +26,26 @@ uint64_t hal_get_tick(void) {
return s_ticks; 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; uint32_t SystemCoreClock = SYS_FREQUENCY;
int hal_radio_err; // Result of hal_radio_init() int hal_radio_err; // Result of hal_radio_init()
void SystemInit(void) { // Called automatically by startup code void SystemInit(void) { // Called automatically by startup code
@@ -152,16 +152,22 @@ static inline void hal_gpio_toggle(uint16_t pin) {
#define MG_HAL_SYSTICK MG_HAL_SYSTICK_NONE #define MG_HAL_SYSTICK MG_HAL_SYSTICK_NONE
#endif #endif
static inline void hal_uart_init(USART_Type *uart, unsigned long baud) { static inline void hal_uart_init(USART_Type *uart, unsigned long baud,
if (uart == USART0) { 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); 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); 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); 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); 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); IO_MUX_SetPinMux(IO_MUX_FC14_USART_DATA);
} else { } else {
return; return;
+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
@@ -1,101 +0,0 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// Ethernet <-> IEEE 802.15.4 radio bridge. Radio frames carry Ethernet frames
// split into pieces, see mg_l2_802154_tx(). Radio nodes, e.g. the
// FRDM-MCXW71 minimal tutorial, are on the Ethernet LAN: they get IPv4
// addresses from its DHCP server. The bridge itself has no IP address.
// Everything from the radio goes to Ethernet. From Ethernet, only broadcasts
// and frames to radio nodes go to the radio, it is much slower
#include "hal.h"
#include "mongoose.h"
#define UART_DEBUG USART3
static void log_fn(char ch, void *param) {
hal_uart_write_buf(param, &ch, 1);
}
uint64_t mg_millis(void) {
return hal_get_tick();
}
// MAC addresses of radio nodes, learned from frames they send
static uint8_t s_nodes[8][6];
static size_t s_next;
// Remember a radio node's MAC address, if new
static void learn(const uint8_t *mac) {
size_t i, n = sizeof(s_nodes) / sizeof(s_nodes[0]);
for (i = 0; i < n && memcmp(s_nodes[i], mac, 6) != 0; i++) (void) 0;
if (i < n) return;
memcpy(s_nodes[s_next], mac, 6);
s_next = (s_next + 1) % n;
MG_INFO(("Radio node %M", mg_print_mac, mac));
}
// Is an Ethernet frame for the radio: broadcast, or to a radio node
static bool for_radio(const uint8_t *dst) {
size_t i, n = sizeof(s_nodes) / sizeof(s_nodes[0]);
if (memcmp(dst, "\xff\xff\xff\xff\xff\xff", 6) == 0) return true;
for (i = 0; i < n && memcmp(s_nodes[i], dst, 6) != 0; i++) (void) 0;
return i < n;
}
int main(void) {
static struct mg_tcpip_driver_rw612_data eth_data = {
.mdc_cr = MG_DRIVER_MDC_CR, .phy_addr = MG_TCPIP_PHY_ADDR};
static struct mg_l2_802154_data radio_data = {
.channel = MG_L2_802154_CHANNEL, .pan_id = MG_L2_802154_PAN_ID};
static struct mg_tcpip_if eth = {.driver = &mg_tcpip_driver_rw612,
.driver_data = &eth_data};
static struct mg_tcpip_if radio = {.driver = &mg_tcpip_driver_rw612_154,
.driver_data = &radio_data};
static char eth_queue[8192]; // Filled by the Ethernet IRQ handler
static uint8_t frame[1540];
uint8_t uid[FSL_OCOTP_UID_LENGTH], digest[32];
uint32_t uid_len = sizeof(uid);
uint64_t timer = 0;
char *p;
size_t n;
hal_clock_init();
hal_uart_init(UART_DEBUG, 115200);
mg_log_set_fn(log_fn, UART_DEBUG);
if (hal_radio_err) {
MG_ERROR(("Radio firmware failed to start, step %d", hal_radio_err));
}
// MAC addresses made from the chip unique ID
memset(uid, 0, sizeof(uid));
OCOTP_ReadUniqueID(uid, &uid_len);
mg_sha256(digest, uid, sizeof(uid));
memcpy(eth.mac, digest, 6);
memcpy(radio.mac, digest + 6, 6);
eth.mac[0] = radio.mac[0] = 0x02; // Locally administered, unicast
hal_ethernet_init();
mg_queue_init(&eth.recv_queue, eth_queue, sizeof(eth_queue));
if (!eth.driver->init(&eth)) MG_ERROR(("Ethernet init failed"));
if (!radio.driver->init(&radio)) MG_ERROR(("Radio init failed"));
ENET->RCR |= ENET_RCR_PROM_MASK; // Receive frames for radio nodes, too
MG_INFO(("Bridge started, radio channel %d", radio_data.channel));
for (;;) {
if ((n = mg_queue_next(&eth.recv_queue, &p)) > 0) { // Ethernet -> radio
if (n >= 14 && for_radio((uint8_t *) p)) radio.driver->tx(p, n, &radio);
mg_queue_del(&eth.recv_queue, n);
}
if ((n = radio.driver->rx(frame, sizeof(frame), &radio)) >= 14) {
learn(frame + 6); // Radio -> Ethernet
eth.driver->tx(frame, n, &eth);
}
if (mg_timer_expired(&timer, 1000, mg_millis())) { // Ethernet link
bool up = eth.driver->poll(&eth, true);
eth.state = up ? MG_TCPIP_STATE_UP : MG_TCPIP_STATE_DOWN;
}
}
return 0;
}
@@ -1,10 +0,0 @@
#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_RW612_154 1 // 802.15.4 radio
#define MG_ENABLE_TCPIP_DRIVER_INIT 0 // main.c drives both, no TCP/IP stack
#define MG_ENABLE_CUSTOM_MILLIS 1
+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 #define MG_HAL_SYSTICK MG_HAL_SYSTICK_NONE
#endif #endif
static inline void hal_uart_init(USART_Type *uart, unsigned long baud) { static inline void hal_uart_init(USART_Type *uart, unsigned long baud,
if (uart == USART0) { 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); 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); 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); 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); 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); IO_MUX_SetPinMux(IO_MUX_FC14_USART_DATA);
} else { } else {
return; return;
+1 -1
View File
@@ -96,7 +96,7 @@ int main(void) {
hal_gpio_write(LED2, true); hal_gpio_write(LED2, true);
hal_gpio_output(LED3); hal_gpio_output(LED3);
hal_gpio_write(LED3, true); 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); mg_log_set_fn(log_fn, UART_DEBUG);
hal_ethernet_init(); hal_ethernet_init();
+1 -1
View File
@@ -31,5 +31,5 @@ clean:
$(RM) build pico_sdk_import.cmake $(SDK_PATH) $(RM) build pico_sdk_import.cmake $(SDK_PATH)
$(SDK_PATH): $(SDK_PATH):
git clone -c advice.detachedHead=false --quiet --depth 1 -b 2.1.1 https://github.com/raspberrypi/pico-sdk $@ git clone -c advice.detachedHead=false --quiet --depth 1 -b 2.2.0 https://github.com/raspberrypi/pico-sdk $@
cd $@ && git submodule update --quiet --init cd $@ && git submodule update --quiet --init
@@ -0,0 +1,73 @@
# Environment setup: https://mongoose.ws/documentation/tutorials/tools/
MGDIR = ../../../..
SDK = silabs_sdk/platform
RAIL = $(SDK)/radio/rail_lib
CFLAGS = -W -Wall -Wextra -Werror -Wshadow # No -Wundef: RAIL headers
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 += -I$(SDK)/Device/SiliconLabs/EFR32MG24/Include -I$(SDK)/common/inc
CFLAGS += -I$(RAIL)/common -I$(RAIL)/protocol/ieee802154
CFLAGS += -I$(RAIL)/chip/efr32/efr32xg2x
CFLAGS += -DEFR32MG24B220F1536IM48
CFLAGS += -D__START=main # For startup_efr32mg24.c
CFLAGS += -mcpu=cortex-m33 -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 -mcmse
CFLAGS += $(CFLAGS_EXTRA)
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 += $(SDK)/Device/SiliconLabs/EFR32MG24/Source/startup_efr32mg24.c
SOURCES += $(SDK)/Device/SiliconLabs/EFR32MG24/Source/system_efr32mg24.c
# RAIL radio library, a binary blob. Simplicity SDK keeps it in Git LFS,
# served by Silicon Labs. The oid is the file's sha256, see the LFS pointer
# platform/radio/rail_lib/autogen/librail_release/librail_efr32xg24_gcc_release.a
SDK_VERSION = v2025.6.3
LIBRAIL = librail_efr32xg24_gcc_release.a
LIBRAIL_SHA256 = 381b123649b66e9e3bbe6d1363c31ee0457b3274207b837ea49685a3a0a2c387
LIBRAIL_URL = https://artifacts.silabs.net/api/lfs/gsdk/object/$(LIBRAIL_SHA256)
all build: firmware.bin
firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
arm-none-eabi-size --format=berkeley $<
firmware.elf: cmsis_core silabs_sdk $(LIBRAIL) $(SOURCES) $(wildcard *.h) $(MGDIR)/mongoose.h link.ld Makefile mesh-config
arm-none-eabi-gcc $(SOURCES) $(LIBRAIL) $(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 $@
# Device headers, startup code and RAIL headers from Simplicity SDK
silabs_sdk:
GIT_LFS_SKIP_SMUDGE=1 git clone -q -c advice.detachedHead=false --depth 1 -b $(SDK_VERSION) --filter=blob:none --no-checkout https://github.com/SiliconLabs/simplicity_sdk $@
cd $@ && git sparse-checkout set --no-cone /platform/Device/SiliconLabs/EFR32MG24/ /platform/common/inc/ /platform/radio/rail_lib/common/ /platform/radio/rail_lib/protocol/ieee802154/ /platform/radio/rail_lib/chip/efr32/efr32xg2x/
cd $@ && GIT_LFS_SKIP_SMUDGE=1 git checkout -q
$(LIBRAIL):
curl -sfL $(LIBRAIL_URL) -o $@.tmp
echo "$(LIBRAIL_SHA256) $@.tmp" | shasum -a 256 -c -s
mv $@.tmp $@
# 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 over the on-board CMSIS-DAP probe. pyocd needs the EFR32MG24 pack:
# pyocd pack install efr32mg24b220f1536im48
# With several probes attached, choose one: make flash PYOCD_FLAGS="-u ID"
flash: firmware.bin
pyocd flash -t efr32mg24b220f1536im48 $(PYOCD_FLAGS) firmware.elf
clean:
rm -rf firmware.* cmsis_* silabs_* librail_*
+262
View File
@@ -0,0 +1,262 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
// Secure Engine data descriptor, sli_se_datatransfer_t in Simplicity SDK
struct se_dt {
volatile void *volatile data; // Data buffer
volatile void *volatile next; // Next descriptor, or 1: last one
volatile uint32_t length; // Length in bytes, plus flags
};
#define SE_CMD_TRNG_GET_RANDOM 0x07000000UL // SE mailbox commands
#define SE_DT_STOP 1UL // Descriptor flags
#define SE_DT_REALIGN 0x20000000UL
#define SE_RESPONSE_MASK 0x000F0000UL // RX_HEADER response code, 0: OK
// Fill buf with n random bytes, n a multiple of 4, from the Secure Engine
// TRNG (RM section 11.2.5), through the SE mailbox, as sl_se_get_random()
// and sli_se_mailbox_execute_command() in Simplicity SDK. buf and the
// descriptor are static: on the stack, at the top of RAM, the SE reported a
// bus error. Return false on SE error
static bool se_random(uint32_t *buf, uint32_t n) {
static struct se_dt out;
out.data = buf, out.next = (void *) SE_DT_STOP;
out.length = n | SE_DT_REALIGN;
while ((SEMAILBOX_HOST->TX_STATUS & SEMAILBOX_TX_STATUS_TXINT) == 0) (void) 0;
SEMAILBOX_HOST->TX_HEADER = sizeof(uint32_t) * 5; // 4 words + 1 parameter
SEMAILBOX_HOST->FIFO = SE_CMD_TRNG_GET_RANDOM;
SEMAILBOX_HOST->FIFO = 0; // No input data
SEMAILBOX_HOST->FIFO = (uint32_t) &out; // Output data
SEMAILBOX_HOST->FIFO = n; // Parameter: number of bytes
while ((SEMAILBOX_HOST->RX_STATUS & SEMAILBOX_RX_STATUS_RXINT) == 0) (void) 0;
return (SEMAILBOX_HOST->RX_HEADER & SE_RESPONSE_MASK) == 0;
}
// Random 32 bits, for mg_random(). One SE request fills a pool of 16 words.
// If the SE fails 3 times in a row, the pool is zeros: see se_random()
uint32_t hal_rng_read(void) {
static uint32_t pool[16];
static size_t avail;
if (avail == 0) {
int i;
for (i = 0; i < 3 && !se_random(pool, sizeof(pool)); i++) (void) 0;
if (i == 3) memset(pool, 0, sizeof(pool));
avail = sizeof(pool) / sizeof(pool[0]);
}
return pool[--avail];
}
// RAIL library needs these Simplicity SDK functions. Implemented as in the
// SDK: sl_core_cortexm.c, em_cmu.c, em_emu.c, em_system.c, sl_gpio.c,
// pa_conversions_efr32.c. Atomic sections mask IRQs with priority number 3
// and above, see hal_radio_init() for the IRQ priorities
#define CORE_ATOMIC_BASE_PRIORITY_LEVEL 3
uint32_t CORE_EnterCritical(void) {
uint32_t state = __get_PRIMASK();
__disable_irq();
return state;
}
void CORE_ExitCritical(uint32_t state) {
if (state == 0) __enable_irq(), __ISB();
}
uint32_t CORE_EnterAtomic(void) {
uint32_t state = __get_BASEPRI();
__set_BASEPRI(CORE_ATOMIC_BASE_PRIORITY_LEVEL << (8U - __NVIC_PRIO_BITS));
return state;
}
void CORE_ExitAtomic(uint32_t state) {
__set_BASEPRI(state);
__ISB();
}
// Clock IDs: bits 0..4 bit number, bits 5..6 register, 1 CLKEN0, 2 CLKEN1
void CMU_ClockEnable(uint32_t clock, bool enable) {
volatile uint32_t *reg = (clock >> 5) == 1 ? &CMU->CLKEN0
: (clock >> 5) == 2 ? &CMU->CLKEN1
: NULL;
if (reg == NULL) return;
if (enable) {
*reg |= BIT(clock & 31);
} else {
*reg &= ~BIT(clock & 31);
}
}
void EMU_DCDCSetPFMXTimeoutMaxCtrl(uint32_t value) {
bool clk_was_on = (CMU->CLKEN0 & CMU_CLKEN0_DCDC) != 0;
bool was_locked;
CMU->CLKEN0_SET = CMU_CLKEN0_DCDC;
was_locked = (DCDC->LOCKSTATUS & DCDC_LOCKSTATUS_LOCK) != 0;
DCDC->LOCK = DCDC_LOCK_LOCKKEY_UNLOCKKEY;
while (DCDC->SYNCBUSY & DCDC_SYNCBUSY_PFMXCTRL) (void) 0;
DCDC->PFMXCTRL = (DCDC->PFMXCTRL & ~_DCDC_PFMXCTRL_IPKTMAXCTRL_MASK) |
(value << _DCDC_PFMXCTRL_IPKTMAXCTRL_SHIFT);
if (was_locked) DCDC->LOCK = ~DCDC_LOCK_LOCKKEY_UNLOCKKEY;
if (!clk_was_on) CMU->CLKEN0_CLR = CMU_CLKEN0_DCDC;
}
// SYSTEM_ChipRevision_TypeDef, em_system.h
struct chip_rev {
uint8_t minor; // Minor revision number
uint8_t major; // Major revision number
uint8_t family; // Device family number
};
void SYSTEM_ChipRevisionGet(struct chip_rev *rev) {
uint32_t chiprev;
CMU->CLKEN0_SET = CMU_CLKEN0_SYSCFG;
chiprev = SYSCFG->CHIPREV;
rev->family = (chiprev & _SYSCFG_CHIPREV_FAMILY_MASK) >>
_SYSCFG_CHIPREV_FAMILY_SHIFT;
rev->major = (chiprev & _SYSCFG_CHIPREV_MAJOR_MASK) >>
_SYSCFG_CHIPREV_MAJOR_SHIFT;
rev->minor = (chiprev & _SYSCFG_CHIPREV_MINOR_MASK) >>
_SYSCFG_CHIPREV_MINOR_SHIFT;
}
// sl_gpio_t, sl_device_gpio.h
struct sl_gpio {
uint8_t port; // GPIO port number
uint8_t pin; // GPIO pin number
};
uint32_t sl_gpio_set_pin(const struct sl_gpio *gpio) {
GPIO->P_SET[gpio->port].DOUT = BIT(gpio->pin);
return 0;
}
uint32_t sl_gpio_clear_pin(const struct sl_gpio *gpio) {
GPIO->P_CLR[gpio->port].DOUT = BIT(gpio->pin);
return 0;
}
// We set TX power as a raw PA level and load no PA curves. RAIL calls this to
// check per-channel dBm limits: RAIL_TX_POWER_MAX means no limit, 255. Without
// curves the SDK returns 0, see RAIL_ConvertDbmToRaw(), pa_conversions_efr32.c
uint8_t RAIL_ConvertDbmToRaw(void *rail, int mode, int16_t power) {
(void) rail, (void) mode;
return power >= 0x7fff ? 255 : 0; // 0x7fff: RAIL_TX_POWER_MAX
}
// 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);
}
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)) void _init(void) {
}
+212
View File
@@ -0,0 +1,212 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// Seeed Studio XIAO MG24 hardware abstraction layer. EFR32MG24B220F1536IM48
// reference: Simplicity SDK, https://github.com/SiliconLabs/simplicity_sdk,
// platform/Device/SiliconLabs/EFR32MG24. Board: Seeed XIAO MG24 schematic,
// pin names from Silicon Labs Arduino core, variants/xiao_mg24
#pragma once
#include "em_device.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.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);
uint32_t hal_rng_read(void); // Random 32 bits, Secure Engine TRNG. See hal.c
#define BIT(x) (1UL << (x))
#define PIN(port, num) ((((port) - 'A') << 8) | (num))
#define PINNO(pin) (pin & 255)
#define PINPORT(pin) (pin >> 8)
#define RF_SW_PIN PIN('B', 4) // Antenna switch VCTL: low onboard, high UFL
#define RF_SW_PWR_PIN PIN('B', 5) // Antenna switch power
// HFXO crystal tuning, XIAO MG24 board value: Silicon Labs Arduino core,
// sl_device_init_hfxo_config.h. XO gets +40, see ctuneDelta in em_cmu.c
#define HFXO_CTUNE 95
#define HFXO_CTUNE_XO_DELTA 40
enum { GPIO_MODE_DISABLED = 0, GPIO_MODE_INPUT = 1, GPIO_MODE_PUSHPULL = 4 };
static inline void gpio_set_mode(uint16_t pin, uint32_t mode) {
volatile uint32_t *r = PINNO(pin) < 8 ? &GPIO->P[PINPORT(pin)].MODEL
: &GPIO->P[PINPORT(pin)].MODEH;
uint32_t shift = (PINNO(pin) & 7) * 4;
*r = (*r & ~(15UL << shift)) | (mode << shift);
}
static inline void gpio_write(uint16_t pin, bool val) {
if (val) {
GPIO->P_SET[PINPORT(pin)].DOUT = BIT(PINNO(pin));
} else {
GPIO->P_CLR[PINPORT(pin)].DOUT = BIT(PINNO(pin));
}
}
static inline void gpio_output(uint16_t pin, bool val) {
gpio_write(pin, val);
gpio_set_mode(pin, GPIO_MODE_PUSHPULL);
}
// Start the HFXO crystal oscillator, 39 MHz, and wait until it is ready.
// Steps and default values as in CMU_HFXOInit() and CMU_HFXOINIT_DEFAULT,
// emlib em_cmu.c. The radio needs HFXO, and RAIL expects it configured
static inline void hal_hfxo_init(void) {
CMU->CLKEN0_SET = CMU_CLKEN0_HFXO0;
HFXO0->LOCK = HFXO_LOCK_LOCKKEY_UNLOCK;
HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND | HFXO_CTRL_DISONDEMANDBUFOUT;
HFXO0->CTRL_CLR = HFXO_CTRL_FORCEEN;
while (HFXO0->STATUS & HFXO_STATUS_ENS) (void) 0; // Wait until disabled
while (HFXO0->STATUS & HFXO_STATUS_SYNCBUSY) (void) 0;
HFXO0->XTALCFG = HFXO_XTALCFG_TIMEOUTCBLSB_T416US |
HFXO_XTALCFG_TIMEOUTSTEADY_T833US | // First lock
(32U << _HFXO_XTALCFG_COREBIASSTARTUP_SHIFT) |
(32U << _HFXO_XTALCFG_COREBIASSTARTUPI_SHIFT);
HFXO0->XTALCTRL =
(HFXO0->XTALCTRL & _HFXO_XTALCTRL_SKIPCOREBIASOPT_MASK) |
HFXO_XTALCTRL_CTUNEFIXANA_XO | // 20 dBm PA parts, see em_cmu.h
((uint32_t) (HFXO_CTUNE + HFXO_CTUNE_XO_DELTA)
<< _HFXO_XTALCTRL_CTUNEXOANA_SHIFT) |
((uint32_t) HFXO_CTUNE << _HFXO_XTALCTRL_CTUNEXIANA_SHIFT) |
(60U << _HFXO_XTALCTRL_COREBIASANA_SHIFT);
HFXO0->CFG &= ~(_HFXO_CFG_SQBUFSCHTRGANA_MASK | _HFXO_CFG_ENXIDCBIASANA_MASK |
_HFXO_CFG_MODE_MASK); // Crystal mode
HFXO0->CTRL = (HFXO0->CTRL & ~(_HFXO_CTRL_FORCEXO2GNDANA_MASK |
_HFXO_CTRL_FORCEXI2GNDANA_MASK |
_HFXO_CTRL_DISONDEMAND_MASK |
_HFXO_CTRL_EM23ONDEMAND_MASK)) |
HFXO_CTRL_FORCEEN;
uint32_t ready = HFXO_STATUS_RDY | HFXO_STATUS_COREBIASOPTRDY |
HFXO_STATUS_ENS;
while ((HFXO0->STATUS & ready) != ready) (void) 0;
HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND;
HFXO0->XTALCFG = (HFXO0->XTALCFG & ~_HFXO_XTALCFG_TIMEOUTSTEADY_MASK) |
HFXO_XTALCFG_TIMEOUTSTEADY_T83US; // Subsequent locks
HFXO0->XTALCTRL_SET = HFXO_XTALCTRL_SKIPCOREBIASOPT;
HFXO0->CTRL_CLR = HFXO_CTRL_DISONDEMAND; // Radio can request it on demand
}
// Lock HFRCODPLL to HFXO at 78 MHz: 39 MHz * (N + 1) / (M + 1), N 3839,
// M 1919, phase lock, falling edge, auto recover. Values and steps as in
// Simplicity SDK CMU_DPLLLock() and the xG24 clock manager configuration.
// HFRCO band 72.6..80 MHz, tuned with its factory calibration from DEVINFO
static inline bool hal_dpll_78mhz(void) {
uint32_t lock = DPLL_IF_LOCK | DPLL_IF_LOCKFAILLOW | DPLL_IF_LOCKFAILHIGH;
CMU->CLKEN0_SET = CMU_CLKEN0_DPLL0 | CMU_CLKEN0_HFRCO0;
DPLL0->EN_CLR = DPLL_EN_EN;
while (DPLL0->EN & DPLL_EN_DISABLING) (void) 0;
while (HFRCO0->STATUS & (HFRCO_STATUS_SYNCBUSY | HFRCO_STATUS_FREQBSY))
(void) 0;
HFRCO0->CAL = 0xCCF09F00UL | (DEVINFO->HFRCODPLLCAL[16].HFRCODPLLCAL &
_HFRCO_CAL_TUNING_MASK);
DPLL0->CFG1 = (3839UL << _DPLL_CFG1_N_SHIFT) | (1919UL << _DPLL_CFG1_M_SHIFT);
CMU->DPLLREFCLKCTRL = CMU_DPLLREFCLKCTRL_CLKSEL_HFXO;
DPLL0->CFG = (1UL << _DPLL_CFG_AUTORECOVER_SHIFT) | // Edge: falling, 0
(_DPLL_CFG_MODE_PLL << _DPLL_CFG_MODE_SHIFT);
DPLL0->IF_CLR = lock;
DPLL0->EN_SET = DPLL_EN_EN;
while ((DPLL0->IF & lock) == 0) (void) 0;
return (DPLL0->IF & lock) == DPLL_IF_LOCK;
}
// Run the CPU at its maximum, 78 MHz, from HFRCODPLL locked to HFXO. First
// set the limits for 78 MHz, datasheet table 4.2 and em_cmu.c: flash 1 wait
// state above 40 MHz; PCLK max 50 MHz, so SYSCLK / 2; radio HCLK max 40 MHz,
// so SYSCLK / 2. Voltage scaling is VSCALE2 after reset, fine for 78 MHz.
// If the DPLL does not lock, stay on HFXO, 39 MHz
static inline void hal_clock_init(void) {
hal_hfxo_init();
CMU->CLKEN1_SET = CMU_CLKEN1_MSC;
MSC->LOCK = MSC_LOCK_LOCKKEY_UNLOCK;
MSC->READCTRL = (MSC->READCTRL & ~_MSC_READCTRL_MODE_MASK) |
MSC_READCTRL_MODE_WS1;
CMU->SYSCLKCTRL_SET = CMU_SYSCLKCTRL_PCLKPRESC | CMU_SYSCLKCTRL_RHCLKPRESC;
if (hal_dpll_78mhz()) {
SystemHFRCODPLLClockSet(78000000);
CMU->SYSCLKCTRL = (CMU->SYSCLKCTRL & ~_CMU_SYSCLKCTRL_CLKSEL_MASK) |
CMU_SYSCLKCTRL_CLKSEL_HFRCODPLL;
} else {
CMU->SYSCLKCTRL = (CMU->SYSCLKCTRL & ~_CMU_SYSCLKCTRL_CLKSEL_MASK) |
CMU_SYSCLKCTRL_CLKSEL_HFXO;
}
SystemCoreClock = SystemSYSCLKGet(); // HCLK = SYSCLK, no prescaler
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
// PCLK, the peripheral clock: SYSCLK, divided by 2 if PCLKPRESC is set
static inline uint32_t hal_pclk(void) {
bool half = (CMU->SYSCLKCTRL & CMU_SYSCLKCTRL_PCLKPRESC) != 0;
return half ? SystemCoreClock / 2 : SystemCoreClock;
}
static inline void hal_gpio_output(uint16_t pin) {
CMU->CLKEN0_SET = CMU_CLKEN0_GPIO;
gpio_set_mode(pin, GPIO_MODE_PUSHPULL);
}
static inline void hal_gpio_toggle(uint16_t pin) {
GPIO->P_TGL[PINPORT(pin)].DOUT = BIT(PINNO(pin));
}
// USART0, 8N1, on tx and rx pins: any pin, through GPIO routing. On XIAO
// MG24, PA8 TX and PA9 RX go to the SAMD11 USB serial bridge. USART0 is
// clocked by PCLK
static inline void hal_uart_init(USART_TypeDef *uart, uint16_t tx, uint16_t rx,
unsigned long baud) {
uint32_t ovs = 16, div, pclk = hal_pclk();
CMU->CLKEN0_SET = CMU_CLKEN0_GPIO | CMU_CLKEN0_USART0;
gpio_output(tx, 1);
gpio_set_mode(rx, GPIO_MODE_INPUT);
GPIO->USARTROUTE[0].TXROUTE =
((uint32_t) PINPORT(tx) << _GPIO_USART_TXROUTE_PORT_SHIFT) |
((uint32_t) PINNO(tx) << _GPIO_USART_TXROUTE_PIN_SHIFT);
GPIO->USARTROUTE[0].RXROUTE =
((uint32_t) PINPORT(rx) << _GPIO_USART_RXROUTE_PORT_SHIFT) |
((uint32_t) PINNO(rx) << _GPIO_USART_RXROUTE_PIN_SHIFT);
GPIO->USARTROUTE[0].ROUTEEN =
GPIO_USART_ROUTEEN_TXPEN | GPIO_USART_ROUTEEN_RXPEN;
uart->EN = USART_EN_EN;
uart->CTRL = 0; // Async, 16x oversampling
uart->FRAME = USART_FRAME_DATABITS_EIGHT | USART_FRAME_STOPBITS_ONE |
USART_FRAME_PARITY_NONE;
// CLKDIV = 256 * (fPCLK / (ovs * baud) - 1), see USART_BaudrateAsyncSet()
div = (32 * pclk + (ovs * baud) / 2) / (ovs * baud) - 32;
uart->CLKDIV = (div * 8) & _USART_CLKDIV_DIV_MASK;
uart->CMD = USART_CMD_TXEN | USART_CMD_RXEN;
}
static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) {
while ((uart->STATUS & USART_STATUS_TXBL) == 0) (void) 0;
uart->TXDATA = byte;
}
static inline void hal_uart_write_buf(USART_TypeDef *uart, const char *buf,
size_t len) {
while (len-- > 0) hal_uart_write_byte(uart, *(const uint8_t *) buf++);
}
// Random numbers come from the Secure Engine TRNG, see hal_rng_read(): clock
// its mailbox
static inline void hal_rng_init(void) {
CMU->CLKEN1_SET = CMU_CLKEN1_SEMAILBOXHOST;
}
// Prepare for RAIL. Set all IRQs to priority 5, as Silicon Labs
// sl_interrupt_manager_init() does: RAIL atomic sections mask priority
// numbers 3 and above. Route the radio to the onboard ceramic antenna.
// FM8625H switch: VCTL low connects ANT to RF1, the chip antenna; high RF2,
// the U.FL socket
static inline void hal_radio_init(void) {
for (int i = 0; i < EXT_IRQ_COUNT; i++) NVIC_SetPriority((IRQn_Type) i, 5);
CMU->CLKEN0_SET = CMU_CLKEN0_GPIO;
gpio_output(RF_SW_PWR_PIN, 1);
gpio_output(RF_SW_PIN, 0);
}
@@ -0,0 +1,48 @@
/* EFR32MG24B220F1536IM48 memory map, see Simplicity SDK linkerfile_base.ld */
ENTRY(Reset_Handler);
MEMORY {
flash(rx) : ORIGIN = 0x08000000, LENGTH = 1536k
sram(rwx) : ORIGIN = 0x20000000, LENGTH = 256k
}
__StackTop = ORIGIN(sram) + LENGTH(sram); /* Stack points here */
__StackLimit = __StackTop - 8192; /* MSPLIM, see startup */
SECTIONS {
.vectors : { KEEP(*(.vectors)) . = ALIGN(4); } > flash
.text : { *(.text* .text.* text_*) } > flash /* text_*: SDK code */
.rodata : { *(.rodata*) . = ALIGN(4); } > flash
/* RAM functions, copied by CopyToRam() in the startup code */
.ramfuncs : {
. = ALIGN(4);
__ramfuncs_start__ = .;
*(.ram .ram.*)
. = ALIGN(4);
__ramfuncs_end__ = .;
} > sram AT > flash
__lma_ramfuncs_start__ = LOADADDR(.ramfuncs);
__lma_ramfuncs_end__ = LOADADDR(.ramfuncs) + SIZEOF(.ramfuncs);
.data : {
__data_start__ = .;
*(.first_data)
*(.iram .iram.*) /* Mongoose MG_IRAM functions */
*(.data SORT(.data.*))
. = ALIGN(4);
__data_end__ = .;
} > sram AT > flash
__etext = LOADADDR(.data); /* Startup copies .data from here */
.bss : {
__bss_start__ = .;
*(.bss SORT(.bss.*) COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > 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 */
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
#include "mongoose.h"
#ifndef UART_DEBUG
#define UART_DEBUG USART0 // SAMD11 USB serial port
#define UART_DEBUG_TX_PIN PIN('A', 8)
#define UART_DEBUG_RX_PIN PIN('A', 9)
#else
#define UART_DEBUG_TX_PIN PIN('C', 6) // D6, D7 header pins
#define UART_DEBUG_RX_PIN PIN('C', 7)
#endif
#define LED1 PIN('A', 7) // User LED, active low
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(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_clock_init();
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);
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 (;;) {
mg_mgr_poll(&mgr, 0);
mg_mqtt_poll(&mgr);
blink_task();
}
return 0;
}
@@ -0,0 +1,34 @@
#pragma once
#include "em_device.h"
// 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 "mg24" // For tutorials/mqtt/mqtt-client
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_DRIVER_MG24 1
#define MG_ENABLE_CUSTOM_MILLIS 1
#define MG_ENABLE_CUSTOM_RANDOM 1 // Secure Engine TRNG, see hal.c
#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 factory EUI-64, DEVINFO, locally administered
#define MG_SET_MAC_ADDRESS(mac) \
do { \
uint32_t eui_[2] = {DEVINFO->EUI64L, DEVINFO->EUI64H}; \
uint8_t b_[32]; \
mg_sha256(b_, (uint8_t *) eui_, sizeof(eui_)); \
(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 "mg24.1.0.0"
@@ -1,4 +1,5 @@
#pragma once #pragma once
#include <stm32h5xx_hal.h>
#define MG_ARCH MG_ARCH_CUBE #define MG_ARCH MG_ARCH_CUBE
#define MG_ENABLE_LFS 1 #define MG_ENABLE_LFS 1
#define MG_LFS_SIZE (128 * 1024) #define MG_LFS_SIZE (128 * 1024)
@@ -0,0 +1,28 @@
[PreviousGenFiles]
AdvancedFolderStructure=true
HeaderFileListSize=4
HeaderFiles#0=..\Core\Inc\FreeRTOSConfig.h
HeaderFiles#1=..\Core\Inc\stm32h7xx_it.h
HeaderFiles#2=..\Core\Inc\stm32h7xx_hal_conf.h
HeaderFiles#3=..\Core\Inc\main.h
HeaderFolderListSize=1
HeaderPath#0=..\Core\Inc
HeaderFiles=;
SourceFileListSize=5
SourceFiles#0=..\Core\Src\freertos.c
SourceFiles#1=..\Core\Src\stm32h7xx_it.c
SourceFiles#2=..\Core\Src\stm32h7xx_hal_msp.c
SourceFiles#3=..\Core\Src\stm32h7xx_hal_timebase_tim.c
SourceFiles#4=..\Core\Src\main.c
SourceFolderListSize=1
SourcePath#0=..\Core\Src
SourceFiles=;
[PreviousLibFiles]
LibFiles=Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_eth.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_eth_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_bus.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_crs.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_system.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_utils.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dmamux.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_mdma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_def.h;Drivers\STM32H7xx_HAL_Driver\Inc\Legacy\stm32_hal_legacy.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rng.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rng_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_rng.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_usart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_lpuart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart_ex.h;Middlewares\Third_Party\FreeRTOS\Source\include\croutine.h;Middlewares\Third_Party\FreeRTOS\Source\include\deprecated_definitions.h;Middlewares\Third_Party\FreeRTOS\Source\include\event_groups.h;Middlewares\Third_Party\FreeRTOS\Source\include\FreeRTOS.h;Middlewares\Third_Party\FreeRTOS\Source\include\list.h;Middlewares\Third_Party\FreeRTOS\Source\include\message_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_prototypes.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_wrappers.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_syscall_numbers.h;Middlewares\Third_Party\FreeRTOS\Source\include\portable.h;Middlewares\Third_Party\FreeRTOS\Source\include\projdefs.h;Middlewares\Third_Party\FreeRTOS\Source\include\queue.h;Middlewares\Third_Party\FreeRTOS\Source\include\semphr.h;Middlewares\Third_Party\FreeRTOS\Source\include\stack_macros.h;Middlewares\Third_Party\FreeRTOS\Source\include\StackMacros.h;Middlewares\Third_Party\FreeRTOS\Source\include\stream_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\task.h;Middlewares\Third_Party\FreeRTOS\Source\include\timers.h;Middlewares\Third_Party\FreeRTOS\Source\include\newlib-freertos.h;Middlewares\Third_Party\FreeRTOS\Source\include\picolibc-freertos.h;Middlewares\Third_Party\FreeRTOS\Source\include\atomic.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_mpool.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_os2.h;Middlewares\Third_Party\FreeRTOS\Source\portable\GCC\ARM_CM4F\portmacro.h;Drivers\CMSIS\RTOS2\Include\cmsis_os2.h;Drivers\CMSIS\RTOS2\Include\os_tick.h;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_cortex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_eth.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_eth_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_gpio.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_hsem.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_mdma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c.c;Drivers\STM32H7xx_Line truncated
[PreviousUsedCMakes]
SourceFiles=Core\Src\main.c;Core\Src\freertos.c;Core\Src\stm32h7xx_it.c;Core\Src\stm32h7xx_hal_msp.c;Core\Src\stm32h7xx_hal_timebase_tim.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_cortex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_eth.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_eth_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_gpio.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_hsem.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_mdma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_exti.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rng.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rng_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart_ex.c;Middlewares\Third_Party\FreeRTOS\Source\portable\Common\mpu_wrappers_v2.c;Middlewares\Third_Party\FreeRTOS\Source\croutine.c;Middlewares\Third_Party\FreeRTOS\Source\event_groups.c;Middlewares\Third_Party\FreeRTOS\Source\list.c;Middlewares\Third_Party\FreeRTOS\Source\queue.c;Middlewares\Third_Party\FreeRTOS\Source\stream_buffer.c;Middlewares\Third_Party\FreeRTOS\Source\tasks.c;Middlewares\Third_Party\FreeRTOS\Source\timers.c;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.c;Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c;Middlewares\Third_Party\FreeRTOS\Source\portable\GCC\ARM_CM4F\port.c;Drivers\CMSIS\Device\ST\STM32H7xx\Source\Templates\system_stm32h7xx.c;Core\Src\system_stm32h7xx.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_cortex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_eth.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_eth_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_gpio.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_hsem.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_mdma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_exti.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rng.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rng_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart_ex.c;Middlewares\Third_Party\FreeRTOS\Source\portable\Common\mpu_wrappers_v2.c;Middlewares\Third_Party\FreeRTOS\Source\croutine.c;Middlewares\Third_Party\FreeRTOS\Source\event_groups.c;Middlewares\Third_Party\FreeRTOS\Source\list.c;Middlewares\Third_Party\FreeRTOS\Source\queue.c;Middlewares\Third_Party\FreeRTOS\Source\stream_buffer.c;Middlewares\Third_Party\FreeRTOS\Source\tasks.c;Middlewares\Third_Party\FreeRTOS\Source\timers.c;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.c;Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c;Middlewares\Third_Party\FreeRTOS\Source\portable\GCC\ARM_CM4F\port.c;Drivers\CMSIS\Device\ST\STM32H7xx\Source\Templates\system_stm32h7xx.c;Core\Src\system_stm32h7xx.c;;;Middlewares\Third_Party\FreeRTOS\Source\portable\Common\mpu_wrappers_v2.c;Middlewares\Third_Party\FreeRTOS\Source\croutine.c;Middlewares\Third_Party\FreeRTOS\Source\event_groups.c;Middlewares\Third_Party\FreeRTOS\Source\list.c;Middlewares\Third_Party\FreeRTOS\Source\queue.c;Middlewares\Third_Party\FreeRTOS\Source\stream_buffer.c;Middlewares\Third_Party\FreeRTOS\Source\tasks.c;Middlewares\Third_Party\FreeRTOS\Source\timers.c;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.c;Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c;Middlewares\Third_Party\FreeRTOS\Source\portable\GCC\ARM_CM4F\port.c;
HeaderPath=Drivers\STM32H7xx_HAL_Driver\Inc;Drivers\STM32H7xx_HAL_Driver\Inc\Legacy;Middlewares\Third_Party\FreeRTOS\Source\include;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2;Middlewares\Third_Party\FreeRTOS\Source\portable\GCC\ARM_CM4F;Drivers\CMSIS\RTOS2\Include;Drivers\CMSIS\Device\ST\STM32H7xx\Include;Drivers\CMSIS\Include;Core\Inc;
CDefines=USE_PWR_LDO_SUPPLY;USE_HAL_DRIVER;STM32H723xx;USE_HAL_DRIVER;USE_HAL_DRIVER;
@@ -0,0 +1,77 @@
cmake_minimum_required(VERSION 3.22)
#
# This file is generated only once,
# and is not re-generated if converter is called multiple times.
#
# User is free to modify the file as much as necessary
#
# Setup compiler settings
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS ON)
# Define the build type
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
# Set the project name
set(CMAKE_PROJECT_NAME nucleo-h723zg)
# Enable compile command to ease indexing with e.g. clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
# Core project settings
project(${CMAKE_PROJECT_NAME})
message("Build type: " ${CMAKE_BUILD_TYPE})
# Enable CMake support for ASM and C languages
enable_language(C ASM)
# Create an executable object type
add_executable(${CMAKE_PROJECT_NAME})
# Add STM32CubeMX generated sources
add_subdirectory(cmake/stm32cubemx)
# Link directories setup
target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
# Add user defined library search paths
)
# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
# Add user sources here
${CMAKE_SOURCE_DIR}/../../../../mongoose.c
)
# Add include paths
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
# Add user defined include paths
${CMAKE_SOURCE_DIR}/mongoose/
${CMAKE_SOURCE_DIR}/../../../../
)
# Add project symbols (macros)
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
# Add user defined symbols
)
# Remove wrong libob.a library dependency when using cpp files
list(REMOVE_ITEM CMAKE_C_IMPLICIT_LINK_LIBRARIES ob)
# Add linked libraries
target_link_libraries(${CMAKE_PROJECT_NAME}
stm32cubemx
# Add user defined libraries
)
# Generate .bin from .elf
add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
COMMAND arm-none-eabi-objcopy -O binary $<TARGET_FILE:${CMAKE_PROJECT_NAME}> ${CMAKE_PROJECT_NAME}.bin
VERBATIM
)
@@ -0,0 +1,38 @@
{
"version": 3,
"configurePresets": [
{
"name": "default",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"toolchainFile": "${sourceDir}/cmake/gcc-arm-none-eabi.cmake",
"cacheVariables": {
}
},
{
"name": "Debug",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "Release",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
],
"buildPresets": [
{
"name": "Debug",
"configurePreset": "Debug"
},
{
"name": "Release",
"configurePreset": "Release"
}
]
}
@@ -0,0 +1,172 @@
/* USER CODE BEGIN Header */
/*
* FreeRTOS Kernel V10.6.2
* Portion Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Portion Copyright (C) 2019 StMicroelectronics, Inc. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE .
*
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
*
* 1 tab == 4 spaces!
*/
/* USER CODE END Header */
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* These parameters and more are described within the 'configuration' section of the
* FreeRTOS API documentation available on the FreeRTOS.org web site.
*
* See http://www.freertos.org/a00110.html
*----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* Section where include file can be added */
/* USER CODE END Includes */
/* Ensure definitions are only used by the compiler, and not by the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
#ifndef CMSIS_device_header
#define CMSIS_device_header "stm32h7xx.h"
#endif /* CMSIS_device_header */
#define configENABLE_FPU 0
#define configENABLE_MPU 0
#define configUSE_PREEMPTION 1
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( SystemCoreClock )
#define configTICK_RATE_HZ ((TickType_t)1000)
#define configMAX_PRIORITIES ( 56 )
#define configUSE_SB_COMPLETED_CALLBACK ( 0 )
#define configUSE_MINI_LIST_ITEM ( 1 )
#define configMINIMAL_STACK_SIZE ((uint16_t)128)
#define configTOTAL_HEAP_SIZE ((size_t)65536)
#define configMAX_TASK_NAME_LEN ( 16 )
#define configHEAP_CLEAR_MEMORY_ON_FREE 0
#define configUSE_TRACE_FACILITY 1
#define configRECORD_STACK_HIGH_ADDRESS 1
#define configUSE_16_BIT_TICKS 0
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 8
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
/* USER CODE BEGIN MESSAGE_BUFFER_LENGTH_TYPE */
/* Defaults to size_t for backward compatibility, but can be changed
if lengths will always be less than the number of bytes in a size_t. */
#define configMESSAGE_BUFFER_LENGTH_TYPE size_t
/* USER CODE END MESSAGE_BUFFER_LENGTH_TYPE */
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 )
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH 256
/* CMSIS-RTOS V2 flags */
#define configUSE_OS2_THREAD_SUSPEND_RESUME 1
#define configUSE_OS2_THREAD_ENUMERATE 1
#define configUSE_OS2_EVENTFLAGS_FROM_ISR 1
#define configUSE_OS2_THREAD_FLAGS 1
#define configUSE_OS2_TIMER 1
#define configUSE_OS2_MUTEX 1
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_xTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_xTaskGetSchedulerState 1
#define INCLUDE_xTimerPendFunctionCall 1
#define INCLUDE_xQueueGetMutexHolder 1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_eTaskGetState 1
/*
* The CMSIS-RTOS V2 FreeRTOS wrapper is dependent on the heap implementation used
* by the application thus the correct define need to be enabled below
*/
#define USE_FreeRTOS_HEAP_4
/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
/* USER CODE BEGIN 1 */
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}
/* USER CODE END 1 */
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
/* IMPORTANT: After 10.3.1 update, Systick_Handler comes from NVIC (if SYS timebase = systick), otherwise from cmsis_os2.c */
#define USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION 0
/* USER CODE BEGIN Defines */
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
/* USER CODE END Defines */
#endif /* FREERTOS_CONFIG_H */
@@ -0,0 +1,69 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.h
* @brief : Header for main.c file.
* This file contains the common defines of the application.
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32h7xx_hal.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */
/* USER CODE END ET */
/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */
/* USER CODE END EC */
/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */
/* USER CODE END EM */
/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);
/* USER CODE BEGIN EFP */
/* USER CODE END EFP */
/* Private defines -----------------------------------------------------------*/
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
#ifdef __cplusplus
}
#endif
#endif /* __MAIN_H */
@@ -0,0 +1,514 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file stm32h7xx_hal_conf.h
* @author MCD Application Team
* @brief HAL configuration file.
******************************************************************************
* @attention
*
* Copyright (c) 2017 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef STM32H7xx_HAL_CONF_H
#define STM32H7xx_HAL_CONF_H
#ifdef __cplusplus
extern "C" {
#endif
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* ########################## Module Selection ############################## */
/**
* @brief This is the list of modules to be used in the HAL driver
*/
#define HAL_MODULE_ENABLED
/* #define HAL_ADC_MODULE_ENABLED */
/* #define HAL_FDCAN_MODULE_ENABLED */
/* #define HAL_FMAC_MODULE_ENABLED */
/* #define HAL_CEC_MODULE_ENABLED */
/* #define HAL_COMP_MODULE_ENABLED */
/* #define HAL_CORDIC_MODULE_ENABLED */
/* #define HAL_CRC_MODULE_ENABLED */
/* #define HAL_CRYP_MODULE_ENABLED */
/* #define HAL_DAC_MODULE_ENABLED */
/* #define HAL_DCMI_MODULE_ENABLED */
/* #define HAL_DMA2D_MODULE_ENABLED */
#define HAL_ETH_MODULE_ENABLED
/* #define HAL_ETH_LEGACY_MODULE_ENABLED */
/* #define HAL_NAND_MODULE_ENABLED */
/* #define HAL_NOR_MODULE_ENABLED */
/* #define HAL_OTFDEC_MODULE_ENABLED */
/* #define HAL_SRAM_MODULE_ENABLED */
/* #define HAL_SDRAM_MODULE_ENABLED */
/* #define HAL_HASH_MODULE_ENABLED */
/* #define HAL_HRTIM_MODULE_ENABLED */
/* #define HAL_HSEM_MODULE_ENABLED */
/* #define HAL_GFXMMU_MODULE_ENABLED */
/* #define HAL_JPEG_MODULE_ENABLED */
/* #define HAL_OPAMP_MODULE_ENABLED */
/* #define HAL_OSPI_MODULE_ENABLED */
/* #define HAL_I2S_MODULE_ENABLED */
/* #define HAL_SMBUS_MODULE_ENABLED */
/* #define HAL_IWDG_MODULE_ENABLED */
/* #define HAL_LPTIM_MODULE_ENABLED */
/* #define HAL_LTDC_MODULE_ENABLED */
/* #define HAL_QSPI_MODULE_ENABLED */
/* #define HAL_RAMECC_MODULE_ENABLED */
#define HAL_RNG_MODULE_ENABLED
/* #define HAL_RTC_MODULE_ENABLED */
/* #define HAL_SAI_MODULE_ENABLED */
/* #define HAL_SD_MODULE_ENABLED */
/* #define HAL_MMC_MODULE_ENABLED */
/* #define HAL_SPDIFRX_MODULE_ENABLED */
/* #define HAL_SPI_MODULE_ENABLED */
/* #define HAL_SWPMI_MODULE_ENABLED */
#define HAL_TIM_MODULE_ENABLED
#define HAL_UART_MODULE_ENABLED
/* #define HAL_USART_MODULE_ENABLED */
/* #define HAL_IRDA_MODULE_ENABLED */
/* #define HAL_SMARTCARD_MODULE_ENABLED */
/* #define HAL_WWDG_MODULE_ENABLED */
/* #define HAL_PCD_MODULE_ENABLED */
/* #define HAL_HCD_MODULE_ENABLED */
/* #define HAL_DFSDM_MODULE_ENABLED */
/* #define HAL_DSI_MODULE_ENABLED */
/* #define HAL_JPEG_MODULE_ENABLED */
/* #define HAL_MDIOS_MODULE_ENABLED */
/* #define HAL_PSSI_MODULE_ENABLED */
/* #define HAL_DTS_MODULE_ENABLED */
#define HAL_GPIO_MODULE_ENABLED
#define HAL_DMA_MODULE_ENABLED
#define HAL_MDMA_MODULE_ENABLED
#define HAL_RCC_MODULE_ENABLED
#define HAL_FLASH_MODULE_ENABLED
#define HAL_EXTI_MODULE_ENABLED
#define HAL_PWR_MODULE_ENABLED
#define HAL_I2C_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED
#define HAL_HSEM_MODULE_ENABLED
/* ########################## Oscillator Values adaptation ####################*/
/**
* @brief Adjust the value of External High Speed oscillator (HSE) used in your application.
* This value is used by the RCC HAL module to compute the system frequency
* (when HSE is used as system clock source, directly or through the PLL).
*/
#if !defined (HSE_VALUE)
#define HSE_VALUE (25000000UL) /*!< Value of the External oscillator in Hz : FPGA case fixed to 60MHZ */
#endif /* HSE_VALUE */
#if !defined (HSE_STARTUP_TIMEOUT)
#define HSE_STARTUP_TIMEOUT (100UL) /*!< Time out for HSE start up, in ms */
#endif /* HSE_STARTUP_TIMEOUT */
/**
* @brief Internal oscillator (CSI) default value.
* This value is the default CSI value after Reset.
*/
#if !defined (CSI_VALUE)
#define CSI_VALUE (4000000UL) /*!< Value of the Internal oscillator in Hz*/
#endif /* CSI_VALUE */
/**
* @brief Internal High Speed oscillator (HSI) value.
* This value is used by the RCC HAL module to compute the system frequency
* (when HSI is used as system clock source, directly or through the PLL).
*/
#if !defined (HSI_VALUE)
#define HSI_VALUE (64000000UL) /*!< Value of the Internal oscillator in Hz*/
#endif /* HSI_VALUE */
/**
* @brief External Low Speed oscillator (LSE) value.
* This value is used by the UART, RTC HAL module to compute the system frequency
*/
#if !defined (LSE_VALUE)
#define LSE_VALUE (32768UL) /*!< Value of the External oscillator in Hz*/
#endif /* LSE_VALUE */
#if !defined (LSE_STARTUP_TIMEOUT)
#define LSE_STARTUP_TIMEOUT (5000UL) /*!< Time out for LSE start up, in ms */
#endif /* LSE_STARTUP_TIMEOUT */
#if !defined (LSI_VALUE)
#define LSI_VALUE (32000UL) /*!< LSI Typical Value in Hz*/
#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz
The real value may vary depending on the variations
in voltage and temperature.*/
/**
* @brief External clock source for I2S peripheral
* This value is used by the I2S HAL module to compute the I2S clock source
* frequency, this source is inserted directly through I2S_CKIN pad.
*/
#if !defined (EXTERNAL_CLOCK_VALUE)
#define EXTERNAL_CLOCK_VALUE 12288000UL /*!< Value of the External clock in Hz*/
#endif /* EXTERNAL_CLOCK_VALUE */
/* Tip: To avoid modifying this file each time you need to use different HSE,
=== you can define the HSE value in your toolchain compiler preprocessor. */
/* ########################### System Configuration ######################### */
/**
* @brief This is the HAL system configuration section
*/
#define VDD_VALUE (3300UL) /*!< Value of VDD in mv */
#define TICK_INT_PRIORITY (15UL) /*!< tick interrupt priority */
#define USE_RTOS 0
#define USE_SD_TRANSCEIVER 0U /*!< use uSD Transceiver */
#define USE_SPI_CRC 0U /*!< use CRC in SPI */
#define USE_HAL_ADC_REGISTER_CALLBACKS 0U /* ADC register callback disabled */
#define USE_HAL_CEC_REGISTER_CALLBACKS 0U /* CEC register callback disabled */
#define USE_HAL_COMP_REGISTER_CALLBACKS 0U /* COMP register callback disabled */
#define USE_HAL_CORDIC_REGISTER_CALLBACKS 0U /* CORDIC register callback disabled */
#define USE_HAL_CRYP_REGISTER_CALLBACKS 0U /* CRYP register callback disabled */
#define USE_HAL_DAC_REGISTER_CALLBACKS 0U /* DAC register callback disabled */
#define USE_HAL_DCMI_REGISTER_CALLBACKS 0U /* DCMI register callback disabled */
#define USE_HAL_DFSDM_REGISTER_CALLBACKS 0U /* DFSDM register callback disabled */
#define USE_HAL_DMA2D_REGISTER_CALLBACKS 0U /* DMA2D register callback disabled */
#define USE_HAL_DSI_REGISTER_CALLBACKS 0U /* DSI register callback disabled */
#define USE_HAL_DTS_REGISTER_CALLBACKS 0U /* DTS register callback disabled */
#define USE_HAL_ETH_REGISTER_CALLBACKS 0U /* ETH register callback disabled */
#define USE_HAL_FDCAN_REGISTER_CALLBACKS 0U /* FDCAN register callback disabled */
#define USE_HAL_FMAC_REGISTER_CALLBACKS 0U /* FMAC register callback disabled */
#define USE_HAL_NAND_REGISTER_CALLBACKS 0U /* NAND register callback disabled */
#define USE_HAL_NOR_REGISTER_CALLBACKS 0U /* NOR register callback disabled */
#define USE_HAL_SDRAM_REGISTER_CALLBACKS 0U /* SDRAM register callback disabled */
#define USE_HAL_SRAM_REGISTER_CALLBACKS 0U /* SRAM register callback disabled */
#define USE_HAL_HASH_REGISTER_CALLBACKS 0U /* HASH register callback disabled */
#define USE_HAL_HCD_REGISTER_CALLBACKS 0U /* HCD register callback disabled */
#define USE_HAL_GFXMMU_REGISTER_CALLBACKS 0U /* GFXMMU register callback disabled */
#define USE_HAL_HRTIM_REGISTER_CALLBACKS 0U /* HRTIM register callback disabled */
#define USE_HAL_I2C_REGISTER_CALLBACKS 0U /* I2C register callback disabled */
#define USE_HAL_I2S_REGISTER_CALLBACKS 0U /* I2S register callback disabled */
#define USE_HAL_IRDA_REGISTER_CALLBACKS 0U /* IRDA register callback disabled */
#define USE_HAL_JPEG_REGISTER_CALLBACKS 0U /* JPEG register callback disabled */
#define USE_HAL_LPTIM_REGISTER_CALLBACKS 0U /* LPTIM register callback disabled */
#define USE_HAL_LTDC_REGISTER_CALLBACKS 0U /* LTDC register callback disabled */
#define USE_HAL_MDIOS_REGISTER_CALLBACKS 0U /* MDIO register callback disabled */
#define USE_HAL_MMC_REGISTER_CALLBACKS 0U /* MMC register callback disabled */
#define USE_HAL_OPAMP_REGISTER_CALLBACKS 0U /* MDIO register callback disabled */
#define USE_HAL_OSPI_REGISTER_CALLBACKS 0U /* OSPI register callback disabled */
#define USE_HAL_OTFDEC_REGISTER_CALLBACKS 0U /* OTFDEC register callback disabled */
#define USE_HAL_PCD_REGISTER_CALLBACKS 0U /* PCD register callback disabled */
#define USE_HAL_QSPI_REGISTER_CALLBACKS 0U /* QSPI register callback disabled */
#define USE_HAL_RNG_REGISTER_CALLBACKS 0U /* RNG register callback disabled */
#define USE_HAL_RTC_REGISTER_CALLBACKS 0U /* RTC register callback disabled */
#define USE_HAL_SAI_REGISTER_CALLBACKS 0U /* SAI register callback disabled */
#define USE_HAL_SD_REGISTER_CALLBACKS 0U /* SD register callback disabled */
#define USE_HAL_SMARTCARD_REGISTER_CALLBACKS 0U /* SMARTCARD register callback disabled */
#define USE_HAL_SPDIFRX_REGISTER_CALLBACKS 0U /* SPDIFRX register callback disabled */
#define USE_HAL_SMBUS_REGISTER_CALLBACKS 0U /* SMBUS register callback disabled */
#define USE_HAL_SPI_REGISTER_CALLBACKS 0U /* SPI register callback disabled */
#define USE_HAL_SWPMI_REGISTER_CALLBACKS 0U /* SWPMI register callback disabled */
#define USE_HAL_TIM_REGISTER_CALLBACKS 0U /* TIM register callback disabled */
#define USE_HAL_UART_REGISTER_CALLBACKS 0U /* UART register callback disabled */
#define USE_HAL_USART_REGISTER_CALLBACKS 0U /* USART register callback disabled */
#define USE_HAL_WWDG_REGISTER_CALLBACKS 0U /* WWDG register callback disabled */
/* ########################### Ethernet Configuration ######################### */
#define ETH_TX_DESC_CNT 4U /* number of Ethernet Tx DMA descriptors */
#define ETH_RX_DESC_CNT 4U /* number of Ethernet Rx DMA descriptors */
#define ETH_MAC_ADDR0 (0x02UL)
#define ETH_MAC_ADDR1 (0x00UL)
#define ETH_MAC_ADDR2 (0x00UL)
#define ETH_MAC_ADDR3 (0x00UL)
#define ETH_MAC_ADDR4 (0x00UL)
#define ETH_MAC_ADDR5 (0x00UL)
/* ########################## Assert Selection ############################## */
/**
* @brief Uncomment the line below to expanse the "assert_param" macro in the
* HAL drivers code
*/
/* #define USE_FULL_ASSERT 1U */
/* Includes ------------------------------------------------------------------*/
/**
* @brief Include module's header file
*/
#ifdef HAL_RCC_MODULE_ENABLED
#include "stm32h7xx_hal_rcc.h"
#endif /* HAL_RCC_MODULE_ENABLED */
#ifdef HAL_GPIO_MODULE_ENABLED
#include "stm32h7xx_hal_gpio.h"
#endif /* HAL_GPIO_MODULE_ENABLED */
#ifdef HAL_DMA_MODULE_ENABLED
#include "stm32h7xx_hal_dma.h"
#endif /* HAL_DMA_MODULE_ENABLED */
#ifdef HAL_MDMA_MODULE_ENABLED
#include "stm32h7xx_hal_mdma.h"
#endif /* HAL_MDMA_MODULE_ENABLED */
#ifdef HAL_HASH_MODULE_ENABLED
#include "stm32h7xx_hal_hash.h"
#endif /* HAL_HASH_MODULE_ENABLED */
#ifdef HAL_DCMI_MODULE_ENABLED
#include "stm32h7xx_hal_dcmi.h"
#endif /* HAL_DCMI_MODULE_ENABLED */
#ifdef HAL_DMA2D_MODULE_ENABLED
#include "stm32h7xx_hal_dma2d.h"
#endif /* HAL_DMA2D_MODULE_ENABLED */
#ifdef HAL_DSI_MODULE_ENABLED
#include "stm32h7xx_hal_dsi.h"
#endif /* HAL_DSI_MODULE_ENABLED */
#ifdef HAL_DFSDM_MODULE_ENABLED
#include "stm32h7xx_hal_dfsdm.h"
#endif /* HAL_DFSDM_MODULE_ENABLED */
#ifdef HAL_DTS_MODULE_ENABLED
#include "stm32h7xx_hal_dts.h"
#endif /* HAL_DTS_MODULE_ENABLED */
#ifdef HAL_ETH_MODULE_ENABLED
#include "stm32h7xx_hal_eth.h"
#endif /* HAL_ETH_MODULE_ENABLED */
#ifdef HAL_ETH_LEGACY_MODULE_ENABLED
#include "stm32h7xx_hal_eth_legacy.h"
#endif /* HAL_ETH_LEGACY_MODULE_ENABLED */
#ifdef HAL_EXTI_MODULE_ENABLED
#include "stm32h7xx_hal_exti.h"
#endif /* HAL_EXTI_MODULE_ENABLED */
#ifdef HAL_CORTEX_MODULE_ENABLED
#include "stm32h7xx_hal_cortex.h"
#endif /* HAL_CORTEX_MODULE_ENABLED */
#ifdef HAL_ADC_MODULE_ENABLED
#include "stm32h7xx_hal_adc.h"
#endif /* HAL_ADC_MODULE_ENABLED */
#ifdef HAL_FDCAN_MODULE_ENABLED
#include "stm32h7xx_hal_fdcan.h"
#endif /* HAL_FDCAN_MODULE_ENABLED */
#ifdef HAL_CEC_MODULE_ENABLED
#include "stm32h7xx_hal_cec.h"
#endif /* HAL_CEC_MODULE_ENABLED */
#ifdef HAL_COMP_MODULE_ENABLED
#include "stm32h7xx_hal_comp.h"
#endif /* HAL_COMP_MODULE_ENABLED */
#ifdef HAL_CORDIC_MODULE_ENABLED
#include "stm32h7xx_hal_cordic.h"
#endif /* HAL_CORDIC_MODULE_ENABLED */
#ifdef HAL_CRC_MODULE_ENABLED
#include "stm32h7xx_hal_crc.h"
#endif /* HAL_CRC_MODULE_ENABLED */
#ifdef HAL_CRYP_MODULE_ENABLED
#include "stm32h7xx_hal_cryp.h"
#endif /* HAL_CRYP_MODULE_ENABLED */
#ifdef HAL_DAC_MODULE_ENABLED
#include "stm32h7xx_hal_dac.h"
#endif /* HAL_DAC_MODULE_ENABLED */
#ifdef HAL_FLASH_MODULE_ENABLED
#include "stm32h7xx_hal_flash.h"
#endif /* HAL_FLASH_MODULE_ENABLED */
#ifdef HAL_GFXMMU_MODULE_ENABLED
#include "stm32h7xx_hal_gfxmmu.h"
#endif /* HAL_GFXMMU_MODULE_ENABLED */
#ifdef HAL_FMAC_MODULE_ENABLED
#include "stm32h7xx_hal_fmac.h"
#endif /* HAL_FMAC_MODULE_ENABLED */
#ifdef HAL_HRTIM_MODULE_ENABLED
#include "stm32h7xx_hal_hrtim.h"
#endif /* HAL_HRTIM_MODULE_ENABLED */
#ifdef HAL_HSEM_MODULE_ENABLED
#include "stm32h7xx_hal_hsem.h"
#endif /* HAL_HSEM_MODULE_ENABLED */
#ifdef HAL_SRAM_MODULE_ENABLED
#include "stm32h7xx_hal_sram.h"
#endif /* HAL_SRAM_MODULE_ENABLED */
#ifdef HAL_NOR_MODULE_ENABLED
#include "stm32h7xx_hal_nor.h"
#endif /* HAL_NOR_MODULE_ENABLED */
#ifdef HAL_NAND_MODULE_ENABLED
#include "stm32h7xx_hal_nand.h"
#endif /* HAL_NAND_MODULE_ENABLED */
#ifdef HAL_I2C_MODULE_ENABLED
#include "stm32h7xx_hal_i2c.h"
#endif /* HAL_I2C_MODULE_ENABLED */
#ifdef HAL_I2S_MODULE_ENABLED
#include "stm32h7xx_hal_i2s.h"
#endif /* HAL_I2S_MODULE_ENABLED */
#ifdef HAL_IWDG_MODULE_ENABLED
#include "stm32h7xx_hal_iwdg.h"
#endif /* HAL_IWDG_MODULE_ENABLED */
#ifdef HAL_JPEG_MODULE_ENABLED
#include "stm32h7xx_hal_jpeg.h"
#endif /* HAL_JPEG_MODULE_ENABLED */
#ifdef HAL_MDIOS_MODULE_ENABLED
#include "stm32h7xx_hal_mdios.h"
#endif /* HAL_MDIOS_MODULE_ENABLED */
#ifdef HAL_MMC_MODULE_ENABLED
#include "stm32h7xx_hal_mmc.h"
#endif /* HAL_MMC_MODULE_ENABLED */
#ifdef HAL_LPTIM_MODULE_ENABLED
#include "stm32h7xx_hal_lptim.h"
#endif /* HAL_LPTIM_MODULE_ENABLED */
#ifdef HAL_LTDC_MODULE_ENABLED
#include "stm32h7xx_hal_ltdc.h"
#endif /* HAL_LTDC_MODULE_ENABLED */
#ifdef HAL_OPAMP_MODULE_ENABLED
#include "stm32h7xx_hal_opamp.h"
#endif /* HAL_OPAMP_MODULE_ENABLED */
#ifdef HAL_OSPI_MODULE_ENABLED
#include "stm32h7xx_hal_ospi.h"
#endif /* HAL_OSPI_MODULE_ENABLED */
#ifdef HAL_OTFDEC_MODULE_ENABLED
#include "stm32h7xx_hal_otfdec.h"
#endif /* HAL_OTFDEC_MODULE_ENABLED */
#ifdef HAL_PSSI_MODULE_ENABLED
#include "stm32h7xx_hal_pssi.h"
#endif /* HAL_PSSI_MODULE_ENABLED */
#ifdef HAL_PWR_MODULE_ENABLED
#include "stm32h7xx_hal_pwr.h"
#endif /* HAL_PWR_MODULE_ENABLED */
#ifdef HAL_QSPI_MODULE_ENABLED
#include "stm32h7xx_hal_qspi.h"
#endif /* HAL_QSPI_MODULE_ENABLED */
#ifdef HAL_RAMECC_MODULE_ENABLED
#include "stm32h7xx_hal_ramecc.h"
#endif /* HAL_RAMECC_MODULE_ENABLED */
#ifdef HAL_RNG_MODULE_ENABLED
#include "stm32h7xx_hal_rng.h"
#endif /* HAL_RNG_MODULE_ENABLED */
#ifdef HAL_RTC_MODULE_ENABLED
#include "stm32h7xx_hal_rtc.h"
#endif /* HAL_RTC_MODULE_ENABLED */
#ifdef HAL_SAI_MODULE_ENABLED
#include "stm32h7xx_hal_sai.h"
#endif /* HAL_SAI_MODULE_ENABLED */
#ifdef HAL_SD_MODULE_ENABLED
#include "stm32h7xx_hal_sd.h"
#endif /* HAL_SD_MODULE_ENABLED */
#ifdef HAL_SDRAM_MODULE_ENABLED
#include "stm32h7xx_hal_sdram.h"
#endif /* HAL_SDRAM_MODULE_ENABLED */
#ifdef HAL_SPI_MODULE_ENABLED
#include "stm32h7xx_hal_spi.h"
#endif /* HAL_SPI_MODULE_ENABLED */
#ifdef HAL_SPDIFRX_MODULE_ENABLED
#include "stm32h7xx_hal_spdifrx.h"
#endif /* HAL_SPDIFRX_MODULE_ENABLED */
#ifdef HAL_SWPMI_MODULE_ENABLED
#include "stm32h7xx_hal_swpmi.h"
#endif /* HAL_SWPMI_MODULE_ENABLED */
#ifdef HAL_TIM_MODULE_ENABLED
#include "stm32h7xx_hal_tim.h"
#endif /* HAL_TIM_MODULE_ENABLED */
#ifdef HAL_UART_MODULE_ENABLED
#include "stm32h7xx_hal_uart.h"
#endif /* HAL_UART_MODULE_ENABLED */
#ifdef HAL_USART_MODULE_ENABLED
#include "stm32h7xx_hal_usart.h"
#endif /* HAL_USART_MODULE_ENABLED */
#ifdef HAL_IRDA_MODULE_ENABLED
#include "stm32h7xx_hal_irda.h"
#endif /* HAL_IRDA_MODULE_ENABLED */
#ifdef HAL_SMARTCARD_MODULE_ENABLED
#include "stm32h7xx_hal_smartcard.h"
#endif /* HAL_SMARTCARD_MODULE_ENABLED */
#ifdef HAL_SMBUS_MODULE_ENABLED
#include "stm32h7xx_hal_smbus.h"
#endif /* HAL_SMBUS_MODULE_ENABLED */
#ifdef HAL_WWDG_MODULE_ENABLED
#include "stm32h7xx_hal_wwdg.h"
#endif /* HAL_WWDG_MODULE_ENABLED */
#ifdef HAL_PCD_MODULE_ENABLED
#include "stm32h7xx_hal_pcd.h"
#endif /* HAL_PCD_MODULE_ENABLED */
#ifdef HAL_HCD_MODULE_ENABLED
#include "stm32h7xx_hal_hcd.h"
#endif /* HAL_HCD_MODULE_ENABLED */
/* Exported macro ------------------------------------------------------------*/
#ifdef USE_FULL_ASSERT
/**
* @brief The assert_param macro is used for function's parameters check.
* @param expr: If expr is false, it calls assert_failed function
* which reports the name of the source file and the source
* line number of the call that failed.
* If expr is true, it returns no value.
* @retval None
*/
#define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t *file, uint32_t line);
#else
#define assert_param(expr) ((void)0U)
#endif /* USE_FULL_ASSERT */
#ifdef __cplusplus
}
#endif
#endif /* STM32H7xx_HAL_CONF_H */
@@ -0,0 +1,60 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file stm32h7xx_it.h
* @brief This file contains the headers of the interrupt handlers.
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32H7xx_IT_H
#define __STM32H7xx_IT_H
#ifdef __cplusplus
extern "C" {
#endif
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */
/* USER CODE END ET */
/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */
/* USER CODE END EC */
/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */
/* USER CODE END EM */
/* Exported functions prototypes ---------------------------------------------*/
void NMI_Handler(void);
void DebugMon_Handler(void);
void TIM6_DAC_IRQHandler(void);
/* USER CODE BEGIN EFP */
/* USER CODE END EFP */
#ifdef __cplusplus
}
#endif
#endif /* __STM32H7xx_IT_H */
Loaded 100 of 272 files, more files were not shown because too many files have changed in this diff. Show more