Compare commits

...
30 Commits
Author SHA1 Message Date
Sergio R. Caprile f59a3f71ed improve RTX tests for coverage and resiliency 2026-11-06 11:35:45 -03:00
Sergio R. Caprile aaf202ba2f Merge pull request #3759 from cesanta/tlstight
misc tightening and cleanup
2026-11-06 09:54:17 -03:00
Sergio R. Caprile 5c7b7b68bc misc tightening and cleanup 2026-11-06 09:51:41 -03:00
Sergio R. Caprile 28f88f016c Merge pull request #3758 from cesanta/4861
be more RFC4861 6.1.2 friendly
2026-11-06 09:47:15 -03:00
Sergio R. Caprile e8bb01b547 per family close on IP change 2026-11-06 09:37:02 -03:00
Sergio R. Caprile 324f59abc5 Merge pull request #3744 from cesanta/hcd
Add coredump RPC method
2026-11-06 07:34:38 +00:00
Sergey Lyubka 3eb33ab349 Merge pull request #3757 from cesanta/m3
Add radio simulation
2026-11-06 07:33:27 +00:00
Sergio R. Caprile 66eb0384be be more RFC4861 6.1.2 friendly 2026-11-05 19:54:31 -03:00
Sergio R. Caprile 1f276ba70a add coredump RPC 2026-11-05 19:49:07 -03:00
Sergio R. Caprile 1a9bc9d3f3 Merge pull request #3742 from cesanta/hesp
Integrate ESP32 health support
2026-11-05 22:34:20 +00:00
cpq d39519b92f Add radio simulation 2026-11-05 22:23:06 +00:00
Sergio R. Caprile 3e11429cac workaround Pico-SDK 2.1.1 vs GCC15 2026-11-05 16:18:45 -03:00
Sergio R. Caprile 9337a78cd0 unbreak BSD on some systems 2026-11-05 11:44:27 -03:00
Sergio R. Caprile d99cc0959b workaround Pico-SDK 2.1.1 vs GCC15 2026-11-05 11:30:23 -03:00
Sergio R. Caprile 02b5504e60 Merge pull request #3756 from cesanta/trasher
fix Docker breaking Win32
2026-11-04 17:15:50 -03:00
Sergio R. Caprile 822c6a9489 fix GCC15 2026-11-04 16:41:04 -03:00
Sergio R. Caprile 03c022080f fix Docker breaking Win32 2026-11-04 16:02:10 -03:00
Sergey Lyubka 84198c7e70 Merge pull request #3753 from cesanta/mesh
Add skeleton for the radio mesh simulator
2026-11-04 18:09:10 +00:00
Sergio R. Caprile f3d531938c Merge pull request #3754 from cesanta/bsdmin
Add missing BSD functions
2026-11-04 14:56:58 -03:00
Sergio R. Caprile b6975ab08f pin Ubuntu runners during migration 2026-11-04 14:56:11 -03:00
Sergio R. Caprile 66afa5e551 Add missing BSD functions 2026-11-04 13:35:49 -03:00
Sergey Lyubka 7886a73807 Merge pull request #3749 from cesanta/defuri
harden root_dir
2026-11-02 12:42:29 +00:00
Sergey Lyubka ccc953318a Merge pull request #3750 from cesanta/strpres
survive to width.precision screw ups in %s
2026-11-02 12:41:05 +00:00
cpq 684ae27dc2 Add udp echo server to tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal 2026-11-02 12:30:23 +00:00
cpq fc71bc0588 Add skeleton for the radio mesh simulator 2026-11-01 11:05:59 +00:00
Sergey Lyubka 08305b9000 Merge pull request #3751 from cesanta/dhcp
Fix: DHCP client stuck in REQUESTING state forever if the REQUEST or ACK is lost
2026-10-30 12:43:26 +00:00
cpq 69e8c0a92f Fix: DHCP client stuck in REQUESTING state forever if the REQUEST or ACK is lost 2026-10-30 09:42:12 +00:00
Sergio R. Caprile 8eceacc0d3 survive to width.precision screw ups in %s 2026-10-27 17:30:26 -03:00
Sergio R. Caprile b6df410570 Integrate ESP32 health support 2026-10-27 11:32:36 -03:00
Sergio R. Caprile 26b971b4a8 harden root_dir 2026-10-27 11:09:08 -03:00
83 changed files with 7515 additions and 997 deletions

No files matched your search

+16 -14
View File
@@ -53,7 +53,7 @@ jobs:
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/linux_${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_${{ matrix.select }}_$(date +"%Y%m%d").json"
c89:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -137,7 +137,7 @@ jobs:
make -C test mip_tap_test
mip89:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -149,7 +149,7 @@ jobs:
with: { fetch-depth: 2 }
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
mipbe:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -168,7 +168,7 @@ jobs:
make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }}
miprtxbe:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
name: MIP RTX be s390 port
env:
IPV6: 0
@@ -204,7 +204,7 @@ jobs:
test/health.awk < log > json
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ipv6 }}_$(date +"%Y%m%d").json"
l289:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
strategy:
fail-fast: false
matrix:
@@ -216,7 +216,7 @@ jobs:
with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }}
l2be:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -272,7 +272,7 @@ jobs:
test/health.awk < log > json
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/ATcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
atcmd89:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
strategy:
fail-fast: false
matrix:
@@ -285,7 +285,7 @@ jobs:
with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }}
atcmdbe:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -325,7 +325,7 @@ jobs:
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/bsd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
bsdbe:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -343,7 +343,7 @@ jobs:
make -C test/bsd ${{ matrix.target }}
s390:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -356,7 +356,7 @@ jobs:
with: { fetch-depth: 2 }
- run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && ./test/setup_mqtt_server.sh && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test s390 SSL=${{ matrix.ssl }}
armhf:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -429,7 +429,7 @@ jobs:
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/macos_test_cc_${{ matrix.ssl }}_${{ matrix.select }}_$(date +"%Y%m%d").json"
windows:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
strategy:
fail-fast: false
max-parallel: 1 # no parallel runs, to minimize MQTT errors
@@ -479,7 +479,7 @@ jobs:
- run: make -C test riscv SSL=${{ matrix.ssl }}
tutorials:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04 # MicroPython v1.22.2 fails with Ubuntu 26.04 GCC 15
strategy:
fail-fast: false
matrix:
@@ -560,7 +560,8 @@ jobs:
# - run: make -C tutorials/${{ matrix.example.path }} build
wizard_examples_arm:
runs-on: ubuntu-latest
# Keep rp-ti on 24.04: Pico SDK 2.1.1 pioasm fails with GCC 15; update SDK
runs-on: ${{ matrix.group == 'rp-ti' && 'ubuntu-24.04' || 'ubuntu-latest' }}
strategy:
fail-fast: false
matrix:
@@ -637,6 +638,7 @@ jobs:
example:
- path: micropython/esp32
- path: esp32/uart-bridge
- path: esp32/minimal
name: ${{ matrix.example.path }}
env:
GO: 0
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
test/health.awk < log > json
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/atcmd_${{ matrix.cc }}_$(date +"%Y%m%d").json"
atcmd89:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
strategy:
fail-fast: false
matrix:
@@ -35,7 +35,7 @@ jobs:
with: { fetch-depth: 2 }
- run: make -C test ${{ matrix.target }}
atcmdbe:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
+10 -6
View File
@@ -35,7 +35,7 @@ jobs:
- if: ${{ env.GO == 1 }}
run: if [ "${{ matrix.target }}" == "test" ]; then ./test/setup_mqtt_server.sh ; fi && make -C test ${{ matrix.target }}
s390:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
TFLAGS: -DMQTT_LOCALHOST
steps:
@@ -45,7 +45,7 @@ jobs:
- if: ${{ env.GO == 1 }}
run: sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && ./test/setup_mqtt_server.sh && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes && make -C test s390
armhf:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
TFLAGS: -DMQTT_LOCALHOST
steps:
@@ -83,7 +83,7 @@ jobs:
/opt/homebrew/opt/mosquitto/sbin/mosquitto -c /Users/runner/work/mongoose/mongoose/test/mosquitto.conf.macos &
make -C test test ASAN_OPTIONS=
windows:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04 # Ubuntu 26.04 Docker 29.4.2 breaks 32-bit Wine; switch at >= 29.4.3
strategy:
fail-fast: false
matrix:
@@ -186,6 +186,7 @@ jobs:
matrix:
example:
- path: esp32/uart-bridge
- path: esp32/minimal
name: ${{ matrix.example.path }}
env:
GO: 0
@@ -200,7 +201,8 @@ jobs:
run: make -C tutorials/${{ matrix.example.path }} build
generic_examples_arm:
runs-on: ubuntu-latest
# Keep RP on 24.04: Pico SDK 2.1.1 pioasm fails with GCC 15; update SDK
runs-on: ${{ startsWith(matrix.example.path, 'rp/') && 'ubuntu-24.04' || 'ubuntu-latest' }}
strategy:
fail-fast: false
matrix:
@@ -243,7 +245,8 @@ jobs:
run: make -C tutorials/${{ matrix.example.path }} build
device_dashboard_examples_arm:
runs-on: ubuntu-latest
# Keep RP on 24.04: Pico SDK 2.1.1 pioasm fails with GCC 15; update SDK
runs-on: ${{ startsWith(matrix.example.path, 'rp/') && 'ubuntu-24.04' || 'ubuntu-latest' }}
strategy:
fail-fast: false
matrix:
@@ -269,7 +272,8 @@ jobs:
run: make -C tutorials/${{ matrix.example.path }} build
mqtt_dashboard_examples_arm:
runs-on: ubuntu-latest
# Pico SDK 2.1.0 pioasm fails with GCC 15; update SDK to unpin
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
+1715 -290
View File
File diff suppressed because it is too large. Load diff
+303 -76
View File
@@ -253,8 +253,11 @@ extern "C" {
#include <esp_system.h>
#include <esp_mac.h>
#include <esp_idf_version.h>
// #include <esp_memory_utils.h> // For health record coredumps
// #include <esp_core_dump.h> // For health record coredumps
#if MG_ENABLE_HEALTH
#include <esp_memory_utils.h>
#include <esp_core_dump.h>
#include <esp_partition.h>
#endif
#define MG_PATH_MAX 128
@@ -4109,6 +4112,8 @@ struct mg_dash {
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
// Resolves the built-in file manager's root dir for user u, e.g. per-user
// upload directories. NULL: file manager disabled
// - The returned directory must be nonempty and must not contain ',' or '=',
// because it is used as an mg_http_serve_opts::root_dir mapping target.
bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
};
@@ -4373,6 +4378,8 @@ void mg_ota_poll(struct mg_mgr *);
#if MG_ENABLE_HEALTH
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
@@ -4381,13 +4388,15 @@ void mg_ota_poll(struct mg_mgr *);
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
#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
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
#define MG_HEALTH_DATA_SIZE 250
#elif MG_ARCH == MG_ARCH_ESP32
#define MG_HEALTH_DATA_SIZE 23
#else
#define MG_HEALTH_DATA_SIZE 10
#endif
#endif // MG_HEALTH_DATA_SIZE
// Places the record in RAM that survives a reset. Define in mongoose_config.h
// to match the linker script, e.g. __attribute__((section(".mg_health")))
@@ -4395,6 +4404,13 @@ void mg_ota_poll(struct mg_mgr *);
#define MG_HEALTH_RAM
#endif
// CPU core architecture
enum mg_health_arch {
MG_HEALTH_ARCH_ARM = 0,
MG_HEALTH_ARCH_XTENSA,
MG_HEALTH_ARCH_RISCV
};
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
@@ -4411,7 +4427,8 @@ enum mg_health_reason {
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint16_t reset_reason; // enum mg_health_reason that started this boot
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
uint32_t cpuid; // ARM CPU ID
uint32_t data[MG_HEALTH_DATA_SIZE];
};
@@ -4444,9 +4461,72 @@ static inline const char *mg_health_reason_str(int reason) {
}
}
#if MG_ARCH == MG_ARCH_ESP32
#ifndef CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
#define CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH 0
#endif
#ifndef ESP_PARTITION_SUBTYPE_DATA_COREDUMP
#define ESP_PARTITION_SUBTYPE_DATA_COREDUMP 0x03
#endif
// clang-format off
static inline uint16_t esp2mg(esp_reset_reason_t rr) {
switch(rr) {
case ESP_RST_POWERON: return (uint16_t) MG_HEALTH_RESET_POWER;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
case ESP_RST_PWR_GLITCH:
#endif
case ESP_RST_BROWNOUT: return (uint16_t) MG_HEALTH_RESET_BROWNOUT;
case ESP_RST_EXT: return (uint16_t) MG_HEALTH_RESET_PIN;
case ESP_RST_SW: return (uint16_t) MG_HEALTH_RESET_SOFTWARE;
case ESP_RST_DEEPSLEEP: return (uint16_t) MG_HEALTH_RESET_LOWPOWER;
case ESP_RST_INT_WDT:
case ESP_RST_TASK_WDT:
case ESP_RST_WDT: return (uint16_t) MG_HEALTH_RESET_WATCHDOG;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
case ESP_RST_CPU_LOCKUP:
#endif
case ESP_RST_PANIC: return (uint16_t) MG_HEALTH_RESET_FAULT;
default: break;
}
return (uint16_t) MG_HEALTH_RESET_UNKNOWN;
}
// clang-format on
#endif
#define MG_HEALTH_INIT() mg_health_init()
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
extern void mg_health_init(void);
extern bool mg_health_coredump_begin(size_t *size);
extern bool mg_health_coredump_read(size_t offset, void *buf, size_t *len);
extern bool mg_health_coredump_end(void);
#else
#define MG_HEALTH_INIT()
static inline int mg_health_reason(void) {
return 0;
}
static inline const char *mg_health_reason_str(int reason) {
(void) reason;
return "unknown";
}
static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg,
va_list *ap) {
(void) ap;
return mg_xprintf(fn, arg, "{}");
}
static inline bool mg_health_coredump_begin(size_t *size) {
(void) size;
return false;
}
static inline bool mg_health_coredump_read(size_t offset, void *buf,
size_t *len) {
(void) offset, (void) buf, (void) len;
return false;
}
static inline bool mg_health_coredump_end(void) {
return false;
}
#endif
// Cloud connector API to https://mdash.net - device management cloud
@@ -4748,41 +4828,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_atcmd;
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).
// Populate and assign to the driver's driver_data field.
@@ -4836,6 +4886,86 @@ struct mg_tcpip_spi {
#endif
#endif
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// Ethernet over radio, through a mesh. Mongoose sees an Ethernet interface,
// the mesh splits Ethernet frames into radio frames. Two parts plug in:
// - struct mg_radio: a radio standard, e.g. mg_radio_802154
// - struct mg_mesh: how the mesh is built and routed
#if MG_ENABLE_TCPIP
// Monotonic time in microseconds.
uint64_t mg_micros(void);
// A radio standard
struct mg_radio {
const char *name; // For logs
size_t max_frame_size; // Largest radio frame, bytes
uint32_t bitrate; // Bits per second on air
size_t overhead; // Bytes on air besides the frame: preamble, FCS
uint32_t slot_us; // Random backoff slot duration, us
uint32_t cca_us; // Channel sensing duration, us
uint32_t turnaround_us; // Idle check to transmission start, us
uint32_t ifs_us; // Inter-frame spacing in us: gap between transmits
uint8_t backoff_min; // Initial backoff exponent
uint8_t backoff_max; // Maximum backoff exponent
bool csma; // Use channel sensing and backoff before TX
size_t header_len; // Radio hardware header length
// Write the radio header, with 0xffff as the broadcast address.
void (*print_hdr)(const struct mg_radio *radio, uint8_t *buf, uint16_t dst,
uint16_t src);
// Return the parsed header length, or zero for an invalid header.
size_t (*parse_hdr)(const struct mg_radio *radio, const uint8_t *buf,
size_t len, uint16_t *dst, uint16_t *src);
};
#define MG_802154_HEADER_LEN 9 // MAC header, short addresses
#define MG_802154_PAN_ID 0xabcd // Our network's PAN ID
#ifndef MG_802154_CHANNEL
#define MG_802154_CHANNEL 15
#endif
extern const struct mg_radio mg_radio_802154;
struct mg_mesh_node {
struct mg_tcpip_if *radio; // Owning radio driver interface
struct mg_tcpip_if *lan; // Bridge LAN interface, NULL for a client
const struct mg_mesh *strategy; // Selected strategy
size_t (*tx)(struct mg_tcpip_if *, const void *, size_t); // Radio frame TX
size_t rx_len; // Complete Ethernet frame in the interface RX buffer
uint64_t now; // Current driver poll time, us
uint64_t tx_at; // Earliest next transmission, us
void *data; // Private state owned by the strategy
size_t ram_used; // Live mesh state bytes
size_t ram_peak; // Peak live mesh state bytes
uint64_t next_tick; // Next deadline in us, or UINT64_MAX
uint16_t radio_address; // Local address supplied by the radio
bool joined; // Client is a member of the mesh
};
struct mg_mesh {
const char *name;
bool (*init)(struct mg_mesh_node *node, struct mg_tcpip_if *radio,
struct mg_tcpip_if *lan, uint16_t radio_address);
void (*tick)(struct mg_mesh_node *node, uint64_t now_microseconds);
void (*free)(struct mg_mesh_node *node);
size_t (*tx)(struct mg_mesh_node *, const void *, size_t); // ETH frame TX
void (*rx)(struct mg_mesh_node *, void *, size_t); // Radio frame RX
bool single_bridge; // Require exactly one bridge
bool scheduled_tx; // The mesh schedules transmission turns
const struct mg_radio *radio; // Radio configuration for this strategy
};
extern const struct mg_mesh mg_mesh_default;
#endif
@@ -4893,6 +5023,15 @@ void mg_bsd_poll(struct mg_mgr *);
#ifndef EPIPE
#define EPIPE 32
#endif
#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT 97
#endif
#ifndef ENOPROTOOPT
#define ENOPROTOOPT 92
#endif
#ifndef ENOSPC
#define ENOSPC 28
#endif
#ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT 93
#endif
@@ -4974,7 +5113,9 @@ typedef struct fd_set {
#define IPPROTO_UDP 17
#define INADDR_ANY 0
#define INADDR_LOOPBACK 0x7f000001
#define INADDR_NONE ((in_addr_t) -1)
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
#define SOL_SOCKET 0xffff
#define SO_REUSEADDR 2
#define SO_REUSEPORT 15
@@ -5168,30 +5309,74 @@ struct mg_tcpip_driver_imxrt_data {
} while (0)
#endif
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
MG_ENABLE_DRIVER_MCXW71
// NXP MCXW71 IEEE 802.15.4 radio, ZLL link layer, as an Ethernet interface:
// Ethernet frames are split into radio frames, see mg_l2_802154_tx().
// The transceiver (XCVR) must be set up for 802.15.4 before mg_tcpip_init(),
// see hal_radio_init() in the FRDM-MCXW71 tutorial.
// driver_data: struct mg_l2_802154_data, channel and PAN ID set
struct mg_tcpip_driver_mcxw71_data {
struct mg_mesh_node *mesh; // Driver-owned runtime state
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
};
#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_mcxw71; \
mif_.driver_data = &driver_data_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: mcxw71, channel %d", MG_L2_802154_CHANNEL)); \
extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
#define MG_TCPIP_DRIVER_MCXW71_INIT(manager) \
do { \
static struct mg_tcpip_driver_mcxw71_data driver_data_; \
static struct mg_tcpip_if mif_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.mgr = (manager); \
mif_.driver = &mg_tcpip_driver_mcxw71; \
mif_.driver_data = &driver_data_; \
mg_tcpip_init((manager), &mif_); \
MG_INFO(("Driver: mcxw71, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MCXW71_INIT(x)
#endif
#endif
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MG24) && MG_ENABLE_DRIVER_MG24
struct mg_tcpip_driver_mg24_data {
struct mg_mesh_node *mesh; // Driver-owned runtime state
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
};
extern struct mg_tcpip_driver mg_tcpip_driver_mg24;
#define MG_TCPIP_DRIVER_MG24_INIT(manager) \
do { \
static struct mg_tcpip_driver_mg24_data driver_data_; \
static struct mg_tcpip_if mif_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.mgr = (manager); \
mif_.driver = &mg_tcpip_driver_mg24; \
mif_.driver_data = &driver_data_; \
mg_tcpip_init((manager), &mif_); \
MG_INFO(("Driver: mg24, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MG24_INIT(x)
#endif
#endif
@@ -5242,6 +5427,40 @@ extern struct mg_tcpip_driver mg_tcpip_driver_netc;
} while (0)
#endif
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_NRF52) && MG_ENABLE_DRIVER_NRF52
struct mg_tcpip_driver_nrf52_data {
struct mg_mesh_node *mesh; // Driver-owned runtime state
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
};
extern struct mg_tcpip_driver mg_tcpip_driver_nrf52;
#define MG_TCPIP_DRIVER_NRF52_INIT(manager) \
do { \
static struct mg_tcpip_driver_nrf52_data driver_data_; \
static struct mg_tcpip_if mif_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.mgr = (manager); \
mif_.driver = &mg_tcpip_driver_nrf52; \
mif_.driver_data = &driver_data_; \
mg_tcpip_init((manager), &mif_); \
MG_INFO(("Driver: nrf52, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_NRF52_INIT(x)
#endif
#endif
#if MG_ENABLE_TCPIP && \
@@ -5394,6 +5613,7 @@ struct mg_tcpip_driver_rw612_data {
#define MG_DRIVER_MDC_HOLDTIME 3
#endif
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(mgr) \
do { \
static struct mg_tcpip_driver_rw612_data driver_data_; \
@@ -5409,34 +5629,41 @@ struct mg_tcpip_driver_rw612_data {
mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#endif
#endif
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_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
#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(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)
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_RW612W_INIT(x)
#endif
#endif
+5 -2
View File
@@ -22,8 +22,11 @@
#include <esp_system.h>
#include <esp_mac.h>
#include <esp_idf_version.h>
// #include <esp_memory_utils.h> // For health record coredumps
// #include <esp_core_dump.h> // For health record coredumps
#if MG_ENABLE_HEALTH
#include <esp_memory_utils.h>
#include <esp_core_dump.h>
#include <esp_partition.h>
#endif
#define MG_PATH_MAX 128
+240 -15
View File
@@ -32,8 +32,9 @@ static struct mg_mgr *s_mgr;
enum bsd_op {
BSD_OP_NONE, BSD_OP_SOCKET, BSD_OP_BIND, BSD_OP_LISTEN, BSD_OP_FCNTL,
BSD_OP_ACCEPT, BSD_OP_CONNECT, BSD_OP_RECV, BSD_OP_SEND, BSD_OP_SENDTO,
BSD_OP_RECVFROM, BSD_OP_CLOSE, BSD_OP_POLL, BSD_OP_SELECT,
BSD_OP_GETHOSTBYNAME,
BSD_OP_RECVFROM, BSD_OP_CLOSE, BSD_OP_SHUTDOWN, BSD_OP_SETSOCKOPT,
BSD_OP_GETSOCKOPT, BSD_OP_GETSOCKNAME, BSD_OP_GETPEERNAME, BSD_OP_POLL,
BSD_OP_SELECT, BSD_OP_GETHOSTBYNAME,
};
// A BSD API request from a user task. Gets sent via the RTOS queue, and
@@ -50,6 +51,7 @@ struct mg_bsd_cmd {
struct { int fd, backlog; } op_listen;
struct { int fd, cmd, arg; } op_fcntl;
struct { int fd; } op_close;
struct { int fd, how; } op_shutdown;
struct { int fd; const struct sockaddr *addr; socklen_t len; } op_bind;
struct { int fd; const struct sockaddr *addr; socklen_t len; } op_connect;
struct { int fd; struct sockaddr *addr; socklen_t *addrlen; } op_accept;
@@ -57,6 +59,9 @@ struct mg_bsd_cmd {
struct { int fd; const void *buf; size_t len; int flags; } op_send;
struct { int fd; const void *buf; size_t len; int flags; const struct sockaddr *addr; socklen_t addrlen; } op_sendto;
struct { int fd; void *buf; size_t len; int flags; struct sockaddr *src; socklen_t *addrlen; } op_recvfrom;
struct { int fd, level, optname; const void *optval; socklen_t optlen; } op_setsockopt;
struct { int fd, level, optname; void *optval; socklen_t *optlen; } op_getsockopt;
struct { int fd; struct sockaddr *addr; socklen_t *addrlen; } op_getname;
struct { struct pollfd *fds; nfds_t nfds; uint64_t deadline; } op_poll;
struct { int nfds; fd_set *r, *w, *e; uint64_t deadline; } op_select;
struct { const char *name; struct hostent *result; } op_gethostbyname;
@@ -73,6 +78,7 @@ struct mg_bsd_sock {
struct mg_connection *c;
struct mg_bsd_sock *listener;
bool used, bound, closed, nonblock, is_udp, orphan; // closed: c was closed
bool error_pending, is_shut_rd, is_shut_wr;
};
static struct mg_bsd_sock s_socks[MG_BSD_MAX_SOCKETS > 0 ?
@@ -93,6 +99,21 @@ static struct mg_bsd_sock *bsd_get(int fd) {
return s;
}
static int bsd_addr4(const struct sockaddr *addr, socklen_t len) {
if (addr == NULL || len < sizeof(struct sockaddr_in)) return EINVAL;
return addr->sa_family == AF_INET ? 0 : EAFNOSUPPORT;
}
static bool bsd_aton4(const char *str, in_addr_t *ip) {
struct mg_addr addr;
size_t len = strlen(str);
if (len == 0 || !mg_aton(mg_str_n(str, len), &addr) || addr.is_ip6)
return false;
memcpy(ip, addr.addr.ip, sizeof(*ip));
return true;
}
static void bsd_attach(struct mg_bsd_sock *s, struct mg_connection *c) {
s->c = c;
c->fn = bsd_ev;
@@ -178,8 +199,10 @@ static void bsd_ev(struct mg_connection *c, int ev, void *ev_data) {
c->is_bsd = 1;
c->is_udp = child->is_udp;
}
} else if (ev == MG_EV_READ && s->is_shut_rd) {
mg_iobuf_del(&c->recv, 0, c->recv.len);
} else if (ev == MG_EV_ERROR) {
if (s->err == 0) s->err = EIO;
if (s->err == 0) s->err = EIO, s->error_pending = true;
#if MG_ENABLE_FREERTOS
bsd_finish_waiters(s, true);
#endif
@@ -289,9 +312,11 @@ static int bsd_ready(struct mg_bsd_sock *s) {
}
return 0;
}
if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ;
if (!s->is_udp && !s->c->is_connecting && !s->c->is_closing &&
!s->c->is_draining)
if (s->is_shut_rd || s->c->recv.len > 0 || s->c->is_draining)
ready |= BSD_READ;
if (s->is_shut_wr) ready |= BSD_ERROR;
if (!s->is_shut_wr && !s->is_udp && !s->c->is_connecting &&
!s->c->is_closing && !s->c->is_draining)
ready |= BSD_WRITE; // Established TCP socket
return ready;
}
@@ -368,9 +393,13 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
}
}
return true;
case BSD_OP_BIND:
case BSD_OP_BIND: {
int err;
if ((s = bsd_get(cmd->op_bind.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if ((err = bsd_addr4(cmd->op_bind.addr, cmd->op_bind.len)) != 0) {
cmd->result = -1, cmd->err = err;
} else {
const struct sockaddr_in *sa =
(const struct sockaddr_in *) cmd->op_bind.addr;
@@ -389,6 +418,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
}
}
return true;
}
case BSD_OP_LISTEN: {
char url[32];
@@ -464,11 +494,17 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true;
#endif
}
case BSD_OP_CONNECT:
case BSD_OP_CONNECT: {
int err;
if ((s = bsd_get(cmd->op_connect.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
return true;
}
if ((err = bsd_addr4(cmd->op_connect.addr, cmd->op_connect.len)) != 0) {
cmd->result = -1, cmd->err = err;
return true;
}
if (!cmd->started && s->c == NULL) {
if (s->closed || (c = bsd_new_conn(s)) == NULL) {
cmd->result = -1, cmd->err = s->closed ? EIO : ENOMEM;
@@ -507,6 +543,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = -1, cmd->err = EINPROGRESS;
return true;
#endif
}
case BSD_OP_RECV:
if ((s = bsd_get(cmd->op_recv.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
@@ -529,7 +566,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = -1, cmd->err = s->err;
return true;
}
if (c->is_draining) { // Orderly shutdown by peer
if (s->is_shut_rd || c->is_draining) { // Local or peer shutdown
cmd->result = 0;
return true;
}
@@ -543,8 +580,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SEND:
if ((s = bsd_get(cmd->op_send.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
c->is_draining) {
} else if (s->is_shut_wr || (c = s->c) == NULL || s->err != 0 ||
c->is_closing || c->is_draining) {
cmd->result = -1, cmd->err = EPIPE;
} else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) {
cmd->result = (int) cmd->op_send.len;
@@ -558,11 +595,16 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
#endif
}
return true;
case BSD_OP_SENDTO:
case BSD_OP_SENDTO: {
int err;
if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
c->is_draining) {
} else if ((err = bsd_addr4(cmd->op_sendto.addr,
cmd->op_sendto.addrlen)) != 0) {
cmd->result = -1, cmd->err = err;
} else if (s->is_shut_wr || (c = s->c) == NULL || s->err != 0 ||
c->is_closing || c->is_draining) {
cmd->result = -1, cmd->err = EPIPE;
} else {
const struct sockaddr_in *sa =
@@ -583,11 +625,16 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
}
}
return true;
}
case BSD_OP_RECVFROM:
if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
return true;
}
if (s->is_shut_rd) {
cmd->result = 0;
return true;
}
if ((c = s->c) == NULL) {
cmd->result = s->closed && s->err == 0 ? 0 : -1;
cmd->err = s->closed ? s->err : ENOTCONN;
@@ -595,6 +642,13 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
}
if (c->recv.len > 0) {
size_t n = cmd->op_recvfrom.len;
if (cmd->op_recvfrom.src != NULL &&
(cmd->op_recvfrom.addrlen == NULL ||
*cmd->op_recvfrom.addrlen < sizeof(struct sockaddr_in))) {
cmd->result = -1, cmd->err = EINVAL;
return true;
}
if (c->recv.len < n) n = c->recv.len;
memcpy(cmd->op_recvfrom.buf, c->recv.buf, n);
if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
@@ -627,6 +681,98 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = 0;
}
return true;
case BSD_OP_SHUTDOWN:
if ((s = bsd_get(cmd->op_shutdown.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if (cmd->op_shutdown.how != SHUT_RD &&
cmd->op_shutdown.how != SHUT_WR &&
cmd->op_shutdown.how != SHUT_RDWR) {
cmd->result = -1, cmd->err = EINVAL;
} else if ((c = s->c) == NULL || c->is_listening || c->is_closing ||
(s->is_udp && !c->is_client)) {
cmd->result = -1, cmd->err = ENOTCONN;
} else {
if (cmd->op_shutdown.how != SHUT_WR) {
s->is_shut_rd = true;
mg_iobuf_del(&c->recv, 0, c->recv.len);
}
if (cmd->op_shutdown.how != SHUT_RD) s->is_shut_wr = true;
cmd->result = 0;
}
return true;
case BSD_OP_SETSOCKOPT:
if ((s = bsd_get(cmd->op_setsockopt.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if (cmd->op_setsockopt.optval == NULL ||
cmd->op_setsockopt.optlen != sizeof(int)) {
cmd->result = -1, cmd->err = EINVAL;
} else if ((cmd->op_setsockopt.level == SOL_SOCKET &&
(cmd->op_setsockopt.optname == SO_REUSEADDR
#ifdef SO_NOSIGPIPE
|| cmd->op_setsockopt.optname == SO_NOSIGPIPE
#endif
)) ||
(!s->is_udp && cmd->op_setsockopt.level == IPPROTO_TCP &&
cmd->op_setsockopt.optname == TCP_NODELAY)) {
cmd->result = 0;
} else {
cmd->result = -1, cmd->err = ENOPROTOOPT;
}
return true;
case BSD_OP_GETSOCKOPT:
if ((s = bsd_get(cmd->op_getsockopt.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if (cmd->op_getsockopt.level != SOL_SOCKET ||
cmd->op_getsockopt.optname != SO_ERROR) {
cmd->result = -1, cmd->err = ENOPROTOOPT;
} else if (cmd->op_getsockopt.optval == NULL ||
cmd->op_getsockopt.optlen == NULL ||
*cmd->op_getsockopt.optlen < sizeof(int)) {
cmd->result = -1, cmd->err = EINVAL;
} else {
*(int *) cmd->op_getsockopt.optval =
s->error_pending ? s->err : 0;
s->error_pending = false;
*cmd->op_getsockopt.optlen = sizeof(int);
cmd->result = 0;
}
return true;
case BSD_OP_GETSOCKNAME:
case BSD_OP_GETPEERNAME:
if ((s = bsd_get(cmd->op_getname.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF;
} else if (cmd->op_getname.addr == NULL ||
cmd->op_getname.addrlen == NULL ||
*cmd->op_getname.addrlen < sizeof(struct sockaddr_in)) {
cmd->result = -1, cmd->err = EINVAL;
} else if ((c = s->c) == NULL &&
(cmd->op == BSD_OP_GETPEERNAME || s->closed)) {
cmd->result = -1, cmd->err = ENOTCONN;
} else if (cmd->op == BSD_OP_GETPEERNAME &&
(c->is_listening || (s->is_udp && !c->is_client))) {
cmd->result = -1, cmd->err = ENOTCONN;
} else if (c != NULL &&
((cmd->op == BSD_OP_GETSOCKNAME && c->loc.is_ip6) ||
(cmd->op == BSD_OP_GETPEERNAME && c->rem.is_ip6))) {
cmd->result = -1, cmd->err = EAFNOSUPPORT;
} else {
struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_getname.addr;
if (c == NULL) {
*sa = s->addr;
sa->sin_family = AF_INET;
} else {
const struct mg_addr *addr = cmd->op == BSD_OP_GETSOCKNAME
? &c->loc
: &c->rem;
sa->sin_family = AF_INET;
sa->sin_port = addr->port;
sa->sin_addr.s_addr = addr->addr.ip4;
}
*cmd->op_getname.addrlen = sizeof(*sa);
cmd->result = 0;
}
return true;
case BSD_OP_POLL: {
struct pollfd *p;
int i, n = 0, ready;
@@ -643,7 +789,7 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
p->revents |= POLLIN;
if ((p->events & POLLOUT) && (ready & BSD_WRITE))
p->revents |= POLLOUT;
if (s->err != 0) p->revents |= POLLERR;
if (s->err != 0 || s->is_shut_wr) p->revents |= POLLERR;
if (s->closed) p->revents |= POLLHUP;
}
if (p->revents != 0) n++;
@@ -895,6 +1041,55 @@ int close(int fd) {
return cmd.result;
}
int shutdown(int fd, int how) {
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
cmd.op = BSD_OP_SHUTDOWN, cmd.op_shutdown.fd = fd,
cmd.op_shutdown.how = how;
bsd_dispatch(&cmd);
errno = cmd.err;
return cmd.result;
}
int setsockopt(int fd, int level, int optname, const void *optval,
socklen_t optlen) {
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
cmd.op = BSD_OP_SETSOCKOPT, cmd.op_setsockopt.fd = fd,
cmd.op_setsockopt.level = level, cmd.op_setsockopt.optname = optname,
cmd.op_setsockopt.optval = optval, cmd.op_setsockopt.optlen = optlen;
bsd_dispatch(&cmd);
errno = cmd.err;
return cmd.result;
}
int getsockopt(int fd, int level, int optname, void *optval,
socklen_t *optlen) {
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
cmd.op = BSD_OP_GETSOCKOPT, cmd.op_getsockopt.fd = fd,
cmd.op_getsockopt.level = level, cmd.op_getsockopt.optname = optname,
cmd.op_getsockopt.optval = optval, cmd.op_getsockopt.optlen = optlen;
bsd_dispatch(&cmd);
errno = cmd.err;
return cmd.result;
}
int getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) {
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
cmd.op = BSD_OP_GETSOCKNAME, cmd.op_getname.fd = fd,
cmd.op_getname.addr = addr, cmd.op_getname.addrlen = addrlen;
bsd_dispatch(&cmd);
errno = cmd.err;
return cmd.result;
}
int getpeername(int fd, struct sockaddr *addr, socklen_t *addrlen) {
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
cmd.op = BSD_OP_GETPEERNAME, cmd.op_getname.fd = fd,
cmd.op_getname.addr = addr, cmd.op_getname.addrlen = addrlen;
bsd_dispatch(&cmd);
errno = cmd.err;
return cmd.result;
}
int poll(struct pollfd *fds, nfds_t nfds, int timeout) {
struct mg_bsd_cmd cmd = BSD_CMD_EMPTY;
uint64_t deadline = timeout < 0 ? (uint64_t) ~0
@@ -926,6 +1121,36 @@ struct hostent *gethostbyname(const char *name) {
return cmd.op_gethostbyname.result;
}
int inet_pton(int af, const char *src, void *dst) {
if (af != AF_INET) {
errno = EAFNOSUPPORT;
return -1;
}
return bsd_aton4(src, dst) ? 1 : 0;
}
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) {
if (af != AF_INET) {
errno = EAFNOSUPPORT;
return NULL;
}
if (mg_snprintf(dst, size, "%M", mg_print_ip4, (void *) src) >= size) {
errno = ENOSPC;
return NULL;
}
return dst;
}
in_addr_t inet_addr(const char *src) {
in_addr_t addr;
return bsd_aton4(src, &addr) ? addr : INADDR_NONE;
}
char *inet_ntoa(struct in_addr addr) {
static char buf[INET_ADDRSTRLEN];
return (char *) inet_ntop(AF_INET, &addr, buf, sizeof(buf));
}
#endif // MG_ENABLE_BSD_SOCKETS
#if MG_ENABLE_BSD_SOCKETS
+11
View File
@@ -55,6 +55,15 @@ void mg_bsd_poll(struct mg_mgr *);
#ifndef EPIPE
#define EPIPE 32
#endif
#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT 97
#endif
#ifndef ENOPROTOOPT
#define ENOPROTOOPT 92
#endif
#ifndef ENOSPC
#define ENOSPC 28
#endif
#ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT 93
#endif
@@ -136,7 +145,9 @@ typedef struct fd_set {
#define IPPROTO_UDP 17
#define INADDR_ANY 0
#define INADDR_LOOPBACK 0x7f000001
#define INADDR_NONE ((in_addr_t) -1)
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
#define SOL_SOCKET 0xffff
#define SO_REUSEADDR 2
#define SO_REUSEPORT 15
+2
View File
@@ -96,6 +96,8 @@ struct mg_dash {
struct mg_fs *upload_fs; // FS for uploads/listing, default &mg_fs_posix
// Resolves the built-in file manager's root dir for user u, e.g. per-user
// upload directories. NULL: file manager disabled
// - The returned directory must be nonempty and must not contain ',' or '=',
// because it is used as an mg_http_serve_opts::root_dir mapping target.
bool (*files_dir)(const struct mg_dash_user *u, char *buf, size_t len);
};
+171 -77
View File
@@ -1,3 +1,5 @@
#include "l2_mesh.h"
#include "log.h"
#include "net_builtin.h"
#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,
T2PRIMECMP, T3CMP, T4CMP, PA_PWR, CHANNEL_NUM0, LQI_AND_RSSI,
MACSHORTADDRS0, MACLONGADDRS0_LSB, MACLONGADDRS0_MSB, RX_FRAME_FILTER,
CCA_LQI_CTRL, CCA2_CTRL, RESERVED_0, DSM_CTRL, RESERVED_1,
MACSHORTADDRS1, MACLONGADDRS1_LSB, MACLONGADDRS1_MSB, DUAL_PAN_CTRL,
CHANNEL_NUM1, SAM_CTRL, SAM_TABLE, SAM_MATCH, SAM_FREE_IDX,
SEQ_CTRL_STS, ACKDELAY, FILTERFAIL_CODE, RX_WTR_MARK, RESERVED_2,
SLOT_PRELOAD, SEQ_STATE, TMR_PRESCALE, LENIENCY_LSB, LENIENCY_MSB,
PART_ID, COEX_CTRL, COEX_PRIORITY, ENHACK_CTRL0;
CCA_LQI_CTRL, CCA2_CTRL, RESERVED_0, DSM_CTRL, RESERVED_1, MACSHORTADDRS1,
MACLONGADDRS1_LSB, MACLONGADDRS1_MSB, DUAL_PAN_CTRL, CHANNEL_NUM1,
SAM_CTRL, SAM_TABLE, SAM_MATCH, SAM_FREE_IDX, SEQ_CTRL_STS, ACKDELAY,
FILTERFAIL_CODE, RX_WTR_MARK, RESERVED_2, SLOT_PRELOAD, SEQ_STATE,
TMR_PRESCALE, LENIENCY_LSB, LENIENCY_MSB, PART_ID, COEX_CTRL,
COEX_PRIORITY, ENHACK_CTRL0;
};
// TrustZone: code built for the secure state (-mcmse) uses the secure alias
@@ -32,111 +34,203 @@ struct mg_zll {
#define MGI_IRQSTS_SEQIRQ MG_BIT(0)
#define MGI_IRQSTS_RXIRQ MG_BIT(2)
#define MGI_IRQSTS_FILTERFAIL MG_BIT(5) // Frame not for us
#define MGI_IRQSTS_CRCVALID MG_BIT(15)
#define MGI_IRQSTS_RX_LEN(x) (((x) >> 24) & 0x7f) // Frame length incl. FCS
#define MGI_PHY_CTRL_XCVSEQ 7U
#define MGI_PHY_CTRL_ALL_IRQ_MASKS 0x8007ff00U // Bits 8..18, 31: mask all IRQs
#define MGI_PHY_CTRL_SEQMSK MG_BIT(8) // Mask the sequence end IRQ
#define MGI_PHY_CTRL_TRCV_MSK MG_BIT(31) // Mask all transceiver IRQs
#define MGI_IRQN 53 // RF_802_15_4_IRQn
#define MGI_NVIC_ISER ((volatile uint32_t *) 0xE000E100) // Enable IRQ
#define MGI_NVIC_ICER ((volatile uint32_t *) 0xE000E180) // Disable IRQ
#define MGI_SEQ_STATE_MASK 0x1fU
#define MGI_SEQ_STATE_RX_WU 0x10U // RX warm up
#define MGI_SEQ_STATE_RX_WU 0x10U // RX warm up
#define MGI_SEQ_CTRL_NO_RX_RECYCLE MG_BIT(5)
#define MGI_FRAME_FILTER 0x70bU // Beacon, data, command; versions 0..2
enum { MGI_SEQ_IDLE = 0, MGI_SEQ_RX = 1, MGI_SEQ_TX = 2 }; // PHY_CTRL[XCVSEQ]
#define MGI_TX_TIMEOUT 400U // Symbols, 16 us: 127 bytes
// PHY_CTRL[XCVSEQ]
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 uint32_t mg_mcxw71_symbols(void) {
return MGI_ZLL->EVENT_TMR >> 8;
}
// 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;
}
static struct mg_tcpip_if *s_mcxw71_ifp;
static struct mg_queue s_mcxw71_rx;
static char s_mcxw71_queue[2048];
static volatile bool s_mcxw71_txing, s_mcxw71_done;
static uint64_t s_mcxw71_sendstart;
// Stop the current sequence, wait for the sequencer to go idle. See PhyAbort()
static void mg_mcxw71_idle(void) {
static void mg_mcxw71_abort(void) {
if (MGI_ZLL->PHY_CTRL & MGI_PHY_CTRL_XCVSEQ) {
while ((MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) == MGI_SEQ_STATE_RX_WU)
(void) 0;
MGI_ZLL->PHY_CTRL &= ~MGI_PHY_CTRL_XCVSEQ;
while (MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) (void) 0;
}
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; // 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->PHY_CTRL |= seq;
}
static bool mg_mcxw71_init(struct mg_tcpip_if *ifp) {
struct mg_l2_802154_data *d =
(struct mg_l2_802154_data *) ifp->driver_data;
if (d == NULL || d->channel < 11 || d->channel > 26) {
MG_ERROR(("Set driver_data, channel 11..26"));
return false;
// Start receiving, with the sequence end IRQ on, see RF_802_15_4_IRQHandler()
static void mg_mcxw71_listen(void) {
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
MGI_ZLL->PHY_CTRL |= MGI_SEQ_RX;
MGI_ZLL->PHY_CTRL &= ~(MGI_PHY_CTRL_SEQMSK | MGI_PHY_CTRL_TRCV_MSK);
MGI_NVIC_ISER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
}
// Keep the IRQ handler out while we change the radio. The NVIC disable is
// atomic, so PHY_CTRL updates here and there do not race
static void mg_mcxw71_lock(void) {
MGI_NVIC_ICER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
__asm volatile("dsb; isb" ::: "memory");
}
// Start the TX sequence for the loaded frame. Its end: see the IRQ handler
static void mg_mcxw71_go(void) {
MGI_ZLL->PHY_CTRL |= MGI_PHY_CTRL_TRCV_MSK | MGI_PHY_CTRL_SEQMSK;
mg_mcxw71_abort();
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
MGI_ZLL->PHY_CTRL |= MGI_SEQ_TX;
MGI_ZLL->PHY_CTRL &= ~(MGI_PHY_CTRL_SEQMSK | MGI_PHY_CTRL_TRCV_MSK);
}
void RF_802_15_4_IRQHandler(void);
void RF_802_15_4_IRQHandler(void) {
uint32_t irqsts = MGI_ZLL->IRQSTS;
if ((irqsts & MGI_IRQSTS_SEQIRQ) != 0) {
mg_mcxw71_abort();
if (s_mcxw71_txing) {
s_mcxw71_txing = false;
s_mcxw71_done = true;
} else if ((irqsts & (MGI_IRQSTS_RXIRQ | MGI_IRQSTS_FILTERFAIL |
MGI_IRQSTS_CRCVALID)) ==
(MGI_IRQSTS_RXIRQ | MGI_IRQSTS_CRCVALID)) {
uint8_t frame[125];
size_t i, n = MGI_IRQSTS_RX_LEN(irqsts);
n = n >= 2 ? n - 2 : 0;
if (n > 0 && n <= sizeof(frame)) {
for (i = 0; i < n; i++) frame[i] = MGI_RX_PB[i];
{
char *dst;
if (mg_queue_book(&s_mcxw71_rx, &dst, n) >= n) {
memcpy(dst, frame, n);
mg_queue_add(&s_mcxw71_rx, n);
} else {
s_mcxw71_ifp->ndrop++;
}
}
}
}
mg_mcxw71_listen();
}
}
static size_t mgi_mcxw71_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
size_t len) {
size_t i;
(void) ifp;
if (len > 125 || s_mcxw71_txing) return 0;
mg_mcxw71_lock();
MGI_TX_PB[0] = (uint8_t) (len + 2); // FCS is added by hardware
for (i = 0; i < len; i++)
MGI_TX_PB[i + 1] = ((const uint8_t *) buf)[i]; // 8-bit access only
s_mcxw71_done = false;
s_mcxw71_txing = true;
s_mcxw71_sendstart = mg_millis();
mg_mcxw71_go();
MGI_NVIC_ISER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
return len;
}
static bool mgi_mcxw71_radio_init(struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
struct mg_mesh_node *node = data->mesh;
s_mcxw71_ifp = ifp;
MGI_ZLL->SEQ_CTRL_STS |= MGI_SEQ_CTRL_NO_RX_RECYCLE;
MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS; // Mask all IRQs, we poll
MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS;
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
MGI_ZLL->SAM_CTRL = 0;
MGI_ZLL->RX_FRAME_FILTER = MGI_FRAME_FILTER;
MGI_ZLL->TMR_PRESCALE = 5; // 16 us timebase, one symbol
MGI_ZLL->CHANNEL_NUM0 = d->channel;
MGI_ZLL->MACSHORTADDRS0 = (0xfffeU << 16) | d->pan_id; // Filter by PAN ID
MGI_ZLL->PA_PWR = 0x3f; // PA slices, max output power
mg_mcxw71_start(MGI_SEQ_RX);
MGI_ZLL->CHANNEL_NUM0 = MG_802154_CHANNEL;
MGI_ZLL->MACSHORTADDRS0 =
((uint32_t) node->radio_address << 16) | MG_802154_PAN_ID;
MGI_ZLL->PA_PWR = 0x3f;
mg_mcxw71_listen();
return true;
}
// Send one radio frame: MAC header and payload, no FCS
static size_t mg_mcxw71_send(const uint8_t *p, size_t len,
struct mg_tcpip_if *ifp) {
size_t i;
bool ok;
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 mgi_mcxw71_tx(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
return data->mesh->strategy->tx(data->mesh, buf, len);
}
static size_t mg_mcxw71_tx(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
return mg_l2_802154_tx(ifp, buf, len, mg_mcxw71_send);
static size_t mgi_mcxw71_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
size_t len = data->mesh->rx_len;
if (len > cap) return 0;
if (len != 0 && buf != ifp->recv_queue.buf)
memcpy(buf, ifp->recv_queue.buf, len);
data->mesh->rx_len = 0;
return len;
}
static size_t mg_mcxw71_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
uint32_t irqsts = MGI_ZLL->IRQSTS;
uint8_t f[125];
size_t i, n = 0;
if ((irqsts & MGI_IRQSTS_SEQIRQ) == 0) return 0; // Still receiving
if (irqsts & MGI_IRQSTS_RXIRQ) {
n = MGI_IRQSTS_RX_LEN(irqsts);
n = n >= 2 ? n - 2 : 0; // Strip FCS
if (n > sizeof(f)) n = sizeof(f);
for (i = 0; i < n; i++) f[i] = MGI_RX_PB[i];
static bool mgi_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) {
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
struct mg_mesh_node *node = data->mesh;
uint64_t now = mg_micros();
char *buf;
size_t len;
(void) s1;
if (s_mcxw71_txing &&
mg_millis() > s_mcxw71_sendstart + MGI_SEND_TIMEOUT_MS) {
mg_mcxw71_lock();
if (s_mcxw71_txing) {
mg_mcxw71_abort();
s_mcxw71_txing = false;
s_mcxw71_done = true;
mg_mcxw71_listen();
} else {
MGI_NVIC_ISER[MGI_IRQN / 32] = MG_BIT(MGI_IRQN % 32);
}
}
mg_mcxw71_idle();
mg_mcxw71_start(MGI_SEQ_RX);
return mg_l2_802154_rx(ifp, f, n, buf, len);
if (s_mcxw71_done) {
s_mcxw71_done = false;
node->tx_at = now + node->strategy->radio->ifs_us;
}
node->now = now;
while (node->rx_len == 0 && (len = mg_queue_next(&s_mcxw71_rx, &buf)) != 0) {
node->strategy->rx(node, buf, len);
mg_queue_del(&s_mcxw71_rx, len);
}
node->strategy->tick(node, now);
return node->joined;
}
static bool mg_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) {
(void) ifp, (void) s1;
return true; // Radio link is always up
static bool mgi_mcxw71_init(struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_mcxw71_data *data = ifp->driver_data;
const struct mg_mesh *strategy = data->strategy;
uint16_t address;
address =
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
if (strategy == NULL) strategy = &mg_mesh_default;
data->mesh = calloc(1, sizeof(*data->mesh));
if (data->mesh == NULL) return false;
if (!strategy->init(data->mesh, ifp, NULL, address)) {
free(data->mesh);
data->mesh = NULL;
return false;
}
data->mesh->strategy = strategy;
data->mesh->tx = mgi_mcxw71_radio_tx;
mg_queue_init(&s_mcxw71_rx, s_mcxw71_queue, sizeof(s_mcxw71_queue));
if (mgi_mcxw71_radio_init(ifp)) return true;
strategy->free(data->mesh);
free(data->mesh);
data->mesh = NULL;
return false;
}
struct mg_tcpip_driver mg_tcpip_driver_mcxw71 = {mg_mcxw71_init, mg_mcxw71_tx,
mg_mcxw71_rx, mg_mcxw71_poll};
struct mg_tcpip_driver mg_tcpip_driver_mcxw71 = {
mgi_mcxw71_init, mgi_mcxw71_tx, mgi_mcxw71_rx, mgi_mcxw71_poll};
#endif
+26 -16
View File
@@ -1,25 +1,35 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#pragma once
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
MG_ENABLE_DRIVER_MCXW71
// NXP MCXW71 IEEE 802.15.4 radio, ZLL link layer, as an Ethernet interface:
// Ethernet frames are split into radio frames, see mg_l2_802154_tx().
// The transceiver (XCVR) must be set up for 802.15.4 before mg_tcpip_init(),
// see hal_radio_init() in the FRDM-MCXW71 tutorial.
// driver_data: struct mg_l2_802154_data, channel and PAN ID set
struct mg_tcpip_driver_mcxw71_data {
struct mg_mesh_node *mesh; // Driver-owned runtime state
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
};
#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_mcxw71; \
mif_.driver_data = &driver_data_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: mcxw71, channel %d", MG_L2_802154_CHANNEL)); \
extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71;
#define MG_TCPIP_DRIVER_MCXW71_INIT(manager) \
do { \
static struct mg_tcpip_driver_mcxw71_data driver_data_; \
static struct mg_tcpip_if mif_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.mgr = (manager); \
mif_.driver = &mg_tcpip_driver_mcxw71; \
mif_.driver_data = &driver_data_; \
mg_tcpip_init((manager), &mif_); \
MG_INFO(("Driver: mcxw71, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MCXW71_INIT(x)
#endif
#endif
+183
View File
@@ -0,0 +1,183 @@
#include "net_builtin.h"
#include "l2_mesh.h"
#include "log.h"
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MG24) && MG_ENABLE_DRIVER_MG24
#include <rail.h>
#include <rail_ieee802154.h>
// RAIL delivers raw frames and transmit completion from interrupt context.
#define MG_MG24_TX_TIMEOUT_US 60000
static struct mg_tcpip_if *s_mg24_ifp;
static RAIL_Handle_t s_mg24_rail;
static struct mg_queue s_mg24_rx;
static char s_mg24_rxbuf[2048];
static volatile bool s_mg24_txing, s_mg24_tx_done;
static RAIL_Time_t s_mg24_txstart; // Send start, us
static uint8_t s_mg24_txfifo[256] __attribute__((aligned(4))); // Power of 2
// Copy a received PSDU, stripping the PHR and any stored FCS.
static void mg_mg24_rx_frame(RAIL_Handle_t h) {
RAIL_RxPacketInfo_t info;
uint8_t f[128];
size_t n;
char *dst;
if (RAIL_GetRxPacketInfo(h, RAIL_RX_PACKET_HANDLE_NEWEST, &info) ==
RAIL_RX_PACKET_HANDLE_INVALID ||
info.packetStatus != RAIL_RX_PACKET_READY_SUCCESS ||
info.packetBytes <= 1 || info.packetBytes > sizeof(f)) {
return;
}
RAIL_CopyRxPacket(f, &info);
n = info.packetBytes - 1U; // Strip PHR
if (n == f[0]) n -= 2; // FCS stored, strip it
if (mg_queue_book(&s_mg24_rx, &dst, n) >= n) {
memcpy(dst, f + 1, n);
mg_queue_add(&s_mg24_rx, n);
} else {
s_mg24_ifp->ndrop++;
}
}
// RAIL event callback, runs in interrupt context
static void mg_mg24_events(RAIL_Handle_t h, RAIL_Events_t events) {
if (events & RAIL_EVENT_CAL_NEEDED) {
RAIL_Calibrate(h, NULL, RAIL_CAL_ALL_PENDING);
}
if (events & RAIL_EVENT_RX_PACKET_RECEIVED) mg_mg24_rx_frame(h);
if (events & RAIL_EVENTS_TX_COMPLETION) s_mg24_tx_done = true;
}
// RAIL adds the FCS.
static size_t mgi_mg24_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
size_t len) {
uint8_t phr = (uint8_t) (len + 2); // FCS is added by hardware
(void) ifp;
if (len > 125 || s_mg24_txing) return 0;
RAIL_WriteTxFifo(s_mg24_rail, &phr, 1, true); // Reset FIFO, write PHR
RAIL_WriteTxFifo(s_mg24_rail, (const uint8_t *) buf, (uint16_t) len, false);
s_mg24_tx_done = false;
s_mg24_txing = true, s_mg24_txstart = RAIL_GetTime();
if (RAIL_StartTx(s_mg24_rail, MG_802154_CHANNEL, RAIL_TX_OPTIONS_DEFAULT,
NULL) != RAIL_STATUS_NO_ERROR) {
s_mg24_txing = false;
return 0;
}
return len;
}
static bool mgi_mg24_radio_init(struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
struct mg_mesh_node *node = data->mesh;
RAIL_Config_t cfg = {0};
RAIL_IEEE802154_Config_t cfg154 = {0};
RAIL_TxPowerConfig_t pwr;
RAIL_StateTransitions_t to_rx = {RAIL_RF_STATE_RX, RAIL_RF_STATE_RX};
RAIL_Events_t events = RAIL_EVENT_CAL_NEEDED | RAIL_EVENT_RX_PACKET_RECEIVED |
RAIL_EVENTS_TX_COMPLETION;
cfg.eventsCallback = mg_mg24_events;
if ((s_mg24_rail = RAIL_Init(&cfg, NULL)) == NULL) {
MG_ERROR(("RAIL_Init failed"));
return false;
}
cfg154.ackConfig.enable = false;
cfg154.ackConfig.ackTimeout = 864;
cfg154.ackConfig.rxTransitions = to_rx;
cfg154.ackConfig.txTransitions = to_rx;
cfg154.timings.idleToRx = 100, cfg154.timings.idleToTx = 100;
cfg154.timings.rxToTx = 192, cfg154.timings.txToRx = 182;
cfg154.framesMask = RAIL_IEEE802154_ACCEPT_STANDARD_FRAMES;
cfg154.promiscuousMode = false; // Filter by PAN ID and our short address
pwr.mode = RAIL_TX_POWER_MODE_2P4GIG_HP, pwr.voltage = 3300;
pwr.rampTime = 10;
if (RAIL_ConfigCal(s_mg24_rail, RAIL_CAL_ALL) != RAIL_STATUS_NO_ERROR ||
RAIL_ConfigEvents(s_mg24_rail, RAIL_EVENTS_ALL, events) !=
RAIL_STATUS_NO_ERROR ||
RAIL_IEEE802154_Config2p4GHzRadio(s_mg24_rail) != RAIL_STATUS_NO_ERROR ||
RAIL_IEEE802154_Init(s_mg24_rail, &cfg154) != RAIL_STATUS_NO_ERROR ||
RAIL_IEEE802154_SetPanId(s_mg24_rail, MG_802154_PAN_ID, 0) !=
RAIL_STATUS_NO_ERROR ||
RAIL_IEEE802154_SetShortAddress(s_mg24_rail, node->radio_address, 0) !=
RAIL_STATUS_NO_ERROR ||
RAIL_SetTxTransitions(s_mg24_rail, &to_rx) != RAIL_STATUS_NO_ERROR ||
RAIL_SetRxTransitions(s_mg24_rail, &to_rx) != RAIL_STATUS_NO_ERROR ||
RAIL_ConfigTxPower(s_mg24_rail, &pwr) != RAIL_STATUS_NO_ERROR ||
RAIL_SetTxPower(s_mg24_rail, RAIL_TX_POWER_LEVEL_2P4_HP_MAX) !=
RAIL_STATUS_NO_ERROR ||
RAIL_SetTxFifo(s_mg24_rail, s_mg24_txfifo, 0, sizeof(s_mg24_txfifo)) !=
sizeof(s_mg24_txfifo)) {
MG_ERROR(("RAIL setup failed"));
return false;
}
s_mg24_ifp = ifp;
return RAIL_StartRx(s_mg24_rail, MG_802154_CHANNEL, NULL) ==
RAIL_STATUS_NO_ERROR;
}
static size_t mgi_mg24_tx(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
return data->mesh->strategy->tx(data->mesh, buf, len);
}
static size_t mgi_mg24_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
size_t len = data->mesh->rx_len;
if (len > cap) return 0;
if (len != 0 && buf != ifp->recv_queue.buf)
memcpy(buf, ifp->recv_queue.buf, len);
data->mesh->rx_len = 0;
return len;
}
static bool mgi_mg24_poll(struct mg_tcpip_if *ifp, bool s1) {
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
struct mg_mesh_node *node = data->mesh;
uint64_t now = mg_micros();
char *buf;
size_t len;
(void) s1;
node->now = now;
if (s_mg24_txing && (s_mg24_tx_done || RAIL_GetTime() - s_mg24_txstart >
MG_MG24_TX_TIMEOUT_US)) {
if (!s_mg24_tx_done) {
RAIL_Idle(s_mg24_rail, RAIL_IDLE_ABORT, true);
RAIL_StartRx(s_mg24_rail, MG_802154_CHANNEL, NULL);
}
s_mg24_txing = false;
node->tx_at = now + node->strategy->radio->ifs_us;
}
while (node->rx_len == 0 && (len = mg_queue_next(&s_mg24_rx, &buf)) != 0) {
node->strategy->rx(node, buf, len);
mg_queue_del(&s_mg24_rx, len);
}
node->strategy->tick(node, now);
return node->joined;
}
static bool mgi_mg24_init(struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_mg24_data *data = ifp->driver_data;
const struct mg_mesh *strategy = data->strategy;
uint16_t address =
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
if (strategy == NULL) strategy = &mg_mesh_default;
data->mesh = calloc(1, sizeof(*data->mesh));
if (data->mesh == NULL) return false;
if (!strategy->init(data->mesh, ifp, NULL, address)) {
free(data->mesh);
data->mesh = NULL;
return false;
}
data->mesh->strategy = strategy;
data->mesh->tx = mgi_mg24_radio_tx;
mg_queue_init(&s_mg24_rx, s_mg24_rxbuf, sizeof(s_mg24_rxbuf));
if (mgi_mg24_radio_init(ifp)) return true;
data->mesh->strategy->free(data->mesh);
free(data->mesh);
data->mesh = NULL;
return false;
}
struct mg_tcpip_driver mg_tcpip_driver_mg24 = {mgi_mg24_init, mgi_mg24_tx,
mgi_mg24_rx, mgi_mg24_poll};
#endif
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#pragma once
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MG24) && MG_ENABLE_DRIVER_MG24
struct mg_tcpip_driver_mg24_data {
struct mg_mesh_node *mesh; // Driver-owned runtime state
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
};
extern struct mg_tcpip_driver mg_tcpip_driver_mg24;
#define MG_TCPIP_DRIVER_MG24_INIT(manager) \
do { \
static struct mg_tcpip_driver_mg24_data driver_data_; \
static struct mg_tcpip_if mif_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.mgr = (manager); \
mif_.driver = &mg_tcpip_driver_mg24; \
mif_.driver_data = &driver_data_; \
mg_tcpip_init((manager), &mif_); \
MG_INFO(("Driver: mg24, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_MG24_INIT(x)
#endif
#endif
+179
View File
@@ -0,0 +1,179 @@
#include "net_builtin.h"
#include "l2_mesh.h"
#include "log.h"
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_NRF52) && MG_ENABLE_DRIVER_NRF52
#include <nrf.h>
static struct mg_tcpip_if *s_nrf52_ifp;
static struct mg_queue s_nrf52_rx;
static char s_nrf52_queue[2048];
static uint8_t s_nrf52_rxbuf[128] __attribute__((aligned(4)));
static uint8_t s_nrf52_txbuf[128] __attribute__((aligned(4)));
static volatile bool s_nrf52_txing, s_nrf52_done;
static uint64_t s_nrf52_started;
static void mgi_nrf52_listen(void) {
NRF_RADIO->PACKETPTR = (uint32_t) s_nrf52_rxbuf;
NRF_RADIO->SHORTS =
RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk;
NRF_RADIO->EVENTS_FRAMESTART = 0;
NRF_RADIO->EVENTS_END = 0;
NRF_RADIO->TASKS_RXEN = 1;
}
static void mgi_nrf52_disable(void) {
NRF_RADIO->SHORTS = 0;
NRF_RADIO->EVENTS_DISABLED = 0;
NRF_RADIO->TASKS_DISABLE = 1;
while (!NRF_RADIO->EVENTS_DISABLED) (void) 0;
NRF_RADIO->EVENTS_DISABLED = 0;
}
void RADIO_IRQHandler(void);
void RADIO_IRQHandler(void) {
if (!NRF_RADIO->EVENTS_DISABLED) return;
NRF_RADIO->EVENTS_DISABLED = 0;
if (s_nrf52_txing) {
s_nrf52_txing = false;
s_nrf52_done = true;
} else if (NRF_RADIO->CRCSTATUS) {
struct mg_tcpip_driver_nrf52_data *data = s_nrf52_ifp->driver_data;
struct mg_mesh_node *node = data->mesh;
size_t len = s_nrf52_rxbuf[0] >= 2 ? s_nrf52_rxbuf[0] - 2U : 0;
uint8_t *frame = s_nrf52_rxbuf + 1;
uint16_t dst, src;
if (len <= 125 &&
mg_radio_802154.parse_hdr(&mg_radio_802154, frame, len, &dst, &src) !=
0 &&
(dst == 0xffffu || dst == node->radio_address)) {
char *dst;
if (mg_queue_book(&s_nrf52_rx, &dst, len) >= len) {
memcpy(dst, frame, len);
mg_queue_add(&s_nrf52_rx, len);
} else {
s_nrf52_ifp->ndrop++;
}
}
}
mgi_nrf52_listen();
}
static size_t mgi_nrf52_radio_tx(struct mg_tcpip_if *ifp, const void *buf,
size_t len) {
(void) ifp;
if (len > 125) return 0;
NVIC_DisableIRQ(RADIO_IRQn);
if (s_nrf52_txing || NRF_RADIO->EVENTS_FRAMESTART) {
NVIC_EnableIRQ(RADIO_IRQn);
return 0;
}
mgi_nrf52_disable();
s_nrf52_txbuf[0] = (uint8_t) (len + 2);
memcpy(s_nrf52_txbuf + 1, buf, len);
s_nrf52_done = false;
s_nrf52_txing = true;
s_nrf52_started = mg_micros();
NRF_RADIO->PACKETPTR = (uint32_t) s_nrf52_txbuf;
NRF_RADIO->SHORTS =
RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk;
NRF_RADIO->TASKS_TXEN = 1;
NVIC_EnableIRQ(RADIO_IRQn);
return len;
}
static bool mgi_nrf52_radio_init(struct mg_tcpip_if *ifp) {
s_nrf52_ifp = ifp;
NRF_RADIO->POWER = 1;
NRF_RADIO->MODE = RADIO_MODE_MODE_Ieee802154_250Kbit;
NRF_RADIO->PCNF0 = (8UL << RADIO_PCNF0_LFLEN_Pos) |
(RADIO_PCNF0_PLEN_32bitZero << RADIO_PCNF0_PLEN_Pos) |
RADIO_PCNF0_CRCINC_Msk;
NRF_RADIO->PCNF1 = 127;
NRF_RADIO->CRCCNF = 0x202;
NRF_RADIO->CRCPOLY = 0x11021;
NRF_RADIO->CRCINIT = 0;
NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_DTX_Center << RADIO_MODECNF0_DTX_Pos) |
RADIO_MODECNF0_RU_Fast;
NRF_RADIO->SFD = 0xa7;
NRF_RADIO->FREQUENCY = 5 * (MG_802154_CHANNEL - 10);
NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos8dBm;
// Correlator threshold 45 matches the RADIO reset value. A threshold of
// 20 caused false busy detections and persistent L2 retries.
NRF_RADIO->CCACTRL =
(RADIO_CCACTRL_CCAMODE_CarrierMode << RADIO_CCACTRL_CCAMODE_Pos) |
(2UL << RADIO_CCACTRL_CCACORRCNT_Pos) |
(45UL << RADIO_CCACTRL_CCACORRTHRES_Pos);
NRF_RADIO->INTENSET = RADIO_INTENSET_DISABLED_Msk;
NVIC_SetPriority(RADIO_IRQn, 0);
NVIC_EnableIRQ(RADIO_IRQn);
mgi_nrf52_listen();
return true;
}
static size_t mgi_nrf52_tx(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
return data->mesh->strategy->tx(data->mesh, buf, len);
}
static size_t mgi_nrf52_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
size_t len = data->mesh->rx_len;
if (len > cap) return 0;
if (len != 0 && buf != ifp->recv_queue.buf)
memcpy(buf, ifp->recv_queue.buf, len);
data->mesh->rx_len = 0;
return len;
}
static bool mgi_nrf52_poll(struct mg_tcpip_if *ifp, bool s1) {
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
struct mg_mesh_node *node = data->mesh;
uint64_t now = mg_micros();
char *buf;
size_t len;
(void) s1;
if (s_nrf52_txing && mg_micros() - s_nrf52_started > 60000) {
NVIC_DisableIRQ(RADIO_IRQn);
mgi_nrf52_disable();
s_nrf52_txing = false;
s_nrf52_done = true;
mgi_nrf52_listen();
NVIC_EnableIRQ(RADIO_IRQn);
}
if (s_nrf52_done) {
s_nrf52_done = false;
node->tx_at = now + node->strategy->radio->ifs_us;
}
node->now = now;
while (node->rx_len == 0 && (len = mg_queue_next(&s_nrf52_rx, &buf)) != 0) {
node->strategy->rx(node, buf, len);
mg_queue_del(&s_nrf52_rx, len);
}
node->strategy->tick(node, now);
return node->joined;
}
static bool mgi_nrf52_init(struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_nrf52_data *data = ifp->driver_data;
const struct mg_mesh *strategy = data->strategy;
uint16_t address;
address =
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
if (strategy == NULL) strategy = &mg_mesh_default;
data->mesh = calloc(1, sizeof(*data->mesh));
if (data->mesh == NULL) return false;
if (!strategy->init(data->mesh, ifp, NULL, address)) {
free(data->mesh);
data->mesh = NULL;
return false;
}
data->mesh->strategy = strategy;
data->mesh->tx = mgi_nrf52_radio_tx;
mg_queue_init(&s_nrf52_rx, s_nrf52_queue, sizeof(s_nrf52_queue));
if (mgi_nrf52_radio_init(ifp)) return true;
strategy->free(data->mesh);
free(data->mesh);
data->mesh = NULL;
return false;
}
struct mg_tcpip_driver mg_tcpip_driver_nrf52 = {mgi_nrf52_init, mgi_nrf52_tx,
mgi_nrf52_rx, mgi_nrf52_poll};
#endif
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#pragma once
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_NRF52) && MG_ENABLE_DRIVER_NRF52
struct mg_tcpip_driver_nrf52_data {
struct mg_mesh_node *mesh; // Driver-owned runtime state
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
};
extern struct mg_tcpip_driver mg_tcpip_driver_nrf52;
#define MG_TCPIP_DRIVER_NRF52_INIT(manager) \
do { \
static struct mg_tcpip_driver_nrf52_data driver_data_; \
static struct mg_tcpip_if mif_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.mgr = (manager); \
mif_.driver = &mg_tcpip_driver_nrf52; \
mif_.driver_data = &driver_data_; \
mg_tcpip_init((manager), &mif_); \
MG_INFO(("Driver: nrf52, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_NRF52_INIT(x)
#endif
#endif
+2
View File
@@ -27,6 +27,7 @@ struct mg_tcpip_driver_rw612_data {
#define MG_DRIVER_MDC_HOLDTIME 3
#endif
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(mgr) \
do { \
static struct mg_tcpip_driver_rw612_data driver_data_; \
@@ -42,5 +43,6 @@ struct mg_tcpip_driver_rw612_data {
mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#endif
#endif
-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
+144 -81
View File
@@ -1,7 +1,16 @@
#include "net_builtin.h"
#include "l2_mesh.h"
#include "log.h"
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \
MG_ENABLE_DRIVER_RW612_154
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612W) && \
MG_ENABLE_DRIVER_RW612W
#include "sha256.h"
#ifdef ENET
#undef ENET
#endif
#include <fsl_ocotp.h>
// The 802.15.4 radio runs NXP combo firmware on CPU2, an OpenThread RCP. We
// are CPU3. Messages go through the IMU mailbox: IMU messages carry IMUMC
@@ -16,13 +25,13 @@ struct mg_rw612_imu {
};
#define MGI_IMU ((struct mg_rw612_imu *) 0x443ff804) // CPU3 -> CPU2
#define MGI_IMU_PEER ((struct mg_rw612_imu *) 0x443ff818) // CPU2 -> CPU3
#define MGI_IMU_LOCKED MG_BIT(0) // STATUS: FIFO locked
#define MGI_IMU_ALMOST_FULL MG_BIT(1) // STATUS: FIFO almost full
#define MGI_IMU_EMPTY MG_BIT(3) // STATUS: FIFO empty
#define MGI_IMU_LOCKED MG_BIT(0) // STATUS: FIFO locked
#define MGI_IMU_ALMOST_FULL MG_BIT(1) // STATUS: FIFO almost full
#define MGI_IMU_EMPTY MG_BIT(3) // STATUS: FIFO empty
#define MGI_IMU_WR_PTR(s) (((s) >> 16) & 15U)
#define MGI_IMU_INT_CLR MG_BIT(0) // CNTL: clear message ready IRQ
#define MGI_IMU_FLUSH MG_BIT(16) // CNTL: flush send FIFO
#define MGI_IMU_WAIT_FOR_ACK MG_BIT(17) // CNTL: lock send FIFO
#define MGI_IMU_INT_CLR MG_BIT(0) // CNTL: clear message ready IRQ
#define MGI_IMU_FLUSH MG_BIT(16) // CNTL: flush send FIFO
#define MGI_IMU_WAIT_FOR_ACK MG_BIT(17) // CNTL: lock send FIFO
#define MGI_IMU_WATERMARK(x) ((uint32_t) (x) << 20)
#define MGI_IMU_WATERMARK_MASK (15U << 20)
@@ -73,8 +82,10 @@ enum {
MGI_PROP_NCP_VERSION = 2,
MGI_PROP_PHY_ENABLED = 0x20,
MGI_PROP_PHY_CHAN = 0x21,
MGI_PROP_SADDR = 0x35, // Short address, little endian
MGI_PROP_PANID = 0x36,
MGI_PROP_RAW_STREAM = 0x37,
MGI_PROP_PROMISCUOUS = 0x38,
MGI_PROP_STREAM_RAW = 0x71
};
#define MGI_RESET_STACK 2 // RESET argument: reset the RCP stack only
@@ -82,7 +93,6 @@ enum {
#define MGI_STATUS_RESET_MAX 127
#define MGI_TX_TIMEOUT_MS 200 // Wait for transmit done, incl. retries
#define MGI_RSP_TIMEOUT_MS 500
#define MGI_RXQ_CNT 16 // A whole Ethernet frame, 15 radio frames
// Driver state, one radio
struct mg_rw612 {
@@ -98,11 +108,13 @@ struct mg_rw612 {
bool rsp; // Response to the pending request received
uint32_t rsp_status; // Its status, if LAST_STATUS, else 0
char version[64]; // RCP version string
uint8_t rxq[MGI_RXQ_CNT][127]; // Received frames, no FCS
uint8_t rxlen[MGI_RXQ_CNT]; // Their lengths
uint8_t rxhead, rxtail; // Queue indexes
bool txing; // A send is on, see mg_rw612_start()
uint64_t txstart; // Its start, ms
struct mg_tcpip_if *ifp; // Our interface, for the L2 receive pool
};
static struct mg_rw612 s_rw612;
static struct mg_queue s_rw612w_rx;
static char s_rw612w_queue[2048];
static void mg_rw612_imu_send(uint8_t type, uint8_t subtype,
const uint32_t *payload, uint8_t len) {
@@ -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) {
struct mg_rw612 *s = &s_rw612;
uint8_t next = (uint8_t) ((s->rxhead + 1) % MGI_RXQ_CNT);
if (next == s->rxtail || len > sizeof(s->rxq[0])) return; // Full: drop
memcpy(s->rxq[s->rxhead], frame, len);
s->rxlen[s->rxhead] = (uint8_t) len;
s->rxhead = next;
{
char *dst;
if (mg_queue_book(&s_rw612w_rx, &dst, len) >= len) {
memcpy(dst, frame, len);
mg_queue_add(&s_rw612w_rx, len);
} else {
s_rw612.ifp->ndrop++;
}
}
}
// Spinel packed unsigned integer: 7 bits per byte, LSB first
@@ -293,14 +308,13 @@ static bool mg_rw612_imumc_send(const uint8_t *p, size_t len) {
return true;
}
// Send a Spinel frame, HDLC encoded, and wait for the response with the
// same transaction ID. Return false on timeout
static bool mg_rw612_request(uint8_t *f, size_t len, uint32_t timeout_ms) {
// Send a Spinel frame, HDLC encoded, with a new transaction ID. Its
// response sets rsp, see mg_rw612_spinel()
static bool mg_rw612_post(uint8_t *f, size_t len) {
struct mg_rw612 *s = &s_rw612;
uint8_t buf[2 * 160 + 6];
uint16_t fcs = 0xffff;
size_t i, n = 0;
uint64_t expire = mg_millis() + timeout_ms;
s->tid = (uint8_t) (s->tid % 15 + 1);
f[0] = (uint8_t) (0x80 | s->tid); // Header: flag, interface 0, TID
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++] = 0x7e;
if (!mg_rw612_imumc_send(buf, n)) return false;
while (!s->rsp && mg_millis() < expire) mg_rw612_poll_imu();
return s->rsp;
return mg_rw612_imumc_send(buf, n);
}
// Send a Spinel frame and wait for its response. Return false on timeout
static bool mg_rw612_request(uint8_t *f, size_t len, uint32_t timeout_ms) {
uint64_t expire = mg_millis() + timeout_ms;
if (!mg_rw612_post(f, len)) return false;
while (!s_rw612.rsp && mg_millis() < expire) mg_rw612_poll_imu();
return s_rw612.rsp;
}
static bool mg_rw612_set(uint32_t prop, const uint8_t *val, size_t len) {
@@ -338,27 +358,43 @@ static bool mg_rw612_fail(const char *msg) {
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_l2_802154_data *d =
(struct mg_l2_802154_data *) ifp->driver_data;
uint8_t f[8], one = 1, pan[2];
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
struct mg_mesh_node *node = data->mesh;
uint8_t f[8], one = 1, zero = 0, pan[2], addr[2], channel = MG_802154_CHANNEL;
uint64_t expire;
if (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) {
MG_ERROR(("Radio firmware is not running on CPU2"));
return false;
}
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()
MGI_IMU->CNTL |= MGI_IMU_FLUSH;
MGI_IMU->CNTL &= ~(MGI_IMU_FLUSH | MGI_IMU_WAIT_FOR_ACK);
MGI_IMU->CNTL = (MGI_IMU->CNTL & ~MGI_IMU_WATERMARK_MASK) |
MGI_IMU_WATERMARK(15);
MGI_IMU->CNTL =
(MGI_IMU->CNTL & ~MGI_IMU_WATERMARK_MASK) | MGI_IMU_WATERMARK(15);
while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) (void) MGI_IMU->RD_MSG;
// Synchronise the IMU link, then connect the IMUMC endpoints
MGI_MBOX->magic[1] = MGI_SYNC_MAGIC;
@@ -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)) {
return mg_rw612_fail("PHY enable failed");
}
pan[0] = (uint8_t) d->pan_id, pan[1] = (uint8_t) (d->pan_id >> 8);
if (!mg_rw612_set(MGI_PROP_PHY_CHAN, &d->channel, 1) ||
!mg_rw612_set(MGI_PROP_PANID, pan, 2)) {
return mg_rw612_fail("Channel or PAN ID set failed");
pan[0] = (uint8_t) MG_802154_PAN_ID;
pan[1] = (uint8_t) (MG_802154_PAN_ID >> 8);
addr[0] = (uint8_t) node->radio_address;
addr[1] = (uint8_t) (node->radio_address >> 8);
if (!mg_rw612_set(MGI_PROP_PHY_CHAN, &channel, 1) ||
!mg_rw612_set(MGI_PROP_PANID, pan, 2) ||
!mg_rw612_set(MGI_PROP_SADDR, addr, 2) ||
!mg_rw612_set(MGI_PROP_PROMISCUOUS, &zero, 1)) {
return mg_rw612_fail("Channel, PAN ID or address set failed");
}
if (!mg_rw612_set(MGI_PROP_RAW_STREAM, &one, 1)) {
return mg_rw612_fail("Raw stream enable failed");
@@ -396,55 +437,77 @@ static bool mg_rw612_154_init(struct mg_tcpip_if *ifp) {
return true;
}
// Send one radio frame: MAC header and payload, no FCS
static size_t mg_rw612_send(const uint8_t *p, size_t len,
static size_t mgi_rw612w_tx(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
struct mg_l2_802154_data *d =
(struct mg_l2_802154_data *) ifp->driver_data;
uint8_t f[3 + 2 + 127 + 4];
size_t n = 3;
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, p, len), n += len;
f[n++] = 0, f[n++] = 0; // FCS, filled in by the radio
f[n++] = d->channel;
f[n++] = 4; // Max CSMA backoffs
f[n++] = 0; // Max frame retries: broadcast frames are not acknowledged
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;
}
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
return data->mesh->strategy->tx(data->mesh, buf, len);
}
static size_t mgi_rw612w_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
size_t len = data->mesh->rx_len;
if (len > cap) return 0;
if (len != 0 && buf != ifp->recv_queue.buf)
memcpy(buf, ifp->recv_queue.buf, len);
data->mesh->rx_len = 0;
return len;
}
static size_t mg_rw612_154_tx(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
return mg_l2_802154_tx(ifp, buf, len, mg_rw612_send);
}
static size_t mg_rw612_154_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
struct mg_rw612 *s = &s_rw612;
size_t res = 0;
static bool mgi_rw612w_poll(struct mg_tcpip_if *ifp, bool s1) {
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
struct mg_mesh_node *node = data->mesh;
uint64_t now = mg_micros();
char *buf;
size_t len;
(void) s1;
bool was_sending = s_rw612.txing;
mg_rw612_poll_imu();
while (s->rxtail != s->rxhead && res == 0) {
res = mg_l2_802154_rx(ifp, s->rxq[s->rxtail], s->rxlen[s->rxtail], buf,
len);
s->rxtail = (uint8_t) ((s->rxtail + 1) % MGI_RXQ_CNT);
if (s_rw612.txing &&
(s_rw612.rsp || mg_millis() > s_rw612.txstart + MGI_TX_TIMEOUT_MS)) {
if (!s_rw612.rsp) MG_ERROR(("TX timeout"));
s_rw612.txing = false;
}
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) {
(void) ifp, (void) s1;
return s_rw612.up;
static bool mgi_rw612w_init(struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_rw612w_data *data = ifp->driver_data;
const struct mg_mesh *strategy = data->strategy;
uint8_t uid[FSL_OCOTP_UID_LENGTH] = {0}, digest[32];
uint32_t uid_len = sizeof(uid);
uint16_t address;
OCOTP_ReadUniqueID(uid, &uid_len);
mg_sha256(digest, uid, sizeof(uid));
memcpy(ifp->mac, digest, 6);
ifp->mac[0] = 0x02;
address =
(uint16_t) (2 + ((unsigned) ifp->mac[4] * 256 + ifp->mac[5]) % 65532u);
if (strategy == NULL) strategy = &mg_mesh_default;
data->mesh = calloc(1, sizeof(*data->mesh));
if (data->mesh == NULL) return false;
if (!strategy->init(data->mesh, ifp, NULL, address)) {
free(data->mesh);
data->mesh = NULL;
return false;
}
data->mesh->strategy = strategy;
data->mesh->tx = mgi_rw612w_radio_tx;
mg_queue_init(&s_rw612w_rx, s_rw612w_queue, sizeof(s_rw612w_queue));
if (mgi_rw612w_radio_init(ifp)) return true;
strategy->free(data->mesh);
free(data->mesh);
data->mesh = NULL;
return false;
}
struct mg_tcpip_driver mg_tcpip_driver_rw612_154 = {
mg_rw612_154_init, mg_rw612_154_tx, mg_rw612_154_rx, mg_rw612_154_poll};
struct mg_tcpip_driver mg_tcpip_driver_rw612w = {
mgi_rw612w_init, mgi_rw612w_tx, mgi_rw612w_rx, mgi_rw612w_poll};
#endif
+35
View File
@@ -0,0 +1,35 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#pragma once
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612W) && \
MG_ENABLE_DRIVER_RW612W
struct mg_tcpip_driver_rw612w_data {
struct mg_mesh_node *mesh; // Driver-owned runtime state
const struct mg_mesh *strategy; // NULL selects mg_mesh_default
};
extern struct mg_tcpip_driver mg_tcpip_driver_rw612w;
#define MG_TCPIP_DRIVER_RW612W_INIT(manager) \
do { \
static struct mg_tcpip_driver_rw612w_data driver_data_; \
static struct mg_tcpip_if mif_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.mgr = (manager); \
mif_.driver = &mg_tcpip_driver_rw612w; \
mif_.driver_data = &driver_data_; \
mg_tcpip_init((manager), &mif_); \
MG_INFO(("Driver: rw612w, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#ifndef MG_TCPIP_DRIVER_INIT
#define MG_TCPIP_DRIVER_INIT(x) MG_TCPIP_DRIVER_RW612W_INIT(x)
#endif
#endif
+6 -1
View File
@@ -214,7 +214,12 @@ size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt,
n++;
} else if (c == 's') {
char *p = va_arg(*ap, char *);
if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
if (p != NULL && pr != ~0U) {
for (j = 0; j < pr && p[j] != '\0'; j++) ; // portable strnlen()
pr = j;
} else {
pr = p == NULL ? 0 : strlen(p);
}
for (j = 0; !minus && pr < w && j + pr < w; j++)
n += scpy(out, param, &pad, 1);
n += scpy(out, param, p, pr);
+103 -37
View File
@@ -1,12 +1,66 @@
#include "config.h"
#include "health.h"
#if MG_ENABLE_HEALTH
#if MG_ARCH == MG_ARCH_ESP32
// reusable for other platforms, currently ESP32-only
struct mg_health_coredump {
const void *ptr;
size_t poffset;
size_t size;
};
static struct mg_health_coredump s_coredump;
#endif
bool mg_health_coredump_begin(size_t *size) {
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
const esp_partition_t *p = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL);
size_t address = 0, len = 0;
esp_err_t err = esp_core_dump_image_get(&address, &len);
if (size == NULL || s_coredump.ptr != NULL || err != ESP_OK || p == NULL)
return false;
s_coredump.ptr = p;
s_coredump.poffset = address - p->address;
s_coredump.size = len;
*size = len;
return true;
#else
(void) size;
return false;
#endif
}
bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) {
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr;
if (p == NULL || offset > s_coredump.size)
return false;
if (*len > s_coredump.size - offset) *len = s_coredump.size - offset;
return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == ESP_OK;
#else
(void) offset, (void) buf, (void) len;
return false;
#endif
}
bool mg_health_coredump_end(void) {
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
bool ok = s_coredump.ptr != NULL;
memset(&s_coredump, 0, sizeof(s_coredump));
return ok;
#else
return false;
#endif
}
// The one health record. Lives in RAM that survives a warm reset, see
// MG_HEALTH_RAM and the .mg_health region in the linker script
struct mg_health mg_health_record MG_HEALTH_RAM;
// STM32 H723 support. Needs the _estack symbol from the linker script
#if MG_ENABLE_HEALTH && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
// STM32 H7, H5, C5 support. Needs the _estack symbol from the linker script
#if defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5)
// Fault handler body. Runs in exception context: no printf, no malloc, no
// blocking calls. Copies the unwind registers and raw stack into the health
// record, then resets.
@@ -48,52 +102,57 @@ void BusFault_Handler(void) __attribute__((alias("HardFault_Handler")));
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
#endif
// Start a new boot: validate the record, count the boot, store the reset
// reason. The current boot moves to prev_* slots first, so the previous
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
// Start a new boot: validate the record. Called by MG_HEALTH_INIT()
void mg_health_init(void) {
char magic[] = MG_HEALTH_MAGIC;
// struct mg_health *h = mg_health_get();
if (!mg_health_valid()) { // First boot, or RAM lost its contents
memset(&mg_health_record, 0, sizeof(mg_health_record));
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
}
#if 0 && MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
{
#if MG_ARCH == MG_ARCH_ESP32
#if CONFIG_IDF_TARGET_ARCH_XTENSA
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_XTENSA;
#elif CONFIG_IDF_TARGET_ARCH_RISCV
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_RISCV;
#endif
esp_reset_reason_t rr = esp_reset_reason();
mg_health_record.reset_reason = esp2mg(rr);
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0
MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n"));
#else
// After a crash, copy the backtrace from the core dump in flash: the same
// addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
// The core dump stays in flash, so check the reset reason, otherwise an
// old crash is reported on every boot
static esp_core_dump_summary_t s; // Big on RISC-V, keep it off the stack
esp_reset_reason_t rr = esp_reset_reason();
esp_core_dump_summary_t *s = calloc(1, sizeof(*s));
uint32_t *out = mg_health_record.data;
size_t n = 0, max = MG_HEALTH_DATA_SIZE;
if (rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT && rr != ESP_RST_TASK_WDT)
return; // Not a crash that went through the panic handler
if (max == 0 || esp_core_dump_get_summary(&s) != ESP_OK) return;
mg_health_record.cpuid = 42; // ESP32
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
size_t n = 0;
memset(out, 0, sizeof(mg_health_record.data));
out[n++] = s.exc_pc;
if (s == NULL) return;
if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
rr != ESP_RST_TASK_WDT) // Not a crash caught by the panic handler
|| esp_core_dump_get_summary(s) != ESP_OK) {
free(s);
return;
}
out[n++] = s->exc_pc;
memcpy(&out[n], s->exc_task, 16), n += 4;
#if CONFIG_IDF_TARGET_ARCH_XTENSA
for (uint32_t i = 0; i < s.exc_bt_info.depth && n < max; i++) {
if (s.exc_bt_info.bt[i] != s.exc_pc) out[n++] = s.exc_bt_info.bt[i];
}
out[n++] = s->ex_info.exc_a[0];
out[n++] = s->ex_info.exc_a[1];
size_t len = s->exc_bt_info.depth;
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
memcpy(&out[n], s->exc_bt_info.bt, 4 * len); // sizeof(uint32_t)
#elif CONFIG_IDF_TARGET_ARCH_RISCV
// No unwinder: take the return address, then words on the stack that
// point into executable memory (a heuristic, may include stale values)
if (n < max) out[n++] = s.ex_info.ra;
for (uint32_t i = 0; i + 4 <= s.exc_bt_info.dump_size && n < max; i += 4) {
uint32_t w;
memcpy(&w, &s.exc_bt_info.stackdump[i], sizeof(w)); // Byte array
if (esp_ptr_executable((void *) (uintptr_t) w)) out[n++] = w;
}
#endif
}
out[n++] = s->ex_info.exc_ra;
out[n++] = s->ex_info.exc_sp;
size_t len = s->exc_bt_info.dump_size / 4 ; // sizeof(uint32_t)
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
memcpy(&out[n], s->exc_bt_info.stackdump, 4 * len);
#endif
free(s);
#endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
#endif // MG_ARCH_ESP32
}
static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
@@ -107,11 +166,17 @@ static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
size_t len = 0;
if (mg_health_valid() &&
mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT) {
(mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_FAULT
#if MG_ARCH == MG_ARCH_ESP32
|| mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_WATCHDOG
#endif
)) {
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%u,%m:[%M]}", //
MG_ESC("version"), mg_health_record.magic[3], //
MG_ESC("cpuid"), mg_health_record.cpuid, //
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%hu,%m:%hu,%m:%u,%m:[%M]}", //
MG_ESC("version"), mg_health_record.magic[3], //
MG_ESC("arch"), mg_health_record.arch, //
MG_ESC("reason"), mg_health_record.reset_reason, //
MG_ESC("cpuid"), mg_health_record.cpuid, //
MG_ESC("data"), print_uint32, n, mg_health_record.data);
} else {
len += mg_xprintf(fn, arg, "{}");
@@ -119,3 +184,4 @@ size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
(void) ap;
return len;
}
#endif
+80 -5
View File
@@ -5,6 +5,8 @@
#include "arch.h"
#include "printf.h"
#if MG_ENABLE_HEALTH
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
@@ -13,13 +15,15 @@
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
#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
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
#define MG_HEALTH_DATA_SIZE 250
#elif MG_ARCH == MG_ARCH_ESP32
#define MG_HEALTH_DATA_SIZE 23
#else
#define MG_HEALTH_DATA_SIZE 10
#endif
#endif // MG_HEALTH_DATA_SIZE
// Places the record in RAM that survives a reset. Define in mongoose_config.h
// to match the linker script, e.g. __attribute__((section(".mg_health")))
@@ -27,6 +31,13 @@
#define MG_HEALTH_RAM
#endif
// CPU core architecture
enum mg_health_arch {
MG_HEALTH_ARCH_ARM = 0,
MG_HEALTH_ARCH_XTENSA,
MG_HEALTH_ARCH_RISCV
};
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
@@ -43,7 +54,8 @@ enum mg_health_reason {
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint16_t reset_reason; // enum mg_health_reason that started this boot
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
uint32_t cpuid; // ARM CPU ID
uint32_t data[MG_HEALTH_DATA_SIZE];
};
@@ -76,6 +88,69 @@ static inline const char *mg_health_reason_str(int reason) {
}
}
#if MG_ARCH == MG_ARCH_ESP32
#ifndef CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
#define CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH 0
#endif
#ifndef ESP_PARTITION_SUBTYPE_DATA_COREDUMP
#define ESP_PARTITION_SUBTYPE_DATA_COREDUMP 0x03
#endif
// clang-format off
static inline uint16_t esp2mg(esp_reset_reason_t rr) {
switch(rr) {
case ESP_RST_POWERON: return (uint16_t) MG_HEALTH_RESET_POWER;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
case ESP_RST_PWR_GLITCH:
#endif
case ESP_RST_BROWNOUT: return (uint16_t) MG_HEALTH_RESET_BROWNOUT;
case ESP_RST_EXT: return (uint16_t) MG_HEALTH_RESET_PIN;
case ESP_RST_SW: return (uint16_t) MG_HEALTH_RESET_SOFTWARE;
case ESP_RST_DEEPSLEEP: return (uint16_t) MG_HEALTH_RESET_LOWPOWER;
case ESP_RST_INT_WDT:
case ESP_RST_TASK_WDT:
case ESP_RST_WDT: return (uint16_t) MG_HEALTH_RESET_WATCHDOG;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2)
case ESP_RST_CPU_LOCKUP:
#endif
case ESP_RST_PANIC: return (uint16_t) MG_HEALTH_RESET_FAULT;
default: break;
}
return (uint16_t) MG_HEALTH_RESET_UNKNOWN;
}
// clang-format on
#endif
#define MG_HEALTH_INIT() mg_health_init()
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
extern void mg_health_init(void);
extern bool mg_health_coredump_begin(size_t *size);
extern bool mg_health_coredump_read(size_t offset, void *buf, size_t *len);
extern bool mg_health_coredump_end(void);
#else
#define MG_HEALTH_INIT()
static inline int mg_health_reason(void) {
return 0;
}
static inline const char *mg_health_reason_str(int reason) {
(void) reason;
return "unknown";
}
static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg,
va_list *ap) {
(void) ap;
return mg_xprintf(fn, arg, "{}");
}
static inline bool mg_health_coredump_begin(size_t *size) {
(void) size;
return false;
}
static inline bool mg_health_coredump_read(size_t offset, void *buf,
size_t *len) {
(void) offset, (void) buf, (void) len;
return false;
}
static inline bool mg_health_coredump_end(void) {
return false;
}
#endif
+3 -1
View File
@@ -734,13 +734,15 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
static bool uri2path(const char *r, struct mg_str uri, char *buf, size_t len) {
struct mg_str k, v, part, s = mg_str(r), u = {NULL, 0}, d = u;
size_t n;
bool matched = false;
while (mg_span(s, &part, &s, ',')) {
if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0);
if (v.len == 0) v = k, k = mg_str("/"), u = k, d = v;
if (uri.len < k.len) continue;
if (mg_strcmp(k, mg_str_n(uri.buf, k.len)) != 0) continue;
u = k, d = v;
u = k, d = v, matched = true;
}
if (!matched) return false;
n = mg_snprintf(buf, len, "%.*s", (int) d.len, d.buf);
if (len == 0 || n + 2 >= len) return false; // Path overflow
if (n > 0 && buf[n - 1] != '/') buf[n++] = '/', buf[n] = '\0'; // Add slash
-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 *) calloc(1, sizeof(*peer));
if (peer == NULL) return;
peer->radio_address = src;
peer->next = data->members;
data->members = peer;
}
memcpy(peer->mac, frame + MG_MESH_FRAME_HDR_LEN, sizeof(peer->mac));
}
// Member peer did not answer its turn. After MGI_MAX_MISSES in a row, it is
// gone: remove it, and its queued downlink frames
static void mgi_mesh_b_member_missed(struct mgi_mesh_b_data *data,
struct mgi_mesh_b_peer *peer) {
if (++peer->misses < MGI_MAX_MISSES) return;
MG_INFO(("Member %M removed: no answer %d times", mg_print_mac, peer->mac,
MGI_MAX_MISSES));
if (data->fanout == peer) {
data->fanout = peer->next;
if (data->fanout == NULL) {
size_t len = mg_queue_next(&data->tx, NULL);
mg_queue_del(&data->tx, len);
}
}
LIST_DELETE(struct mgi_mesh_b_peer, &data->members, peer);
free(peer);
}
// A broadcast occupies one record and is sent once to each member in list
// order.
static size_t mgi_mesh_b_bridge_burst(struct mgi_mesh_b_data *data,
struct mgi_mesh_b_peer *peer) {
char *record;
size_t len;
while ((len = mg_queue_next(&data->tx, &record)) != 0) {
uint16_t dst = mgi_mesh_b_join_field((const uint8_t *) record);
struct mgi_mesh_b_peer *member;
if (dst == 0xffffu) {
if (data->fanout == NULL) data->fanout = data->members;
return data->fanout == peer ? len : 0;
}
if (dst == peer->radio_address) return len;
for (member = data->members; member != NULL; member = member->next) {
if (member->radio_address == dst) return 0;
}
mg_queue_del(&data->tx, len); // Its destination has left.
}
return 0;
}
static uint64_t mgi_mesh_b_queued_burst_time(struct mgi_mesh_b_data *data,
size_t len) {
char *record;
size_t frames;
if (len == 0) return mgi_mesh_b_burst_time(0);
mg_queue_next(&data->tx, &record);
// Fragment zero carries the final index. Count overhead and IFS per frame.
frames =
((uint8_t) record[MGI_TX_DST_LEN + MG_MESH_FRAME_HDR_LEN] & 15u) + 1u;
return mgi_mesh_b_airtime(len - MGI_TX_DST_LEN - frames +
(frames - 1) * s_mesh_default_radio->overhead) +
(frames - 1) * s_mesh_default_radio->ifs_us;
}
static void mgi_mesh_b_bridge_io(uint64_t now, struct mg_mesh_node *b) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) b->data;
mgi_mesh_b_bridge_queue_lan(b);
if (now >= data->deadline) {
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
if (data->phase == MGI_TURN_DATA && data->current != NULL) {
struct mgi_mesh_b_peer *peer = data->current;
data->current = peer->next;
mgi_mesh_b_member_missed(data, peer);
} else if (data->phase == MGI_TURN_JOIN) {
data->current = data->members;
}
data->phase = MGI_TURN_IDLE;
if (data->current != NULL) {
size_t burst = mgi_mesh_b_bridge_burst(data, data->current);
uint64_t down = mgi_mesh_b_queued_burst_time(data, burst);
data->rx_expected = 0xff;
mgi_mesh_b_tx_begin(data, burst != 0, now,
now + down + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
2 * s_mesh_default_radio->ifs_us +
MGI_JOIN_TURN_US);
} else if (mgi_mesh_b_control_send(b, 0xffffu, MGI_JOIN_DOWN)) {
data->phase = MGI_TURN_JOIN;
data->deadline = now + MGI_JOIN_WINDOW_US;
}
if (data->deadline <= now) data->deadline = now + MGI_JOIN_TURN_US;
}
}
static void mgi_mesh_b_bridge_free(struct mgi_mesh_b_data *data) {
while (data->members != NULL) {
struct mgi_mesh_b_peer *peer = data->members;
data->members = peer->next;
free(peer);
}
}
static void mgi_mesh_b_node_rx(struct mg_mesh_node *n, uint64_t now,
uint8_t *frame, size_t len) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) n->data;
uint16_t dst = 0, src = 0;
unsigned command = mgi_mesh_b_frame_parse(frame, len, &dst, &src);
if (command == MGI_JOIN_DOWN && len == MG_MESH_FRAME_HDR_LEN &&
dst == 0xffffu) {
if (n->joined && src == data->coordinator &&
++data->missed_rounds >= MGI_MAX_MISSES) {
n->joined = false;
data->phase = MGI_TURN_IDLE;
data->tx_at = (uint64_t) -1;
data->tx_offset = 0;
data->rx_expected = 0xff;
}
if (!n->joined && (mgi_mesh_b_random(n, data) % 64) == 0) {
// Spread contenders across windows as well as within the reply window.
data->coordinator = src;
data->phase = MGI_TURN_JOIN;
data->tx_at = now + MGI_JOIN_TURN_US +
mgi_mesh_b_random(n, data) % MGI_JOIN_JITTER_US;
}
} else if (command == MGI_DATA_DOWN && data->coordinator != 0 &&
dst == n->radio_address && src == data->coordinator) {
int received = mgi_mesh_b_data_receive(data, frame, len, now);
if (received < 0) return;
n->joined = true;
data->missed_rounds =
-1; // This round had a grant; do not count it as missed.
if (received != 0) {
struct mg_tcpip_if *ifp = n->radio;
if (n->rx_len != 0 || (size_t) received > ifp->recv_queue.size) {
ifp->ndrop++;
} else {
memcpy(ifp->recv_queue.buf, data->rx_buf, (size_t) received);
n->rx_len = (size_t) received;
}
}
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
mgi_mesh_b_tx_begin(data, mg_queue_next(&data->tx, NULL) != 0, now,
now + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
2 * s_mesh_default_radio->ifs_us +
MGI_JOIN_TURN_US);
}
}
static void mgi_mesh_b_input(struct mg_mesh_node *node, void *buf, size_t len) {
uint64_t ready = node->now + node->strategy->radio->turnaround_us;
if (node->tx_at < ready) node->tx_at = ready;
node->next_tick = node->now;
if (node->lan != NULL)
mgi_mesh_b_bridge_rx(node->now, node, (const uint8_t *) buf, len);
else
mgi_mesh_b_node_rx(node, node->now, (uint8_t *) buf, len);
}
static void mgi_mesh_b_tick(struct mg_mesh_node *node, uint64_t now) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
node->now = now;
if (node->lan != NULL) {
mgi_mesh_b_bridge_io(now, node);
mgi_mesh_b_tx_step(data, now);
node->next_tick = data->deadline;
if (data->tx_at < node->next_tick) node->next_tick = data->tx_at;
return;
}
if (data->phase == MGI_TURN_JOIN) {
if (data->tx_at <= now) {
if (now - data->tx_at <= MGI_JOIN_TURN_US && !node->joined)
mgi_mesh_b_control_send(node, data->coordinator, MGI_JOIN_UP);
data->tx_at = (uint64_t) -1;
data->phase = MGI_TURN_IDLE;
}
} else {
mgi_mesh_b_tx_step(data, now);
}
node->next_tick = data->tx_at;
}
static bool mgi_mesh_b_init(struct mg_mesh_node *node,
struct mg_tcpip_if *radio, struct mg_tcpip_if *lan,
uint16_t radio_address) {
struct mgi_mesh_b_data *data =
(struct mgi_mesh_b_data *) calloc(1, sizeof(*data));
memset(node, 0, sizeof(*node));
node->radio = radio;
node->lan = lan;
node->radio_address = radio_address;
node->next_tick = (uint64_t) -1;
node->ram_used = node->ram_peak = sizeof(*node);
if (data == NULL) return false;
node->ram_used += sizeof(*data);
node->ram_peak = node->ram_used;
data->rx_expected = 0xff;
node->data = data;
data->node = node;
data->tx_at = (uint64_t) -1;
mg_queue_init(&data->tx, data->tx_buf, sizeof(data->tx_buf));
if (lan != NULL) node->next_tick = 0;
return true;
}
static void mgi_mesh_b_free(struct mg_mesh_node *node) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
if (data == NULL) return;
mgi_mesh_b_bridge_free(data);
free(data);
node->data = NULL;
}
const struct mg_mesh mg_mesh_default = {
"default",
mgi_mesh_b_init,
mgi_mesh_b_tick,
mgi_mesh_b_free,
mgi_mesh_b_enqueue,
mgi_mesh_b_input,
true,
true,
&mg_radio_802154,
};
#endif
+80
View File
@@ -0,0 +1,80 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// Ethernet over radio, through a mesh. Mongoose sees an Ethernet interface,
// the mesh splits Ethernet frames into radio frames. Two parts plug in:
// - struct mg_radio: a radio standard, e.g. mg_radio_802154
// - struct mg_mesh: how the mesh is built and routed
#pragma once
#include "net_builtin.h"
#if MG_ENABLE_TCPIP
// Monotonic time in microseconds.
uint64_t mg_micros(void);
// A radio standard
struct mg_radio {
const char *name; // For logs
size_t max_frame_size; // Largest radio frame, bytes
uint32_t bitrate; // Bits per second on air
size_t overhead; // Bytes on air besides the frame: preamble, FCS
uint32_t slot_us; // Random backoff slot duration, us
uint32_t cca_us; // Channel sensing duration, us
uint32_t turnaround_us; // Idle check to transmission start, us
uint32_t ifs_us; // Inter-frame spacing in us: gap between transmits
uint8_t backoff_min; // Initial backoff exponent
uint8_t backoff_max; // Maximum backoff exponent
bool csma; // Use channel sensing and backoff before TX
size_t header_len; // Radio hardware header length
// Write the radio header, with 0xffff as the broadcast address.
void (*print_hdr)(const struct mg_radio *radio, uint8_t *buf, uint16_t dst,
uint16_t src);
// Return the parsed header length, or zero for an invalid header.
size_t (*parse_hdr)(const struct mg_radio *radio, const uint8_t *buf,
size_t len, uint16_t *dst, uint16_t *src);
};
#define MG_802154_HEADER_LEN 9 // MAC header, short addresses
#define MG_802154_PAN_ID 0xabcd // Our network's PAN ID
#ifndef MG_802154_CHANNEL
#define MG_802154_CHANNEL 15
#endif
extern const struct mg_radio mg_radio_802154;
struct mg_mesh_node {
struct mg_tcpip_if *radio; // Owning radio driver interface
struct mg_tcpip_if *lan; // Bridge LAN interface, NULL for a client
const struct mg_mesh *strategy; // Selected strategy
size_t (*tx)(struct mg_tcpip_if *, const void *, size_t); // Radio frame TX
size_t rx_len; // Complete Ethernet frame in the interface RX buffer
uint64_t now; // Current driver poll time, us
uint64_t tx_at; // Earliest next transmission, us
void *data; // Private state owned by the strategy
size_t ram_used; // Live mesh state bytes
size_t ram_peak; // Peak live mesh state bytes
uint64_t next_tick; // Next deadline in us, or UINT64_MAX
uint16_t radio_address; // Local address supplied by the radio
bool joined; // Client is a member of the mesh
};
struct mg_mesh {
const char *name;
bool (*init)(struct mg_mesh_node *node, struct mg_tcpip_if *radio,
struct mg_tcpip_if *lan, uint16_t radio_address);
void (*tick)(struct mg_mesh_node *node, uint64_t now_microseconds);
void (*free)(struct mg_mesh_node *node);
size_t (*tx)(struct mg_mesh_node *, const void *, size_t); // ETH frame TX
void (*rx)(struct mg_mesh_node *, void *, size_t); // Radio frame RX
bool single_bridge; // Require exactly one bridge
bool scheduled_tx; // The mesh schedules transmission turns
const struct mg_radio *radio; // Radio configuration for this strategy
};
extern const struct mg_mesh mg_mesh_default;
#endif
+33
View File
@@ -89,6 +89,7 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
}
mg_iobuf_free(&io);
} else if (ev == MG_EV_CLOSE) {
mg_health_coredump_end();
mg_mdash_conn = NULL;
MG_INFO(("disconnected, ram %lu", 0));
}
@@ -146,6 +147,35 @@ static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
// MG_ESC("percent"));
}
static void rpc_coredump_begin(struct mg_rpc_req *r) {
size_t size = 0;
if (mg_health_coredump_begin(&size)) {
mg_rpc_ok(r, "{%m:%llu}", MG_ESC("size"), (uint64_t) size);
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
static void rpc_coredump_read(struct mg_rpc_req *r) {
uint8_t buf[768];
size_t len = sizeof(buf);
size_t offset = (size_t) mg_json_get_long(r->frame, "$.params.offset", -1);
if (mg_health_coredump_read(offset, buf, &len)) {
mg_rpc_ok(r, "{%m:\"%M\"}", MG_ESC("data"), mg_print_base64,
(int) len, buf);
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
static void rpc_coredump_end(struct mg_rpc_req *r) {
if (mg_health_coredump_end()) {
mg_rpc_ok(r, "true");
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
void mg_mdash_init(struct mg_mgr *mgr) {
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL);
@@ -153,6 +183,9 @@ void mg_mdash_init(struct mg_mgr *mgr) {
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Write"), mg_mdash_rpc_ota_write, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Begin"), rpc_coredump_begin, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.Read"), rpc_coredump_read, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("CoreDump.End"), rpc_coredump_end, NULL);
}
void mg_mdash_poll(struct mg_mgr *mgr) {
+19 -8
View File
@@ -10,8 +10,14 @@
#define MG_TCPIP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
#endif
#ifndef MG_TCPIP_ACK_MS
#define MG_TCPIP_ACK_MS 150 // Timeout for ACKing
#endif
#ifndef MG_TCPIP_ARP_MS
#define MG_TCPIP_ARP_MS 100 // Timeout for ARP response
#endif
#define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment
#define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection
#define MG_TCPIP_RTX_MS 1000 // Initial retransmission timeout (RFC-6298, 2.1)
@@ -364,6 +370,7 @@ static const struct mg_addr ip6_allrouters = {
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}}, 0, 0, true};
static const struct mg_addr ip6_allnodes = {
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}}, 0, 0, true};
static const uint8_t locpx[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0};
#define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1])
#endif
@@ -418,7 +425,7 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
if (ifp->is_ip_changed) {
struct mg_connection *c;
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
if (!c->is_listening && !c->is_udp) c->is_closing = 1;
if (!c->is_listening && !c->is_udp && !c->loc.is_ip6) c->is_closing = 1;
}
ifp->is_ip_changed = false;
}
@@ -734,6 +741,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
: pkt->dhcp->siaddr ? pkt->dhcp->siaddr
: pkt->ip->src);
ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
ifp->lease_expire = ifp->now + 5000; // No ACK by then: start over
} else if (msgtype == 5) { // DHCPACK
if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP
uint64_t rand;
@@ -1014,12 +1022,13 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (pkt->pay.len < sizeof(*ra)) return;
if (ifp->state6 == MG_TCPIP_STATE_UP) {
MG_DEBUG(("Received NDP RA")); // fill gw6 address
MG_DEBUG(("Received NDP RA")); // fill gw6 address if RFC 4861 6.1.2
if (memcmp(pkt->ip6->src, locpx, 8) != 0 || pkt->ip6->hops != 255) return;
// parse options
while (opt_left >= 2) {
uint8_t type = opts[0], len = opts[1];
size_t length = (size_t) len * 8;
if (length == 0 || length > opt_left) break; // malformed
if (length == 0 || length > opt_left) return; // RFC 4861 6.1.2
if (type == 1 && length >= 8) {
// Received router's L2 address
if (!mg_l2_ip6get(ifp->l2type, l2, opts + 2, len)) break;
@@ -1100,7 +1109,7 @@ static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
}
} break;
case 134: // Router Advertisement
rx_ndp_ra(ifp, pkt);
if (pkt->icmp6->code == 0) rx_ndp_ra(ifp, pkt); // RFC 4861 6.1.2
break;
case 135: // Neighbor Solicitation
rx_ndp_ns(ifp, pkt);
@@ -1137,7 +1146,7 @@ static void onstate6change(struct mg_tcpip_if *ifp) {
if (ifp->is_ip6_changed) {
struct mg_connection *c;
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
if (!c->is_listening && !c->is_udp) c->is_closing = 1;
if (!c->is_listening && !c->is_udp && c->loc.is_ip6) c->is_closing = 1;
}
ifp->is_ip6_changed = false;
}
@@ -2041,6 +2050,9 @@ static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) {
if (ifp->enable_dhcp_client && s1) {
if (ifp->state == MG_TCPIP_STATE_UP) {
tx_dhcp_discover(ifp); // INIT (4.4.1)
} else if (ifp->state == MG_TCPIP_STATE_REQ &&
ifp->now >= ifp->lease_expire) {
ifp->state = MG_TCPIP_STATE_UP; // REQUEST or ACK lost: DISCOVER again
} else if (ifp->state == MG_TCPIP_STATE_READY &&
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
if (ifp->now >= ifp->lease_expire) {
@@ -2082,12 +2094,11 @@ static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) {
static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
bool cur_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN);
bool cur_l2 = (ifp->state6 >= MG_TCPIP_STATE_UP);
const uint8_t px[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0}; // RFC-4291 2.5.6
if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed
if (ifp->l2type == MG_TCPIP_L2_ETH && l2_up && ifp->ip6ll[0] == 0 &&
ifp->ip6ll[1] == 0) { // gen ll address
mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac);
memcpy(ifp->ip6ll, px, 8); // RFC-2464 5
memcpy(ifp->ip6ll, locpx, 8); // RFC-4291 2.5.6; RFC-2464 5
} // just got our link local address if we didn't have one.
// If static configuration is used, global addresses,
// prefix length, and gw are already filled at this point.
@@ -2107,7 +2118,7 @@ static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
if ((ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) &&
ifp->state6 == MG_TCPIP_STATE_UP && ifp->ip6ll[0] == 0 &&
ifp->ip6ll[1] != 0) { // IPV6CP has got an IFCID, gen ll address
memcpy(ifp->ip6ll, px, 8); // RFC-5072 5
memcpy(ifp->ip6ll, locpx, 8); // RFC-5072 5
onstate6change(ifp);
} // just got our link local address if we didn't have one.
}
+4 -34
View File
@@ -154,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_atcmd;
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);
#include "drivers/mg24.h"
#include "drivers/mcxw71.h"
#include "drivers/nrf52.h"
#include "drivers/rw612w.h"
// SPI bus abstraction for SPI-attached network chips (e.g. W5500).
// Populate and assign to the driver's driver_data field.
+6 -6
View File
@@ -1552,8 +1552,8 @@ static int mg_tls_parse_cert_der(void *buf, size_t dersz,
return -1;
}
if (mg_der_next(&field, &after) <= 0 || ((after.type != 0x17 || after.len != 13) && (after.type != 0x18 || after.len != 15))) return -1;
if (memcmp(after.value, "99991231235959Z", 15) == 0) { // RFC-5280 4.1.2.5
MG_ERROR(("No well-defined expiration date"));
if (after.len == 15 && memcmp(after.value, "99991231235959Z", 15) == 0) {
MG_ERROR(("No well-defined expiration date")); // RFC-5280 4.1.2.5
return -1;
}
if (now > (t = asnt2t(after.value, after.type))) {
@@ -1641,7 +1641,7 @@ static int mg_tls_parse_cert_der(void *buf, size_t dersz,
}
if (i.type != 0x04) return -1;
if (mg_der_next(&i, &b) <= 0 || b.type != 0x03) return -1;
if (!(b.value[1] & MG_BIT(2))) certsign = false;
if (b.len < 2 || !(b.value[1] & MG_BIT(2))) certsign = false;
}
}
if (ca && certsign) cert->is_ca = true;
@@ -2055,15 +2055,15 @@ static int mg_tls_recv_cert_verify(struct mg_connection *c) {
mg_error(c, "certverify scheme %04x does not match the key", sigalg);
return -1;
}
if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0) {
if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0 || seq.type != 0x30) {
mg_error(c, "verification message is not an ASN.1 DER sequence");
return -1;
}
if (mg_der_to_tlv(seq.value, seq.len, &r) < 0) {
if (mg_der_next(&seq, &r) <= 0 || r.type != 2) {
mg_error(c, "missing first part of the signature");
return -1;
}
if (mg_der_to_tlv(r.value + r.len, seq.len - r.len, &s) < 0) {
if (mg_der_next(&seq, &s) <= 0 || s.type != 2) {
mg_error(c, "missing second part of the signature");
return -1;
}
+2 -2
View File
@@ -16,7 +16,7 @@ IPV6 ?= 1
MDASH ?= 1
ASAN ?= -fsanitize=address,undefined,alignment -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common
ASAN_OPTIONS ?= detect_leaks=1
TUTORIALS_EMBEDDED := $(dir $(wildcard ../tutorials/esp32/*/Makefile)) $(dir $(wildcard ../tutorials/infineon/*/Makefile)) $(dir $(wildcard ../tutorials/microchip/*/*/Makefile)) $(dir $(wildcard ../tutorials/nordic/*/Makefile)) $(dir $(wildcard ../tutorials/nxp/*/*/Makefile)) $(dir $(wildcard ../tutorials/pico-sdk/*/Makefile)) $(dir $(wildcard ../tutorials/rp/*/Makefile)) $(dir $(wildcard ../tutorials/renesas/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/*/Makefile)) $(dir $(wildcard ../tutorials/ti/*/Makefile)) $(dir $(wildcard ../tutorials/wch/*/Makefile))
TUTORIALS_EMBEDDED := $(dir $(wildcard ../tutorials/esp32/*/Makefile)) $(dir $(wildcard ../tutorials/infineon/*/Makefile)) $(dir $(wildcard ../tutorials/microchip/*/*/Makefile)) $(dir $(wildcard ../tutorials/nordic/*/Makefile)) $(dir $(wildcard ../tutorials/nordic/*/*/Makefile)) $(dir $(wildcard ../tutorials/nxp/*/*/Makefile)) $(dir $(wildcard ../tutorials/pico-sdk/*/Makefile)) $(dir $(wildcard ../tutorials/rp/*/Makefile)) $(dir $(wildcard ../tutorials/renesas/*/Makefile)) $(dir $(wildcard ../tutorials/silabs/*/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/Makefile)) $(dir $(wildcard ../tutorials/stm32/*/*/Makefile)) $(dir $(wildcard ../tutorials/ti/*/Makefile)) $(dir $(wildcard ../tutorials/wch/*/Makefile))
TUTORIALS_ZEPHYR := $(dir $(wildcard ../tutorials/zephyr/*/Makefile))
TUTORIALS := ../tutorials/micropython/ $(filter-out $(TUTORIALS_EMBEDDED) $(TUTORIALS_ZEPHYR), $(dir $(wildcard ../tutorials/*/*/Makefile)))
TUTORIALS_QUICK := $(filter-out ../tutorials/micropython/ ../tutorials/tcpip/pcap-driver/, $(TUTORIALS))
@@ -98,7 +98,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c)
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
mongoose.h: $(HDRS) Makefile
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/lfs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_aes128.h src/tls_p256.h src/tls_p384.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/lfs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_aes128.h src/tls_p256.h src/tls_p384.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/l2_mesh.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
# Check that all external (exported) symbols have "mg_" prefix
mg_prefix: mongoose.c mongoose.h
+11 -2
View File
@@ -92,7 +92,7 @@ fail:
exit(1);
}
enum wait_mode { WAIT_ABORT, WAIT_DRAIN, WAIT_HOLD, WAIT_CLOSE };
enum wait_mode { WAIT_ABORT, WAIT_DRAIN, WAIT_HOLD, WAIT_CLOSE, WAIT_SEND };
// Wait for output, optionally draining through the final marker.
static int wait_data(const char *host, enum wait_mode mode) {
@@ -117,6 +117,13 @@ static int wait_data(const char *host, enum wait_mode mode) {
close(fd);
return 0;
}
if (mode == WAIT_SEND) {
usleep(100000); // Let the server shut down its read side
n = send(fd, "x", 1, 0);
usleep(500000); // Keep peer connected for server SHUT_WR
close(fd);
return n == 1 ? 0 : 1;
}
if (mode == WAIT_CLOSE) {
n = recv(fd, buf, sizeof(buf), 0);
close(fd);
@@ -151,10 +158,12 @@ int main(int argc, char *argv[]) {
return wait_data(argv[1], WAIT_HOLD);
if (argc == 3 && strcmp(argv[2], "expectclose") == 0)
return wait_data(argv[1], WAIT_CLOSE);
if (argc == 3 && strcmp(argv[2], "sendlater") == 0)
return wait_data(argv[1], WAIT_SEND);
if (argc == 3) connections = atoi(argv[2]);
if ((argc != 2 && argc != 3) || connections < 1 ||
connections > NUM_CONNECTIONS) {
fprintf(stderr, "usage: %s IP [connections|waitandabort|waitanddrain|hold|expectclose]\n",
fprintf(stderr, "usage: %s IP [connections|waitandabort|waitanddrain|hold|expectclose|sendlater]\n",
argv[0]);
return 1;
}
+139 -7
View File
@@ -91,6 +91,22 @@ static void wait_ready(struct mg_mgr *mgr) {
MG_INFO(("Interface started, starting main loop"));
}
static void test_inet(void) {
struct in_addr addr;
char buf[INET_ADDRSTRLEN];
char *str;
ASSERT(inet_pton(AF_INET, "192.0.2.1", &addr) == 1);
ASSERT(inet_ntop(AF_INET, &addr, buf, sizeof(buf)) != NULL);
ASSERT(strcmp(buf, "192.0.2.1") == 0);
str = inet_ntoa(addr);
ASSERT(str != NULL);
ASSERT(strcmp(str, "192.0.2.1") == 0);
ASSERT(inet_addr("192.0.2.1") == addr.s_addr);
ASSERT(inet_pton(AF_INET, "not-an-ip", &addr) == 0);
ASSERT(inet_pton(AF_INET6, "::1", &addr) < 0 && errno == EAFNOSUPPORT);
}
#if !MG_ENABLE_FREERTOS
static bool s_fail_alloc;
@@ -132,9 +148,10 @@ static volatile bool s_ready_waiting, s_ready_done;
static volatile bool s_backlog_ready, s_backlog_full, s_backlog_accept;
static volatile bool s_tx_held, s_tx_waits, s_tx_fail, s_tx_error;
static volatile bool s_send_waiting;
static volatile bool s_send_done, s_send_expect_error;
static volatile bool s_send_done, s_send_expect_error, s_send_expect_shutdown;
static volatile bool s_recv_start, s_recv_waiting, s_recv_done;
static volatile bool s_recv_expect_error;
static volatile bool s_shutdown_waiting, s_shutdown_done;
static size_t (*s_tx)(const void *, size_t, struct mg_tcpip_if *);
struct client_data {
@@ -152,6 +169,7 @@ static void select_task(void *args);
static void readiness_task(void *args);
static void backlog_task(void *args);
static void tx_gate_task(void *args);
static void shutdown_task(void *args);
struct atask_args {
bool listener_nonblock;
@@ -300,8 +318,13 @@ static void tx_gate_task(void *args) {
// receiver/sender > network task > gate: final send is waiting
while (!s_send_waiting) vTaskDelay(1);
ASSERT(!s_send_done);
if (s_send_expect_error) s_tx_error = s_tx_fail = true;
else s_tx_held = false;
if (s_send_expect_shutdown) {
ASSERT(shutdown((int) (uintptr_t) args, SHUT_WR) == 0);
} else if (s_send_expect_error) {
s_tx_error = s_tx_fail = true;
} else {
s_tx_held = false;
}
vTaskDelete(NULL);
(void) args;
}
@@ -342,15 +365,19 @@ static void send_task(void *args) {
n = send(fd, buf, sizeof(buf), MSG_DONTWAIT);
ASSERT(n < 0 && errno == EAGAIN); // Same full c->send: MSG_DONTWAIT
if (!s_send_expect_error) {
if (!s_send_expect_error && !s_send_expect_shutdown) {
s_recv_start = true; // O_NONBLOCK is per socket; now start the receiver
while (!s_recv_waiting) vTaskDelay(1);
}
s_send_waiting = true; // Gate task releases TX after this send blocks
n = send(fd, &marker, sizeof(marker), 0);
if (s_send_expect_error) ASSERT(n < 0 && errno == EPIPE);
else ASSERT(n == (ssize_t) sizeof(marker));
if (s_send_expect_error || s_send_expect_shutdown) {
ASSERT(n < 0 && errno == EPIPE);
} else {
ASSERT(n == (ssize_t) sizeof(marker));
}
s_send_done = true;
if (s_send_expect_shutdown) close(fd);
vTaskDelete(NULL);
}
@@ -388,7 +415,7 @@ static void atask(void *args) {
ASSERT(result == pdPASS);
}
if (cfg->gate != NULL) {
result = xTaskCreate(cfg->gate, "gate", 256, NULL,
result = xTaskCreate(cfg->gate, "gate", 256, (void *) (uintptr_t) fd,
configMAX_PRIORITIES - 3, NULL);
ASSERT(result == pdPASS);
}
@@ -514,6 +541,59 @@ static void backlog_task(void *args) {
vTaskDelete(NULL);
}
static void shutdown_task(void *args) {
struct sockaddr_in local, peer;
struct timeval zero = {0, 0};
struct pollfd p;
fd_set e;
socklen_t len;
char buf;
int fd, lfd;
(void) args;
lfd = open_listener(5, true);
s_shutdown_waiting = true;
while ((fd = accept(lfd, NULL, NULL)) < 0) {
ASSERT(errno == EAGAIN);
vTaskDelay(1);
}
len = sizeof(local);
ASSERT(getsockname(lfd, (struct sockaddr *) &local, &len) == 0);
ASSERT(len == sizeof(local) && local.sin_family == AF_INET &&
local.sin_port == htons(1234) &&
local.sin_addr.s_addr == INADDR_ANY);
len = sizeof(peer);
ASSERT(getpeername(lfd, (struct sockaddr *) &peer, &len) < 0 &&
errno == ENOTCONN);
len = sizeof(local);
ASSERT(getsockname(fd, (struct sockaddr *) &local, &len) == 0);
ASSERT(len == sizeof(local) && local.sin_family == AF_INET &&
local.sin_port == htons(1234));
len = sizeof(peer);
ASSERT(getpeername(fd, (struct sockaddr *) &peer, &len) == 0);
ASSERT(len == sizeof(peer) && peer.sin_family == AF_INET &&
peer.sin_port != 0);
ASSERT(shutdown(lfd, SHUT_RDWR) < 0 && errno == ENOTCONN);
ASSERT(shutdown(fd, -1) < 0 && errno == EINVAL);
ASSERT(shutdown(fd, SHUT_RD) == 0);
ASSERT(recv(fd, &buf, sizeof(buf), 0) == 0);
vTaskDelay(pdMS_TO_TICKS(200)); // Delayed client write must be discarded
ASSERT(recv(fd, &buf, sizeof(buf), 0) == 0);
ASSERT(shutdown(fd, SHUT_WR) == 0);
ASSERT(send(fd, &buf, sizeof(buf), 0) < 0 && errno == EPIPE);
FD_ZERO(&e);
FD_SET(fd, &e);
ASSERT(select(fd + 1, NULL, NULL, &e, &zero) == 1);
ASSERT(FD_ISSET(fd, &e));
p.fd = fd, p.events = POLLOUT, p.revents = 0;
ASSERT(poll(&p, 1, 0) == 1);
ASSERT(p.revents == POLLERR);
ASSERT(close(fd) == 0);
ASSERT(close(lfd) == 0);
s_shutdown_done = true;
vTaskDelete(NULL);
}
// task2: tests owner.
static void task2(void *args) {
struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager
@@ -555,6 +635,16 @@ static void task2(void *args) {
DASHBOARD("select_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_backlog_ready = s_backlog_full = s_backlog_accept = false;
ASSERT(xTaskCreate(backlog_task, "backlog", 512, NULL,
@@ -669,12 +759,27 @@ static void task2(void *args) {
while (s_wait) vTaskDelay(pdMS_TO_TICKS(10));
s_tx_held = s_tx_fail = s_tx_error = s_send_expect_error = false;
s_error = false;
s_wait = s_tx_held = s_tx_waits = s_tx_fail = s_tx_error = false;
s_send_waiting = s_send_done = s_send_expect_error = false;
s_send_expect_shutdown = true;
ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_send_error_server,
configMAX_PRIORITIES - 1, NULL) == pdPASS);
run_client(ip, "hold");
ASSERT(s_send_done);
DASHBOARD("shutdown_send_wakeup");
s_wait = true;
while (s_wait) vTaskDelay(pdMS_TO_TICKS(10));
s_tx_held = s_send_expect_shutdown = false;
mg_free(ip);
s_error = false;
{
fd_set e;
struct pollfd p;
socklen_t len;
int err;
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
@@ -694,6 +799,12 @@ static void task2(void *args) {
ASSERT(poll(&p, 1, 0) == 1);
ASSERT((p.revents & POLLERR) != 0);
ASSERT((p.revents & POLLOUT) == 0);
len = sizeof(err);
ASSERT(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) == 0);
ASSERT(len == sizeof(err) && err == EIO);
len = sizeof(err), err = -1;
ASSERT(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) == 0);
ASSERT(len == sizeof(err) && err == 0);
ASSERT(close(fd) == 0);
}
DASHBOARD("select_error");
@@ -943,6 +1054,25 @@ static void nortos_test(struct mg_mgr *mgr) {
DASHBOARD("nortos_poll_children");
DASHBOARD("nortos_echo");
lfd = open_listener(5, true);
pid = start_client(ip, "hold");
fd = nortos_accept(mgr, lfd);
ASSERT(shutdown(fd, SHUT_RD) == 0);
ASSERT(recv(fd, &buf, sizeof(buf), 0) == 0);
ASSERT(shutdown(fd, SHUT_WR) == 0);
ASSERT(send(fd, &buf, sizeof(buf), 0) < 0 && errno == EPIPE);
FD_ZERO(&e);
FD_SET(fd, &e);
ASSERT(select(fd + 1, NULL, NULL, &e, &zero) == 1);
ASSERT(FD_ISSET(fd, &e));
p.fd = fd, p.events = POLLOUT, p.revents = 0;
ASSERT(poll(&p, 1, 0) == 1);
ASSERT(p.revents == POLLERR);
nortos_wait_client(mgr, pid);
ASSERT(close(fd) == 0);
ASSERT(close(lfd) == 0);
DASHBOARD("nortos_shutdown");
fd = socket(AF_INET, SOCK_STREAM, 0);
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
@@ -983,6 +1113,8 @@ static bool bsd_x_test(struct mg_tcpip_driver *driver, void *driver_data, const
mg_tcpip_init(&mgr, &mif);
ASSERT(mgr.ifp != NULL); // driver init succeeded
test_inet();
DASHBOARD("inet");
MG_INFO(("Init done, starting interface"));
usleep(200000); // 200 ms
+35
View File
@@ -0,0 +1,35 @@
# Mesh simulator. `make` builds and runs an example
ROOT = ../..
PROG ?= simulator
SRCS = simulator.c mesh_a.c $(ROOT)/mongoose.c
HDRS = simulator.h $(ROOT)/mongoose.h
WARN = -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common \
-Wconversion -Wundef -Wmissing-prototypes -Wstrict-prototypes
DEFS = -DMG_ENABLE_TCPIP=1 -DMG_ENABLE_TCPIP_DRIVER_INIT=0 -DMG_ENABLE_IPV6=0 \
-DMG_ENABLE_CUSTOM_MILLIS=1 -DMG_ENABLE_TCPIP_TCPRTX=1 \
-DMG_IO_SIZE=512 \
-DMG_TCPIP_ARP_MS=1000 -DMG_TCPIP_ACK_MS=500 # Slow radio: wait longer
CFLAGS ?= -O2 -g0 $(WARN)
X ?= 100
Y ?= 100
ROWS ?= 10
COLS ?= 10
# Nodes at every corner of a ROWS x COLS grid over X,Y, edges included
GRID = $(shell for r in $$(seq 0 $(ROWS)); do \
for c in $$(seq 0 $(COLS)); do \
echo -node $$((c * $(X) / $(COLS))),$$((r * $(Y) / $(ROWS))); \
done; done)
all: $(PROG)
$(RUN) ./$(PROG) -area $(X),$(Y) \
-bridge $$(($(X) / 2)),$$(($(Y) / 2)) \
$(GRID) $(ARGS)
$(PROG): $(SRCS) $(HDRS) Makefile
$(CC) $(SRCS) $(CFLAGS) $(DEFS) -I. -I$(ROOT) -o $@
clean:
rm -rf $(PROG) $(PROG).dSYM *.exe
.PHONY: all clean
+324
View File
@@ -0,0 +1,324 @@
# Ethernet over a radio mesh
## The idea
Small radio devices, e.g. IEEE 802.15.4, sub-GHz or LoRa boards, should run
Mongoose as if they were on an ordinary Ethernet LAN: get an IP address over
DHCP, answer HTTP, talk MQTT. No 6LoWPAN, no Thread, no special stack.
To do that, the radio mesh looks like an Ethernet switch to Mongoose.
One or more bridges connect the radio network to the wired LAN: a bridge has
an Ethernet port and a radio. A node in range of a bridge talks to it
directly; a node out of range reaches a bridge through other nodes, which
forward its frames. That makes the radio network a mesh. With several
bridges, each node uses one of them, e.g. the nearest; the mesh covers a
bigger area, and keeps working if a bridge goes down. A PC on the LAN sees
the radio nodes as regular hosts on the same LAN.
```
PC --- LAN --- bridge ~~~ radio nodes
```
## Two parts plug in
- `struct mg_radio` describes frame size, bitrate, airtime overhead, signal
parameters and channel-access timings.
- `struct mg_mesh` has `init`, `tick`, `free`, `tx` and `rx` callbacks. The most important
one is the `tick()` callback which is called with a microsecond timestamp,
obtained from `mg_micros()`, and handles routing and mesh management.
The mesh queues and fragments Ethernet frames and reassembles incoming radio
frames. Driver `tx()` queues Ethernet into the mesh; `rx()` returns a
reassembled Ethernet frame. A private callback transmits one radio frame.
The default slotted mesh in `../../src/l2_mesh.c` uses the bridge as a coordinator.
It opens windows for new nodes to join and gives joined nodes DATA turns
in round-robin order.
Mongoose calls driver's `poll()`. Radio driver calls `mg_micros()` to get
the current timestamp and calls `mg_mesh :: tick()`.
The goal is to swap radios and mesh strategies without changing application
code. The slotted star deliberately omits radio ACKs to avoid
the extra airtime and radio turnaround. Other strategies may use them.
## Why a simulator
Real boards are slow to test with, and you can't put 500 of them on a desk.
The simulator runs a whole network in one process: every node is a real
Mongoose instance, only the radio and the air between nodes are emulated.
That lets us try mesh strategies with many nodes, and measure how long it
takes nodes to join, get an address, and answer a request.
## Virtual time
The simulator exposes its virtual clock through `mg_micros()`.
TCP/IP reads the same clock through `mg_millis()`, rounded down to milliseconds.
Nothing waits for real time. `-t SEC` sets the duration, 10 minutes by default.
Radio channel checks, transmission starts and ends run at their scheduled
microsecond timestamps. The clock jumps to the earliest radio event, mesh
deadline, or periodic stack poll.
`STEP_MICROSECONDS` is 1000: periodic polling services Mongoose's internal
stack timers every millisecond. Packet delivery triggers immediate work:
the simulator repeatedly polls DHCP, the tester, bridges and nodes at the
same virtual timestamp until that work drains. Ethernet forwarding and
radio receive processing therefore do not wait for the periodic tick.
Internal timer handling can still be delayed by up to about 1 ms.
## The setup
```
DHCP server ---------+
10.0.0.1 |
02:01:00:00:00:00 +-- LAN ---- bridge ~~~ nodes
|
test node --------+
10.0.0.2
02:02:00:00:00:00
```
The LAN is assumed infinitely fast, see "How frames move on the LAN" below.
Two nodes are on it:
- The DHCP server, 10.0.0.1, MAC 02:01:00:00:00:00, gives each node an
address made from its MAC: 02:00:00:X:Y:Z gets 10.X+1.Y.Z, so node
addresses never clash with the LAN nodes' 10.0.0.x. It also answers
ARP requests for its advertised gateway IP, 10.0.0.1, so nodes can resolve
the gateway MAC and stop retrying. It does not implement IP routing,
and does not have a full Mongoose instance
- The test node, 10.0.0.2, MAC 02:02:00:00:00:00, is a Mongoose
instance. It waits for the mesh to settle, 60 seconds by default
(`-w SEC`), then sends `GET /` to each node in turn, one at a time, at the
node's own IP address. It wraps back to the first node and keeps querying
until five seconds before the end, when it stops starting new requests.
An in-flight request may still complete or time out during the remaining
time. Runs lasting five seconds or less start no requests. A node with
no address, a connection error, or a request exceeding 10 seconds counts
as failed. The summary reports successful and failed attempts, plus
minimum, mean and maximum response times for successful requests. A
request still pending at the end is not included in those counts.
Bridges are on the LAN too, and get every frame, as if in promiscuous mode:
a frame for one of their radio nodes isn't addressed to the bridge itself.
A bridge forwards every frame from the LAN to the radio and every frame
from the radio to the LAN.
Bridges and radio nodes get MACs 02:00:00:xx:xx:xx, numbered from 1 in
command-line order: 02:00:00:00:00:01, 02:00:00:00:00:02, and so on.
### How frames move on the LAN
Every LAN node, the DHCP server, the test node and each bridge, has a
LAN interface, `lif`: a `struct mg_tcpip_if` with a receive queue. All of
them use the same driver, `s_lan_driver`. There are no cables and no switch:
- Sending. A node sends a frame with its driver's `tx`, which is
`lan_tx()`. It looks at the destination MAC and copies the frame into the
receive queues of the nodes it is for:
- a broadcast or multicast goes to every node but the sender;
- a unicast goes to the node with that MAC;
- bridges get every frame, as a real bridge in promiscuous mode would:
a frame for a radio node is addressed to the node, not to its bridge.
- Receiving. Each node takes the frames from its own queue when
the main loop polls it:
- the test node runs Mongoose, which reads the queue by itself, as it
would with a real Ethernet driver that queues frames from an interrupt;
- the DHCP server reads it in `dhcp_poll()`, and replies by sending
through its own `lif`;
- bridges read it in `bridges_poll()`, and forward it to the radio.
Enqueuing marks immediate work pending. The main loop drains that work
without advancing virtual time, so the LAN has no simulated propagation,
serialization or polling delay. Sending never calls receiver code directly;
replies are processed by subsequent polls at the same timestamp.
For example, a node asking for an address:
1. The node's DHCP DISCOVER crosses the radio to a bridge, which sends it
out of its `lif`. It is a broadcast, so `lan_tx()` puts it into the
queues of the DHCP server, the test node and the other bridges.
2. On its next poll, the DHCP server reads it and sends an OFFER, also a
broadcast, which lands in the test node's and the bridges' queues.
3. On their next poll, the bridges read the OFFER and send it over the
radio to the node. The test node's Mongoose drops it: the test node
has a fixed address and no DHCP client.
### How frames move on the radio
The simulator keeps one accepted transmission per sender in a singly linked
list. A busy sender's driver returns zero, so the mesh retains the fragment
until the driver accepts it. Different senders can still overlap on air.
1. A sender waits a random number of backoff slots, then listens for
`cca_us` to check whether the channel was idle throughout that interval.
2. If busy, it increases its backoff exponent up to `backoff_max`, waits
again, and repeats. There is no channel-access attempt limit yet.
3. If idle, it schedules transmission after `turnaround_us`. Another sender
can also pass its check before either starts: the channel check does not
reserve the air.
4. Transmission takes `(len + overhead) * 8 / bitrate` seconds, rounded up
to a microsecond. Overlapping transmissions are both marked collided.
Channel checks at the same timestamp run before transmission starts,
so list order cannot make one simultaneous check win.
5. At the end, a collided frame is dropped. Otherwise it is queued for all
other nodes for broadcast/multicast, or the matching node for unicast.
Every other bridge receives it too. Receive processing happens at the
same virtual timestamp.
A receiver drops a frame if any part of that frame overlaps its own channel
backoff, sensing, turnaround or transmission interval, even if it is
available again when the frame ends.
The radio summary reports total sent and collided frames, and lost receptions.
Losses include range or random loss, plus missed unicast receptions when the
destination was transmitting.
The 802.15.4 definition uses 250 kbit/s, 8 bytes overhead, 320 us backoff
slots, 128 us channel sensing and 100 us turnaround. The initial backoff
exponent is 3 (0–7 slots), increasing to 5 (0–31 slots) on busy checks.
After transmitting, a sender waits the radio's inter-frame spacing,
`mg_radio::ifs_us`, 640 us for 802.15.4, before starting its next frame's
backoff. This wait applies even if the frame collided or the sender's
queue was temporarily empty. Reception remains enabled during the
inter-frame wait.
The backoff generator is seeded from the wall clock.
The mesh limits each transmission to 125 bytes plus the 2-byte FCS.
Overhead and inter-frame spacing apply to each fragment.
A receiver must be within `-range METRES` of the sender (default 185 m).
`-loss PERCENT` adds independent random loss for each in-range reception
(default 0%). The radio summary counts range and random losses together.
Collisions and channel sensing still apply across the whole simulated air.
## Files
| File | What |
|------|------|
| `../../src/l2_mesh.h`, `../../src/l2_mesh.c` | `struct mg_radio`, `struct mg_mesh`, shared node initialization, and IEEE 802.15.4 radio |
| `mesh_a.c` | Alternative unslotted star strategy |
| `simulator.h`, `simulator.c` | The simulator |
| `Makefile` | Builds and runs the simulator |
## Where we are
The slotted strategy uses direct radio hops between nodes and a bridge.
The simulator models airtime, half-duplex reception and collisions.
- Frames outside reception range are not received, so nodes out of a bridge's
range never join. But collisions and channel sensing are still global:
every overlap is a collision, wherever the receiver is, and a busy
channel is busy for everyone.
- Mesh routing remains unimplemented: bridge selection, forwarding through other
nodes and recovery are not implemented.
Next steps include distance-aware collision and channel sensing, mesh
routing and bridge filtering.
## Strategy A — unslotted (simulator default)
Each sender contends for the channel using CSMA. The mesh queues radio
fragments and submits them one at a time. The receiver reassembles bursts
per sender, and the bridge forwards Ethernet frames between LAN and radio.
## Default strategy — slotted mesh
One bridge coordinates the network and starts with an empty member list.
It broadcasts a JOIN beacon to open a 3 ms window. Unjoined nodes choose
windows at random and add up to 640 µs of jitter before sending their JOIN
request with their Ethernet MAC to the bridge. The bridge adds each new
radio address and its Ethernet MAC to the head of its member list.
After the JOIN window, the bridge grants each member a DATA turn in list
order, then opens another JOIN window. The bridge advances as soon as it
reassembles a valid uplink. If the reply is missing, the turn times out
after allowing for the downlink and a maximum-size uplink burst, including
inter-frame spacing. A member must answer every turn,
with an empty `DATA_UP` if it has nothing to send. A member that misses 3
turns in a row is removed. With no
members, the bridge opens JOIN windows back to back. A node recognizes its
first `DATA_DOWN` as
confirmation that it joined.
Slotted nodes have a separate TCP/IP interface that reports link up after
the first grant. Each mesh interface has one 2 KiB TX queue of split
radio bursts. The bridge shares that queue among members and sends the
head burst in its destination's turn. It directs DHCP replies by the
client MAC in `chaddr` and keeps one copy of other multicast and broadcast
bursts until each member has had its turn. A frame that does not fit is
rejected in full. The hardware and simulator drivers accept one raw frame
at a time. JOIN messages fit in a single radio frame. A DATA
burst carries one Ethernet frame across up to 14 radio frames. Each
fragment has a 9-byte short-address MAC header, a one-byte type, and a
byte containing the fragment index and final index in the high and low
nibbles. Empty DATA messages use a single header-only frame. The node
starts its uplink after receiving the whole downlink, and the bridge
forwards only complete uplinks to the LAN.
## Running
```sh
make # 100x100 m, a bridge, 11x11 nodes
make X=200 Y=50 ROWS=2 COLS=8 # Another area and grid
```
`make` puts a bridge in the middle of the area, and a node at every corner
of a `ROWS` x `COLS` grid over it. The simulator itself takes:
```
./mesh_sim -area X,Y,Z -bridge X,Y,Z -node X,Y,Z ... -t SEC -w SEC -m A|default -range METRES -loss PERCENT
```
Positions are in metres; `X,Y` means `Z` is 0. Repeat `-bridge` and `-node`
for more.
## Hardware
The FRDM-RW612 example is the Ethernet bridge. The XIAO MG24 and
FRDM-MCXW71 examples are radio clients. All three use the slotted mesh.
```sh
make -C tutorials/nxp/frdm-rw612/bridge
make -C tutorials/silabs/xiao-mg24/minimal
make -C tutorials/nxp/frdm-mcxw71/minimal
```
Use each tutorial's `flash` target. Connect the
bridge to an Ethernet LAN with a DHCP server. Find the client's assigned
IP in its serial log, then send an HTTP GET to that IP from the LAN.
All three boards use channel 15 and PAN `0xabcd` by default. Each board derives its radio address from its Ethernet MAC.
MG24 is a node only; its radio address is derived from its MAC.
The drivers in `src/drivers` send and receive raw 802.15.4 frames.
They use the same slotted mesh implementation as the simulator.
The board driver is initialized by `mg_mgr_init()`. The RW612 bridge
has its own `bridge/main.c`, which initializes and polls the two drivers
directly, without an event manager or local IP stack.
## Hardware integration
The default mesh and radio drivers are included in `mongoose.c` and `mongoose.h`.
Radio drivers live in `src/drivers/`: `mg24`, `mcxw71`, `rw612w`,
and `nrf52`. MG24 driver data contains its mesh state and `strategy`; it has
no bridge LAN interface.
A NULL strategy selects `mg_mesh_default`, named `"default"`; assigning another
`const struct mg_mesh *` overrides it. Hardware callbacks transmit and receive
raw radio frames; the selected strategy owns joining, scheduling and Ethernet
fragmentation. Driver initialization goes through `mg_tcpip_init()`.
Board examples are under `tutorials/nxp/frdm-rw612/bridge`,
`tutorials/nxp/frdm-mcxw71/minimal`, `tutorials/silabs/xiao-mg24/minimal`, and
`tutorials/nordic/xiao-52840/minimal`. Their HALs supply `mg_micros()`.
The simulator and all radio drivers keep the hardware driver installed throughout the
interface lifetime. Mesh state belongs to `driver_data`. Driver `tx()` queues
Ethernet frames; `poll()` feeds raw received frames to the mesh and calls
strategy `tick()`. The strategy sends scheduled fragments through the driver's
private `radio_tx` callback. Driver `rx()` returns a reassembled Ethernet frame
using the stack's existing receive buffer. There is no mesh polling timer,
copied radio interface, or mesh-specific change in `net_builtin.*`.
The simulator explicitly frees its mesh state after `mg_mgr_free()`; board
drivers keep their state for the firmware lifetime. MG24, MCXW71 and nRF52 are
radio nodes. The RW612 bridge application owns Ethernet initialization, its RX
queue, promiscuous mode and link polling, and attaches the LAN to the mesh.
The RW612W driver owns only the radio interface.
+261
View File
@@ -0,0 +1,261 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
// Radio header followed by type and fragment index/last index.
#define MGI_FRAME_HDR_LEN (MG_802154_HEADER_LEN + 2)
#define MGI_ETH_MAX 1514
#define MGI_TX_DST_LEN 2
#define MGI_RX_TIMEOUT_US 1000000
#define MGI_TX_QUEUE_SIZE 4096
#define MGI_DATA_TAG \
0x80u // High bit identifies DATA, low seven bits tag a burst
struct mgi_mesh_a_rx {
struct mgi_mesh_a_rx *next;
uint64_t expires;
uint16_t src;
uint8_t tag, index, last;
size_t len;
uint8_t buf[MGI_ETH_MAX];
};
struct mgi_mesh_a_data {
struct mg_mesh_node *node;
struct mg_tcpip_if *ethernet;
struct mgi_mesh_a_rx *rx;
uint8_t tag;
struct mg_queue tx;
char tx_buf[MGI_TX_QUEUE_SIZE];
size_t tx_offset;
uint64_t tx_at;
};
static const struct mg_radio *const s_radio = &mg_radio_802154;
static uint64_t mgi_mesh_a_airtime(size_t len) {
const struct mg_radio *radio = &mg_radio_802154;
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
radio->bitrate;
}
// Each queue record holds a destination and a complete burst.
static size_t mgi_mesh_a_queue(struct mg_mesh_node *node, struct mg_queue *q,
const void *buf, size_t len, uint16_t dst, uint8_t type) {
const struct mg_radio *radio = &mg_radio_802154;
const uint8_t *eth = (const uint8_t *) buf;
size_t hdr = MGI_FRAME_HDR_LEN, cap = radio->max_frame_size - hdr;
size_t frames, size, i;
char *record;
uint8_t *out;
if (len < 14 || len > MGI_ETH_MAX) return 0;
frames = (len + cap - 1) / cap;
size = MGI_TX_DST_LEN + len + frames * (1 + hdr);
if (mg_queue_next(q, NULL) == 0) mg_queue_init(q, q->buf, q->size);
if (mg_queue_book(q, &record, size) < size) return 0;
out = (uint8_t *) record;
*out++ = (uint8_t) (dst >> 8);
*out++ = (uint8_t) dst;
for (i = 0; i < frames; i++) {
size_t chunk = len - i * cap;
if (chunk > cap) chunk = cap;
*out++ = (uint8_t) (hdr + chunk);
radio->print_hdr(radio, out, dst, node->radio_address);
out[radio->header_len] = type;
out[radio->header_len + 1] = (uint8_t) (i << 4 | (frames - 1));
memcpy(out + hdr, eth + i * cap, chunk);
out += hdr + chunk;
}
mg_queue_add(q, size);
return len;
}
static size_t mgi_mesh_a_send(struct mgi_mesh_a_data *data, const void *buf,
size_t len) {
uint8_t tag = (uint8_t) ((data->tag + 1) & 0x7f);
size_t queued = mgi_mesh_a_queue(data->node, &data->tx, buf, len, 0xffffu,
(uint8_t) (MGI_DATA_TAG | tag));
if (queued != 0) data->tag = tag;
return queued;
}
static void mgi_mesh_a_tx(struct mgi_mesh_a_data *data, uint64_t now) {
struct mg_tcpip_if *radio = data->node->radio;
char *record;
size_t size = mg_queue_next(&data->tx, &record);
if (size == 0) return;
if (now >= data->tx_at) {
const uint8_t *part = (const uint8_t *) record + data->tx_offset;
size_t len = part[0];
if (now >= data->node->tx_at &&
data->node->tx(radio, part + 1, len) == len) {
data->tx_offset += len + 1;
if (data->tx_offset == size) {
mg_queue_del(&data->tx, size);
data->tx_offset = MGI_TX_DST_LEN;
}
data->tx_at = now + mgi_mesh_a_airtime(len) + s_radio->ifs_us;
} else {
data->tx_at = now + s_radio->slot_us;
}
}
if (mg_queue_next(&data->tx, NULL) != 0) data->node->next_tick = data->tx_at;
}
static size_t mgi_mesh_a_enqueue(struct mg_mesh_node *node, const void *buf,
size_t len) {
return mgi_mesh_a_send(node->data, buf, len);
}
static void mgi_mesh_a_deliver(struct mgi_mesh_a_data *data, uint8_t *buf,
size_t len) {
struct mg_tcpip_if *lan = data->node->lan;
if (len < 14) return;
if (lan != NULL) {
lan->driver->tx(buf, len, lan);
} else {
struct mg_tcpip_if *ifp = data->node->radio;
if (data->node->rx_len != 0 || len > ifp->recv_queue.size) {
ifp->ndrop++;
} else {
memcpy(ifp->recv_queue.buf, buf, len);
data->node->rx_len = len;
}
}
}
static void mgi_mesh_a_rx_free(struct mgi_mesh_a_data *data,
struct mgi_mesh_a_rx **p) {
struct mgi_mesh_a_rx *r = *p;
*p = r->next;
data->node->ram_used -= sizeof(*r);
free(r);
}
static void mgi_mesh_a_receive(struct mgi_mesh_a_data *data, uint8_t *frame,
size_t len, uint64_t now) {
struct mgi_mesh_a_rx **p = &data->rx, *r;
size_t cap = s_radio->max_frame_size - MGI_FRAME_HDR_LEN, chunk;
uint16_t dst, src;
uint8_t tag, index, last;
if (len <= MGI_FRAME_HDR_LEN || len > s_radio->max_frame_size ||
s_radio->parse_hdr(s_radio, frame, len, &dst, &src) !=
s_radio->header_len ||
(dst != 0xffffu && dst != data->node->radio_address))
return;
tag = frame[MGI_FRAME_HDR_LEN - 2];
index = frame[MGI_FRAME_HDR_LEN - 1] >> 4;
last = frame[MGI_FRAME_HDR_LEN - 1] & 15;
chunk = len - MGI_FRAME_HDR_LEN;
if ((tag & MGI_DATA_TAG) == 0 || index > last ||
last > (MGI_ETH_MAX - 1) / cap || (index < last && chunk != cap))
return;
if (index == 0 &&
(chunk < 14 ||
(data->node->lan == NULL && (frame[MGI_FRAME_HDR_LEN] & 1) == 0 &&
memcmp(frame + MGI_FRAME_HDR_LEN, data->ethernet->mac, 6) != 0)))
return;
while (*p != NULL && (*p)->src != src) p = &(*p)->next;
if (index == 0) {
if (*p != NULL) mgi_mesh_a_rx_free(data, p);
if (last == 0) {
mgi_mesh_a_deliver(data, frame + MGI_FRAME_HDR_LEN, chunk);
return;
}
r = calloc(1, sizeof(*r));
if (r == NULL) return;
data->node->ram_used += sizeof(*r);
if (data->node->ram_peak < data->node->ram_used)
data->node->ram_peak = data->node->ram_used;
r->next = *p;
*p = r;
r->src = src;
r->tag = tag;
r->last = last;
}
r = *p;
if (r == NULL || r->tag != tag) return;
if (r->index != index || r->last != last || chunk > sizeof(r->buf) - r->len) {
mgi_mesh_a_rx_free(data, p);
return;
}
memcpy(r->buf + r->len, frame + MGI_FRAME_HDR_LEN, chunk);
r->len += chunk;
r->index++;
r->expires = now + MGI_RX_TIMEOUT_US;
if (index == last) {
mgi_mesh_a_deliver(data, r->buf, r->len);
mgi_mesh_a_rx_free(data, p);
}
}
static void mgi_mesh_a_input(struct mg_mesh_node *node, void *buf, size_t len) {
uint64_t ready = node->now + node->strategy->radio->turnaround_us;
if (node->tx_at < ready) node->tx_at = ready;
node->next_tick = node->now;
mgi_mesh_a_receive(node->data, buf, len, node->now);
}
static void mgi_mesh_a_tick(struct mg_mesh_node *node, uint64_t now) {
struct mgi_mesh_a_data *data = node->data;
struct mgi_mesh_a_rx **p = &data->rx;
size_t len;
node->now = now;
node->next_tick = (uint64_t) -1;
while (*p != NULL) {
if ((*p)->expires <= now)
mgi_mesh_a_rx_free(data, p);
else
p = &(*p)->next;
}
if (node->lan != NULL) {
struct mg_queue *q = &node->lan->recv_queue;
char *buf;
while ((len = mg_queue_next(q, &buf)) > 0) {
mgi_mesh_a_send(data, buf, len);
mg_queue_del(q, len);
}
}
mgi_mesh_a_tx(data, now);
for (p = &data->rx; *p != NULL; p = &(*p)->next) {
if ((*p)->expires < node->next_tick) node->next_tick = (*p)->expires;
}
}
static void mgi_mesh_a_free(struct mg_mesh_node *node) {
struct mgi_mesh_a_data *data = node->data;
if (data == NULL) return;
while (data->rx != NULL) mgi_mesh_a_rx_free(data, &data->rx);
free(data);
node->data = NULL;
}
static bool mgi_mesh_a_init(struct mg_mesh_node *node,
struct mg_tcpip_if *radio, struct mg_tcpip_if *lan,
uint16_t radio_address) {
struct mgi_mesh_a_data *data = calloc(1, sizeof(*data));
memset(node, 0, sizeof(*node));
node->radio = radio;
node->lan = lan;
node->radio_address = radio_address;
node->next_tick = (uint64_t) -1;
node->ram_used = node->ram_peak = sizeof(*node);
if (data == NULL) return false;
node->ram_used += sizeof(*data);
node->ram_peak = node->ram_used;
data->node = node;
data->tx_offset = MGI_TX_DST_LEN;
node->data = data;
mg_queue_init(&data->tx, data->tx_buf, sizeof(data->tx_buf));
data->ethernet = radio;
node->joined = lan == NULL;
return true;
}
const struct mg_mesh mg_mesh_a = {
"A", mgi_mesh_a_init, mgi_mesh_a_tick, mgi_mesh_a_free,
mgi_mesh_a_enqueue, mgi_mesh_a_input, false, false, &mg_radio_802154,
};
+776
View File
@@ -0,0 +1,776 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// Radio mesh simulator. Bridges and radio nodes have positions in an area. Time
// is virtual: see mg_millis(). On the LAN: a DHCP server, and a test node that
// sends HTTP requests to the nodes
#include "simulator.h"
#define STEP_MICROSECONDS 1000 // Periodic stack timer polling only
static const struct mg_mesh *s_mesh = &mg_mesh_default;
static struct simulator s_sim; // The simulator
// Mongoose's clock: virtual time. A minute of network traffic takes far less
// than a minute to simulate
uint64_t mg_micros(void) {
return s_sim.now;
}
uint64_t mg_millis(void) {
return mg_micros() / 1000;
}
// Parse "X,Y,Z", or "X,Y" with Z 0. Return false if it is not that
static bool parse_pos(const char *s, struct pos *p) {
int n = 0; // Characters parsed
p->x = p->y = p->z = 0;
return (sscanf(s, "%lf,%lf,%lf%n", &p->x, &p->y, &p->z, &n) == 3 ||
sscanf(s, "%lf,%lf%n", &p->x, &p->y, &n) == 2) &&
s[n] == 0;
}
// Is position p inside the area of sim
static bool inside(const struct simulator *sim, const struct pos *p) {
return p->x >= 0 && p->x <= sim->area.x && p->y >= 0 && p->y <= sim->area.y &&
p->z >= 0 && p->z <= sim->area.z;
}
static void usage(const struct simulator *sim, const char *prog) {
fprintf(stderr,
"Usage: %s OPTIONS\n"
"Positions: X,Y,Z in metres, or X,Y with Z 0\n"
" -area X,Y,Z - area, a box, default: %g,%g,%g\n"
" -bridge X,Y,Z - a bridge; repeat for more\n"
" -node X,Y,Z - a radio node; repeat for more\n"
" -t SEC - simulated time, seconds, default: %g\n"
" -w SEC - mesh settle time before requests, default: %g\n"
" -range METRES - radio range, default: %g\n"
" -loss PERCENT - random reception loss, default: %g\n"
" -m A|default - mesh strategy, default: default\n",
prog, sim->area.x, sim->area.y, sim->area.z,
(double) (int64_t) sim->duration / 1e6,
(double) (int64_t) sim->tester.wait / 1e6, sim->radio_range,
sim->radio_loss);
exit(EXIT_FAILURE);
}
// The next radio MAC, to mac: 02:00:00:xx:xx:xx, numbered from 1
static void next_mac(struct simulator *sim, uint8_t *mac) {
uint32_t n = ++sim->last_mac;
mac[0] = 2, mac[1] = mac[2] = 0, mac[3] = (uint8_t) (n >> 16);
mac[4] = (uint8_t) (n >> 8), mac[5] = (uint8_t) n;
}
// Add a bridge at position s. Return false if s is not a position
static bool add_bridge(struct simulator *sim, const char *s) {
struct bridge *b = (struct bridge *) calloc(1, sizeof(*b));
if (b == NULL || !parse_pos(s, &b->pos)) return free(b), false;
if (sim->last_mac >= 0xffffu - 1u) return free(b), false;
next_mac(sim, b->rif.mac);
b->radio.address = (uint16_t) sim->last_mac;
LIST_ADD_TAIL(struct bridge, &sim->bridges, b);
sim->num_bridges++;
return true;
}
// Add a radio node at position s. Return false if s is not a position
static bool add_node(struct simulator *sim, const char *s) {
struct radio_node *n = (struct radio_node *) calloc(1, sizeof(*n));
if (n == NULL || !parse_pos(s, &n->pos)) return free(n), false;
if (sim->last_mac >= 0xffffu - 1u) return free(n), false;
next_mac(sim, n->rif.mac);
n->radio.address = (uint16_t) sim->last_mac;
LIST_ADD_TAIL(struct radio_node, &sim->nodes, n);
sim->num_nodes++;
return true;
}
// Exit if position x is outside the area of sim
static void check_inside(const struct simulator *sim, const struct pos *x) {
if (!inside(sim, x)) {
fprintf(stderr, "%g,%g,%g is outside the area\n", x->x, x->y, x->z);
exit(EXIT_FAILURE);
}
}
// Check that all bridges and nodes are inside the area of sim
static void check_area(const struct simulator *sim) {
struct bridge *b;
struct radio_node *n;
for (b = sim->bridges; b != NULL; b = b->next) check_inside(sim, &b->pos);
for (n = sim->nodes; n != NULL; n = n->next) check_inside(sim, &n->pos);
}
#define LAN_QUEUE_SIZE (64 * 1024) // Receive queue of a LAN interface
// Put frame buf, len bytes, into the receive queue of LAN interface to, if
// it is not the sender, from
static void lan_put(struct mg_tcpip_if *to, struct mg_tcpip_if *from,
const void *buf, size_t len) {
if (to != from) {
mg_tcpip_qwrite((void *) buf, len, to);
s_sim.work_pending = true;
}
}
// LAN driver, all LAN interfaces share it. The LAN is infinitely fast: tx
// puts the frame straight into the receive queues of the interfaces it is
// for: all, if broadcast; else the one with the destination MAC. Bridges get
// every frame: some are for their radio nodes. Each node takes frames
// from its queue when polled, see dhcp_poll() and the mesh bridge callback;
// Mongoose does it for the test node
static size_t lan_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
const uint8_t *dst = (const uint8_t *) buf;
bool all = dst[0] & 1; // Broadcast, multicast
struct bridge *b;
if (all || memcmp(dst, s_sim.dhcp.lif.mac, 6) == 0) {
lan_put(&s_sim.dhcp.lif, ifp, buf, len);
}
if (all || memcmp(dst, s_sim.tester.lif.mac, 6) == 0) {
lan_put(&s_sim.tester.lif, ifp, buf, len);
}
for (b = s_sim.bridges; b != NULL; b = b->next) {
lan_put(&b->lif, ifp, buf, len);
}
return len;
}
static bool lan_init(struct mg_tcpip_if *ifp) {
(void) ifp;
return true;
}
static bool lan_link(struct mg_tcpip_if *ifp, bool s1) {
(void) ifp, (void) s1;
return true;
}
static struct mg_tcpip_driver s_lan_driver = {lan_init, lan_tx, NULL, lan_link};
// Internet checksum of len bytes
static uint16_t ip_csum(const uint8_t *buf, size_t len) {
uint32_t sum = 0;
size_t i;
for (i = 0; i + 1 < len; i += 2) sum += (uint32_t) (buf[i] << 8 | buf[i + 1]);
if (len & 1) sum += (uint32_t) (buf[len - 1] << 8);
while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
return (uint16_t) ~sum;
}
// A node's address, from its MAC 02:00:00:X:Y:Z: 10.X+1.Y.Z, so it never
// clashes with the LAN nodes' 10.0.0.x
static void node_ip(const uint8_t *mac, uint8_t *ip) {
ip[0] = 10, ip[1] = (uint8_t) (mac[3] + 1), ip[2] = mac[4], ip[3] = mac[5];
}
// DHCP server, a frame from its queue: a DISCOVER gets an OFFER, a REQUEST
// an ACK. Replies are broadcast. The address comes from the client's MAC
static void dhcp_rx(struct simulator *sim, const uint8_t *buf, size_t len) {
static const uint8_t opts[] = {53, 1, 0, // Message type, set below
1, 4, 255, 0, 0, 0, // Mask
3, 4, 10, 0, 0, 1, // Router
54, 4, 10, 0, 0, 1, // Server
51, 4, 0, 0, 14, 16, // Lease, 3600 s
255};
uint8_t r[14 + 20 + 8 + 240 + sizeof(opts)], type = 0;
const uint8_t *dh, *p, *end = buf + len;
size_t ihl, rlen = sizeof(r);
if ((buf[0] & 1) == 0) return; // Bcast
if (len < 34 || buf[12] != 8 || buf[13] != 0 || buf[23] != 17) return; // UDP
ihl = (size_t) (buf[14] & 15) * 4, dh = buf + 14 + ihl + 8; // DHCP
if (dh + 240 > end || buf[14 + ihl + 2] != 0 || buf[14 + ihl + 3] != 67) {
return;
}
for (p = dh + 240; p + 2 <= end && p[0] != 255; p += p[0] ? p[1] + 2 : 1) {
if (p[0] == 53 && p[1] == 1 && p + 3 <= end) type = p[2];
}
if (type != 1 && type != 3) return; // DISCOVER, REQUEST
memset(r, 0, sizeof(r));
memset(r, 0xff, 6), memcpy(r + 6, sim->dhcp.lif.mac, 6),
r[12] = 8; // Ethernet
r[14] = 0x45, r[16] = (uint8_t) ((rlen - 14) >> 8); // IPv4
r[17] = (uint8_t) (rlen - 14), r[22] = 64, r[23] = 17;
r[26] = 10, r[29] = 1, memset(r + 30, 0xff, 4); // 10.0.0.1 -> bcast
r[24] = (uint8_t) (ip_csum(r + 14, 20) >> 8);
r[25] = (uint8_t) ip_csum(r + 14, 20);
r[35] = 67, r[37] = 68; // UDP, csum 0: none
r[38] = (uint8_t) ((rlen - 34) >> 8), r[39] = (uint8_t) (rlen - 34);
r[42] = 2, r[43] = 1, r[44] = 6; // DHCP: reply, Ethernet
memcpy(r + 46, dh + 4, 4); // xid
memcpy(r + 52, dh + 10, 2); // flags
node_ip(dh + 28, r + 58); // yiaddr
r[62] = 10, r[65] = 1; // siaddr
memcpy(r + 70, dh + 28, 16); // chaddr
r[278] = 99, r[279] = 130, r[280] = 83, r[281] = 99; // Magic
memcpy(r + 282, opts, sizeof(opts));
r[284] = type == 1 ? 2 : 5; // OFFER, ACK
sim->dhcp.lif.driver->tx(r, rlen, &sim->dhcp.lif);
}
// The DHCP node also owns the advertised gateway IP, 10.0.0.1.
static bool dhcp_arp(struct simulator *sim, const uint8_t *f, size_t n) {
static const uint8_t header[] = {0, 1, 8, 0, 6, 4, 0, 1};
static const uint8_t ip[] = {10, 0, 0, 1};
uint8_t reply[60];
if (n < 42 || f[12] != 8 || f[13] != 6 ||
memcmp(f + 14, header, sizeof(header)) != 0 ||
memcmp(f + 38, ip, sizeof(ip)) != 0)
return false;
memset(reply, 0, sizeof(reply));
memcpy(reply, f + 6, 6);
memcpy(reply + 6, sim->dhcp.lif.mac, 6);
memcpy(reply + 12, f + 12, 8);
reply[21] = 2;
memcpy(reply + 22, sim->dhcp.lif.mac, 6);
memcpy(reply + 28, ip, sizeof(ip));
memcpy(reply + 32, f + 22, 10);
sim->dhcp.lif.driver->tx(reply, sizeof(reply), &sim->dhcp.lif);
return true;
}
// DHCP server's work: take the frames from its queue
static void dhcp_poll(struct simulator *sim) {
char *buf;
size_t len;
while ((len = mg_queue_next(&sim->dhcp.lif.recv_queue, &buf)) > 0) {
if (!dhcp_arp(sim, (const uint8_t *) buf, len)) {
dhcp_rx(sim, (const uint8_t *) buf, len);
}
mg_queue_del(&sim->dhcp.lif.recv_queue, len);
}
}
// The request on the way finished: ok, or failed
static void tester_done(struct lan_tester *t, uint64_t now, bool ok) {
double ms = (double) (int64_t) (now - t->start) / 1000;
if (ok) {
t->ok++, t->sum += ms;
if (t->ok == 1 || ms < t->min) t->min = ms;
if (ms > t->max) t->max = ms;
} else {
t->failed++;
}
if (t->c != NULL) t->c->is_closing = 1, t->c->fn_data = NULL;
t->c = NULL;
s_sim.work_pending = true;
}
// HTTP client: GET /, the reply ends the request
static void tester_fn(struct mg_connection *c, int ev, void *ev_data) {
struct lan_tester *t = (struct lan_tester *) c->fn_data;
if (t == NULL) return; // Done with
if (ev == MG_EV_CONNECT) {
mg_printf(c, "GET / HTTP/1.0\r\n\r\n");
} else if (ev == MG_EV_HTTP_MSG) {
tester_done(t, s_sim.now, true);
} else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
tester_done(t, s_sim.now, false);
}
(void) ev_data;
}
// Test node's work: Mongoose, then requests, one at a time, once the mesh
// had time to settle. Cycle through nodes until five seconds before the end.
static void tester_poll(struct simulator *sim) {
struct radio_node *n;
char url[64];
mg_mgr_poll(&sim->tester.mgr, 0);
if (sim->tester.c != NULL && sim->now - sim->tester.start >
(uint64_t) sim->request_timeout_ms * 1000) {
tester_done(&sim->tester, sim->now, false);
}
if (sim->now < sim->tester.wait || sim->tester.c != NULL ||
sim->tester.next == NULL || sim->duration <= 5000000U ||
sim->now >= sim->duration - 5000000U)
return;
n = sim->tester.next;
sim->tester.next = n->next == NULL ? sim->nodes : n->next;
if (n->mgr.ifp == NULL) return;
sim->tester.start = sim->now;
if (n->mgr.ifp->ip == 0) {
// Wait for the next poll rather than endlessly cycling at this time.
sim->tester.failed++;
return;
}
mg_snprintf(url, sizeof(url), "http://%M/", mg_print_ip4, &n->mgr.ifp->ip);
if ((sim->tester.c = mg_http_connect(&sim->tester.mgr, url, tester_fn,
&sim->tester)) == NULL) {
tester_done(&sim->tester, sim->now, false);
}
}
// Set up sim: defaults, LAN nodes
static void init(struct simulator *sim) {
static const uint8_t dmac[6] = {2, 1, 0, 0, 0, 0}, // DHCP server MAC
tmac[6] = {2, 2, 0, 0, 0, 0}; // Test node MAC
memset(sim, 0, sizeof(*sim));
sim->area.x = sim->area.y = 100;
sim->radio_range = 185;
sim->loss_random = 1;
sim->duration = 600 * 1000000U, sim->tester.wait = 60 * 1000000U;
sim->request_timeout_ms = 10000;
memcpy(sim->dhcp.lif.mac, dmac, 6), sim->dhcp.lif.driver = &s_lan_driver;
mg_queue_init(&sim->dhcp.lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
LAN_QUEUE_SIZE);
memcpy(sim->tester.lif.mac, tmac, 6);
sim->tester.lif.ip = MG_IPV4(10, 0, 0, 2),
sim->tester.lif.mask = MG_IPV4(255, 0, 0, 0);
sim->tester.lif.driver = &s_lan_driver,
sim->tester.lif.enable_mac_check = true;
sim->tester.lif.recv_queue.size = 128 * 1024;
}
// The driver queues each submitted frame for transmission over the air.
// All nodes hear one channel. CCA and turnaround are separate events:
// two idle checks can succeed before either node starts transmitting.
// Inter-frame spacing is per sender, not a reservation of the shared air.
uint64_t radio_airtime(size_t len) {
const struct mg_radio *radio = s_mesh->radio;
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
radio->bitrate;
}
static void radio_schedule(struct simulator *sim, struct radio_frame *f,
bool busy) {
struct radio_port *port = (struct radio_port *) f->from->driver_data;
const struct mg_radio *radio = s_mesh->radio;
bool checked = f->state == RADIO_CCA;
if (s_mesh->scheduled_tx || !radio->csma || (checked && !busy)) {
f->state = RADIO_START;
f->due = sim->now + (checked ? radio->turnaround_us : 0);
return;
}
if (!checked) {
f->backoff = radio->backoff_min;
} else if (f->backoff < radio->backoff_max) {
f->backoff++;
}
if (port->random == 0) {
size_t i;
port->random = (uint32_t) mg_now();
for (i = 0; i < 6; i++) {
port->random = port->random * 33u + f->from->mac[i];
}
if (port->random == 0) port->random = 1;
}
port->random ^= port->random << 13;
port->random ^= port->random >> 17;
port->random ^= port->random << 5;
f->state = RADIO_BACKOFF;
f->due = sim->now + (port->random % (1U << f->backoff)) * radio->slot_us;
if (f->due > sim->now) port->rx_off_until = f->due;
}
static size_t radio_send(struct mg_tcpip_if *ifp, const void *buf, size_t len) {
struct radio_port *port = (struct radio_port *) ifp->driver_data;
struct simulator *sim = port->sim;
struct radio_frame *f, **p = &sim->air_head;
const struct mg_radio *radio = s_mesh->radio;
if (len < radio->header_len || len > sizeof(f->buf) ||
len > radio->max_frame_size || port->tx_ready_at > sim->now ||
radio->bitrate == 0)
return 0;
while (*p != NULL) {
if ((*p)->from == ifp) return 0;
p = &(*p)->next;
}
if ((f = (struct radio_frame *) calloc(1, sizeof(*f))) == NULL) return 0;
f->from = ifp, f->len = len;
memcpy(f->buf, buf, len);
*p = f;
sim->work_pending = true;
radio_schedule(sim, f, false);
return len;
}
static void radio_receive(struct radio_port *port,
const struct radio_frame *f) {
char *buf;
if (mg_queue_book(&port->rx, &buf, f->len) >= f->len) {
memcpy(buf, f->buf, f->len);
mg_queue_add(&port->rx, f->len);
port->sim->work_pending = true;
}
}
// Test range and sample independent interference loss for this reception.
static bool visible(struct simulator *sim, const struct pos *a,
const struct pos *b) {
double dx = a->x - b->x, dy = a->y - b->y, dz = a->z - b->z;
double range = sim->radio_range;
if (dx * dx + dy * dy + dz * dz > range * range) return false;
if (sim->radio_loss == 0) return true;
sim->loss_random ^= sim->loss_random << 13;
sim->loss_random ^= sim->loss_random >> 17;
sim->loss_random ^= sim->loss_random << 5;
return (double) sim->loss_random * (100.0 / 4294967296.0) >= sim->radio_loss;
}
static void radio_deliver_to(struct radio_port *port,
const struct radio_port *from,
const struct radio_frame *f, uint16_t dst) {
bool all = dst == 0xffffu;
if (port == from || (!all && port->address != dst)) return;
if (!visible(port->sim, from->pos, port->pos)) {
port->sim->air_lost++;
} else if (port->tx_until > f->start) {
if (!all) port->sim->air_lost++;
} else if (!f->collided && port->rx_off_until <= f->start) {
radio_receive(port, f);
}
}
static void radio_deliver(struct simulator *sim, const struct radio_frame *f) {
const struct mg_radio *radio = s_mesh->radio;
const struct radio_port *from = f->from->driver_data;
uint16_t dst, src;
struct bridge *b;
struct radio_node *n;
if (radio->parse_hdr(radio, f->buf, f->len, &dst, &src) == 0) return;
for (b = sim->bridges; b != NULL; b = b->next)
radio_deliver_to(&b->radio, from, f, dst);
for (n = sim->nodes; n != NULL; n = n->next)
radio_deliver_to(&n->radio, from, f, dst);
}
// Process ends, then all channel checks, then starts at this timestamp.
// Checks therefore see the same air state regardless of list order.
static void radio_poll(struct simulator *sim) {
struct radio_frame **p = &sim->air_head, *f, *g;
while ((f = *p) != NULL) {
if (f->state != RADIO_END || f->due > sim->now) {
p = &f->next;
continue;
}
if (f->collided) {
sim->air_collisions++;
}
radio_deliver(sim, f);
*p = f->next;
free(f);
}
for (f = sim->air_head; f != NULL; f = f->next) {
if (f->state == RADIO_BACKOFF && f->due <= sim->now) {
f->state = RADIO_CCA;
f->due = sim->now + s_mesh->radio->cca_us;
((struct radio_port *) f->from->driver_data)->rx_off_until = f->due;
}
}
for (f = sim->air_head; f != NULL; f = f->next) {
if (f->state != RADIO_CCA || f->due > sim->now) continue;
radio_schedule(sim, f,
sim->air_busy_until > sim->now - s_mesh->radio->cca_us);
if (f->state == RADIO_START) {
((struct radio_port *) f->from->driver_data)->rx_off_until = f->due;
}
}
for (f = sim->air_head; f != NULL; f = f->next) {
struct radio_port *port = f->from->driver_data;
if (f->state != RADIO_START || f->due > sim->now) continue;
f->state = RADIO_END;
f->start = sim->now;
f->due = sim->now + radio_airtime(f->len);
port->rx_off_until = port->tx_until = f->due;
port->tx_ready_at = f->due + s_mesh->radio->ifs_us;
for (g = sim->air_head; g != NULL; g = g->next) {
if (g != f && g->state == RADIO_END && g->due > sim->now) {
f->collided = g->collided = true;
}
}
if (f->due > sim->air_busy_until) sim->air_busy_until = f->due;
sim->air_sent++;
}
}
// Radio events keep microsecond precision between periodic timer polls.
static uint64_t radio_next(const struct simulator *sim, uint64_t next) {
const struct radio_frame *f;
for (f = sim->air_head; f != NULL; f = f->next) {
if (f->due < next) next = f->due;
}
return next;
}
static size_t radio_receive_frame(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
struct radio_port *port = (struct radio_port *) ifp->driver_data;
char *src;
size_t len;
while ((len = mg_queue_next(&port->rx, &src)) > 0) {
if (len <= cap) memcpy(buf, src, len);
mg_queue_del(&port->rx, len);
if (len <= cap) {
if (mg_queue_next(&port->rx, &src) > 0) port->sim->work_pending = true;
return len;
}
}
return 0;
}
static size_t radio_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
struct radio_port *port = ifp->driver_data;
return port->mesh->strategy->tx(port->mesh, buf, len);
}
static size_t radio_rx(void *buf, size_t cap, struct mg_tcpip_if *ifp) {
struct radio_port *port = ifp->driver_data;
size_t len = port->mesh->rx_len;
if (len > cap) return 0;
if (len != 0 && buf != ifp->recv_queue.buf)
memcpy(buf, ifp->recv_queue.buf, len);
port->mesh->rx_len = 0;
return len;
}
static bool radio_driver_poll(struct mg_tcpip_if *ifp, bool s1) {
struct radio_port *port = ifp->driver_data;
uint8_t frame[128];
size_t len;
(void) s1;
port->mesh->now = mg_micros();
while (port->mesh->rx_len == 0 &&
(len = radio_receive_frame(frame, sizeof(frame), ifp)) != 0)
port->mesh->strategy->rx(port->mesh, frame, len);
s_mesh->tick(port->mesh, mg_micros());
return port->mesh->joined;
}
static bool radio_init(struct mg_tcpip_if *ifp) {
struct radio_port *port = ifp->driver_data;
char *buf = malloc(LAN_QUEUE_SIZE);
if (buf == NULL) return false;
mg_queue_init(&port->rx, buf, LAN_QUEUE_SIZE);
port->mesh = calloc(1, sizeof(*port->mesh));
if (port->mesh == NULL ||
!s_mesh->init(port->mesh, ifp, port->lan, port->address)) {
free(port->mesh);
port->mesh = NULL;
free(buf);
port->rx.buf = NULL;
return false;
}
port->mesh->strategy = s_mesh;
port->mesh->tx = radio_send;
return true;
}
static struct mg_tcpip_driver s_radio_driver = {
radio_init, radio_tx, radio_rx, radio_driver_poll};
#define MG_TCPIP_DRIVER_SIM_RADIO_INIT(manager, interface) \
do { \
struct mg_tcpip_if *mif_ = (interface); \
mif_->mgr = (manager); \
mg_tcpip_init((manager), mif_); \
MG_INFO(("Driver: sim_radio, MAC: %M", mg_print_mac, mif_->mac)); \
} while (0)
#define MG_TCPIP_DRIVER_SIM_LAN_INIT(manager, interface) \
do { \
struct mg_tcpip_if *mif_ = (interface); \
mg_tcpip_init((manager), mif_); \
MG_INFO(("Driver: sim_lan, MAC: %M", mg_print_mac, mif_->mac)); \
} while (0)
// A node's HTTP server: the test node requests GET /
static void node_http_ev_handler(struct mg_connection *c, int ev,
void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
mg_http_reply(c, 200, "", "Hi from Mongoose, my MAC is %M\n", mg_print_mac,
c->fn_data);
}
(void) ev_data;
}
// Start each node's Mongoose: its radio interface, no IP address, so DHCP,
// and an HTTP server
static void nodes_init(struct simulator *sim) {
struct radio_node *n;
for (n = sim->nodes; n != NULL; n = n->next) {
n->rif.driver = &s_radio_driver;
n->rif.driver_data = &n->radio;
n->radio.sim = sim;
n->radio.pos = &n->pos;
mg_mgr_init(&n->mgr);
MG_TCPIP_DRIVER_SIM_RADIO_INIT(&n->mgr, &n->rif);
if (n->mgr.ifp != NULL) {
mg_http_listen(&n->mgr, "http://0.0.0.0:80", node_http_ev_handler,
n->mgr.ifp->mac);
}
}
}
// Start each bridge: its LAN and radio drivers, with no IP stack.
static void bridges_init(struct simulator *sim) {
struct bridge *b;
for (b = sim->bridges; b != NULL; b = b->next) {
b->lif.driver = &s_lan_driver, b->rif.driver = &s_radio_driver;
b->rif.driver_data = &b->radio;
b->radio.sim = sim;
b->radio.pos = &b->pos;
mg_mgr_init(&b->mgr);
b->radio.lan = &b->lif;
MG_TCPIP_DRIVER_SIM_RADIO_INIT(&b->mgr, &b->rif);
mg_queue_init(&b->lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
LAN_QUEUE_SIZE);
b->lif.driver->init(&b->lif);
}
}
int main(int argc, char *argv[]) {
uint64_t poll_at = 0, mesh_at = (uint64_t) -1;
char *pending;
clock_t started; // CPU time, to show how fast the simulation runs
int i;
init(&s_sim);
for (i = 1; i < argc; i++) {
if (i + 1 >= argc) {
usage(&s_sim, argv[0]);
} else if (strcmp(argv[i], "-area") == 0) {
if (!parse_pos(argv[++i], &s_sim.area)) usage(&s_sim, argv[0]);
} else if (strcmp(argv[i], "-bridge") == 0) {
if (!add_bridge(&s_sim, argv[++i])) usage(&s_sim, argv[0]);
} else if (strcmp(argv[i], "-node") == 0) {
if (!add_node(&s_sim, argv[++i])) usage(&s_sim, argv[0]);
} else if (strcmp(argv[i], "-t") == 0) {
s_sim.duration = (uint64_t) (atof(argv[++i]) * 1e6);
} else if (strcmp(argv[i], "-w") == 0) {
s_sim.tester.wait = (uint64_t) (atof(argv[++i]) * 1e6);
} else if (strcmp(argv[i], "-range") == 0 ||
strcmp(argv[i], "-loss") == 0) {
bool loss = strcmp(argv[i], "-loss") == 0;
const char *arg = argv[++i];
char *end;
double value = strtod(arg, &end);
if (end == arg || *end != '\0' ||
!(value >= 0 && value <= (loss ? 100 : 1e100)))
usage(&s_sim, argv[0]);
if (loss)
s_sim.radio_loss = value;
else
s_sim.radio_range = value;
} else if (strcmp(argv[i], "-m") == 0) {
const char *name = argv[++i];
if (strcmp(name, mg_mesh_a.name) == 0)
s_mesh = &mg_mesh_a;
else if (strcmp(name, mg_mesh_default.name) == 0)
s_mesh = &mg_mesh_default;
else
usage(&s_sim, argv[0]);
} else {
usage(&s_sim, argv[0]);
}
}
printf("Area %gx%gx%g m, radio %s, mesh %s, bridges: %d, nodes: %d\n",
s_sim.area.x, s_sim.area.y, s_sim.area.z, s_mesh->radio->name,
s_mesh->name, s_sim.num_bridges, s_sim.num_nodes);
check_area(&s_sim);
if (s_mesh->single_bridge && s_sim.num_bridges != 1) {
fprintf(stderr, "%s requires exactly one bridge\n", s_mesh->name);
return EXIT_FAILURE;
}
// mg_log_set(MG_LL_INFO);
mg_mgr_init(&s_sim.tester.mgr);
MG_TCPIP_DRIVER_SIM_LAN_INIT(&s_sim.tester.mgr, &s_sim.tester.lif);
s_sim.tester.next = s_sim.nodes;
bridges_init(&s_sim);
nodes_init(&s_sim);
// Drain packet processing at the current time. Only idle timer polling
// waits for STEP_MICROSECONDS; radio deadlines remain exact.
started = clock();
while (s_sim.now < s_sim.duration) {
radio_poll(&s_sim);
if (s_sim.now == poll_at || s_sim.now == mesh_at || s_sim.work_pending) {
do {
s_sim.work_pending = false;
dhcp_poll(&s_sim);
tester_poll(&s_sim);
{
struct bridge *b;
struct radio_node *n;
mesh_at = (uint64_t) -1;
for (b = s_sim.bridges; b != NULL; b = b->next) {
mg_mgr_poll(&b->mgr, 0);
if (b->radio.mesh->next_tick < mesh_at)
mesh_at = b->radio.mesh->next_tick;
}
for (n = s_sim.nodes; n != NULL; n = n->next) {
mg_mgr_poll(&n->mgr, 0);
if (n->radio.mesh->next_tick < mesh_at)
mesh_at = n->radio.mesh->next_tick;
}
}
if (mg_queue_next(&s_sim.tester.lif.recv_queue, &pending) > 0) {
s_sim.work_pending = true;
}
} while (s_sim.work_pending);
if (s_sim.now == poll_at) poll_at += STEP_MICROSECONDS;
}
s_sim.now = radio_next(&s_sim, mesh_at < poll_at ? mesh_at : poll_at);
if (s_sim.now > s_sim.duration) s_sim.now = s_sim.duration;
}
printf("Requests: %d ok, %d failed, of %d nodes", s_sim.tester.ok,
s_sim.tester.failed, s_sim.num_nodes);
if (s_sim.tester.ok > 0) {
printf("; ms: min %.1f, mean %.1f, max %.1f", s_sim.tester.min,
s_sim.tester.sum / s_sim.tester.ok, s_sim.tester.max);
}
printf("\nSimulated %g s in %.3f s", (double) (int64_t) s_sim.now / 1e6,
(double) (clock() - started) / CLOCKS_PER_SEC);
if (s_sim.nodes != NULL) {
const struct radio_node *n;
size_t min = (size_t) -1, max = 0;
double total = 0;
for (n = s_sim.nodes; n != NULL; n = n->next) {
size_t peak = n->radio.mesh->ram_peak;
if (peak < min) min = peak;
if (peak > max) max = peak;
total += (double) peak;
}
printf("; peak mesh RAM per node (bytes): min %zu, max %zu, avg %.1f", min,
max, total / s_sim.num_nodes);
}
putchar('\n');
while (s_sim.air_head != NULL) {
struct radio_frame *f = s_sim.air_head;
s_sim.air_head = f->next;
free(f);
}
printf("Radio: %lu sent, %lu collided, %lu lost\n",
s_sim.air_sent, s_sim.air_collisions, s_sim.air_lost);
{
struct radio_node *n;
int count = 0, joined = 0;
for (n = s_sim.nodes; n != NULL; n = n->next) {
if (n->mgr.ifp != NULL && n->mgr.ifp->ip != 0) count++;
if (n->radio.mesh->joined) joined++;
}
printf("Nodes with IP: %d of %d, joined: %d of %d\n", count,
s_sim.num_nodes, joined, s_sim.num_nodes);
}
{
struct bridge *b;
struct radio_node *n;
mg_log_set(MG_LL_NONE);
for (b = s_sim.bridges; b != NULL; b = b->next) {
mg_mgr_free(&b->mgr);
s_mesh->free(b->radio.mesh);
free(b->radio.mesh);
free(b->radio.rx.buf);
}
for (n = s_sim.nodes; n != NULL; n = n->next) {
mg_mgr_free(&n->mgr);
s_mesh->free(n->radio.mesh);
free(n->radio.mesh);
free(n->radio.rx.buf);
}
}
return 0;
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#pragma once
#define MG_ENABLE_TCPIP 1
#include "mongoose.h"
extern const struct mg_mesh mg_mesh_a;
// A position in metres
struct pos {
double x, y, z; // Coordinates
};
struct simulator;
// Private state of the simulated radio driver.
struct radio_port {
struct simulator *sim;
struct mg_tcpip_if *lan; // Bridge LAN interface, NULL for a node
const struct pos *pos; // Location of the radio
uint64_t tx_until; // Latest transmission end, us
uint64_t tx_ready_at; // Inter-frame wait ends, us
uint64_t rx_off_until; // Latest backoff/sensing/turnaround/TX end, us
struct mg_mesh_node *mesh; // Mesh state owned by the driver
struct mg_queue rx;
uint32_t random; // Driver backoff PRNG
uint16_t address; // Preassigned radio address
};
// A bridge: forwards frames between the LAN and the radio, no IP stack
struct bridge {
struct bridge *next; // Next in simulator::bridges
struct pos pos; // Where it is
struct mg_mgr mgr; // Polls the raw radio driver
struct mg_tcpip_if lif; // LAN side: LAN driver, gets every LAN frame
struct mg_tcpip_if rif; // Radio side: radio driver, gets every frame
struct radio_port radio;
};
// A radio node: Mongoose on a radio interface
struct radio_node {
struct radio_node *next; // Next in simulator::nodes
struct pos pos; // Where it is
struct mg_mgr mgr; // Mongoose event manager
struct mg_tcpip_if rif; // Radio interface. rif.ip from DHCP, 0: none yet
struct radio_port radio;
};
struct simulator;
// LAN node: a DHCP server, 10.0.0.1. Gives each client an address made
// from its MAC, see node_ip()
struct lan_dhcp_server {
struct mg_tcpip_if lif; // Its LAN interface, no Mongoose on it
};
// LAN node: the test node, 10.0.0.2, a Mongoose instance. Waits for
// the mesh to settle, then cycles GET / through nodes until 5 s before the end
struct lan_tester {
struct mg_mgr mgr; // Its Mongoose
struct mg_tcpip_if lif; // Its LAN interface
uint64_t wait; // Mesh settle time: start requests then, us
struct radio_node *next; // Node to request next, wraps at the end
struct mg_connection *c; // Request on the way, NULL: none
uint64_t start; // When it started, us
int ok, failed; // Requests done, by result
double min, max, sum; // Times of successful requests, ms
};
enum { RADIO_BACKOFF, RADIO_CCA, RADIO_START, RADIO_END };
// One radio frame waiting on the simulated radio channel
struct radio_frame {
struct radio_frame *next;
struct mg_tcpip_if *from;
uint64_t due; // Next radio event, us
uint64_t start; // Transmission start, for receive overlap checks
uint8_t state, backoff;
bool collided;
size_t len;
uint8_t buf[1540];
};
uint64_t radio_airtime(size_t len);
// Simulator state
struct simulator {
struct radio_frame *air_head; // Pending and active frames, singly linked
uint64_t air_busy_until; // Last transmission end, for channel sensing
unsigned long air_sent, air_collisions;
unsigned long air_lost; // Receptions lost to range, random loss or TX overlap
double radio_range; // Maximum reception distance, metres
double radio_loss; // In-range reception loss probability, percent
uint32_t loss_random; // Reception-loss PRNG state
bool work_pending; // Drain immediate work before advancing time
struct pos area; // A box: all positions inside
struct bridge *bridges; // Bridges, a list
struct radio_node *nodes; // Radio nodes, a list
int num_bridges, num_nodes; // How many
uint64_t now; // Virtual time, us since the start
uint64_t duration; // Run this long, us
uint32_t last_mac; // Low 3 bytes of the last MAC given
int request_timeout_ms; // A request that takes longer failed
struct lan_dhcp_server dhcp; // DHCP server on the LAN
struct lan_tester tester; // Test node on the LAN
};
+16 -8
View File
@@ -5,6 +5,9 @@
#include "mip_x_test.c"
static bool drop_tcp_segment(const void *buf, size_t len,
struct mg_tcpip_if *ifp);
struct port_driver_data {
int sockfd;
struct sockaddr_in port_addr;
@@ -16,6 +19,7 @@ static size_t sock_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
ssize_t received = recv(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
usleep(1); // This is to avoid 100% CPU
if (received < 0) return 0;
if (drop_tcp_segment(buf, (size_t) received, ifp)) return 0;
return (size_t) received;
}
@@ -37,15 +41,19 @@ static bool is_tcp_segment(const void *buf, size_t len) {
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
}
static size_t sock_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
static bool drop_tcp_segment(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
uint32_t random;
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss
mg_random(&random, sizeof(random));
if (random % 10 < 3) {
MG_DEBUG(("dropping TCP segment"));
return len;
}
}
if (!ifp->enable_tcp_retransmit || !is_tcp_segment(buf, len)) return false;
// Introduce a 30% segment loss
mg_random(&random, sizeof(random));
if (random % 10 >= 3) return false;
MG_DEBUG(("dropping TCP segment"));
return true;
}
static size_t sock_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
if (drop_tcp_segment(buf, len, ifp)) return len;
ssize_t res = send(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
if (res < 0) {
MG_ERROR(("sock_tx failed: %d", errno));
+15 -8
View File
@@ -13,12 +13,15 @@
#include "mip_x_test.c"
static bool drop_tcp_segment(const void *buf, size_t len,
struct mg_tcpip_if *ifp);
// MIP TUNTAP driver
static size_t tap_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
ssize_t received = read(*(int *) ifp->driver_data, buf, len);
usleep(1); // This is to avoid 100% CPU
if (received < 0) return 0;
if (drop_tcp_segment(buf, (size_t) received, ifp)) return 0;
return (size_t) received;
}
@@ -40,15 +43,19 @@ static bool is_tcp_segment(const void *buf, size_t len) {
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
}
static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
static bool drop_tcp_segment(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
uint32_t random;
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss
mg_random(&random, sizeof(random));
if (random % 10 < 3) {
MG_DEBUG(("dropping TCP segment"));
return len;
}
}
if (!ifp->enable_tcp_retransmit || !is_tcp_segment(buf, len)) return false;
// Introduce a 30% segment loss
mg_random(&random, sizeof(random));
if (random % 10 >= 3) return false;
MG_DEBUG(("dropping TCP segment"));
return true;
}
static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
if (drop_tcp_segment(buf, len, ifp)) return len;
ssize_t res = write(*(int *) ifp->driver_data, buf, len);
if (res < 0) {
MG_ERROR(("tap_tx failed: %d", errno));
+4
View File
@@ -80,6 +80,10 @@ static void test_statechange(void) {
onstatechange(&iface);
ASSERT(executed == true);
executed = false;
iface.enable_dhcp_client = true; // DHCP REQUEST unanswered: start over
iface.state = MG_TCPIP_STATE_REQ, iface.lease_expire = 0;
mg_ip_poll(&iface, true);
ASSERT(iface.state == MG_TCPIP_STATE_UP);
}
#if MG_ENABLE_IPV6
static void mif6_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
+69 -47
View File
@@ -76,6 +76,7 @@ static int s_num_tests = 0;
static bool s_error = false;
#define POLL_LIMIT(n) (mgr->ifp->enable_tcp_retransmit ? (n) * 10 : (n))
#define RTX_ATTEMPTS(mgr) ((mgr)->ifp->enable_tcp_retransmit ? 3 : 1)
#ifdef NO_ABORT
static int s_abort = 0;
@@ -151,13 +152,15 @@ static void eh1(struct mg_connection *c, int ev, void *ev_data) {
struct fetch_data {
char *buf;
const char *url;
int code, closed;
int code;
bool connected, received, closed;
};
static void fcb(struct mg_connection *c, int ev, void *ev_data) {
struct fetch_data *fd = (struct fetch_data *) c->fn_data;
if (ev == MG_EV_CONNECT) {
MG_DEBUG(("CONNECT"));
fd->connected = true;
if (mg_url_is_ssl(fd->url)) {
struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts)); // read CA from packed_fs
@@ -173,6 +176,7 @@ static void fcb(struct mg_connection *c, int ev, void *ev_data) {
}
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
fd->received = true;
snprintf(fd->buf, FETCH_BUF_SIZE, "%.*s", (int) hm->message.len,
hm->message.buf);
fd->code = atoi(hm->uri.buf);
@@ -191,32 +195,38 @@ static void fcb(struct mg_connection *c, int ev, void *ev_data) {
static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
const char *fmt, ...) {
struct fetch_data fd = {buf, url, 0, 0};
int i;
int i, attempt;
struct mg_connection *c = NULL;
va_list ap;
mg_mgr_poll(mgr, 0); // update ifp->now to avoid ARP lookup using an old
// timestamp (from an ancient call in other test)
mg_mgr_poll(mgr, 0); // (prior idling collects lots of frames)
c = mg_http_connect(mgr, url, fcb, &fd);
ASSERT(c != NULL);
va_start(ap, fmt);
mg_vprintf(c, fmt, &ap);
va_end(ap);
buf[0] = '\0';
// - TLS: multiple (small) records: allow enough loops so mg_mgr_poll can
// process buffered records when no more frames are coming in
for (i = 0; i < POLL_LIMIT(500) && buf[0] == '\0' && !fd.closed; i++) {
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
struct fetch_data fd = {buf, url, 0, false, false, false};
mg_mgr_poll(mgr, 0); // update ifp->now to avoid ARP lookup using an old
// timestamp (from an ancient call in other test)
mg_mgr_poll(mgr, 0); // (prior idling collects lots of frames)
c = mg_http_connect(mgr, url, fcb, &fd);
ASSERT(c != NULL);
va_start(ap, fmt);
mg_vprintf(c, fmt, &ap);
va_end(ap);
buf[0] = '\0';
// - TLS: multiple (small) records: allow enough loops so mg_mgr_poll can
// process buffered records when no more frames are coming in
for (i = 0; i < POLL_LIMIT(500) && buf[0] == '\0' && !fd.closed; i++) {
mg_mgr_poll(mgr, 0);
usleep(5000); // 5 ms. Slow down poll loop to ensure packet transit, but
// allow enough loops to get the ARP response, otherwise,
// given enough traffic, the timer expires before we get a
// chance to see the response
}
if (!fd.closed) // failed, force closure and conn removal, and loop end
((struct connstate *) (c + 1))->is_closing = 1;
mg_mgr_poll(mgr, 0);
usleep(5000); // 5 ms. Slow down poll loop to ensure packet transit, but
// allow enough loops to get the ARP response, otherwise,
// given enough traffic, the timer expires before we get a
// chance to see the response
if (!(fd.connected && fd.closed && !fd.received) ||
attempt + 1 == RTX_ATTEMPTS(mgr))
return fd.code;
MG_INFO(("HTTP retry %d", attempt + 2));
}
if (!fd.closed) // failed, force closure and conn removal, and loop end
((struct connstate *) (c + 1))->is_closing = 1;
mg_mgr_poll(mgr, 0);
return fd.code;
return 0;
}
static void test_http_client(struct mg_mgr *mgr) {
@@ -256,13 +266,14 @@ static char s_topic[16];
struct mqtt_data {
char *url;
bool passed;
bool connected, opened, passed;
};
static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
struct mqtt_data *data = (struct mqtt_data *) c->fn_data;
if (ev == MG_EV_CONNECT) {
MG_DEBUG(("CONNECT"));
data->connected = true;
#if MG_TLS
struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts));
@@ -277,6 +288,7 @@ static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
#endif
} else if (ev == MG_EV_MQTT_OPEN) {
MG_DEBUG(("MQTT CONNECT"));
data->opened = true;
struct mg_mqtt_opts sub_opts;
memset(&sub_opts, 0, sizeof(sub_opts));
sub_opts.topic = mg_str(mg_random_str(s_topic, sizeof(s_topic)));
@@ -326,12 +338,30 @@ static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
}
}
static bool mqtt_attempt(struct mg_mgr *mgr, struct mqtt_data *data,
struct mg_mqtt_opts *opts) {
int i;
data->connected = data->opened = data->passed = false;
mg_mgr_poll(mgr, 0); // update interface timing (*)
mg_mgr_poll(mgr, 0);
s_conn = mg_mqtt_connect(mgr, data->url, opts, mqtt_fn, data);
ASSERT(s_conn != NULL);
for (i = 0; i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
i++) {
mg_mgr_poll(mgr, 0);
usleep(5000); // 5 ms (*) See fetch() above for reasons
}
mg_mgr_poll(mgr, 0);
return data->connected && !data->opened && s_conn == NULL;
}
static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
struct mqtt_data data;
struct mg_mqtt_opts opts;
bool retry;
int attempt;
memset(&opts, 0, sizeof(opts));
opts.clean = true, opts.version = 4;
data.passed = false;
#if defined(MQTT_HOST) && MG_TLS != MG_TLS_BUILTIN
if (host_ip == NULL) {
printf("\nMQTT_HOST defined but no HOST_IP provided, skipping MQTT tests\n");
@@ -359,15 +389,10 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
data.url = strdup(MQTT_URL);
#endif
#endif // MG_TLS
mg_mgr_poll(mgr, 0); // update interface timing (*)
mg_mgr_poll(mgr, 0);
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data);
ASSERT(s_conn != NULL);
for (int i = 0;
i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
i++) {
mg_mgr_poll(mgr, 0);
usleep(5000); // 5 ms (*) See fetch() above for reasons
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
retry = mqtt_attempt(mgr, &data, &opts);
if (!retry || attempt + 1 == RTX_ATTEMPTS(mgr)) break;
MG_INFO(("MQTT retry %d", attempt + 2));
}
ASSERT(data.passed);
mg_mgr_poll(mgr, 0);
@@ -406,16 +431,10 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
//data.url = strdup(MQTT_URL);
#endif
#endif // MG_TLS
data.passed = false;
mg_mgr_poll(mgr, 0); // update interface timing (*)
mg_mgr_poll(mgr, 0);
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data);
ASSERT(s_conn != NULL);
for (int i = 0;
i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
i++) {
mg_mgr_poll(mgr, 0);
usleep(5000); // 5 ms (*) See fetch() above for reasons
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
retry = mqtt_attempt(mgr, &data, &opts);
if (!retry || attempt + 1 == RTX_ATTEMPTS(mgr)) break;
MG_INFO(("MQTT retry %d", attempt + 2));
}
ASSERT(data.passed);
mg_mgr_poll(mgr, 0);
@@ -445,7 +464,7 @@ static void test_http_server(struct mg_mgr *mgr) {
struct mg_connection *c;
char *cmd;
pthread_t thread_id = (pthread_t) 0;
int rc;
int attempt, rc;
#if MG_TLS
struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts));
@@ -468,8 +487,11 @@ static void test_http_server(struct mg_mgr *mgr) {
s_poll_done = false;
pthread_create(&thread_id, NULL, poll_thread,
mgr); // simpler this way, no concurrency anyway
MG_DEBUG(("CURL"));
rc = system(cmd); // wait for curl
for (attempt = 0; attempt < RTX_ATTEMPTS(mgr); attempt++) {
MG_DEBUG(("CURL attempt %d", attempt + 1));
rc = system(cmd); // wait for curl
if (rc == 0) break;
}
MG_DEBUG(("MONGOOSE"));
s_poll_done = true;
pthread_join(thread_id, NULL); // wait for Mongoose
+11
View File
@@ -1475,6 +1475,11 @@ static void h4(struct mg_connection *c, int ev, void *ev_data) {
opts.root_dir = "/a=./data";
opts.page404 = "./data/404.html"; // existing 404 page
mg_http_serve_dir(c, hm, &opts);
} else if (mg_match(hm->uri, mg_str("/unmapped"), NULL)) {
struct mg_http_serve_opts opts;
memset(&opts, 0, sizeof(opts));
opts.root_dir = "/a=./data";
mg_http_serve_dir(c, hm, &opts);
} else if (mg_match(hm->uri, mg_str("/b/#"), NULL)) {
struct mg_http_serve_opts opts;
memset(&opts, 0, sizeof(opts));
@@ -1502,6 +1507,7 @@ static void test_http_404(void) {
ASSERT(cmpbody(buf, "hello\n") == 0);
ASSERT(fetch(&mgr, buf, url, "GET /a/a.txt HTTP/1.0\n\n") == 200);
ASSERT(cmpbody(buf, "hello\n") == 0);
ASSERT(fetch(&mgr, buf, url, "GET /unmapped HTTP/1.0\n\n") == 400);
ASSERT(fetch(&mgr, buf, url, "GET /b/a.txt HTTP/1.0\n\n") == 200);
ASSERT(cmpbody(buf, "hello\n") == 0);
@@ -2399,10 +2405,15 @@ static void test_str(void) {
ASSERT(sn("%.*s", 1, "ab"));
ASSERT(sn("%.1s", "ab"));
ASSERT(sn("%.99s", "a"));
ASSERT(sn("%10.8s", "abc"));
ASSERT(sn("%-6.4s", "ab"));
ASSERT(sn("%5.1s", ""));
ASSERT(sn("%11s", "a"));
ASSERT(sn("%s", "a\0b"));
ASSERT(sn("%2s", "a"));
ASSERT(sn("%.*s", 3, "a\0b"));
ASSERT(mg_snprintf(NULL, 0, "%.8s", (char *) NULL) == 0);
ASSERT(mg_snprintf(NULL, 0, "%8.4s", (char *) NULL) == 8);
ASSERT(sn("%d", 7));
ASSERT(sn("%d", 123));
#if MG_ARCH == MG_ARCH_UNIX
+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 wifi_init(const char *ssid, const char *pass);
MG_HEALTH_INIT();
wifi_init(WIFI_SSID, WIFI_PASS);
struct mg_mgr mgr;
@@ -1 +1,3 @@
#define MG_ARCH MG_ARCH_ESP32
#define MG_ENABLE_HEALTH 1
@@ -3,8 +3,12 @@
#include "mongoose.h"
#ifndef MQTT_SERVER_URL
#define MQTT_SERVER_URL "mqtt://broker.hivemq.com:1883"
#endif
#ifndef MQTT_CLIENT_ID
#define MQTT_CLIENT_ID "d3"
#endif
#define MQTT_USER MQTT_CLIENT_ID
#define MQTT_PASS ""
#define MQTT_PUBLISH_TOPIC "mg/" MQTT_CLIENT_ID "/tx"
@@ -0,0 +1,61 @@
# Environment setup: https://mongoose.ws/documentation/tutorials/tools/
MGDIR = ../../../..
# Flash over USB: the board's Adafruit UF2 bootloader does serial DFU. The
# firmware resets into it on a 1200 baud touch, see usb.c. PORT: the board's
# USB serial port. adafruit-nrfutil: pip install adafruit-nrfutil
PORT ?= /dev/cu.usbmodem11301
NRFUTIL ?= adafruit-nrfutil
CFLAGS = -W -Wall -Wextra -Werror -Wshadow -Wdouble-promotion
CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include
CFLAGS += -Inrfx/mdk -Inrfx -Inrfx/hal -Inrfx/templates -Inrfx/drivers/src -Itinyusb/src
CFLAGS += -Wno-cast-align -Wno-shadow -Wno-unused-parameter
CFLAGS += -DNRF52840_XXAA -D__STARTUP_CLEAR_BSS -D__START=main -D__HEAP_SIZE=0
CFLAGS += -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
CFLAGS += $(CFLAGS_EXTRA)
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lm -lgcc -Wl,--gc-sections
SOURCES = main.c hal.c usb.c $(MGDIR)/mongoose.c
SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c
SOURCES += nrfx/mdk/system_nrf52840.c nrfx/mdk/gcc_startup_nrf52840.S
# TinyUSB, for the USB serial console: the board has no USB-UART chip
SOURCES += tinyusb/src/tusb.c tinyusb/src/common/tusb_fifo.c
SOURCES += tinyusb/src/device/usbd.c tinyusb/src/device/usbd_control.c
SOURCES += tinyusb/src/class/cdc/cdc_device.c
SOURCES += tinyusb/src/portable/nordic/nrf5x/dcd_nrf5x.c
all build: firmware.hex
firmware.hex: firmware.elf
arm-none-eabi-objcopy -O ihex $< $@
arm-none-eabi-size --format=berkeley $<
firmware.elf: cmsis_core nrfx tinyusb $(SOURCES) $(wildcard *.h) link.ld Makefile
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
firmware.zip: firmware.hex
$(NRFUTIL) dfu genpkg --dev-type 0x0052 --sd-req 0xFFFE --application $< $@
flash: firmware.zip
$(NRFUTIL) dfu serial --package $< -p $(PORT) -b 115200 --singlebank --touch 1200
cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
# nRF52840 device headers and SystemInit(), nrf_* register helpers for TinyUSB
nrfx:
git clone -q -c advice.detachedHead=false --depth 1 -b v2.11.0 https://github.com/NordicSemiconductor/nrfx $@
tinyusb:
git clone -q -c advice.detachedHead=false --depth 1 -b 0.14.0 https://github.com/hathach/tinyusb $@
clean:
rm -rf firmware.* *.dSYM tinyusb nrfx cmsis_core
.PHONY: all build flash clean
+141
View File
@@ -0,0 +1,141 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
// Added to minimise the bin size: avoid pulling printf and friends
void __assert_func(const char *s, int line, const char *fn, const char *expr) {
(void) s, (void) line, (void) fn, (void) expr;
for (;;) (void) 0; // or trigger the fault handler
}
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
if (period == 0) return false; // Avoid division by zero
if (diff < period) return false; // Period has not elapsed yet
*t += (diff / period) * period; // Preserve cadence, skip missed periods
return true;
}
static volatile uint64_t s_ticks; // Milliseconds since boot
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
s_ticks++;
}
uint64_t hal_get_tick(void) {
return s_ticks;
};
struct stat;
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
(void) fd, (void) st;
return -1;
}
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
static unsigned char *s_current_heap_end = _end;
size_t hal_ram_used(void) {
return (size_t) (s_current_heap_end - _end);
}
size_t hal_ram_free(void) {
unsigned char endofstack;
return (size_t) (&endofstack - s_current_heap_end);
}
void *_sbrk(int incr) {
unsigned char *prev_heap;
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
prev_heap = s_current_heap_end;
// Check how much space we got from the heap end to the stack end
if (s_current_heap_end + incr > heap_end) return (void *) -1;
s_current_heap_end += incr;
return prev_heap;
}
__attribute__((weak)) int _open(const char *path) {
(void) path;
return -1;
}
__attribute__((weak)) int _close(int fd) {
(void) fd;
return -1;
}
__attribute__((weak)) int _isatty(int fd) {
(void) fd;
return 1;
}
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
(void) fd, (void) ptr, (void) dir;
return 0;
}
__attribute__((weak)) void _exit(int status) {
(void) status;
for (;;) asm volatile("BKPT #0");
}
__attribute__((weak)) void _kill(int pid, int sig) {
(void) pid, (void) sig;
}
__attribute__((weak)) int _getpid(void) {
return -1;
}
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _link(const char *a, const char *b) {
(void) a, (void) b;
return -1;
}
__attribute__((weak)) int _unlink(const char *a) {
(void) a;
return -1;
}
__attribute__((weak)) int _stat(const char *path, struct stat *st) {
(void) path, (void) st;
return -1;
}
__attribute__((weak)) int mkdir(const char *path, int mode) {
(void) path, (void) mode;
return -1;
}
__attribute__((weak)) void _init(void) {
}
uint64_t mg_micros(void);
// SysTick runs at the core clock and reloads every millisecond. Sample
// the tick counter and the sub-millisecond counter in one critical section.
uint64_t mg_micros(void) {
uint32_t primask = __get_PRIMASK(), val, pending;
uint64_t ms;
__disable_irq();
ms = hal_get_tick();
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
val = SysTick->VAL;
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
pending = SCB_ICSR_PENDSTSET_Msk;
val = SysTick->VAL;
}
__set_PRIMASK(primask);
if (pending != 0 && val != 0) ms++;
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
(SysTick->LOAD + 1);
}
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "nrf.h"
uint64_t hal_get_tick(void);
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
size_t hal_ram_used(void);
size_t hal_ram_free(void);
void usb_init(void);
void usb_poll(void);
void usb_log(char ch, void *param);
#define BIT(x) (1UL << (x))
#define PIN(port, num) (((port) << 5) | (num))
#define PINNO(pin) ((pin) & 31)
#define PINPORT(pin) ((pin) >> 5)
static inline NRF_GPIO_Type *hal_gpio_bank(uint16_t pin) {
return PINPORT(pin) == 0 ? NRF_P0 : NRF_P1;
}
static inline void hal_gpio_write(uint16_t pin, bool val) {
NRF_GPIO_Type *gpio = hal_gpio_bank(pin);
if (val) {
gpio->OUTSET = BIT(PINNO(pin));
} else {
gpio->OUTCLR = BIT(PINNO(pin));
}
}
static inline void hal_gpio_toggle(uint16_t pin) {
hal_gpio_write(pin, (hal_gpio_bank(pin)->OUT & BIT(PINNO(pin))) == 0);
}
static inline void hal_gpio_output(uint16_t pin) {
hal_gpio_bank(pin)->PIN_CNF[PINNO(pin)] =
(GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
(GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos);
}
static inline void hal_system_init(void) {
extern uint32_t __isr_vector[];
SCB->VTOR = (uint32_t) __isr_vector; // Application vectors above the bootloader
__DSB();
__ISB();
__enable_irq();
NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
NRF_CLOCK->TASKS_HFCLKSTART = 1;
while (!NRF_CLOCK->EVENTS_HFCLKSTARTED) (void) 0;
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock / 1000);
}
static inline void hal_rng_init(void) {
NRF_RNG->CONFIG = 1;
NRF_RNG->TASKS_START = 1;
}
static inline uint32_t hal_rng_read(void) {
uint32_t value = 0;
for (unsigned i = 0; i < 4; i++) {
while (!NRF_RNG->EVENTS_VALRDY) (void) 0;
value |= (uint32_t) NRF_RNG->VALUE << (i * 8);
NRF_RNG->EVENTS_VALRDY = 0;
}
return value;
}
@@ -0,0 +1,62 @@
/* nRF52840 on the XIAO nRF52840, with the Adafruit UF2 bootloader. Flash:
* MBR and SoftDevice S140 below 0x27000, the bootloader from 0xED000 up: the
* application in between. The SoftDevice is not enabled, so all RAM is ours,
* except the first 24k it would take: kept free, as the Arduino core does */
ENTRY(Reset_Handler);
MEMORY {
flash(rx) : ORIGIN = 0x00027000, LENGTH = 0xC6000
sram(rwx) : ORIGIN = 0x20006000, LENGTH = 0x3A000
}
SECTIONS {
.vectors : { KEEP(*(.isr_vector)) . = ALIGN(4); } > flash
.text : { *(.text* .text.*) } > flash
.rodata : { *(.rodata*) . = ALIGN(4); } > flash
.data : {
_sdata = .;
*(.first_data)
*(.iram .iram.*) /* Mongoose MG_IRAM functions */
*(.data SORT(.data.*))
. = ALIGN(4);
_edata = .;
} > sram AT > flash
__data_start = _sdata;
__data_end = _edata;
__data_load_start = LOADADDR(.data);
/* Unused data regions expected by Nordic's standard startup. */
__sdata_start = _edata;
__sdata_end = _edata;
__sdata_load_start = _edata;
__tdata_start = _edata;
__tdata_end = _edata;
__tdata_load_start = _edata;
__fast_start = _edata;
__fast_end = _edata;
__fast_load_start = _edata;
.bss : {
_sbss = .;
*(.bss SORT(.bss.*) COMMON)
. = ALIGN(4);
_ebss = .;
} > sram
__bss_start__ = _sbss;
__bss_end__ = _ebss;
__tbss_start__ = _ebss;
__tbss_end__ = _ebss;
__sbss_start__ = _ebss;
__sbss_end__ = _ebss;
/* Health record, see MG_HEALTH_RAM: not cleared, survives a warm reset */
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > sram
. = ALIGN(8);
_end = .; /* Start of heap, see _sbrk() in hal.c */
/* Nordic startup reserves its default 16 KiB stack at the top of RAM. */
.stack ORIGIN(sram) + LENGTH(sram) - 0x4000 (NOLOAD) : {
KEEP(*(.stack))
} > sram
ASSERT(_end <= __StackLimit, "RAM sections overlap the stack")
}
+105
View File
@@ -0,0 +1,105 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
#include "mongoose.h"
#define LED1 PIN(0, 26) // P0.26, user LED, active low
static void log_fn(char ch, void *param) {
usb_log(ch, param);
}
static void blink_task(void) {
static uint64_t blink_timer = 0;
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
hal_gpio_toggle(LED1);
}
}
uint64_t mg_millis(void) {
return hal_get_tick();
}
bool mg_random(void *buf, size_t len) {
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
uint32_t r = hal_rng_read();
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
}
return true;
}
// Self-signed P-256 certificate and key for the HTTPS listener. Created by:
// openssl ecparam -genkey -name prime256v1 -noout -out key.pem
// openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg
#define TLS_CERT \
"-----BEGIN CERTIFICATE-----\n" \
"MIIBgjCCASegAwIBAgIUIJOq77Qn7lHbH6WseMemeqorR50wCgYIKoZIzj0EAwIw\n" \
"FjEUMBIGA1UEAwwLbnVjbGVvLWg3MjMwHhcNMjYxMDEyMTUwMjE5WhcNMzYxMDA5\n" \
"MTUwMjE5WjAWMRQwEgYDVQQDDAtudWNsZW8taDcyMzBZMBMGByqGSM49AgEGCCqG\n" \
"SM49AwEHA0IABB6IQJNmzrsdQL+2BnZYS7GYOaj7sz8f6cjBmz3n8D9Em19XodlU\n" \
"uy4ybIvyhr766lL0ERoTmMUivb/lGmzx9C+jUzBRMB0GA1UdDgQWBBQqxMuc4gj7\n" \
"MTMzreWlRMNI3QMqTzAfBgNVHSMEGDAWgBQqxMuc4gj7MTMzreWlRMNI3QMqTzAP\n" \
"BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQDY4xaJpuxq6b3rHK3b\n" \
"lOIQyfkQX+orCRqQUkseplqpqwIhAKswZFNn+2zUNRRNY8+cHmdIwmdpsmxtoWYP\n" \
"BCjX++LI\n" \
"-----END CERTIFICATE-----\n"
#define TLS_KEY \
"-----BEGIN EC PRIVATE KEY-----\n" \
"MHcCAQEEIJ/GBl0TlD52q9PMg1KWaNIdXqp8b/HyIsABmBpqC+O1oAoGCCqGSM49\n" \
"AwEHoUQDQgAEHohAk2bOux1Av7YGdlhLsZg5qPuzPx/pyMGbPefwP0SbX1eh2VS7\n" \
"LjJsi/KGvvrqUvQRGhOYxSK9v+UabPH0Lw==\n" \
"-----END EC PRIVATE KEY-----\n"
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_ACCEPT && c->is_tls) { // Accepted on the https:// listener
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
} else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) {
__asm volatile("udf #0"); // Trigger HardFault
} else {
mg_http_reply(c, 200, "",
"Hi from Mongoose!\n"
"tick: %llu\n"
"ram used: %zu\n"
"ram free: %zu\n",
hal_get_tick(), hal_ram_used(), hal_ram_free());
}
}
}
int main(void) {
MG_HEALTH_INIT();
hal_system_init();
usb_init();
mg_log_set_fn(log_fn, NULL);
hal_rng_init();
hal_gpio_write(LED1, true);
hal_gpio_output(LED1);
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
mg_http_listen(&mgr, "https://0.0.0.0", http_ev_handler, NULL);
mg_mqtt_init(&mgr);
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
MG_INFO(("RAM used: %zu, free: %zu", hal_ram_used(), hal_ram_free()));
MG_INFO(("Crash record: %M", mg_print_crash_record));
for (;;) {
usb_poll();
mg_mgr_poll(&mgr, 0);
mg_mqtt_poll(&mgr);
blink_task();
}
return 0;
}
@@ -0,0 +1,24 @@
#pragma once
#include "nrf.h"
#define MG_ARCH MG_ARCH_ARMGCC
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_DRIVER_NRF52 1
#define MG_ENABLE_TCPIP_TCPRTX 1
#define MG_ENABLE_CUSTOM_MILLIS 1
#define MG_ENABLE_CUSTOM_RANDOM 1
#define MG_TLS MG_TLS_BUILTIN
#define MQTT_CLIENT_ID "xiao-nrf52840"
#ifndef MQTT_SERVER_URL
#define MQTT_SERVER_URL "mqtt://broker.emqx.io:1883"
#endif
// Stable, locally administered MAC derived from the factory device ID.
#define MG_SET_MAC_ADDRESS(mac) \
do { \
uint32_t id_[2] = {NRF_FICR->DEVICEID[0], NRF_FICR->DEVICEID[1]}; \
uint8_t hash_[32]; \
mg_sha256(hash_, (uint8_t *) id_, sizeof(id_)); \
memcpy(mac, hash_, 6); \
(mac)[0] = ((mac)[0] & 0xfc) | 2; \
} while (0)
@@ -0,0 +1,9 @@
#pragma once
#define CFG_TUSB_MCU OPT_MCU_NRF5X
#define CFG_TUSB_OS OPT_OS_NONE
#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE
#define CFG_TUD_ENDPOINT0_SIZE 64
#define CFG_TUD_CDC 1
#define CFG_TUD_CDC_RX_BUFSIZE 256
#define CFG_TUD_CDC_TX_BUFSIZE 4096
#define CFG_TUD_CDC_EP_BUFSIZE 64
+99
View File
@@ -0,0 +1,99 @@
#include "hal.h"
#include "tusb.h"
extern void tusb_hal_nrf_power_event(uint32_t event);
void USBD_IRQHandler(void) {
tud_int_handler(0);
}
void POWER_CLOCK_IRQHandler(void) {
if (NRF_POWER->EVENTS_USBDETECTED) {
NRF_POWER->EVENTS_USBDETECTED = 0;
tusb_hal_nrf_power_event(0);
}
if (NRF_POWER->EVENTS_USBREMOVED) {
NRF_POWER->EVENTS_USBREMOVED = 0;
tusb_hal_nrf_power_event(1);
}
if (NRF_POWER->EVENTS_USBPWRRDY) {
NRF_POWER->EVENTS_USBPWRRDY = 0;
tusb_hal_nrf_power_event(2);
}
}
void usb_init(void) {
NRF_POWER->INTENSET = POWER_INTENSET_USBDETECTED_Msk |
POWER_INTENSET_USBREMOVED_Msk |
POWER_INTENSET_USBPWRRDY_Msk;
NVIC_SetPriority(POWER_CLOCK_IRQn, 3);
NVIC_SetPriority(USBD_IRQn, 3);
if (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk)
tusb_hal_nrf_power_event(0);
if (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_OUTPUTRDY_Msk)
tusb_hal_nrf_power_event(2);
NRF_POWER->EVENTS_USBDETECTED = 0;
NRF_POWER->EVENTS_USBPWRRDY = 0;
NRF_POWER->EVENTS_USBREMOVED = 0;
NVIC_ClearPendingIRQ(POWER_CLOCK_IRQn);
tusb_init();
NVIC_EnableIRQ(POWER_CLOCK_IRQn);
}
void usb_poll(void) {
tud_task();
tud_cdc_write_flush();
}
void usb_log(char ch, void *param) {
(void) param;
if (tud_cdc_connected()) tud_cdc_write_char(ch);
}
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
cdc_line_coding_t lc;
(void) rts;
tud_cdc_n_get_line_coding(itf, &lc);
if (!dtr && lc.bit_rate == 1200) {
NRF_POWER->GPREGRET = 0x57;
NVIC_SystemReset();
}
}
static const tusb_desc_device_t s_dev = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = 64,
.idVendor = 0xcafe,
.idProduct = 0x4001,
.bcdDevice = 0x0100,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = 1};
uint8_t const *tud_descriptor_device_cb(void) {
return (const uint8_t *) &s_dev;
}
static const uint8_t s_cfg[] = {
TUD_CONFIG_DESCRIPTOR(1, 2, 0, TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN, 0,
100),
TUD_CDC_DESCRIPTOR(0, 0, 0x81, 8, 0x02, 0x82, 64)};
uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
(void) index;
return s_cfg;
}
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
static uint16_t buf[32];
const char *strings[] = {"", "Cesanta", "XIAO nRF52840", "mongoose-nrf52840"};
size_t n = 0;
(void) langid;
if (index == 0) {
buf[1] = 0x0409;
n = 1;
} else if (index < 4) {
const char *s = strings[index];
while (s[n] && n < 31) {
buf[n + 1] = s[n];
n++;
}
} else
return NULL;
buf[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * n + 2));
return buf;
}
+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
SOURCES = main.c hal.c $(MGDIR)/mongoose.c
SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c
SOURCES += cmsis_mcxw71/MCXW/MCXW716C/gcc/startup_MCXW716C.S
@@ -23,9 +24,13 @@ firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
arm-none-eabi-size --format=berkeley $<
firmware.elf: cmsis_core cmsis_mcxw71 $(SOURCES) $(wildcard *.h) link.ld Makefile
firmware.elf: cmsis_core cmsis_mcxw71 $(SOURCES) $(wildcard *.h) $(MGDIR)/mongoose.h link.ld Makefile mesh-config
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
# Rebuild when overriding the board address or timing.
.PHONY: mesh-config
mesh-config:
cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
+36 -14
View File
@@ -9,7 +9,7 @@
// 802.15.4 register setup. Then select the legacy 802.15.4 demodulator and
// CCA from RX digital. See PhyHwInit() in NXP ieee_802_15_4 PHY sources,
// PhyPacketProcessor.c
bool hal_radio_init(void) {
void hal_radio_init(void) {
uint32_t stat = RFMC_RF2P4GHZ_STAT_RST_STAT_MASK |
RFMC_RF2P4GHZ_STAT_LP_ACK_STAT_MASK;
size_t i, n = 1000000;
@@ -20,7 +20,10 @@ bool hal_radio_init(void) {
RFMC_RF2P4GHZ_CTRL_RST_MASK); // Release radio reset, no low power
while ((RFMC->RF2P4GHZ_STAT & stat) != 0 ||
(RFMC->XO_STAT & RFMC_XO_STAT_XTAL_RDY_MASK) == 0) {
if (--n == 0) 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;
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(2);
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:
// receiver RSSI, sampled while the radio receives, mixed with SysTick by
// SHA-256. Good enough for random addresses and nonces, not a certified TRNG
bool mg_random(void *buf, size_t len) {
static uint32_t counter;
uint8_t digest[32], *p = (uint8_t *) buf;
// Random 32 bits, for mg_random(). Entropy is radio noise: receiver RSSI,
// sampled while the radio receives, mixed with SysTick by SHA-256. One hash
// fills a pool of 8 words. Good enough for random addresses and nonces, not
// a certified TRNG
uint32_t hal_rng_read(void) {
static uint32_t counter, pool[8];
static size_t avail;
size_t i, n;
while (len > 0) {
if (avail == 0) {
mg_sha256_ctx ctx;
mg_sha256_init(&ctx);
counter++;
@@ -51,11 +54,10 @@ bool mg_random(void *buf, size_t len) {
mg_sha256_update(&ctx, (uint8_t *) sample, sizeof(sample));
for (n = 0; n < 50; n++) __NOP(); // Let the RSSI update
}
mg_sha256_final(digest, &ctx);
n = len < sizeof(digest) ? len : sizeof(digest);
memcpy(p, digest, n), p += n, len -= n;
mg_sha256_final((uint8_t *) pool, &ctx);
avail = sizeof(pool) / sizeof(pool[0]);
}
return true;
return pool[--avail];
}
// Added to minimise the bin size: avoid pulling printf and friends
@@ -81,6 +83,26 @@ uint64_t hal_get_tick(void) {
return s_ticks;
}
uint64_t mg_micros(void);
// SysTick runs at the core clock and reloads every millisecond. Sample
// the tick counter and the sub-millisecond counter in one critical section.
uint64_t mg_micros(void) {
uint32_t primask = __get_PRIMASK(), val, pending;
uint64_t ms;
__disable_irq();
ms = hal_get_tick();
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
val = SysTick->VAL;
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
pending = SCB_ICSR_PENDSTSET_Msk;
val = SysTick->VAL;
}
__set_PRIMASK(primask);
if (pending != 0 && val != 0) ms++;
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
(SysTick->LOAD + 1);
}
uint32_t SystemCoreClock;
// Called by the startup code before .data and .bss are initialised, so it
+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);
size_t hal_ram_used(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()
#define BIT(x) (1UL << (x))
#define PIN(port, num) ((((port) - 'A') << 8) | (num))
#define PINNO(pin) (pin & 255)
#define PINPORT(pin) (pin >> 8)
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
#define SIRC_FREQUENCY 6000000UL // Slow IRC (FRO6M), Hz
@@ -92,15 +95,53 @@ static inline void hal_periph_clock(volatile uint32_t *mrcc, uint32_t src) {
__DSB();
}
// LPUART1 is wired to the MCU-Link debug probe USB serial port.
// TX = PTC3, RX = PTC2, both pin mux alternative 3. See board.h, pin_mux.c
static inline void hal_uart_init(LPUART_Type *uart, unsigned long baud) {
// PORT, the pin multiplexer, of a pin. Ports A..C
static inline PORT_Type *hal_pin_port(uint16_t pin) {
PORT_Type *ports[] = {PORTA, PORTB, PORTC};
return ports[PINPORT(pin)];
}
// GPIO of a pin. Ports A..C
static inline GPIO_Type *hal_pin_gpio(uint16_t pin) {
GPIO_Type *gpios[] = {GPIOA, GPIOB, GPIOC};
return gpios[PINPORT(pin)];
}
// Route a pin to function mux: 1 GPIO, others see the datasheet pinout.
// Clocks its PORT: MRCC_PORTA..C are consecutive
static inline void hal_pin_mux(uint16_t pin, uint32_t mux) {
hal_periph_clock(&MRCC->MRCC_PORTA + PINPORT(pin), 0);
hal_pin_port(pin)->PCR[PINNO(pin)] = PORT_PCR_MUX(mux);
}
static inline void hal_gpio_output(uint16_t pin) {
hal_pin_mux(pin, 1);
hal_periph_clock(&MRCC->MRCC_GPIOA + PINPORT(pin), 0); // Consecutive, too
hal_pin_gpio(pin)->PDDR |= BIT(PINNO(pin));
}
static inline void hal_gpio_write(uint16_t pin, bool val) {
if (val) {
hal_pin_gpio(pin)->PSOR = BIT(PINNO(pin));
} else {
hal_pin_gpio(pin)->PCOR = BIT(PINNO(pin));
}
}
static inline void hal_gpio_toggle(uint16_t pin) {
hal_pin_gpio(pin)->PTOR = BIT(PINNO(pin));
}
// LPUART1, on tx and rx pins, pin mux alternative 3. On FRDM-MCXW71 it is
// wired to the MCU-Link debug probe USB serial port: TX PTC3, RX PTC2, see
// board.h, pin_mux.c
static inline void hal_uart_init(LPUART_Type *uart, uint16_t tx, uint16_t rx,
unsigned long baud) {
uint32_t src = MRCC_SRC_FRO192M, freq = hal_firc_freq();
uint32_t best_osr = 16, best_sbr = 1, best_err = UINT32_MAX, osr;
if (freq == 0) src = MRCC_SRC_FRO6M, freq = SIRC_FREQUENCY; // FIRC off
hal_periph_clock(&MRCC->MRCC_PORTC, 0);
PORTC->PCR[2] = PORT_PCR_MUX(3); // RX
PORTC->PCR[3] = PORT_PCR_MUX(3); // TX
hal_pin_mux(rx, 3);
hal_pin_mux(tx, 3);
hal_periph_clock(&MRCC->MRCC_LPUART1, src);
// Find the oversampling ratio (4..32) that gives the lowest baud rate error
for (osr = 4; osr <= 32; osr++) {
@@ -141,6 +182,11 @@ static inline void hal_wdog_disable(void) {
while ((WDOG0->CS & WDOG_CS_RCS_MASK) == 0) (void) 0;
}
// Random numbers come from radio noise, see hal_rng_read(): nothing to set
// up here. The radio must be running, see hal_radio_init()
static inline void hal_rng_init(void) {
}
static inline void hal_sys_init(void) {
SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU
hal_wdog_disable();
@@ -33,6 +33,9 @@ SECTIONS {
_ebss = .;
} > sram
/* Health record, see MG_HEALTH_RAM: not cleared, survives a warm reset */
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > sram
. = ALIGN(8);
_end = .; /* Start of heap, see _sbrk() in hal.c */
}
+35 -11
View File
@@ -1,24 +1,42 @@
// Copyright (c) 2026 Cesanta Software Limited
// 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 "mongoose.h"
#ifndef UART_DEBUG
#define UART_DEBUG LPUART1 // MCU-Link USB serial port
#define UART_DEBUG_TX_PIN PIN('C', 3)
#define UART_DEBUG_RX_PIN PIN('C', 2)
#endif
#define LED1 PIN('A', 19)
#define LED2 PIN('A', 20)
#define LED3 PIN('A', 21)
static void log_fn(char ch, void *param) {
hal_uart_write_buf(param, &ch, 1);
}
static void blink_task(void) {
static uint64_t blink_timer = 0;
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
hal_gpio_toggle(LED2);
}
}
uint64_t mg_millis(void) {
return hal_get_tick();
}
bool mg_random(void *buf, size_t len) {
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
uint32_t r = hal_rng_read();
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
}
return true;
}
// Self-signed P-256 certificate and key for the HTTPS listener. Created by:
// openssl ecparam -genkey -name prime256v1 -noout -out key.pem
// openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg
@@ -70,16 +88,21 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
}
int main(void) {
hal_clock_init();
hal_uart_init(UART_DEBUG, 115200);
mg_log_set_fn(log_fn, UART_DEBUG);
MG_HEALTH_INIT();
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;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0:80", http_ev_handler, NULL);
mg_http_listen(&mgr, "https://0.0.0.0:443", 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", http_ev_handler, NULL);
mg_mqtt_init(&mgr);
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
@@ -89,6 +112,7 @@ int main(void) {
for (;;) {
mg_mgr_poll(&mgr, 0);
mg_mqtt_poll(&mgr);
blink_task();
}
return 0;
@@ -1,9 +1,10 @@
#pragma once
#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_TLS MG_TLS_BUILTIN
#define MQTT_CLIENT_ID "w71" // For tutorials/mqtt/mqtt-client
#define MG_OTA MG_OTA_MCXW71
#define MG_ENABLE_TCPIP 1
@@ -21,3 +22,12 @@
(mac)[0] = 2, (mac)[1] = b_[1], (mac)[2] = b_[2], (mac)[3] = b_[3]; \
(mac)[4] = b_[4], (mac)[5] = b_[5]; \
} while (0)
// Crash report support. Health record lives in the .mg_health region, see link.ld
#define MG_ENABLE_HEALTH 1
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
// mdash.net device management service support
#define MG_ENABLE_MDASH 0
#define MG_MDASH_KEY "..." // Set this - mDash device token
#define MG_FIRMWARE_VERSION "w71.1.0.0"
@@ -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
SOURCES = main.c hal.c radio.c startup.c flexspi.c flash_config.c $(MGDIR)/mongoose.c
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_clock.c
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_trng.c
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_reset.c
@@ -35,9 +36,13 @@ firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
arm-none-eabi-size --format=berkeley $<
firmware.elf: cmsis_core cmsis_rw612 $(RADIO_FW) $(SOURCES) $(wildcard *.h) 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 $@
# 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 $@
@@ -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 '
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
clean:
@@ -26,6 +26,26 @@ uint64_t hal_get_tick(void) {
return s_ticks;
};
uint64_t mg_micros(void);
// SysTick runs at the core clock and reloads every millisecond. Sample
// the tick counter and the sub-millisecond counter in one critical section.
uint64_t mg_micros(void) {
uint32_t primask = __get_PRIMASK(), val, pending;
uint64_t ms;
__disable_irq();
ms = hal_get_tick();
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
val = SysTick->VAL;
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
pending = SCB_ICSR_PENDSTSET_Msk;
val = SysTick->VAL;
}
__set_PRIMASK(primask);
if (pending != 0 && val != 0) ms++;
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
(SysTick->LOAD + 1);
}
uint32_t SystemCoreClock = SYS_FREQUENCY;
int hal_radio_err; // Result of hal_radio_init()
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
#endif
static inline void hal_uart_init(USART_Type *uart, unsigned long baud) {
if (uart == USART0) {
static inline void hal_uart_init(USART_Type *uart, unsigned long baud,
uint16_t tx_pin, uint16_t rx_pin) {
if (uart == USART0 && tx_pin == PIN(0, 3) &&
rx_pin == PIN(0, 2)) {
IO_MUX_SetPinMux(IO_MUX_FC0_USART_DATA);
} else if (uart == USART1) {
} else if (uart == USART1 && tx_pin == PIN(0, 8) &&
rx_pin == PIN(0, 9)) {
IO_MUX_SetPinMux(IO_MUX_FC1_USART_DATA);
} else if (uart == USART2) {
} else if (uart == USART2 && tx_pin == PIN(0, 14) &&
rx_pin == PIN(0, 13)) {
IO_MUX_SetPinMux(IO_MUX_FC2_USART_DATA);
} else if (uart == USART3) {
} else if (uart == USART3 && tx_pin == PIN(0, 26) &&
rx_pin == PIN(0, 24)) {
IO_MUX_SetPinMux(IO_MUX_FC3_USART_DATA);
} else if (uart == USART14) {
} else if (uart == USART14 && tx_pin == PIN(1, 24) &&
rx_pin == PIN(1, 25)) {
IO_MUX_SetPinMux(IO_MUX_FC14_USART_DATA);
} else {
return;
+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
#endif
static inline void hal_uart_init(USART_Type *uart, unsigned long baud) {
if (uart == USART0) {
static inline void hal_uart_init(USART_Type *uart, unsigned long baud,
uint16_t tx_pin, uint16_t rx_pin) {
if (uart == USART0 && tx_pin == PIN(0, 3) &&
rx_pin == PIN(0, 2)) {
IO_MUX_SetPinMux(IO_MUX_FC0_USART_DATA);
} else if (uart == USART1) {
} else if (uart == USART1 && tx_pin == PIN(0, 8) &&
rx_pin == PIN(0, 9)) {
IO_MUX_SetPinMux(IO_MUX_FC1_USART_DATA);
} else if (uart == USART2) {
} else if (uart == USART2 && tx_pin == PIN(0, 14) &&
rx_pin == PIN(0, 13)) {
IO_MUX_SetPinMux(IO_MUX_FC2_USART_DATA);
} else if (uart == USART3) {
} else if (uart == USART3 && tx_pin == PIN(0, 26) &&
rx_pin == PIN(0, 24)) {
IO_MUX_SetPinMux(IO_MUX_FC3_USART_DATA);
} else if (uart == USART14) {
} else if (uart == USART14 && tx_pin == PIN(1, 24) &&
rx_pin == PIN(1, 25)) {
IO_MUX_SetPinMux(IO_MUX_FC14_USART_DATA);
} else {
return;
+1 -1
View File
@@ -96,7 +96,7 @@ int main(void) {
hal_gpio_write(LED2, true);
hal_gpio_output(LED3);
hal_gpio_write(LED3, true);
hal_uart_init(UART_DEBUG, 115200);
hal_uart_init(UART_DEBUG, 115200, PIN(0, 26), PIN(0, 24));
mg_log_set_fn(log_fn, UART_DEBUG);
hal_ethernet_init();
+1 -1
View File
@@ -31,5 +31,5 @@ clean:
$(RM) build pico_sdk_import.cmake $(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
@@ -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"
@@ -43,6 +43,29 @@ bool mg_random(void *buf, size_t len) {
return true;
}
// BSD sockets API UDP echo server: sends each datagram back to its sender
static int s_udp_sock = -1;
static void udp_echo_init(uint16_t port) {
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = {.s_addr = INADDR_ANY},
};
s_udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
bind(s_udp_sock, (struct sockaddr *) &sa, sizeof(sa));
MG_INFO(("UDP echo server on port %hu, sock %d", port, s_udp_sock));
}
static void udp_echo_poll(void) {
char buf[1500];
struct sockaddr_in sin;
struct sockaddr *sa = (struct sockaddr *) &sin;
socklen_t len = sizeof(sin);
ssize_t n = recvfrom(s_udp_sock, buf, sizeof(buf), MSG_DONTWAIT, sa, &len);
if (n > 0) sendto(s_udp_sock, buf, (size_t) n, MSG_DONTWAIT, sa, len);
}
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
@@ -83,10 +106,14 @@ int main(void) {
// connections. Note - baremetal implementation is non-blocking only.
echo_init(1234);
// This is BSD sockets API UDP echo server, non-blocking too
udp_echo_init(1234);
// Baremetal superloop. Each function in the loop must not block
for (;;) {
mg_mgr_poll(&mgr, 0);
echo_poll();
udp_echo_poll();
blink_task();
}