Compare commits

..
7 Commits
Author SHA1 Message Date
Sergio R. Caprile 278c4ca116 Integrate ESP32 health support 2026-10-21 11:48:33 -03:00
cpq 88b01ef6f0 Add security.md 2026-10-21 11:48:41 +01:00
cpq bd8ee86f9e Cube pack 7.23.3 2026-10-21 11:33:05 +01:00
Sergey Lyubka ef96f7a28c Merge pull request #3741 from cesanta/w71_2
Ethernet + IPv4 over 802.15.4 + bridge, mcxw71 and rw612
2026-10-21 10:22:43 +01:00
Sergey Lyubka 03f240a244 Merge pull request #3743 from cesanta/c5
Add nucleo-c5a3zg
2026-10-21 10:21:25 +01:00
cpq c775f31ca7 Add nucleo-c5a3zg 2026-10-21 10:16:02 +01:00
cpq 1ca4fd1fef Add frdm-mcxw71 support 2026-10-20 21:48:34 +01:00
57 changed files with 5244 additions and 2569 deletions

No files matched your search

+1
View File
@@ -637,6 +637,7 @@ jobs:
example:
- path: micropython/esp32
- path: esp32/uart-bridge
- path: esp32/minimal
name: ${{ matrix.example.path }}
env:
GO: 0
+1
View File
@@ -186,6 +186,7 @@ jobs:
matrix:
example:
- path: esp32/uart-bridge
- path: esp32/minimal
name: ${{ matrix.example.path }}
env:
GO: 0
+41
View File
@@ -0,0 +1,41 @@
# Security Policy
Mongoose is a full-stack embedded networking library by Cesanta Software Ltd,
<https://mongoose.ws>. This policy covers how to report vulnerabilities
and how Cesanta handles them.
## Supported versions
Security fixes go to the `master` branch and the latest release.
## Reporting a vulnerability
Do not report security issues in public GitHub issues. Report them
privately using GitHub Security Advisories:
<https://github.com/cesanta/mongoose/security/advisories/new>
Include the Mongoose version or commit, steps or a proof of concept to reproduce, and
the impact. Cesanta usually responds the same business day.
## Disclosure process
1. Cesanta reviews the report and coordinates a fix with the reporter.
2. Eligible commercial customers receive a notification and a private fix
one month before the CVE becomes public.
3. The fix and the CVE are then published. The reporter is credited in the
GitHub Security Advisory.
## Security testing
- CI on every push, unit tests built with ASAN and UBSan
- CodeQL static analysis on every commit
- Continuous fuzzing on OSS-Fuzz, plus nightly libFuzzer runs
- AI-assisted security review on every pull request
- Regular review by independent security research groups worldwide
## EU Cyber Resilience Act (CRA)
Mongoose supports CRA compliance for connected products: a documented
vulnerability handling process (this file), TLS 1.3, signed OTA firmware
updates, and an SBOM (`sbom.cdx.json`). Details:
<https://mongoose.ws/security/cra-compliance/>
+228 -1053
View File
File diff suppressed because it is too large. Load diff
+154 -176
View File
@@ -165,6 +165,7 @@ extern "C" {
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && \
!defined(MG_ENABLE_DRIVER_STM32H) && !defined(MG_ENABLE_DRIVER_STM32N) && \
!defined(MG_ENABLE_DRIVER_STM32C) && \
!defined(MG_ENABLE_DRIVER_CYW) && !defined(MG_ENABLE_DRIVER_CYW_SDIO) && \
!defined(MG_ENABLE_DRIVER_ST67W6)
#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7)
@@ -173,6 +174,8 @@ extern "C" {
#define MG_ENABLE_DRIVER_STM32H 1
#elif defined(STM32N6)
#define MG_ENABLE_DRIVER_STM32N 1
#elif defined(STM32C5)
#define MG_ENABLE_DRIVER_STM32C 1
#else
#error Select a driver in mongoose_config.h
#endif
@@ -189,70 +192,8 @@ extern "C" {
#define MG_OTA MG_OTA_STM32H5
#elif !defined(MG_OTA) && defined(STM32H7)
#define MG_OTA MG_OTA_STM32H7
#endif
// OTA rollback timer via IWDG. Fixed 10 s hang-detection window (prescaler
// /1024, LSI ~32 kHz: reload = 10 * 32000 / 1024 = 312). mg_ota_poll() feeds
// the watchdog every 500 ms while in MG_OTA_TESTING state; if the event loop
// hangs for 10 s, the IWDG fires and the device rolls back.
// STM32H7 names the peripheral IWDG1; all others use IWDG.
// The total evaluation period is set by MG_OTA_ROLLBACK_TIMEOUT_SECONDS.
// STM32H7 names the watchdog peripheral IWDG1; all other families use IWDG.
#if defined(IWDG1) && !defined(IWDG)
#define MG_IWDG IWDG1
#else
#define MG_IWDG IWDG
#endif
// PR=6 → /256 prescaler, RLR=1250 → ~10 s at LSI 32 kHz.
// Works on F4/F7/H5/H7; uses direct register writes so it is safe before HAL_Init().
#ifndef MG_OTA_ROLLBACK_TIMER_START
#define MG_OTA_ROLLBACK_TIMER_START() \
do { \
MG_IWDG->KR = 0xCCCCU; /* start + LSI */ \
MG_IWDG->KR = 0x5555U; /* unlock PR/RLR */ \
MG_IWDG->PR = 6U; /* /256 */ \
MG_IWDG->RLR = 1250U; /* 10 s */ \
while (MG_IWDG->SR & (IWDG_SR_PVU | IWDG_SR_RVU)) (void) 0; \
MG_IWDG->KR = 0xAAAAU; /* reload */ \
} while (0)
#endif
// Feed (reload) the IWDG. Called from mg_ota_poll() every 500 ms.
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
#define MG_OTA_ROLLBACK_TIMER_FEED() (MG_IWDG->KR = 0xAAAAU)
#endif
// OTA state in a backup register that survives warm resets but clears on POR.
// STATE_GET reads work without any setup. STATE_SET enables the RTC APB clock
// and unlocks backup domain write access via direct register writes, so it is
// safe to call from any context (before or after HAL_Init).
// H5 (TAMP->BKP0R), H7 (RTC->BKP0R), F4/F7 (RTC->BKP0R).
#ifndef MG_OTA_STATE_GET
#if defined(PWR_DBPCR_DBP)
#define MG_OTA_DBP_ENABLE() (PWR->DBPCR |= PWR_DBPCR_DBP)
#elif defined(PWR_CR1_DBP)
#define MG_OTA_DBP_ENABLE() (PWR->CR1 |= PWR_CR1_DBP)
#else
#define MG_OTA_DBP_ENABLE() (PWR->CR |= PWR_CR_DBP)
#endif
#if defined(TAMP)
#define MG_OTA_STATE_GET() \
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, TAMP->BKP0R)
#define MG_OTA_STATE_SET(v) \
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
(TAMP->BKP0R = (uint32_t) (v)), TAMP->BKP0R)
#elif defined(RCC_APB4ENR_RTCAPBEN)
#define MG_OTA_STATE_GET() (RTC->BKP0R)
#define MG_OTA_STATE_SET(v) \
(RCC->APB4ENR |= RCC_APB4ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
(RTC->BKP0R = (uint32_t) (v)))
#else
#define MG_OTA_STATE_GET() (RTC->BKP0R)
#define MG_OTA_STATE_SET(v) \
(RCC->APB1ENR |= RCC_APB1ENR_PWREN, MG_OTA_DBP_ENABLE(), \
(RTC->BKP0R = (uint32_t) (v)))
#endif
#elif !defined(MG_OTA) && defined(STM32C5)
#define MG_OTA MG_OTA_STM32C5
#endif
// use HAL-defined execute-in-ram section
@@ -312,8 +253,10 @@ 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>
#endif
#define MG_PATH_MAX 128
@@ -1251,13 +1194,8 @@ struct timeval {
#endif
#ifndef MG_ENABLE_IPV6
#if (defined(MG_ENABLE_DRIVER_MCXW71) && MG_ENABLE_DRIVER_MCXW71) || \
(defined(MG_ENABLE_DRIVER_RW612_154) && MG_ENABLE_DRIVER_RW612_154)
#define MG_ENABLE_IPV6 1 // 802.15.4 radio: 6LoWPAN carries IPv6 only
#else
#define MG_ENABLE_IPV6 0
#endif
#endif
#ifndef MG_IPV6_V6ONLY
#define MG_IPV6_V6ONLY 0 // IPv6 socket binds only to V6, not V4 address
@@ -1402,8 +1340,6 @@ struct timeval {
#endif
// Set the interface hardware address. Ethernet: 6-byte MAC. 6LoWPAN: 8-byte
// EUI-64, the IPv6 interface ID is made from it. Not set: random per boot
#ifndef MG_SET_MAC_ADDRESS
#define MG_SET_MAC_ADDRESS(mac)
#endif
@@ -1445,7 +1381,6 @@ struct timeval {
// Macros to record timestamped events that happens with a connection.
// They are saved into a c->prof IO buffer, each event is a name and a 32-bit
// timestamp in milliseconds since connection init time.
@@ -4242,6 +4177,7 @@ extern void mg_mqtt_poll(struct mg_mgr *);
#define MG_OTA_STM32H7 2 // STM32 H7
#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core
#define MG_OTA_STM32F 4 // STM32 F7/F4/F2
#define MG_OTA_STM32C5 5 // STM32 C5, same flash controller as H5
#define MG_OTA_CH32V307 100 // WCH CH32V307
#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6
#define MG_OTA_RT1020 300 // IMXRT1020
@@ -4437,21 +4373,25 @@ void mg_ota_poll(struct mg_mgr *);
#if MG_ENABLE_HEALTH
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5))
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
#endif
#if !defined(MG_HEALTH_DATA_SIZE) && (defined(STM32H7_DEV_ID) || defined(STM32H5))
#define MG_HEALTH_DATA_SIZE 250
#endif
#ifndef MG_HEALTH_DATA_SIZE
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
#define MG_HEALTH_DATA_SIZE 250
#elif MG_ARCH == MG_ARCH_ESP32
#define MG_HEALTH_DATA_SIZE 23
#else
#define MG_HEALTH_DATA_SIZE 10
#endif
#endif // MG_HEALTH_DATA_SIZE
// Places the record in RAM that survives a reset. Define in mongoose_config.h
// to match the linker script, e.g. __attribute__((section(".mg_health")))
@@ -4459,6 +4399,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
@@ -4475,7 +4422,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];
};
@@ -4508,9 +4456,57 @@ 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);
#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, "{}");
}
#endif
// Cloud connector API to https://mdash.net - device management cloud
@@ -4606,13 +4602,7 @@ bool mg_wifi_ap_stop(void);
#if MG_ENABLE_TCPIP
// no config defaults to 0 => Ethernet
enum mg_l2type {
MG_TCPIP_L2_ETH = 0,
MG_TCPIP_L2_PPP,
MG_TCPIP_L2_PPPoE,
MG_TCPIP_L2_LOWPAN,
MG_TCPIP_L2_USER
};
enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE, MG_TCPIP_L2_USER };
#if defined(__DCC__)
#pragma pack(1)
@@ -4635,27 +4625,10 @@ struct eth_data {
struct pppoe_data { // (struct eth_data *) &pppoe_data = pppoedata.eth
struct eth_data eth;
};
struct lowpan_data {
uint16_t pan_id; // User-set PAN ID
uint8_t channel; // User-set channel, 11..26
uint16_t short_addr; // Our short address, 0xfffe if none
uint8_t seq; // MAC sequence number
uint8_t proto; // Protocol of the frame being sent, enum mg_l2proto
uint8_t dst[8]; // Destination of the frame being sent
uint16_t tag; // Next fragmentation datagram tag
uint8_t *buf; // Reassembly and decompression buffer
uint16_t size; // Datagram being reassembled: size, 0 if none
uint16_t received; // Bytes received so far
uint16_t tag_rx; // Datagram tag
uint8_t src[8]; // Source address
bool udp; // UDP header was compressed
uint64_t expire; // Reassembly deadline, ms
};
union mg_l2data {
struct eth_data eth;
struct pppoe_data pppoe;
struct lowpan_data lowpan;
};
#if defined(__DCC__)
@@ -4664,6 +4637,7 @@ union mg_l2data {
#pragma pack(pop)
#endif
enum mg_l2proto {
MG_TCPIP_L2PROTO_IPV4 = 0,
MG_TCPIP_L2PROTO_IPV6,
@@ -4724,24 +4698,6 @@ enum {
};
// clang-format on
#if MG_ENABLE_IPV6
// IPv6 neighbour learned from forwarded packets, see mg_tcpip_route()
struct mg_tcpip_nb6 {
uint64_t ip[2]; // Neighbour IPv6 address
uint8_t l2[sizeof(struct mg_l2addr)]; // Its hardware address
};
// NAT64 session, see mg_tcpip_if::nat64
struct mg_tcpip_nat64 {
uint64_t ip6[2]; // Node's IPv6 address
uint32_t ip4; // IPv4 server address
uint16_t port6; // Node's port
uint16_t port4; // IPv4 server port
uint16_t lport; // Our port on the IPv4 interface
uint8_t proto; // 6: TCP, 17: UDP, 0: unused entry
};
#endif
// Network interface. Zero-initialise, then set the fields marked "user-set"
// before calling mg_tcpip_init(). Fields in the "internal state" section
// below are managed by the stack; read them freely but do not modify them.
@@ -4776,8 +4732,6 @@ struct mg_tcpip_if {
uint8_t prefix[8]; // Global prefix bytes
uint8_t prefix_len; // Global prefix length in bits
uint64_t gw6[2]; // IPv6 default gateway
uint8_t nat64[12]; // NAT64 /96 prefix, RFC 6052. If set, DNS asks
// for IPv4 (A) and makes IPv6 from it (DNS64)
bool enable_slaac; // Enable SLAAC (stateless address autoconfiguration)
bool enable_dhcp6_client; // Enable DHCPv6 client (not yet fully implemented)
bool is_ip6_changed; // Set by stack when IPv6 address changes
@@ -4809,13 +4763,6 @@ struct mg_tcpip_if {
uint8_t gw6mac[sizeof(struct mg_l2addr)]; // IPv6 gateway hardware address
uint8_t state6; // Current IPv6 link/IP state (MG_TCPIP_STATE_*)
bool gw6_ready; // IPv6 gateway MAC address has been resolved
struct mg_tcpip_if *route; // Forward to this interface, see mg_tcpip_route()
struct mg_tcpip_nb6 nb6[4]; // Neighbours learned from forwarded packets
uint8_t nb6_next; // Next nb6 entry to replace
struct mg_tcpip_nat64 nat64s[8]; // NAT64 sessions, if nat64 is set
uint8_t nat64_next; // Next nat64s entry to replace
uint64_t ra_expire; // Time of the next router advertisement, ms
char *dns6_url; // DNS server URL from router advertisement
#endif
};
@@ -4829,15 +4776,6 @@ void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
// Frees the TX buffer and RX queue allocated by mg_tcpip_init().
void mg_tcpip_free(struct mg_tcpip_if *);
#if MG_ENABLE_IPV6
// Forwards IPv6 packets between two interfaces: packets not addressed to us
// go out the other interface. An interface with a static IPv6 prefix (ip6,
// prefix and prefix_len set, SLAAC off) gets router advertisements for it,
// the other interface gets a route to it, RFC 4191. Call after both
// mg_tcpip_init(). Replies to the other side need its sender seen first
void mg_tcpip_route(struct mg_tcpip_if *if1, struct mg_tcpip_if *if2);
#endif
// Pushes a received raw frame into ifp->recv_queue. Used by interrupt-driven
// or DMA-based drivers (those with rx=NULL) to hand frames to the stack from
// an ISR or DMA completion callback. Drops the frame and increments ndrop if
@@ -4873,6 +4811,39 @@ 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.
struct mg_tcpip_spi {
@@ -5262,24 +5233,23 @@ struct mg_tcpip_driver_imxrt_data {
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
MG_ENABLE_DRIVER_MCXW71
// NXP MCXW71 IEEE 802.15.4 radio, ZLL link layer. Frames passed to tx() and
// returned by rx() are raw 802.15.4 frames: MAC header and payload, no FCS.
// 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.
// Use with MG_TCPIP_L2_LOWPAN: channel, PAN ID and addresses are taken from
// ifp->l2data.lowpan and ifp->mac. No driver_data
// driver_data: struct mg_l2_802154_data, channel and PAN ID set
#define MG_TCPIP_DRIVER_INIT(mgr) \
do { \
static struct mg_tcpip_if mif_; \
mif_.l2type = MG_TCPIP_L2_LOWPAN; \
mif_.driver = &mg_tcpip_driver_mcxw71; \
mif_.l2data.lowpan.channel = 15; \
mif_.l2data.lowpan.pan_id = 0xabcd; \
MG_SET_MAC_ADDRESS(mif_.mac); /* EUI-64 */ \
mg_tcpip_init(mgr, &mif_); \
(mgr)->use_dns6 = true; /* IPv6 only: no IPv4 over 6LoWPAN */ \
MG_INFO(("Driver: mcxw71, 6LoWPAN")); \
#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)); \
} while (0)
#endif
@@ -5506,27 +5476,26 @@ struct mg_tcpip_driver_rw612_data {
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \
MG_ENABLE_DRIVER_RW612_154
// NXP RW612 IEEE 802.15.4 radio. 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 lowpan
// tutorial. The driver talks to it over the CPU2 <-> CPU3 IMU mailbox:
// IMUMC messages carrying HDLC framed Spinel. Frames passed to tx() and
// returned by rx() are raw 802.15.4 frames: MAC header and payload, no FCS.
// Use with MG_TCPIP_L2_LOWPAN: channel, PAN ID and addresses are taken from
// ifp->l2data.lowpan and ifp->mac. No driver_data
// 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_tcpip_if mif_; \
mif_.l2type = MG_TCPIP_L2_LOWPAN; \
mif_.driver = &mg_tcpip_driver_rw612_154; \
mif_.l2data.lowpan.channel = 15; \
mif_.l2data.lowpan.pan_id = 0xabcd; \
MG_SET_MAC_ADDRESS(mif_.mac); /* EUI-64 */ \
mg_tcpip_init(mgr, &mif_); \
(mgr)->use_dns6 = true; /* IPv6 only: no IPv4 over 6LoWPAN */ \
MG_INFO(("Driver: rw612_154, 6LoWPAN")); \
#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
@@ -5694,6 +5663,13 @@ struct mg_tcpip_driver_stm32f_data {
#if !defined(MG_ENABLE_DRIVER_STM32H)
#define MG_ENABLE_DRIVER_STM32H 0
#endif
#if !defined(MG_ENABLE_DRIVER_STM32C)
#define MG_ENABLE_DRIVER_STM32C 0
#endif
#if MG_ENABLE_DRIVER_STM32C // STM32C5: STM32H5 MAC, the IRQ is ETH1
#undef MG_ENABLE_DRIVER_STM32H
#define MG_ENABLE_DRIVER_STM32H 1
#endif
#if !defined(MG_ENABLE_DRIVER_MCXN)
#define MG_ENABLE_DRIVER_MCXN 0
#endif
@@ -5733,9 +5709,11 @@ struct mg_tcpip_driver_stm32h_data {
#define MG_DRIVER_MDC_CR 4
#endif
#if MG_ENABLE_DRIVER_STM32H && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
#if MG_ENABLE_DRIVER_STM32H && !MG_ENABLE_DRIVER_STM32C && \
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn)
#elif MG_ENABLE_DRIVER_STM32N && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
#elif (MG_ENABLE_DRIVER_STM32N || MG_ENABLE_DRIVER_STM32C) && \
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH1_IRQn)
#elif !defined(MG_ENABLE_ETH_IRQ)
#define MG_ENABLE_ETH_IRQ()
@@ -15,6 +15,7 @@
</environment>
</environments>
<releases>
<release date="2026-10-21" version="7.23.3">See https://github.com/cesanta/mongoose/releases/tag/7.23</release>
<release date="2026-10-19" version="7.23.2">See https://github.com/cesanta/mongoose/releases/tag/7.23</release>
<release date="2026-09-28" version="7.23.1">See https://github.com/cesanta/mongoose/releases/tag/7.23</release>
<release date="2026-05-11" version="7.21.7">See https://github.com/cesanta/mongoose/releases/tag/7.21</release>
@@ -28,7 +29,7 @@
<keyword>IMPLEMENTED_WITH_STM32CUBEMX</keyword>
</keywords>
<components>
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.23.2" maxInstances="1">
<component Cclass="Mongoose" Cgroup="Networking Core" Cversion="7.23.3" maxInstances="1">
<description>Networking Core</description>
<files>
<file category="doc" name="README.md"/>
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION = 7.23.2
VERSION = 7.23.3
REPO_ROOT = ../..
MX ?= /Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX
FILES = Cesanta.I-CUBE-Mongoose.pdsc Release_Notes.html mongoose_config.h README.md .config/mongoose_parameters.json
+5 -64
View File
@@ -32,6 +32,7 @@
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && \
!defined(MG_ENABLE_DRIVER_STM32H) && !defined(MG_ENABLE_DRIVER_STM32N) && \
!defined(MG_ENABLE_DRIVER_STM32C) && \
!defined(MG_ENABLE_DRIVER_CYW) && !defined(MG_ENABLE_DRIVER_CYW_SDIO) && \
!defined(MG_ENABLE_DRIVER_ST67W6)
#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7)
@@ -40,6 +41,8 @@
#define MG_ENABLE_DRIVER_STM32H 1
#elif defined(STM32N6)
#define MG_ENABLE_DRIVER_STM32N 1
#elif defined(STM32C5)
#define MG_ENABLE_DRIVER_STM32C 1
#else
#error Select a driver in mongoose_config.h
#endif
@@ -56,70 +59,8 @@
#define MG_OTA MG_OTA_STM32H5
#elif !defined(MG_OTA) && defined(STM32H7)
#define MG_OTA MG_OTA_STM32H7
#endif
// OTA rollback timer via IWDG. Fixed 10 s hang-detection window (prescaler
// /1024, LSI ~32 kHz: reload = 10 * 32000 / 1024 = 312). mg_ota_poll() feeds
// the watchdog every 500 ms while in MG_OTA_TESTING state; if the event loop
// hangs for 10 s, the IWDG fires and the device rolls back.
// STM32H7 names the peripheral IWDG1; all others use IWDG.
// The total evaluation period is set by MG_OTA_ROLLBACK_TIMEOUT_SECONDS.
// STM32H7 names the watchdog peripheral IWDG1; all other families use IWDG.
#if defined(IWDG1) && !defined(IWDG)
#define MG_IWDG IWDG1
#else
#define MG_IWDG IWDG
#endif
// PR=6 → /256 prescaler, RLR=1250 → ~10 s at LSI 32 kHz.
// Works on F4/F7/H5/H7; uses direct register writes so it is safe before HAL_Init().
#ifndef MG_OTA_ROLLBACK_TIMER_START
#define MG_OTA_ROLLBACK_TIMER_START() \
do { \
MG_IWDG->KR = 0xCCCCU; /* start + LSI */ \
MG_IWDG->KR = 0x5555U; /* unlock PR/RLR */ \
MG_IWDG->PR = 6U; /* /256 */ \
MG_IWDG->RLR = 1250U; /* 10 s */ \
while (MG_IWDG->SR & (IWDG_SR_PVU | IWDG_SR_RVU)) (void) 0; \
MG_IWDG->KR = 0xAAAAU; /* reload */ \
} while (0)
#endif
// Feed (reload) the IWDG. Called from mg_ota_poll() every 500 ms.
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
#define MG_OTA_ROLLBACK_TIMER_FEED() (MG_IWDG->KR = 0xAAAAU)
#endif
// OTA state in a backup register that survives warm resets but clears on POR.
// STATE_GET reads work without any setup. STATE_SET enables the RTC APB clock
// and unlocks backup domain write access via direct register writes, so it is
// safe to call from any context (before or after HAL_Init).
// H5 (TAMP->BKP0R), H7 (RTC->BKP0R), F4/F7 (RTC->BKP0R).
#ifndef MG_OTA_STATE_GET
#if defined(PWR_DBPCR_DBP)
#define MG_OTA_DBP_ENABLE() (PWR->DBPCR |= PWR_DBPCR_DBP)
#elif defined(PWR_CR1_DBP)
#define MG_OTA_DBP_ENABLE() (PWR->CR1 |= PWR_CR1_DBP)
#else
#define MG_OTA_DBP_ENABLE() (PWR->CR |= PWR_CR_DBP)
#endif
#if defined(TAMP)
#define MG_OTA_STATE_GET() \
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, TAMP->BKP0R)
#define MG_OTA_STATE_SET(v) \
(RCC->APB3ENR |= RCC_APB3ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
(TAMP->BKP0R = (uint32_t) (v)), TAMP->BKP0R)
#elif defined(RCC_APB4ENR_RTCAPBEN)
#define MG_OTA_STATE_GET() (RTC->BKP0R)
#define MG_OTA_STATE_SET(v) \
(RCC->APB4ENR |= RCC_APB4ENR_RTCAPBEN, MG_OTA_DBP_ENABLE(), \
(RTC->BKP0R = (uint32_t) (v)))
#else
#define MG_OTA_STATE_GET() (RTC->BKP0R)
#define MG_OTA_STATE_SET(v) \
(RCC->APB1ENR |= RCC_APB1ENR_PWREN, MG_OTA_DBP_ENABLE(), \
(RTC->BKP0R = (uint32_t) (v)))
#endif
#elif !defined(MG_OTA) && defined(STM32C5)
#define MG_OTA MG_OTA_STM32C5
#endif
// use HAL-defined execute-in-ram section
+4 -2
View File
@@ -22,8 +22,10 @@
#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>
#endif
#define MG_PATH_MAX 128
-8
View File
@@ -65,13 +65,8 @@
#endif
#ifndef MG_ENABLE_IPV6
#if (defined(MG_ENABLE_DRIVER_MCXW71) && MG_ENABLE_DRIVER_MCXW71) || \
(defined(MG_ENABLE_DRIVER_RW612_154) && MG_ENABLE_DRIVER_RW612_154)
#define MG_ENABLE_IPV6 1 // 802.15.4 radio: 6LoWPAN carries IPv6 only
#else
#define MG_ENABLE_IPV6 0
#endif
#endif
#ifndef MG_IPV6_V6ONLY
#define MG_IPV6_V6ONLY 0 // IPv6 socket binds only to V6, not V4 address
@@ -216,8 +211,6 @@
#endif
// Set the interface hardware address. Ethernet: 6-byte MAC. 6LoWPAN: 8-byte
// EUI-64, the IPv6 interface ID is made from it. Not set: random per boot
#ifndef MG_SET_MAC_ADDRESS
#define MG_SET_MAC_ADDRESS(mac)
#endif
@@ -255,4 +248,3 @@
#ifndef MG_ENABLE_HEALTH
#define MG_ENABLE_HEALTH 0
#endif
+16 -1
View File
@@ -533,6 +533,21 @@ static void mg_dash_handle_add(struct mg_connection *c, struct mg_dash *dash,
}
}
// Firmware upload callback for mg_http_stream_body(). Uses *p as a flag:
// non-NULL means an OTA session is open and must be closed by mg_ota_end()
static bool otacb(struct mg_http_message *hm, struct mg_str *data, void **p) {
if (hm != NULL) { // Start
if (!mg_ota_begin(hm->body.len)) return false;
*p = (void *) (ptrdiff_t) 1;
return true;
} else if (data != NULL) { // Next chunk
return mg_ota_write(data->buf, data->len);
}
if (*p == NULL) return false; // End. Begin failed, nothing to finish
*p = NULL;
return mg_ota_end();
}
static void mg_serve_fs(struct mg_connection *c, struct mg_http_message *hm,
struct mg_dash_user *u, bool stream) {
struct mg_dash *dash = (struct mg_dash *) c->fn_data;
@@ -581,7 +596,7 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
} else if (mg_match(hm->uri, mg_str("/api/ota"), NULL)) {
// Authenticated above. If streaming started, the stream handler owns
// the connection and c->data, so skip the marker check below
if (mg_http_stream_body(c, hm, mg_http_ota_cb, NULL)) return;
if (mg_http_stream_body(c, hm, otacb, NULL)) return;
}
if (d->marker != '\0') mg_log_http_req(c, hm);
} else if (ev == MG_EV_HTTP_MSG && d->marker != '\0') {
+1 -22
View File
@@ -173,15 +173,6 @@ bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
return true;
}
#if MG_ENABLE_TCPIP && MG_ENABLE_IPV6
// NAT64 prefix of the built-in stack interface, or NULL if none
static const uint8_t *nat64_prefix(struct mg_mgr *mgr) {
static const uint8_t zero[12] = {0};
if (mgr->ifp == NULL || memcmp(mgr->ifp->nat64, zero, 12) == 0) return NULL;
return mgr->ifp->nat64;
}
#endif
static void dns_cb(struct mg_connection *c, int ev, void *ev_data) {
struct dns_data *d, *tmp;
struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests;
@@ -205,14 +196,6 @@ static void dns_cb(struct mg_connection *c, int ev, void *ev_data) {
// MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
if (dm.txnid != d->txnid) continue;
if (d->c->is_resolving) {
#if MG_ENABLE_TCPIP && MG_ENABLE_IPV6
if (dm.resolved && !dm.addr.is_ip6 && nat64_prefix(c->mgr) != NULL) {
uint32_t ip4 = dm.addr.addr.ip4; // DNS64: prefix + IPv4, RFC 6052
memcpy(dm.addr.addr.ip, nat64_prefix(c->mgr), 12);
memcpy(&dm.addr.addr.ip[12], &ip4, 4);
dm.addr.is_ip6 = true;
}
#endif
if (dm.resolved) {
dm.addr.port = d->c->rem.port; // Save port
d->c->rem = dm.addr; // Copy resolved address
@@ -331,11 +314,7 @@ void mg_resolve(struct mg_connection *c, const char *url) {
} else {
// host is not an IP nor a .local, send DNS resolution request
struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4;
bool aaaa = c->mgr->use_dns6;
#if MG_ENABLE_TCPIP && MG_ENABLE_IPV6
if (nat64_prefix(c->mgr) != NULL) aaaa = false; // DNS64: ask for A
#endif
sendnsreq(c, &host, c->mgr->dnstimeout, dns, aaaa);
sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
}
}
+31 -69
View File
@@ -17,6 +17,8 @@ struct mg_zll {
PART_ID, COEX_CTRL, COEX_PRIORITY, ENHACK_CTRL0;
};
// TrustZone: code built for the secure state (-mcmse) uses the secure alias
// of the radio registers, 0x5xxxxxxx. Otherwise, the non-secure alias
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE & 2)
#define MGI_ZLL ((struct mg_zll *) 0x58A01000)
#define MGI_PKT_RAM ((volatile uint8_t *) 0x58A08000)
@@ -24,6 +26,7 @@ struct mg_zll {
#define MGI_ZLL ((struct mg_zll *) 0x48A01000)
#define MGI_PKT_RAM ((volatile uint8_t *) 0x48A08000)
#endif
#define MGI_TX_PB MGI_PKT_RAM // Length byte (incl. FCS), then frame
#define MGI_RX_PB (MGI_PKT_RAM + 0x80) // Received frame, no length byte
@@ -31,35 +34,20 @@ struct mg_zll {
#define MGI_IRQSTS_RXIRQ MG_BIT(2)
#define MGI_IRQSTS_RX_LEN(x) (((x) >> 24) & 0x7f) // Frame length incl. FCS
#define MGI_PHY_CTRL_XCVSEQ 7U
#define MGI_PHY_CTRL_AUTOACK MG_BIT(3)
#define MGI_PHY_CTRL_RXACKRQD MG_BIT(4)
#define MGI_PHY_CTRL_CCABFRTX MG_BIT(5)
#define MGI_PHY_CTRL_SLOTTED MG_BIT(6)
#define MGI_PHY_CTRL_PROMISCUOUS MG_BIT(25)
#define MGI_PHY_CTRL_ALL_IRQ_MASKS 0x8007ff00U // Bits 8..18, 31: mask all IRQs
#define MGI_SEQ_STATE_MASK 0x1fU
#define MGI_SEQ_STATE_RX_WU 0x10U // RX warm up
#define MGI_SEQ_CTRL_NO_RX_RECYCLE MG_BIT(5)
#define MGI_FRAME_FILTER 0x70bU // Beacon, data, command; versions 0..2
#define MGI_ACKDELAY_MASK 0x7fU
#define MGI_ACKDELAY_VALUE 0x7cU // -4, 802.15.4 turnaround. See PhyHwInit()
enum { MGI_SEQ_IDLE = 0, MGI_SEQ_RX = 1, MGI_SEQ_TX = 2, MGI_SEQ_TR = 4 }; // PHY_CTRL[XCVSEQ]
#define MGI_FCF_ACK_REQUEST 0x20U // First frame byte: acknowledgment request
#define MGI_TX_TIMEOUT 400U // Symbols, 16 us: 127 bytes, then ACK wait
#define MGI_TX_TRIES 10 // Tries for unacknowledged frames
#define MGI_TX_BACKOFF 64U // Symbols, 1 ms: backoff grows by this per try
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
// Symbol timer, 16 us, 24 bits
static uint32_t mg_mcxw71_symbols(void) {
return MGI_ZLL->EVENT_TMR >> 8;
}
static void mg_mcxw71_delay(uint32_t symbols) {
uint32_t start = mg_mcxw71_symbols();
while (((mg_mcxw71_symbols() - start) & 0xffffffU) < symbols) (void) 0;
}
// Wait for the sequence to end, up to MGI_TX_TIMEOUT symbols
static bool mg_mcxw71_wait(void) {
uint32_t start = mg_mcxw71_symbols();
@@ -72,7 +60,8 @@ static bool mg_mcxw71_wait(void) {
// Stop the current sequence, wait for the sequencer to go idle. See PhyAbort()
static void mg_mcxw71_idle(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;
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;
}
@@ -85,88 +74,61 @@ static void mg_mcxw71_start(uint32_t seq) {
MGI_ZLL->PHY_CTRL |= seq;
}
// Program our addresses for hardware filtering and auto-ACK: PAN ID, short
// address and EUI-64 (little endian), when they change. The L2 sets them
static void mg_mcxw71_addrs(struct mg_tcpip_if *ifp) {
struct lowpan_data *l = &ifp->l2data.lowpan;
const uint8_t *e = ifp->mac;
uint32_t s = ((uint32_t) l->short_addr << 16) | l->pan_id;
uint32_t lo = (uint32_t) (e[7] | (e[6] << 8) | (e[5] << 16)) |
((uint32_t) e[4] << 24);
uint32_t hi = (uint32_t) (e[3] | (e[2] << 8) | (e[1] << 16)) |
((uint32_t) e[0] << 24);
if (MGI_ZLL->MACSHORTADDRS0 != s || MGI_ZLL->MACLONGADDRS0_LSB != lo ||
MGI_ZLL->MACLONGADDRS0_MSB != hi) {
mg_mcxw71_idle();
MGI_ZLL->MACSHORTADDRS0 = s;
MGI_ZLL->MACLONGADDRS0_LSB = lo, MGI_ZLL->MACLONGADDRS0_MSB = hi;
mg_mcxw71_start(MGI_SEQ_RX);
}
}
static bool mg_mcxw71_init(struct mg_tcpip_if *ifp) {
uint8_t channel = ifp->l2data.lowpan.channel;
if (ifp->l2type != MG_TCPIP_L2_LOWPAN || channel < 11 || channel > 26) {
MG_ERROR(("Use MG_TCPIP_L2_LOWPAN, channel 11..26"));
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;
}
MGI_ZLL->SEQ_CTRL_STS |= MGI_SEQ_CTRL_NO_RX_RECYCLE;
// Mask all interrupts, we poll. Filter frames by address, auto-ACK
MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS | MGI_PHY_CTRL_AUTOACK;
MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS; // Mask all IRQs, we poll
MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS;
MGI_ZLL->SAM_CTRL = 0;
MGI_ZLL->RX_FRAME_FILTER = MGI_FRAME_FILTER;
MGI_ZLL->ACKDELAY = (MGI_ZLL->ACKDELAY & ~MGI_ACKDELAY_MASK) | MGI_ACKDELAY_VALUE;
MGI_ZLL->TMR_PRESCALE = 5; // 16 us timebase, one symbol
MGI_ZLL->CHANNEL_NUM0 = channel;
MGI_ZLL->PA_PWR = 0x3f; // PA slices, max output power
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);
mg_mcxw71_addrs(ifp);
return true;
}
static size_t mg_mcxw71_tx(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
const uint8_t *p = (const uint8_t *) buf;
// 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 = false, ack = len > 0 && (p[0] & MGI_FCF_ACK_REQUEST);
int tries;
if (len == 0 || len > 125) return 0; // 127 bytes max, incl. 2 bytes FCS
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
MGI_ZLL->PHY_CTRL &= ~(MGI_PHY_CTRL_CCABFRTX | MGI_PHY_CTRL_RXACKRQD | MGI_PHY_CTRL_SLOTTED);
if (ack) MGI_ZLL->PHY_CTRL |= MGI_PHY_CTRL_RXACKRQD;
// Frames with ACK request: transmit, then receive the ACK (TR sequence),
// retry with growing backoff. See PhyPdDataRequest(), PhyISR.c
for (tries = 0; tries < (ack ? MGI_TX_TRIES : 1) && !ok; tries++) {
if (tries > 0) mg_mcxw71_delay(MGI_TX_BACKOFF * (uint32_t) tries);
mg_mcxw71_start(ack ? MGI_SEQ_TR : MGI_SEQ_TX);
ok = mg_mcxw71_wait() && (!ack || (MGI_ZLL->IRQSTS & MGI_IRQSTS_RXIRQ));
mg_mcxw71_idle();
}
MGI_ZLL->PHY_CTRL &= ~MGI_PHY_CTRL_RXACKRQD;
mg_mcxw71_start(MGI_SEQ_TX);
ok = mg_mcxw71_wait();
mg_mcxw71_idle();
mg_mcxw71_start(MGI_SEQ_RX);
if (!ok) MG_DEBUG(("TX failed, %d tries", tries));
(void) ifp;
return ok ? len : 0;
}
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 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;
mg_mcxw71_addrs(ifp);
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 > len) n = len;
for (i = 0; i < n; i++) ((uint8_t *) buf)[i] = MGI_RX_PB[i];
if (n > sizeof(f)) n = sizeof(f);
for (i = 0; i < n; i++) f[i] = MGI_RX_PB[i];
}
mg_mcxw71_idle();
mg_mcxw71_start(MGI_SEQ_RX);
(void) ifp;
return n;
return mg_l2_802154_rx(ifp, f, n, buf, len);
}
static bool mg_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) {
+14 -15
View File
@@ -3,24 +3,23 @@
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \
MG_ENABLE_DRIVER_MCXW71
// NXP MCXW71 IEEE 802.15.4 radio, ZLL link layer. Frames passed to tx() and
// returned by rx() are raw 802.15.4 frames: MAC header and payload, no FCS.
// 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.
// Use with MG_TCPIP_L2_LOWPAN: channel, PAN ID and addresses are taken from
// ifp->l2data.lowpan and ifp->mac. No driver_data
// driver_data: struct mg_l2_802154_data, channel and PAN ID set
#define MG_TCPIP_DRIVER_INIT(mgr) \
do { \
static struct mg_tcpip_if mif_; \
mif_.l2type = MG_TCPIP_L2_LOWPAN; \
mif_.driver = &mg_tcpip_driver_mcxw71; \
mif_.l2data.lowpan.channel = 15; \
mif_.l2data.lowpan.pan_id = 0xabcd; \
MG_SET_MAC_ADDRESS(mif_.mac); /* EUI-64 */ \
mg_tcpip_init(mgr, &mif_); \
(mgr)->use_dns6 = true; /* IPv6 only: no IPv4 over 6LoWPAN */ \
MG_INFO(("Driver: mcxw71, 6LoWPAN")); \
#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)); \
} while (0)
#endif
+29 -50
View File
@@ -73,8 +73,6 @@ enum {
MGI_PROP_NCP_VERSION = 2,
MGI_PROP_PHY_ENABLED = 0x20,
MGI_PROP_PHY_CHAN = 0x21,
MGI_PROP_LADDR = 0x34,
MGI_PROP_SADDR = 0x35,
MGI_PROP_PANID = 0x36,
MGI_PROP_RAW_STREAM = 0x37,
MGI_PROP_STREAM_RAW = 0x71
@@ -84,7 +82,7 @@ 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 4
#define MGI_RXQ_CNT 16 // A whole Ethernet frame, 15 radio frames
// Driver state, one radio
struct mg_rw612 {
@@ -103,9 +101,6 @@ struct mg_rw612 {
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
uint16_t pan_id, short_addr; // Addresses programmed in the RCP
uint8_t eui[8];
uint8_t channel;
};
static struct mg_rw612 s_rw612;
@@ -337,31 +332,6 @@ static bool mg_rw612_set(uint32_t prop, const uint8_t *val, size_t len) {
s_rw612.rsp_status == 0;
}
// Program channel, PAN ID, short address and EUI-64 when they change
static void mg_rw612_addrs(struct mg_tcpip_if *ifp) {
struct mg_rw612 *s = &s_rw612;
struct lowpan_data *l = &ifp->l2data.lowpan;
uint8_t v[2];
if (s->channel != l->channel) {
s->channel = l->channel;
if (!mg_rw612_set(MGI_PROP_PHY_CHAN, &l->channel, 1)) MG_ERROR(("chan"));
}
if (s->pan_id != l->pan_id) {
s->pan_id = l->pan_id;
v[0] = (uint8_t) l->pan_id, v[1] = (uint8_t) (l->pan_id >> 8);
if (!mg_rw612_set(MGI_PROP_PANID, v, 2)) MG_ERROR(("panid"));
}
if (s->short_addr != l->short_addr) {
s->short_addr = l->short_addr;
v[0] = (uint8_t) l->short_addr, v[1] = (uint8_t) (l->short_addr >> 8);
if (!mg_rw612_set(MGI_PROP_SADDR, v, 2)) MG_ERROR(("saddr"));
}
if (memcmp(s->eui, ifp->mac, 8) != 0) {
memcpy(s->eui, ifp->mac, 8);
if (!mg_rw612_set(MGI_PROP_LADDR, s->eui, 8)) MG_ERROR(("laddr"));
}
}
// Log an init error, return false
static bool mg_rw612_fail(const char *msg) {
MG_ERROR(("%s", msg));
@@ -370,11 +340,12 @@ static bool mg_rw612_fail(const char *msg) {
static bool mg_rw612_154_init(struct mg_tcpip_if *ifp) {
struct mg_rw612 *s = &s_rw612;
uint8_t f[8], one = 1;
struct mg_l2_802154_data *d =
(struct mg_l2_802154_data *) ifp->driver_data;
uint8_t f[8], one = 1, pan[2];
uint64_t expire;
if (ifp->l2type != MG_TCPIP_L2_LOWPAN || ifp->l2data.lowpan.channel < 11 ||
ifp->l2data.lowpan.channel > 26) {
MG_ERROR(("Use MG_TCPIP_L2_LOWPAN, channel 11..26"));
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) {
@@ -383,7 +354,6 @@ static bool mg_rw612_154_init(struct mg_tcpip_if *ifp) {
}
memset(s, 0, sizeof(*s));
s->free_bufs = 0xffff;
s->pan_id = s->short_addr = 0xffff; // Force programming
// 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);
@@ -415,25 +385,31 @@ 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");
}
mg_rw612_addrs(ifp);
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");
}
if (!mg_rw612_set(MGI_PROP_RAW_STREAM, &one, 1)) {
return mg_rw612_fail("Raw stream enable failed");
}
return true;
}
static size_t mg_rw612_154_tx(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
// Send one radio frame: MAC header and payload, no FCS
static size_t mg_rw612_send(const uint8_t *p, 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;
if (len == 0 || len > 125) return 0; // 127 bytes max, incl. 2 bytes FCS
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;
memcpy(f + n, p, len), n += len;
f[n++] = 0, f[n++] = 0; // FCS, filled in by the radio
f[n++] = ifp->l2data.lowpan.channel;
f[n++] = d->channel;
f[n++] = 4; // Max CSMA backoffs
f[n++] = 3; // Max frame retries, for frames with ACK request
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"));
@@ -446,18 +422,21 @@ static size_t mg_rw612_154_tx(const void *buf, size_t len,
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 n = 0;
size_t res = 0;
mg_rw612_poll_imu();
mg_rw612_addrs(ifp);
if (s->rxtail != s->rxhead) {
n = s->rxlen[s->rxtail];
if (n > len) n = len;
memcpy(buf, s->rxq[s->rxtail], n);
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);
}
return n;
return res;
}
static bool mg_rw612_154_poll(struct mg_tcpip_if *ifp, bool s1) {
+18 -19
View File
@@ -3,27 +3,26 @@
#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \
MG_ENABLE_DRIVER_RW612_154
// NXP RW612 IEEE 802.15.4 radio. 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 lowpan
// tutorial. The driver talks to it over the CPU2 <-> CPU3 IMU mailbox:
// IMUMC messages carrying HDLC framed Spinel. Frames passed to tx() and
// returned by rx() are raw 802.15.4 frames: MAC header and payload, no FCS.
// Use with MG_TCPIP_L2_LOWPAN: channel, PAN ID and addresses are taken from
// ifp->l2data.lowpan and ifp->mac. No driver_data
// 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_tcpip_if mif_; \
mif_.l2type = MG_TCPIP_L2_LOWPAN; \
mif_.driver = &mg_tcpip_driver_rw612_154; \
mif_.l2data.lowpan.channel = 15; \
mif_.l2data.lowpan.pan_id = 0xabcd; \
MG_SET_MAC_ADDRESS(mif_.mac); /* EUI-64 */ \
mg_tcpip_init(mgr, &mif_); \
(mgr)->use_dns6 = true; /* IPv6 only: no IPv4 over 6LoWPAN */ \
MG_INFO(("Driver: rw612_154, 6LoWPAN")); \
#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
+6 -3
View File
@@ -2,6 +2,7 @@
#if MG_ENABLE_TCPIP && (MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || \
MG_ENABLE_DRIVER_STM32N)
// STM32C: vendor modded single-queue Synopsys v5.4, same settings as STM32H
// STM32H: vendor modded single-queue Synopsys v4.2
// STM32N: dual-queue GbE Synopsys v5.2 with no hash table option, 64-bit AXI
// MCXNx4x: dual-queue Synopsys v5.2 with no hash table option
@@ -310,7 +311,7 @@ static uint32_t s_rxno;
#if MG_ENABLE_DRIVER_MCXN
void ETHERNET_IRQHandler(void);
void ETHERNET_IRQHandler(void) {
#elif MG_ENABLE_DRIVER_STM32H
#elif MG_ENABLE_DRIVER_STM32H && !MG_ENABLE_DRIVER_STM32C
void ETH_IRQHandler(void);
void ETH_IRQHandler(void) {
#else
@@ -339,8 +340,10 @@ void ETH1_IRQHandler(void) {
}
ETH->DMACSR =
MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing
ETH->DMACRDTPR =
(uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1]; // and resume RX
// Resume RX: tail is the last descriptor given back to DMA. It must differ
// from the DMA current descriptor, or some DMAs, e.g. STM32C5, stay suspended
ETH->DMACRDTPR = (uint32_t) (uintptr_t) &s_rxdesc[(s_rxno + ETH_DESC_CNT - 1) %
ETH_DESC_CNT];
}
struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
+11 -2
View File
@@ -4,6 +4,13 @@
#if !defined(MG_ENABLE_DRIVER_STM32H)
#define MG_ENABLE_DRIVER_STM32H 0
#endif
#if !defined(MG_ENABLE_DRIVER_STM32C)
#define MG_ENABLE_DRIVER_STM32C 0
#endif
#if MG_ENABLE_DRIVER_STM32C // STM32C5: STM32H5 MAC, the IRQ is ETH1
#undef MG_ENABLE_DRIVER_STM32H
#define MG_ENABLE_DRIVER_STM32H 1
#endif
#if !defined(MG_ENABLE_DRIVER_MCXN)
#define MG_ENABLE_DRIVER_MCXN 0
#endif
@@ -43,9 +50,11 @@ struct mg_tcpip_driver_stm32h_data {
#define MG_DRIVER_MDC_CR 4
#endif
#if MG_ENABLE_DRIVER_STM32H && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
#if MG_ENABLE_DRIVER_STM32H && !MG_ENABLE_DRIVER_STM32C && \
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn)
#elif MG_ENABLE_DRIVER_STM32N && MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
#elif (MG_ENABLE_DRIVER_STM32N || MG_ENABLE_DRIVER_STM32C) && \
MG_ARCH == MG_ARCH_CUBE && !defined(MG_ENABLE_ETH_IRQ)
#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH1_IRQn)
#elif !defined(MG_ENABLE_ETH_IRQ)
#define MG_ENABLE_ETH_IRQ()
+50 -37
View File
@@ -1,12 +1,13 @@
#include "config.h"
#include "health.h"
#if MG_ENABLE_HEALTH
// The one health record. Lives in RAM that survives a warm reset, see
// MG_HEALTH_RAM and the .mg_health region in the linker script
struct mg_health mg_health_record MG_HEALTH_RAM;
// STM32 H723 support. Needs the _estack symbol from the linker script
#if MG_ENABLE_HEALTH && (defined(STM32H7_DEV_ID) || defined(STM32H5))
// STM32 H7, H5, C5 support. Needs the _estack symbol from the linker script
#if defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5)
// Fault handler body. Runs in exception context: no printf, no malloc, no
// blocking calls. Copies the unwind registers and raw stack into the health
// record, then resets.
@@ -48,52 +49,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 +113,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 +131,4 @@ size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
(void) ap;
return len;
}
#endif
+66 -6
View File
@@ -5,21 +5,25 @@
#include "arch.h"
#include "printf.h"
#if MG_ENABLE_HEALTH
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5))
#if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5))
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
#endif
#if !defined(MG_HEALTH_DATA_SIZE) && (defined(STM32H7_DEV_ID) || defined(STM32H5))
#define MG_HEALTH_DATA_SIZE 250
#endif
#ifndef MG_HEALTH_DATA_SIZE
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
#define MG_HEALTH_DATA_SIZE 250
#elif MG_ARCH == MG_ARCH_ESP32
#define MG_HEALTH_DATA_SIZE 23
#else
#define MG_HEALTH_DATA_SIZE 10
#endif
#endif // MG_HEALTH_DATA_SIZE
// Places the record in RAM that survives a reset. Define in mongoose_config.h
// to match the linker script, e.g. __attribute__((section(".mg_health")))
@@ -27,6 +31,13 @@
#define MG_HEALTH_RAM
#endif
// CPU core architecture
enum mg_health_arch {
MG_HEALTH_ARCH_ARM = 0,
MG_HEALTH_ARCH_XTENSA,
MG_HEALTH_ARCH_RISCV
};
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
@@ -43,7 +54,8 @@ enum mg_health_reason {
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint16_t reset_reason; // enum mg_health_reason that started this boot
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
uint32_t cpuid; // ARM CPU ID
uint32_t data[MG_HEALTH_DATA_SIZE];
};
@@ -76,6 +88,54 @@ 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);
#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, "{}");
}
#endif
+10 -25
View File
@@ -56,19 +56,6 @@ extern uint8_t *mg_l2_pppoe_header(struct mg_tcpip_if *, enum mg_l2proto, struct
extern size_t mg_l2_pppoe_trailer(struct mg_tcpip_if *, size_t, uint8_t *);
extern bool mg_l2_pppoe_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *);
extern void mg_l2_lowpan_init(struct mg_tcpip_if *);
extern bool mg_l2_lowpan_poll(struct mg_tcpip_if *, bool);
extern uint8_t *mg_l2_lowpan_header(struct mg_tcpip_if *, enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *);
extern size_t mg_l2_lowpan_trailer(struct mg_tcpip_if *, size_t, uint8_t *);
extern bool mg_l2_lowpan_rx(struct mg_tcpip_if *, enum mg_l2proto *, struct mg_str *, struct mg_str *);
extern struct mg_l2addr *mg_l2_lowpan_getaddr(struct mg_tcpip_if *, uint8_t *);
extern struct mg_l2addr *mg_l2_lowpan_mapip(enum mg_l2addrtype, struct mg_addr *);
#if MG_ENABLE_IPV6
extern bool mg_l2_lowpan_genip6(uint64_t *, uint8_t, struct mg_l2addr *);
extern bool mg_l2_lowpan_ip6get(struct mg_l2addr *, uint8_t *, uint8_t);
extern uint8_t mg_l2_lowpan_ip6put(struct mg_l2addr *, uint8_t *);
#endif
typedef void (*l2_init_fn)(struct mg_tcpip_if *);
typedef bool (*l2_poll_fn)(struct mg_tcpip_if *, bool);
typedef uint8_t *((*l2_header_fn)(struct mg_tcpip_if *, enum mg_l2proto, struct mg_l2addr *, struct mg_l2addr *, uint8_t *));
@@ -84,27 +71,25 @@ typedef uint8_t (*l2_ip6put_fn)(struct mg_l2addr *, uint8_t *);
// clang-format on
static const l2_init_fn l2_init[] = {mg_l2_eth_init, mg_l2_ppp_init,
mg_l2_pppoe_init, mg_l2_lowpan_init};
mg_l2_pppoe_init};
static const l2_poll_fn l2_poll[] = {mg_l2_eth_poll, mg_l2_ppp_poll,
mg_l2_pppoe_poll, mg_l2_lowpan_poll};
mg_l2_pppoe_poll};
static const l2_header_fn l2_header[] = {mg_l2_eth_header, mg_l2_ppp_header,
mg_l2_pppoe_header,
mg_l2_lowpan_header};
mg_l2_pppoe_header};
static const l2_trailer_fn l2_trailer[] = {mg_l2_eth_trailer, mg_l2_ppp_trailer,
mg_l2_pppoe_trailer, mg_l2_lowpan_trailer};
static const l2_rx_fn l2_rx[] = {mg_l2_eth_rx, mg_l2_ppp_rx, mg_l2_pppoe_rx,
mg_l2_lowpan_rx};
mg_l2_pppoe_trailer};
static const l2_rx_fn l2_rx[] = {mg_l2_eth_rx, mg_l2_ppp_rx, mg_l2_pppoe_rx};
static const l2_getaddr_fn l2_getaddr[] = {mg_l2_eth_getaddr, mg_l2_ppp_getaddr,
mg_l2_ppp_getaddr, mg_l2_lowpan_getaddr};
mg_l2_ppp_getaddr};
static const l2_mapip_fn l2_mapip[] = {mg_l2_eth_mapip, mg_l2_ppp_mapip,
mg_l2_ppp_mapip, mg_l2_lowpan_mapip};
mg_l2_ppp_mapip};
#if MG_ENABLE_IPV6
static const l2_genip6_fn l2_genip6[] = {mg_l2_eth_genip6, mg_l2_ppp_genip6,
mg_l2_ppp_genip6, mg_l2_lowpan_genip6};
mg_l2_ppp_genip6};
static const l2_ip6get_fn l2_ip6get[] = {mg_l2_eth_ip6get, mg_l2_ppp_ip6get,
mg_l2_ppp_ip6get, mg_l2_lowpan_ip6get};
mg_l2_ppp_ip6get};
static const l2_ip6put_fn l2_ip6put[] = {mg_l2_eth_ip6put, mg_l2_ppp_ip6put,
mg_l2_ppp_ip6put, mg_l2_lowpan_ip6put};
mg_l2_ppp_ip6put};
#endif
void mg_l2_init(struct mg_tcpip_if *ifp) {
+2 -24
View File
@@ -6,13 +6,7 @@
#if MG_ENABLE_TCPIP
// no config defaults to 0 => Ethernet
enum mg_l2type {
MG_TCPIP_L2_ETH = 0,
MG_TCPIP_L2_PPP,
MG_TCPIP_L2_PPPoE,
MG_TCPIP_L2_LOWPAN,
MG_TCPIP_L2_USER
};
enum mg_l2type { MG_TCPIP_L2_ETH = 0, MG_TCPIP_L2_PPP, MG_TCPIP_L2_PPPoE, MG_TCPIP_L2_USER };
#if defined(__DCC__)
#pragma pack(1)
@@ -35,27 +29,10 @@ struct eth_data {
struct pppoe_data { // (struct eth_data *) &pppoe_data = pppoedata.eth
struct eth_data eth;
};
struct lowpan_data {
uint16_t pan_id; // User-set PAN ID
uint8_t channel; // User-set channel, 11..26
uint16_t short_addr; // Our short address, 0xfffe if none
uint8_t seq; // MAC sequence number
uint8_t proto; // Protocol of the frame being sent, enum mg_l2proto
uint8_t dst[8]; // Destination of the frame being sent
uint16_t tag; // Next fragmentation datagram tag
uint8_t *buf; // Reassembly and decompression buffer
uint16_t size; // Datagram being reassembled: size, 0 if none
uint16_t received; // Bytes received so far
uint16_t tag_rx; // Datagram tag
uint8_t src[8]; // Source address
bool udp; // UDP header was compressed
uint64_t expire; // Reassembly deadline, ms
};
union mg_l2data {
struct eth_data eth;
struct pppoe_data pppoe;
struct lowpan_data lowpan;
};
#if defined(__DCC__)
@@ -64,6 +41,7 @@ union mg_l2data {
#pragma pack(pop)
#endif
enum mg_l2proto {
MG_TCPIP_L2PROTO_IPV4 = 0,
MG_TCPIP_L2PROTO_IPV6,
+60
View File
@@ -0,0 +1,60 @@
#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)(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(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
-551
View File
@@ -1,551 +0,0 @@
#include "l2.h"
#include "log.h"
#include "net.h"
#include "net_builtin.h"
#include "printf.h"
#include "util.h"
// IPv6 over IEEE 802.15.4, 6LoWPAN. RFC 4944: frame format, fragmentation.
// RFC 6282: IPHC header compression, UDP next header compression.
// Driver tx()/rx() carry raw 802.15.4 frames: MAC header, payload, no FCS.
//
// L2 addresses are 8 bytes, struct mg_l2addr. Extended address: EUI-64, big
// endian. Short address S: 0000:00ff:fe00:SSSS, which is also its IPv6
// interface identifier, RFC 6282 3.2.2. Broadcast is short address 0xffff.
// No link-layer security: secured frames are dropped
#if MG_ENABLE_TCPIP && MG_ENABLE_IPV6
#define MGI_LOWPAN_FRAME_MAX 125 // 802.15.4 frame without FCS: 127 - 2
#define MGI_LOWPAN_MTU 1280 // IPv6 minimum MTU, RFC 8200 5
#define MGI_LOWPAN_REASM_MS 5000 // Drop incomplete datagrams after this
#define MGI_FCF_DATA 1U // Frame control: data frame
#define MGI_FCF_SECURITY 0x08U // Frame control: security enabled
#define MGI_FCF_ACK_REQUEST 0x20U // Frame control: acknowledgment request
#define MGI_FCF_PANID_COMP 0x40U // Frame control: PAN ID compression
#define MGI_FCF_IE 0x200U // Frame control: IE present, 2015 frames
#define MGI_FCF_VERSION_2006 0x1000U
#define MGI_AM_SHORT 2U // Addressing mode: 16-bit short address
#define MGI_AM_EXT 3U // Addressing mode: 64-bit extended address
#define MGI_DISPATCH_IPV6 0x41 // Uncompressed IPv6, RFC 4944 5.1
#define MGI_DISPATCH_FRAG1 0xc0 // First fragment, 11000xxx
#define MGI_DISPATCH_FRAGN 0xe0 // Subsequent fragment, 11100xxx
// Parsed 802.15.4 MAC header
struct lowpan_mhr {
uint8_t dst[8], src[8]; // Addresses, struct mg_l2addr format
uint16_t dst_pan; // Destination PAN ID
size_t len; // MAC header length, without security header
bool secured; // Security enabled
};
static struct mg_l2addr s_rxsrc; // Source address returned by getaddr()
static void set_short(uint8_t *a, uint16_t s) {
a[0] = a[1] = a[2] = a[5] = 0, a[3] = 0xff, a[4] = 0xfe;
a[6] = (uint8_t) (s >> 8), a[7] = (uint8_t) s;
}
static bool is_zero(const uint8_t *p, size_t n) {
while (n-- > 0)
if (*p++ != 0) return false;
return true;
}
// Read an address field, over-the-air byte order is little endian
static size_t read_addr(const uint8_t *p, unsigned mode, uint8_t *a) {
int i;
if (mode == MGI_AM_SHORT) {
set_short(a, (uint16_t) (p[0] | (p[1] << 8)));
return 2;
}
for (i = 0; i < 8; i++) a[i] = p[7 - i];
return 8;
}
// Parse MAC header of a data frame, 2003/2006 format
static bool parse_mhr(const uint8_t *f, size_t n, struct lowpan_mhr *h) {
unsigned fcf, dm, sm, ver;
size_t i = 3;
if (n < 3) return false;
fcf = (unsigned) (f[0] | (f[1] << 8));
dm = (fcf >> 10) & 3, sm = (fcf >> 14) & 3, ver = (fcf >> 12) & 3;
if ((fcf & 7) != MGI_FCF_DATA || (fcf & MGI_FCF_IE) || ver > 1) return false;
h->secured = (fcf & MGI_FCF_SECURITY) != 0;
if (dm < MGI_AM_SHORT || sm < MGI_AM_SHORT) return false; // Need both addresses
if (i + 2 > n) return false;
h->dst_pan = (uint16_t) (f[i] | (f[i + 1] << 8)), i += 2;
if (i + (dm == MGI_AM_EXT ? 8 : 2) > n) return false;
i += read_addr(f + i, dm, h->dst);
if ((fcf & MGI_FCF_PANID_COMP) == 0) i += 2; // Source PAN ID, if present
if (i + (sm == MGI_AM_EXT ? 8 : 2) > n) return false;
i += read_addr(f + i, sm, h->src);
h->len = i;
return true;
}
static bool is_short(const uint8_t *a) {
return a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0xff && a[4] == 0xfe &&
a[5] == 0;
}
static bool is_bcast(const uint8_t *a) {
return is_short(a) && a[6] == 0xff && a[7] == 0xff;
}
// IPv6 interface identifier for an L2 address, RFC 6282 3.2.2
static void make_iid(const uint8_t *a, uint8_t *iid) {
memcpy(iid, a, 8);
if (!is_short(a)) iid[0] ^= 2; // EUI-64: flip universal/local bit
}
static size_t write_addr(uint8_t *p, const uint8_t *a) {
int i;
if (is_short(a)) {
p[0] = a[7], p[1] = a[6];
return 2;
}
for (i = 0; i < 8; i++) p[i] = a[7 - i];
return 8;
}
// Write MAC header for a data frame from us to dst. Return its length
static size_t write_mhr(struct mg_tcpip_if *ifp, const uint8_t *dst,
uint8_t *f) {
struct lowpan_data *d = &ifp->l2data.lowpan;
unsigned fcf = MGI_FCF_DATA | MGI_FCF_PANID_COMP | MGI_FCF_VERSION_2006;
size_t i = 3;
if (!is_bcast(dst)) fcf |= MGI_FCF_ACK_REQUEST; // Unicast: MAC retries
fcf |= (is_short(dst) ? MGI_AM_SHORT : MGI_AM_EXT) << 10;
fcf |= (is_short(ifp->mac) ? MGI_AM_SHORT : MGI_AM_EXT) << 14;
f[0] = (uint8_t) fcf, f[1] = (uint8_t) (fcf >> 8), f[2] = d->seq++;
f[i++] = (uint8_t) d->pan_id, f[i++] = (uint8_t) (d->pan_id >> 8);
i += write_addr(f + i, dst);
i += write_addr(f + i, ifp->mac);
return i;
}
// Compress a unicast IPv6 address: 2 bits for the SAM/DAM field, inline
// bytes go to out. RFC 6282 3.1.1, SAC/DAC = 0
static unsigned compress_addr(const uint8_t *ip, const uint8_t *l2,
uint8_t *out, size_t *n) {
static const uint8_t ll[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0};
uint8_t iid[8];
if (memcmp(ip, ll, 8) != 0) {
memcpy(out + *n, ip, 16), *n += 16;
return 0;
}
make_iid(l2, iid);
if (memcmp(ip + 8, iid, 8) == 0) return 3; // Derived from L2 address
if (is_short(ip + 8)) { // 0000:00ff:fe00:XXXX
memcpy(out + *n, ip + 14, 2), *n += 2;
return 2;
}
memcpy(out + *n, ip + 8, 8), *n += 8;
return 1;
}
// Compress IPv6 header to IPHC, RFC 6282 3.1. Next header is carried inline.
// Return compressed header length
static size_t iphc_compress(const uint8_t *ip, const uint8_t *src_l2,
const uint8_t *dst_l2, uint8_t *out) {
unsigned tc = (unsigned) (((ip[0] & 0x0f) << 4) | (ip[1] >> 4));
unsigned ecn = tc & 3, dscp = tc >> 2, tf, hl;
uint32_t flow = ((uint32_t) (ip[1] & 0x0f) << 16) |
((uint32_t) ip[2] << 8) | ip[3];
size_t n = 2;
if (tc == 0 && flow == 0) {
tf = 3;
} else if (flow == 0) {
tf = 2, out[n++] = (uint8_t) ((ecn << 6) | dscp);
} else if (dscp == 0) {
tf = 1, out[n++] = (uint8_t) ((ecn << 6) | (flow >> 16));
out[n++] = (uint8_t) (flow >> 8), out[n++] = (uint8_t) flow;
} else {
tf = 0, out[n++] = (uint8_t) ((ecn << 6) | dscp);
out[n++] = (uint8_t) (flow >> 16), out[n++] = (uint8_t) (flow >> 8);
out[n++] = (uint8_t) flow;
}
out[n++] = ip[6]; // Next header, inline
hl = ip[7] == 1 ? 1 : ip[7] == 64 ? 2 : ip[7] == 255 ? 3 : 0;
if (hl == 0) out[n++] = ip[7];
out[0] = (uint8_t) (0x60 | (tf << 3) | hl);
out[1] = 0;
if (is_zero(ip + 8, 16)) {
out[1] |= 0x40; // SAC = 1, SAM = 00: unspecified address
} else {
out[1] |= (uint8_t) (compress_addr(ip + 8, src_l2, out, &n) << 4);
}
if (ip[24] == 0xff) { // Multicast, M = 1
out[1] |= 0x08;
if (ip[25] == 0x02 && is_zero(ip + 26, 13)) { // ff02::00XX
out[1] |= 3, out[n++] = ip[39];
} else if (is_zero(ip + 26, 11)) { // ffXX::00XX:XXXX
out[1] |= 2, out[n++] = ip[25];
memcpy(out + n, ip + 37, 3), n += 3;
} else if (is_zero(ip + 26, 9)) { // ffXX::00XX:XXXX:XXXX
out[1] |= 1, out[n++] = ip[25];
memcpy(out + n, ip + 35, 5), n += 5;
} else {
memcpy(out + n, ip + 24, 16), n += 16;
}
} else {
out[1] |= (uint8_t) compress_addr(ip + 24, dst_l2, out, &n);
}
return n;
}
// Decompress a unicast address. Prefix is the context prefix for SAC/DAC = 1,
// or NULL for link-local. Return inline bytes used
static size_t decompress_addr(unsigned mode, const uint8_t *p, size_t n,
const uint8_t *l2, const uint8_t *prefix,
uint8_t *ip) {
static const size_t sizes[] = {16, 8, 2, 0};
if (sizes[mode] > n) return n + 1; // Truncated: caller sees i > n
if (mode == 0) {
memcpy(ip, p, 16);
} else {
memset(ip, 0, 16), ip[0] = 0xfe, ip[1] = 0x80;
if (prefix != NULL) memcpy(ip, prefix, 8);
if (mode == 1) memcpy(ip + 8, p, 8);
if (mode == 2) set_short(ip + 8, (uint16_t) ((p[0] << 8) | p[1]));
if (mode == 3) make_iid(l2, ip + 8);
}
return sizes[mode];
}
// Decompress IPHC or uncompressed IPv6 header, RFC 6282 3.1, 4.3.
// Write IPv6 header (and UDP header if compressed) to ip. Return its length,
// or 0 on error. Set *used to the number of compressed bytes consumed
static size_t iphc_decompress(const struct lowpan_mhr *h, const uint8_t *p,
size_t n, uint8_t *ip, size_t *used, bool *udp) {
static const size_t tf_sizes[] = {4, 3, 1, 0};
unsigned b0, b1, tf, tc = 0, sam, dam;
uint32_t flow = 0;
size_t i = 2;
*udp = false;
if (n > 0 && p[0] == MGI_DISPATCH_IPV6) {
if (n < 41) return 0;
memcpy(ip, p + 1, 40);
*used = 41;
return 40;
}
if (n < 2 || (p[0] & 0xe0) != 0x60) return 0;
b0 = p[0], b1 = p[1], tf = (b0 >> 3) & 3, sam = (b1 >> 4) & 3, dam = b1 & 3;
if (b1 & 0x80) return 0; // CID: no compression contexts
if (i + tf_sizes[tf] + ((b0 & 4) ? 0 : 1) + ((b0 & 3) ? 0 : 1) > n)
return 0; // TF, next header, hop limit
if (tf == 0 || tf == 2) tc = (unsigned) (((p[i] & 0x3f) << 2) | (p[i] >> 6));
if (tf == 1) tc = p[i] >> 6;
if (tf == 0) flow = ((uint32_t) (p[i + 1] & 0x0f) << 16) |
((uint32_t) p[i + 2] << 8) | p[i + 3];
if (tf == 1) flow = ((uint32_t) (p[i] & 0x0f) << 16) |
((uint32_t) p[i + 1] << 8) | p[i + 2];
i += tf_sizes[tf];
ip[0] = (uint8_t) (0x60 | (tc >> 4));
ip[1] = (uint8_t) ((tc << 4) | (flow >> 16));
ip[2] = (uint8_t) (flow >> 8), ip[3] = (uint8_t) flow;
ip[4] = ip[5] = 0; // Payload length, set by caller
if ((b0 & 4) == 0) ip[6] = p[i++]; // Next header inline
if ((b0 & 3) == 0) ip[7] = p[i++];
if ((b0 & 3) != 0) ip[7] = (b0 & 3) == 1 ? 1 : (b0 & 3) == 2 ? 64 : 255;
// Source address
if ((b1 & 0x40) && sam == 0) { // SAC = 1, SAM = 00: unspecified address
memset(ip + 8, 0, 16);
} else {
if (b1 & 0x40) return 0; // SAC = 1: no compression contexts
i += decompress_addr(sam, p + i, n - i, h->src, NULL, ip + 8);
if (i > n) return 0;
}
// Destination address
if ((b1 & 0x0c) == 0x08) { // Multicast, stateless
static const size_t sizes[] = {16, 6, 4, 1};
if (i + sizes[dam] > n) return 0;
memset(ip + 24, 0, 16), ip[24] = 0xff;
if (dam == 0) memcpy(ip + 24, p + i, 16);
if (dam == 1) ip[25] = p[i], memcpy(ip + 35, p + i + 1, 5);
if (dam == 2) ip[25] = p[i], memcpy(ip + 37, p + i + 1, 3);
if (dam == 3) ip[25] = 2, ip[39] = p[i];
i += sizes[dam];
} else if ((b1 & 0x08) == 0) { // Unicast
if (b1 & 0x04) return 0; // DAC = 1: no compression contexts
i += decompress_addr(dam, p + i, n - i, h->dst, NULL, ip + 24);
if (i > n) return 0;
} else {
return 0; // Stateful multicast is not supported
}
if (b0 & 4) { // Next header compressed: only UDP, RFC 6282 4.3
static const size_t port_sizes[] = {4, 3, 3, 1};
uint8_t *u = ip + 40;
unsigned nhc;
if (i >= n || (p[i] & 0xf8) != 0xf0) return 0;
nhc = p[i++];
if (nhc & 4) return 0; // Elided checksum is not supported
if (i + port_sizes[nhc & 3] + 2 > n) return 0; // Ports, checksum
switch (nhc & 3) {
case 0: memcpy(u, p + i, 4), i += 4; break;
case 1: memcpy(u, p + i, 2), u[2] = 0xf0, u[3] = p[i + 2], i += 3; break;
case 2: u[0] = 0xf0, u[1] = p[i], memcpy(u + 2, p + i + 1, 2), i += 3; break;
default:
u[0] = u[2] = 0xf0, u[1] = (uint8_t) (0xb0 | (p[i] >> 4));
u[3] = (uint8_t) (0xb0 | (p[i] & 0x0f)), i += 1;
break;
}
u[4] = u[5] = 0; // UDP length, set by caller
memcpy(u + 6, p + i, 2), i += 2; // Checksum
ip[6] = 17;
*udp = true;
}
if (i > n) return 0;
*used = i;
return *udp ? 48 : 40;
}
// Set IPv6 payload length and, for compressed UDP, UDP length
static void set_lengths(uint8_t *ip, size_t len, bool udp) {
size_t plen = len - 40;
ip[4] = (uint8_t) (plen >> 8), ip[5] = (uint8_t) plen;
if (udp) ip[44] = (uint8_t) (plen >> 8), ip[45] = (uint8_t) plen;
}
void mg_l2_lowpan_init(struct mg_tcpip_if *ifp) {
struct lowpan_data *d = &ifp->l2data.lowpan;
if (is_zero(ifp->mac, 8)) {
ifp->mac[0] = 0x02; // Locally administered EUI-64
mg_random(&ifp->mac[1], 7);
MG_INFO(("EUI-64 not set. Generated random: %M", mg_print_hex, 8,
ifp->mac));
}
ifp->l2mtu = MGI_LOWPAN_MTU;
ifp->framesize = MGI_LOWPAN_MTU; // TX buffer holds an uncompressed packet
// Polling drivers receive one raw 802.15.4 frame at a time into the queue
// buffer, so it does not need to be framesize: 127 bytes max
if (ifp->recv_queue.size == 0) ifp->recv_queue.size = 128;
d->short_addr = 0xfffe;
d->buf = (uint8_t *) mg_calloc(1, MGI_LOWPAN_MTU);
if (d->buf == NULL) MG_ERROR(("OOM"));
}
bool mg_l2_lowpan_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) {
struct lowpan_data *d = &ifp->l2data.lowpan;
if (expired_1000ms && d->size != 0 && ifp->now > d->expire) d->size = 0;
return true;
}
uint8_t *mg_l2_lowpan_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto,
struct mg_l2addr *src, struct mg_l2addr *dst,
uint8_t *frame) {
struct lowpan_data *d = &ifp->l2data.lowpan;
d->proto = (uint8_t) proto;
memcpy(d->dst, dst, sizeof(d->dst));
(void) src;
return frame; // Uncompressed packet starts at the frame start
}
// Compress the packet, send all fragments but the last directly, put the
// last fragment into the TX buffer for the stack to send
size_t mg_l2_lowpan_trailer(struct mg_tcpip_if *ifp, size_t len,
uint8_t *cur) {
struct lowpan_data *d = &ifp->l2data.lowpan;
uint8_t *ip = cur - len, hdr[48], f[MGI_LOWPAN_FRAME_MAX];
const uint8_t *dst = d->dst;
size_t hlen, mlen, avail, chunk, off, n;
if (d->proto != MG_TCPIP_L2PROTO_IPV6 || len < 40 || len > MGI_LOWPAN_MTU)
return 0;
hlen = iphc_compress(ip, ifp->mac, dst, hdr);
mlen = write_mhr(ifp, dst, f);
avail = MGI_LOWPAN_FRAME_MAX - mlen;
if (hlen + len - 40 <= avail) { // Fits into one frame
memcpy(f + mlen, hdr, hlen);
memcpy(f + mlen + hlen, ip + 40, len - 40);
n = mlen + hlen + len - 40;
} else { // Fragment, RFC 4944 5.3. Offsets are in uncompressed bytes
uint16_t tag = d->tag++;
chunk = (avail - 4 - hlen) & ~(size_t) 7; // 40 + chunk: multiple of 8
f[mlen] = (uint8_t) (MGI_DISPATCH_FRAG1 | (len >> 8));
f[mlen + 1] = (uint8_t) len;
f[mlen + 2] = (uint8_t) (tag >> 8), f[mlen + 3] = (uint8_t) tag;
memcpy(f + mlen + 4, hdr, hlen);
memcpy(f + mlen + 4 + hlen, ip + 40, chunk);
n = mlen + 4 + hlen + chunk;
ifp->driver->tx(f, n, ifp);
for (off = 40 + chunk;;) {
mlen = write_mhr(ifp, dst, f);
chunk = len - off;
if (chunk > ((avail - 5) & ~(size_t) 7)) chunk = (avail - 5) & ~(size_t) 7;
f[mlen] = (uint8_t) (MGI_DISPATCH_FRAGN | (len >> 8));
f[mlen + 1] = (uint8_t) len;
f[mlen + 2] = (uint8_t) (tag >> 8), f[mlen + 3] = (uint8_t) tag;
f[mlen + 4] = (uint8_t) (off / 8);
memcpy(f + mlen + 5, ip + off, chunk);
n = mlen + 5 + chunk;
off += chunk;
if (off >= len) break;
ifp->driver->tx(f, n, ifp);
}
}
memcpy(ifp->tx.buf, f, n);
return n;
}
bool mg_l2_lowpan_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
struct mg_str *pay, struct mg_str *raw) {
struct lowpan_data *d = &ifp->l2data.lowpan;
uint8_t *p = (uint8_t *) raw->buf;
size_t n = raw->len, hlen, used, size, off;
struct lowpan_mhr h;
bool udp;
if (d->buf == NULL || !parse_mhr(p, n, &h)) return false;
if (h.dst_pan != d->pan_id && h.dst_pan != 0xffff) return false;
if (!is_bcast(h.dst) && memcmp(h.dst, ifp->mac, 8) != 0 &&
!(is_short(h.dst) && d->short_addr != 0xfffe &&
MG_LOAD_BE16(h.dst + 6) == d->short_addr))
return false;
if (h.secured) return false; // No link-layer security
p += h.len, n -= h.len;
if (n < 1) return false;
if ((p[0] & 0xf8) == MGI_DISPATCH_FRAG1 || (p[0] & 0xf8) == MGI_DISPATCH_FRAGN) {
bool first = (p[0] & 0xf8) == MGI_DISPATCH_FRAG1;
uint16_t tag;
if (n < (first ? 4U : 5U)) return false;
size = (size_t) (((p[0] & 7) << 8) | p[1]);
tag = (uint16_t) ((p[2] << 8) | p[3]);
if (size < 40 || size > MGI_LOWPAN_MTU) return false;
if (first) {
p += 4, n -= 4;
hlen = iphc_decompress(&h, p, n, d->buf, &used, &udp);
if (hlen == 0 || hlen + n - used > size) return false;
memcpy(d->buf + hlen, p + used, n - used);
d->size = (uint16_t) size, d->tag_rx = tag, d->udp = udp;
d->received = (uint16_t) (hlen + n - used);
memcpy(d->src, h.src, sizeof(d->src));
d->expire = ifp->now + MGI_LOWPAN_REASM_MS;
} else {
off = (size_t) p[4] * 8;
p += 5, n -= 5;
if (d->size != size || d->tag_rx != tag ||
memcmp(d->src, h.src, sizeof(d->src)) != 0 || off + n > size)
return false; // Not the datagram being reassembled
memcpy(d->buf + off, p, n);
d->received = (uint16_t) (d->received + n);
}
if (d->size == 0 || d->received < d->size) return false;
set_lengths(d->buf, d->size, d->udp);
pay->buf = (char *) d->buf, pay->len = d->size;
d->size = 0;
} else {
hlen = iphc_decompress(&h, p, n, d->buf, &used, &udp);
if (hlen == 0 || hlen + n - used > MGI_LOWPAN_MTU) return false;
memcpy(d->buf + hlen, p + used, n - used);
set_lengths(d->buf, hlen + n - used, udp);
pay->buf = (char *) d->buf, pay->len = hlen + n - used;
}
*proto = MG_TCPIP_L2PROTO_IPV6;
return true;
}
struct mg_l2addr *mg_l2_lowpan_getaddr(struct mg_tcpip_if *ifp,
uint8_t *frame) {
struct lowpan_mhr h;
memset(&s_rxsrc, 0, sizeof(s_rxsrc));
if (parse_mhr(frame, MGI_LOWPAN_FRAME_MAX, &h))
memcpy(&s_rxsrc, h.src, sizeof(s_rxsrc));
(void) ifp;
return &s_rxsrc;
}
extern struct mg_l2addr s_mapip;
// All broadcast and multicast goes to the 802.15.4 broadcast address
struct mg_l2addr *mg_l2_lowpan_mapip(enum mg_l2addrtype addrtype,
struct mg_addr *addr) {
set_short((uint8_t *) &s_mapip, 0xffff);
(void) addrtype, (void) addr;
return &s_mapip;
}
bool mg_l2_lowpan_genip6(uint64_t *ip6, uint8_t prefix_len,
struct mg_l2addr *l2addr) {
if (prefix_len > 64) {
MG_ERROR(("Prefix length > 64, UNSUPPORTED"));
return false;
}
ip6[0] = 0;
make_iid((uint8_t *) l2addr, (uint8_t *) &ip6[1]); // RFC 6282 3.2.2
return true;
}
// Link-layer address of a neighbour from its IPv6 address: its interface ID
// is made from it, RFC 6282 3.2.2. make_iid() is its own inverse
void mg_l2_lowpan_ip6l2(const uint64_t *ip6, uint8_t *l2) {
make_iid((const uint8_t *) &ip6[1], l2);
}
// Link-layer address option, RFC 4944 8: length 2 for EUI-64, 1 for short
bool mg_l2_lowpan_ip6get(struct mg_l2addr *l2addr, uint8_t *opts,
uint8_t len) {
if (len == 2) {
memcpy(l2addr, opts, 8);
} else if (len == 1) {
set_short((uint8_t *) l2addr, (uint16_t) ((opts[0] << 8) | opts[1]));
} else {
return false;
}
return true;
}
uint8_t mg_l2_lowpan_ip6put(struct mg_l2addr *l2addr, uint8_t *opts) {
uint8_t *a = (uint8_t *) l2addr;
if (is_short(a)) {
opts[0] = a[6], opts[1] = a[7];
return 1;
}
memcpy(opts, a, 8);
return 2;
}
#elif MG_ENABLE_TCPIP
// No IPv6: 6LoWPAN is not available. Stubs for the L2 dispatch tables
extern struct mg_l2addr s_mapip;
void mg_l2_lowpan_init(struct mg_tcpip_if *ifp) {
MG_ERROR(("6LoWPAN needs MG_ENABLE_IPV6"));
(void) ifp;
}
bool mg_l2_lowpan_poll(struct mg_tcpip_if *ifp, bool expired_1000ms) {
(void) ifp, (void) expired_1000ms;
return false; // Link is never up
}
uint8_t *mg_l2_lowpan_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto,
struct mg_l2addr *src, struct mg_l2addr *dst,
uint8_t *frame) {
(void) ifp, (void) proto, (void) src, (void) dst;
return frame;
}
size_t mg_l2_lowpan_trailer(struct mg_tcpip_if *ifp, size_t len,
uint8_t *cur) {
(void) ifp, (void) len, (void) cur;
return 0; // Nothing to send
}
bool mg_l2_lowpan_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
struct mg_str *pay, struct mg_str *raw) {
(void) ifp, (void) proto, (void) pay, (void) raw;
return false;
}
struct mg_l2addr *mg_l2_lowpan_getaddr(struct mg_tcpip_if *ifp,
uint8_t *frame) {
(void) ifp, (void) frame;
return &s_mapip;
}
struct mg_l2addr *mg_l2_lowpan_mapip(enum mg_l2addrtype addrtype,
struct mg_addr *addr) {
(void) addrtype, (void) addr;
return &s_mapip;
}
#endif
+1 -7
View File
@@ -211,13 +211,7 @@ struct mg_connection *mg_connect_svc(struct mg_mgr *mgr, const char *url,
if (url == NULL || url[0] == '\0') {
MG_ERROR(("null url"));
#if MG_ENABLE_TCPIP
} else if (mgr->ifp != NULL && mgr->ifp->state != MG_TCPIP_STATE_READY
#if MG_ENABLE_IPV6
// IPv6-only links: a link-local address is enough for local peers
&& (mgr->ifp->state6 < MG_TCPIP_STATE_UP ||
mgr->ifp->ip6ll[0] == 0)
#endif
) {
} else if (mgr->ifp != NULL && mgr->ifp->state != MG_TCPIP_STATE_READY) {
MG_ERROR(("Network is down"));
#endif
} else if ((c = mg_alloc_conn(mgr)) == NULL) {
+19 -262
View File
@@ -1,7 +1,6 @@
#include "net_builtin.h"
#include "bsd.h"
#include "profile.h"
#include "url.h"
#if MG_ENABLE_TCPIP
#define MG_EPHEMERAL_PORT_BASE 32768
@@ -970,7 +969,7 @@ static void tx_ndp_rs(struct mg_tcpip_if *ifp) {
(struct mg_addr *) &ip6_allrouters),
ifp->ip6ll, (uint64_t *) ip6_allrouters.addr.ip6, 133, 0, payload,
payload_len);
MG_VERBOSE(("NDP Router Solicitation sent"));
MG_DEBUG(("NDP Router Solicitation sent"));
}
static void fill_prefix(uint8_t *dst, uint8_t *src, uint8_t len) {
@@ -1009,10 +1008,9 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
uint8_t *opts = (uint8_t *) (ra + 1);
size_t opt_left = pkt->pay.len - sizeof(*ra);
bool gotl2addr = false, gotprefix = false, changed = false;
bool ip6only = ifp->ip == 0 && !ifp->enable_dhcp_client;
uint8_t l2[sizeof(struct mg_l2addr)];
uint32_t mtu = 0;
uint8_t *prefix = NULL, prefix_len = 0, *dns = NULL;
uint8_t *prefix = NULL, prefix_len = 0;
if (pkt->pay.len < sizeof(*ra)) return;
if (ifp->state6 == MG_TCPIP_STATE_UP) {
@@ -1048,21 +1046,11 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (prefix_len != ifp->prefix_len ||
!match_prefix(prefix, ifp->prefix, ifp->prefix_len))
changed = true;
} else if (type == 38 && length >= 16 && ip6only && (opts[3] & 7) == 0) {
memcpy(ifp->nat64, opts + 4, sizeof(ifp->nat64)); // RFC-8781, /96
} else if (type == 25 && length >= 24 && ip6only) {
dns = opts + 8; // DNS server, RFC-8106
}
opts += length;
opt_left -= length;
}
if (dns != NULL && ifp->dns6_url == NULL &&
(ifp->dns6_url = (char *) mg_calloc(1, 64)) != NULL) { // First wins
mg_snprintf(ifp->dns6_url, 64, "udp://%M:53", mg_print_ip6, dns);
ifp->mgr->dns6.url = ifp->dns6_url, ifp->mgr->use_dns6 = true;
if (ifp->mgr->dns6.c != NULL) mg_close_conn(ifp->mgr->dns6.c);
}
// fill prefix and global
if (gotprefix && !fill_global(ifp, prefix, prefix_len)) return;
if (changed) ifp->is_ip6_changed = true;
@@ -1088,208 +1076,6 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
}
}
// Routing between two interfaces, see mg_tcpip_route()
void mg_l2_lowpan_ip6l2(const uint64_t *ip6, uint8_t *l2);
// Interface with a user-set IPv6 prefix, which the router advertises
static bool has_static_prefix(struct mg_tcpip_if *ifp) {
return ifp->route != NULL && !ifp->enable_slaac && ifp->prefix_len > 0 &&
ifp->prefix_len <= 64 && ifp->ip6[0] != 0;
}
// Router Advertisement, 4.2: our static prefix (on-link, SLAAC, we are the
// default router), and a route to the other interface's static prefix
static void tx_ndp_ra(struct mg_tcpip_if *ifp) {
struct mg_tcpip_if *peer = ifp->route;
uint8_t buf[sizeof(struct ndp_ra) + 16 + 32 + 16 + 16 + 24], *o;
struct ndp_ra *ra = (struct ndp_ra *) buf;
bool own = has_static_prefix(ifp), other = has_static_prefix(peer);
bool nat64 = own && (ifp->nat64[0] | ifp->nat64[1]) != 0; // Prefix set
size_t n = sizeof(*ra);
if (!own && !other) return;
memset(buf, 0, sizeof(buf));
ra->cur_hop_limit = 64;
ra->router_lifetime = mg_htons(own ? 1800 : 0);
buf[n] = 1; // Source link-layer address, 4.6.1
buf[n + 1] = mg_l2_ip6put(ifp->l2type, ifp->mac, buf + n + 2);
n += (size_t) (8 * buf[n + 1]);
if (own) { // Prefix information, 4.6.2
o = buf + n, n += 32;
o[0] = 3, o[1] = 4, o[2] = ifp->prefix_len, o[3] = 0xc0; // L, A
MG_STORE_BE32(o + 4, 86400); // Valid lifetime
MG_STORE_BE32(o + 8, 14400); // Preferred lifetime
memcpy(o + 16, ifp->prefix, sizeof(ifp->prefix));
}
if (other) { // Route information, RFC-4191 2.3
o = buf + n, n += 16;
o[0] = 24, o[1] = 2, o[2] = peer->prefix_len;
MG_STORE_BE32(o + 4, 1800); // Route lifetime
memcpy(o + 8, peer->prefix, sizeof(peer->prefix));
}
if (nat64) { // We translate to IPv4: NAT64 prefix and a DNS server
struct mg_addr a;
o = buf + n, n += 16; // NAT64 prefix, RFC-8781
o[0] = 38, o[1] = 2;
MG_STORE_BE16(o + 2, (1800 / 8) << 3); // Lifetime in 8s units, PLC 0: /96
memcpy(o + 4, ifp->nat64, sizeof(ifp->nat64));
o = buf + n, n += 24; // DNS server, RFC-8106: NAT64 prefix + IPv4 DNS
o[0] = 25, o[1] = 3;
MG_STORE_BE32(o + 4, 1800); // Lifetime
memcpy(o + 8, ifp->nat64, sizeof(ifp->nat64));
if (!mg_aton(mg_url_host(peer->mgr->dns4.url), &a) || a.is_ip6)
a.addr.ip4 = MG_U32(8, 8, 8, 8);
memcpy(o + 20, &a.addr.ip4, 4);
}
tx_icmp6(ifp,
mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6,
(struct mg_addr *) &ip6_allnodes),
ifp->ip6ll, (uint64_t *) ip6_allnodes.addr.ip6, 134, 0, buf, n);
}
// Remember the hardware address of a sender, to forward replies to it
static void nb6_learn(struct mg_tcpip_if *ifp, uint64_t *ip, uint8_t *l2) {
size_t i, n = sizeof(ifp->nb6) / sizeof(ifp->nb6[0]);
for (i = 0; i < n && !MG_IP6MATCH(ifp->nb6[i].ip, ip); i++) (void) 0;
if (i == n) i = ifp->nb6_next, ifp->nb6_next = (uint8_t) ((i + 1) % n);
ifp->nb6[i].ip[0] = ip[0], ifp->nb6[i].ip[1] = ip[1];
memcpy(ifp->nb6[i].l2, l2, sizeof(ifp->nb6[i].l2));
}
// Hardware address to send a forwarded packet to: a learned neighbour, the
// 6LoWPAN address made from the interface ID, or our default router
static bool nb6_l2(struct mg_tcpip_if *ifp, uint64_t *ip, uint8_t *l2) {
size_t i, n = sizeof(ifp->nb6) / sizeof(ifp->nb6[0]);
for (i = 0; i < n && !MG_IP6MATCH(ifp->nb6[i].ip, ip); i++) (void) 0;
if (i < n) {
memcpy(l2, ifp->nb6[i].l2, sizeof(ifp->nb6[i].l2));
} else if (ifp->l2type == MG_TCPIP_L2_LOWPAN) {
mg_l2_lowpan_ip6l2(ip, l2);
} else if (ifp->gw6_ready) {
memcpy(l2, ifp->gw6mac, sizeof(ifp->gw6mac));
} else {
return false;
}
return true;
}
// NAT64, RFC-6146: translate a TCP or UDP packet sent to our nat64 prefix to
// IPv4, send it from the other interface. Sessions are kept in ifp->nat64s,
// when it is full the oldest one is replaced
static void nat64_out(struct mg_tcpip_if *ifp, struct pkt *pkt, size_t len) {
struct mg_tcpip_if *out = ifp->route;
struct mg_tcpip_nat64 *e = NULL;
struct ip *ip;
uint64_t src[2], dst[2];
uint8_t *p, next = pkt->ip6->next;
size_t i, n = sizeof(ifp->nat64s) / sizeof(ifp->nat64s[0]);
size_t plen = len - sizeof(*pkt->ip6);
struct tcp *tcp = (struct tcp *) (pkt->ip6 + 1);
struct udp *udp = (struct udp *) tcp; // Ports are at the same place
uint32_t dst4;
if ((next != 6 && next != 17) ||
plen < (next == 6 ? sizeof(*tcp) : sizeof(*udp)) ||
sizeof(*ip) + plen > out->mtu || out->state != MG_TCPIP_STATE_READY)
return;
src[0] = pkt->ip6->src[0], src[1] = pkt->ip6->src[1];
dst[0] = pkt->ip6->dst[0], dst[1] = pkt->ip6->dst[1];
memcpy(&dst4, (uint8_t *) dst + 12, sizeof(dst4)); // RFC-6052, /96
for (i = 0; i < n; i++) {
e = &ifp->nat64s[i];
if (e->proto == next && e->port6 == udp->sport &&
MG_IP6MATCH(e->ip6, src) && e->port4 == udp->dport && e->ip4 == dst4)
break;
}
if (i == n) { // New session
e = &ifp->nat64s[ifp->nat64_next];
ifp->nat64_next = (uint8_t) ((ifp->nat64_next + 1) % n);
if (out->eport < MG_EPHEMERAL_PORT_BASE) out->eport = MG_EPHEMERAL_PORT_BASE;
e->ip6[0] = src[0], e->ip6[1] = src[1], e->ip4 = dst4, e->proto = next;
e->port6 = udp->sport, e->port4 = udp->dport;
e->lport = mg_htons(out->eport++);
MG_DEBUG(("NAT64 %M:%hu -> %M:%hu", mg_print_ip6, src,
mg_ntohs(udp->sport), mg_print_ip4, &dst4, mg_ntohs(udp->dport)));
}
ip = tx_ip(out, out->gwmac, next, 0, out->ip, dst4, plen);
p = (uint8_t *) (ip + 1);
memcpy(p, tcp, plen);
tcp = (struct tcp *) p, udp = (struct udp *) p;
udp->sport = e->lport;
if (next == 6) tcp->csum = 0, tcp->csum = pcsum(ip, p, plen);
if (next == 17) udp->csum = 0, udp->csum = pcsum(ip, p, plen);
driver_output(out, mg_l2_trailer(out, sizeof(*ip) + plen, p + plen));
}
// NAT64: translate an IPv4 packet of a NAT64 session back to IPv6, send it to
// the node. Return true if the packet was taken
static bool nat64_in(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct mg_tcpip_if *out = ifp->route;
struct mg_tcpip_nat64 *e = NULL;
struct udp *udp = pkt->udp != NULL ? pkt->udp : (struct udp *) pkt->tcp;
struct ip6 *ip6;
uint64_t src[2];
uint8_t l2[sizeof(struct mg_l2addr)], *p, next = pkt->ip->proto;
size_t i, n = sizeof(ifp->nat64s) / sizeof(ifp->nat64s[0]);
size_t plen = PDIFF(udp, pkt->pay.buf + pkt->pay.len);
if (out == NULL || pkt->ip->dst != ifp->ip) return false;
for (i = 0; i < n; i++) {
e = &out->nat64s[i];
if (e->proto == next && e->lport == udp->dport &&
e->port4 == udp->sport && e->ip4 == pkt->ip->src)
break;
}
if (i == n) return false;
if (sizeof(*ip6) + plen > out->mtu || !nb6_l2(out, e->ip6, l2))
return true; // Drop
memcpy(src, out->nat64, sizeof(out->nat64));
memcpy((uint8_t *) src + 12, &pkt->ip->src, 4);
ip6 = tx_ip6(out, l2, next, 0, src, e->ip6, plen);
p = (uint8_t *) (ip6 + 1);
memcpy(p, udp, plen);
((struct udp *) p)->dport = e->port6;
if (next == 6) {
struct tcp *tcp = (struct tcp *) p;
tcp->csum = 0, tcp->csum = p6csum(ip6, p, 6, plen);
} else {
udp = (struct udp *) p;
udp->csum = 0, udp->csum = p6csum(ip6, p, 17, plen);
if (udp->csum == 0) udp->csum = 0xffff; // 0 is not allowed in IPv6
}
driver_output(out, mg_l2_trailer(out, sizeof(*ip6) + plen, p + plen));
return true;
}
// Forward a packet that is not addressed to us out the other interface.
// Return true if the packet was taken: forwarded or dropped
static bool fwd_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt, size_t len) {
struct mg_tcpip_if *out = ifp->route;
uint64_t src[2], dst[2];
uint8_t l2[sizeof(struct mg_l2addr)], *d = (uint8_t *) dst, *p;
src[0] = pkt->ip6->src[0], src[1] = pkt->ip6->src[1];
dst[0] = pkt->ip6->dst[0], dst[1] = pkt->ip6->dst[1];
if (d[0] == 0xff || (d[0] == 0xfe && (d[1] & 0xc0) == 0x80))
return false; // Multicast or link-local: not forwarded
if (MG_IP6MATCH(dst, ifp->ip6) || MG_IP6MATCH(dst, ifp->ip6ll))
return false; // Ours
if (src[0] != 0 || src[1] != 0) {
nb6_learn(ifp, src, mg_l2_getaddr(ifp, pkt->l2));
}
if ((ifp->nat64[0] | ifp->nat64[1]) != 0 && // NAT64 prefix set, and ours
memcmp(dst, ifp->nat64, sizeof(ifp->nat64)) == 0) {
nat64_out(ifp, pkt, len);
return true;
}
if (MG_IP6MATCH(dst, out->ip6) || out->state6 < MG_TCPIP_STATE_UP ||
pkt->ip6->hops <= 1 || len > out->mtu || !nb6_l2(out, dst, l2)) {
return true; // Drop. TODO(): ICMPv6 errors, RFC-4443
}
p = mg_l2_header(out, MG_TCPIP_L2PROTO_IPV6, out->mac, l2,
(uint8_t *) out->tx.buf);
memcpy(p, pkt->ip6, len);
((struct ip6 *) p)->hops--;
driver_output(out, mg_l2_trailer(out, len, p + len));
return true;
}
static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (!icmp6csum_ok(pkt->ip6, pkt->icmp6, sizeof(*pkt->icmp6) + pkt->pay.len))
return;
@@ -1313,9 +1099,6 @@ static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
tx_icmp6(ifp, l2addr, target, ips.addr.ip6, 129, 0, pkt->pay.buf, plen);
}
} break;
case 133: // Router Solicitation: answer if we route
if (ifp->route != NULL) tx_ndp_ra(ifp);
break;
case 134: // Router Advertisement
rx_ndp_ra(ifp, pkt);
break;
@@ -1577,9 +1360,9 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn,
if (lsn->loc.is_ip6) {
c->rem.addr.ip6[0] = pkt->ip6->src[0],
c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true;
// Reply from the address the peer connected to: link-local or global
c->loc.addr.ip6[0] = pkt->ip6->dst[0],
c->loc.addr.ip6[1] = pkt->ip6->dst[1], c->loc.is_ip6 = true;
c->loc.addr.ip6[0] = c->mgr->ifp->ip6[0],
c->loc.addr.ip6[1] = c->mgr->ifp->ip6[1], c->loc.is_ip6 = true;
// TODO(): compare lsn to link-local, or rem as link-local: use ll instead
} else
#endif
{
@@ -2096,9 +1879,6 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
#if MG_ENABLE_IPV6
if (nat64_in(ifp, pkt)) return;
#endif
if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68) &&
len >= offsetof(struct dhcp, options)) {
pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
@@ -2124,9 +1904,6 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
#if MG_ENABLE_IPV6
if (nat64_in(ifp, pkt)) return;
#endif
rx_tcp(ifp, pkt);
} else {
MG_DEBUG(("Unknown IP proto %x", (int) pkt->ip->proto));
@@ -2144,8 +1921,6 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if ((pkt->ip6->ver >> 4) != 0x6) return; // Not IPv6
plen = mg_ntohs(pkt->ip6->plen);
if (plen > (pkt->pay.len - sizeof(*pkt->ip6))) return; // malformed
if (ifp->route != NULL && fwd_ip6(ifp, pkt, sizeof(*pkt->ip6) + plen))
return;
next = pkt->ip6->next;
nhdr = (uint8_t *) (pkt->ip6 + 1);
while (loop) {
@@ -2155,7 +1930,7 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
case 43: // Routing 4.4
case 60: // Destination Options 4.6
case 51: // Authentication RFC-4302
MG_DEBUG(("IPv6 extension header %d", (int) next));
MG_INFO(("IPv6 extension header %d", (int) next));
if (((uint32_t) len + 2) > plen) return; // nhdr[0, 1]; malformed
next = nhdr[0];
hlen = (uint16_t) (8 * (nhdr[1] + 1));
@@ -2188,8 +1963,8 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
pkt->icmp6 = (struct icmp6 *) (pkt->pay.buf);
if (pkt->pay.len < sizeof(*pkt->icmp6)) return;
mkpay(pkt, pkt->icmp6 + 1);
MG_VERBOSE(("ICMPv6 %M -> %M len %u", mg_print_ip6, &pkt->ip6->src,
mg_print_ip6, &pkt->ip6->dst, (int) pkt->pay.len));
MG_DEBUG(("ICMPv6 %M -> %M len %u", mg_print_ip6, &pkt->ip6->src,
mg_print_ip6, &pkt->ip6->dst, (int) pkt->pay.len));
rx_icmp6(ifp, pkt);
} else if (next == 17) {
pkt->udp = (struct udp *) (pkt->pay.buf);
@@ -2199,9 +1974,9 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return; // malformed
pkt->pay.len = len; // strip excess data
mkpay(pkt, pkt->udp + 1);
MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src,
mg_ntohs(pkt->udp->sport), mg_print_ip6, &pkt->ip6->dst,
mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
MG_DEBUG(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src,
mg_ntohs(pkt->udp->sport), mg_print_ip6, &pkt->ip6->dst,
mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
if (ifp->enable_dhcp6_client && pkt->udp->dport == mg_htons(546)) {
pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1);
mkpay(pkt, pkt->dhcp6 + 1);
@@ -2223,9 +1998,9 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (off < sizeof(*pkt->tcp) / 4 || pkt->pay.len < (uint16_t) (4 * off))
return;
mkpay(pkt, (uint32_t *) pkt->tcp + off);
MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src,
mg_ntohs(pkt->tcp->sport), mg_print_ip6, &pkt->ip6->dst,
mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
MG_DEBUG(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src,
mg_ntohs(pkt->tcp->sport), mg_print_ip6, &pkt->ip6->dst,
mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
rx_tcp(ifp, pkt);
} else {
MG_DEBUG(("Unknown IPv6 next hdr %x", (int) next));
@@ -2303,18 +2078,13 @@ static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) {
if (ifp->state6 < MG_TCPIP_STATE_UP) return;
if (ifp->enable_slaac && s1 && ifp->state6 == MG_TCPIP_STATE_UP)
tx_ndp_rs(ifp);
if (ifp->route != NULL && s1 && ifp->now >= ifp->ra_expire) {
tx_ndp_ra(ifp); // Unsolicited router advertisements, 6.2.4
ifp->ra_expire = ifp->now + 10000;
}
}
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 || ifp->l2type == MG_TCPIP_L2_LOWPAN) &&
l2_up && ifp->ip6ll[0] == 0 &&
if (ifp->l2type == MG_TCPIP_L2_ETH && l2_up && ifp->ip6ll[0] == 0 &&
ifp->ip6ll[1] == 0) { // gen ll address
mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac);
memcpy(ifp->ip6ll, px, 8); // RFC-2464 5
@@ -2506,8 +2276,8 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
ifp->mgr = mgr;
mgr->extraconnsize = sizeof(struct connstate);
if (ifp->ip == 0 && ifp->l2type != MG_TCPIP_L2_PPP &&
ifp->l2type != MG_TCPIP_L2_PPPoE && ifp->l2type != MG_TCPIP_L2_LOWPAN)
ifp->enable_dhcp_client = true; // No IPv4 over 6LoWPAN
ifp->l2type != MG_TCPIP_L2_PPPoE)
ifp->enable_dhcp_client = true;
mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535
ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
// MG_EPHEMERAL_PORT_BASE to 65535
@@ -2529,18 +2299,8 @@ void mg_tcpip_free(struct mg_tcpip_if *ifp) {
mg_free(ifp->recv_queue.buf);
mg_free(ifp->tx.buf);
mg_free(ifp->dns4_url);
#if MG_ENABLE_IPV6
mg_free(ifp->dns6_url);
#endif
}
#if MG_ENABLE_IPV6
void mg_tcpip_route(struct mg_tcpip_if *if1, struct mg_tcpip_if *if2) {
if1->route = if2, if2->route = if1;
if1->ra_expire = if2->ra_expire = 0; // Advertise at the next poll
}
#endif
static void send_syn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1);
uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
@@ -2714,13 +2474,10 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
struct mg_tcpip_if *ifp = c->mgr->ifp;
bool res = false;
// While resolving, the address family is unknown yet: just queue the data
if (!c->is_resolving && !c->loc.is_ip6 &&
(ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY)) {
if (!c->loc.is_ip6 && (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY)) {
proto_error(c, false, "net down");
#if MG_ENABLE_IPV6
} else if (!c->is_resolving && c->loc.is_ip6 &&
ifp->state6 != MG_TCPIP_STATE_READY) {
} else if (c->loc.is_ip6 && ifp->state6 != MG_TCPIP_STATE_READY) {
proto_error(c, false, "net down");
#endif
} else if (c->is_udp && (c->is_arplooking || c->is_resolving)) {
+33 -36
View File
@@ -44,24 +44,6 @@ enum {
};
// clang-format on
#if MG_ENABLE_IPV6
// IPv6 neighbour learned from forwarded packets, see mg_tcpip_route()
struct mg_tcpip_nb6 {
uint64_t ip[2]; // Neighbour IPv6 address
uint8_t l2[sizeof(struct mg_l2addr)]; // Its hardware address
};
// NAT64 session, see mg_tcpip_if::nat64
struct mg_tcpip_nat64 {
uint64_t ip6[2]; // Node's IPv6 address
uint32_t ip4; // IPv4 server address
uint16_t port6; // Node's port
uint16_t port4; // IPv4 server port
uint16_t lport; // Our port on the IPv4 interface
uint8_t proto; // 6: TCP, 17: UDP, 0: unused entry
};
#endif
// Network interface. Zero-initialise, then set the fields marked "user-set"
// before calling mg_tcpip_init(). Fields in the "internal state" section
// below are managed by the stack; read them freely but do not modify them.
@@ -96,8 +78,6 @@ struct mg_tcpip_if {
uint8_t prefix[8]; // Global prefix bytes
uint8_t prefix_len; // Global prefix length in bits
uint64_t gw6[2]; // IPv6 default gateway
uint8_t nat64[12]; // NAT64 /96 prefix, RFC 6052. If set, DNS asks
// for IPv4 (A) and makes IPv6 from it (DNS64)
bool enable_slaac; // Enable SLAAC (stateless address autoconfiguration)
bool enable_dhcp6_client; // Enable DHCPv6 client (not yet fully implemented)
bool is_ip6_changed; // Set by stack when IPv6 address changes
@@ -129,13 +109,6 @@ struct mg_tcpip_if {
uint8_t gw6mac[sizeof(struct mg_l2addr)]; // IPv6 gateway hardware address
uint8_t state6; // Current IPv6 link/IP state (MG_TCPIP_STATE_*)
bool gw6_ready; // IPv6 gateway MAC address has been resolved
struct mg_tcpip_if *route; // Forward to this interface, see mg_tcpip_route()
struct mg_tcpip_nb6 nb6[4]; // Neighbours learned from forwarded packets
uint8_t nb6_next; // Next nb6 entry to replace
struct mg_tcpip_nat64 nat64s[8]; // NAT64 sessions, if nat64 is set
uint8_t nat64_next; // Next nat64s entry to replace
uint64_t ra_expire; // Time of the next router advertisement, ms
char *dns6_url; // DNS server URL from router advertisement
#endif
};
@@ -149,15 +122,6 @@ void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
// Frees the TX buffer and RX queue allocated by mg_tcpip_init().
void mg_tcpip_free(struct mg_tcpip_if *);
#if MG_ENABLE_IPV6
// Forwards IPv6 packets between two interfaces: packets not addressed to us
// go out the other interface. An interface with a static IPv6 prefix (ip6,
// prefix and prefix_len set, SLAAC off) gets router advertisements for it,
// the other interface gets a route to it, RFC 4191. Call after both
// mg_tcpip_init(). Replies to the other side need its sender seen first
void mg_tcpip_route(struct mg_tcpip_if *if1, struct mg_tcpip_if *if2);
#endif
// Pushes a received raw frame into ifp->recv_queue. Used by interrupt-driven
// or DMA-based drivers (those with rx=NULL) to hand frames to the stack from
// an ISR or DMA completion callback. Drops the frame and increments ndrop if
@@ -193,6 +157,39 @@ 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.
struct mg_tcpip_spi {
-15
View File
@@ -243,21 +243,6 @@ void mg_ota_url_check(struct mg_mgr *mgr, const char *json_url,
}
}
// Uses *p as a flag: non-NULL means an OTA session is open and must be
// closed by mg_ota_end()
bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p) {
if (hm != NULL) { // Start
if (!mg_ota_begin(hm->body.len)) return false;
*p = (void *) (ptrdiff_t) 1;
return true;
} else if (data != NULL) { // Next chunk
return mg_ota_write(data->buf, data->len);
}
if (*p == NULL) return false; // End. Begin failed, nothing to finish
*p = NULL;
return mg_ota_end();
}
void mg_ota_poll(struct mg_mgr *mgr) {
static uint64_t t = 5000; // Fire first time 5 sec after boot
+1
View File
@@ -35,6 +35,7 @@
#define MG_OTA_STM32H7 2 // STM32 H7
#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core
#define MG_OTA_STM32F 4 // STM32 F7/F4/F2
#define MG_OTA_STM32C5 5 // STM32 C5, same flash controller as H5
#define MG_OTA_CH32V307 100 // WCH CH32V307
#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6
#define MG_OTA_RT1020 300 // IMXRT1020
-2
View File
@@ -172,8 +172,6 @@ bool mg_ota_end(void) {
ok = mg_ota_flash_end(&s_mg_flash_mcxw71);
s_mcxw71_size = s_mcxw71_pending = 0;
if (ok) {
// Like RW612, this overwrites the application. It does not provide
// rollback or power-loss recovery during the copy. Keep power connected.
MG_INFO(("Applying %lu bytes from staging flash", (unsigned long) size));
mg_mcxw71_apply(size);
}
+1 -1
View File
@@ -2,7 +2,7 @@
#include "log.h"
#include "ota.h"
#if MG_OTA == MG_OTA_STM32H5
#if MG_OTA == MG_OTA_STM32H5 || MG_OTA == MG_OTA_STM32C5
static bool mg_stm32h5_write(void *, const void *, size_t);
static bool mg_stm32h5_swap(void);
-4
View File
@@ -138,10 +138,6 @@ size_t mg_print_l2addr(void (*out)(char, void *), void *arg, va_list *ap) {
uint8_t *p = va_arg(*ap, uint8_t *);
return print_mac(out, arg, p);
} break;
case MG_TCPIP_L2_LOWPAN: {
uint8_t *p = va_arg(*ap, uint8_t *);
return print_ieee64(out, arg, p);
} break;
default: break;
}
return 0;
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
// See https://mongoose.ws/docs/getting-started/build-environment/
// See https://mongoose.ws/documentation/#build-options
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
+3 -1
View File
@@ -65,8 +65,10 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
}
}
extern void wifi_init(const char *ssid, const char *pass);
void app_main(void) {
void wifi_init(const char *ssid, const char *pass);
MG_HEALTH_INIT();
wifi_init(WIFI_SSID, WIFI_PASS);
struct mg_mgr mgr;
@@ -1,4 +1,6 @@
#define MG_ARCH MG_ARCH_ESP32
#define MG_ENABLE_HEALTH 1
#define MG_TLS MG_TLS_BUILTIN
//#define MG_TLS MG_TLS_MBED
+6 -14
View File
@@ -1,10 +1,10 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// IPv6 over IEEE 802.15.4 on FRDM-MCXW71, Mongoose built-in TCP/IP stack.
// Plain 6LoWPAN on channel 15, PAN ID 0xabcd. With a router on the radio
// link, e.g. the FRDM-RW612 lowpan tutorial, gets a global address and is
// reachable from the LAN. Serves pages on ports 80 and 443
// 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"
@@ -47,14 +47,6 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/ota"), NULL) &&
!mg_http_stream_body(c, hm, mg_http_ota_cb, NULL)) {
mg_http_reply(c, 400, "", "POST firmware with Content-Length\n");
c->is_draining = 1;
mg_iobuf_del(&c->recv, 0, c->recv.len);
}
} 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)) {
@@ -81,8 +73,8 @@ int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://[::]:80", http_ev_handler, NULL);
mg_http_listen(&mgr, "https://[::]:443", http_ev_handler, NULL);
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_mqtt_init(&mgr);
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
@@ -12,12 +12,12 @@
#define MG_ENABLE_CUSTOM_RANDOM 1
#define MG_ENABLE_TCPIP_TCPRTX 1 // Link can be lossy, enable retransmit
// Stable EUI-64, so the IPv6 addresses stay the same across reboots: a hash
// of the 128-bit chip unique ID, MSCM UID, locally administered
#define MG_SET_MAC_ADDRESS(mac) \
do { \
uint8_t b_[32]; \
mg_sha256(b_, (uint8_t *) MSCM->UID, sizeof(MSCM->UID)); \
(mac)[0] = 2, (mac)[1] = b_[1], (mac)[2] = b_[2], (mac)[3] = b_[3]; \
(mac)[4] = b_[4], (mac)[5] = b_[5], (mac)[6] = b_[6], (mac)[7] = b_[7]; \
// Stable MAC, so the DHCP address stays the same across reboots: a hash of
// the 128-bit chip unique ID, MSCM UID, locally administered
#define MG_SET_MAC_ADDRESS(mac) \
do { \
uint8_t b_[32]; \
mg_sha256(b_, (uint8_t *) MSCM->UID, sizeof(MSCM->UID)); \
(mac)[0] = 2, (mac)[1] = b_[1], (mac)[2] = b_[2], (mac)[3] = b_[3]; \
(mac)[4] = b_[4], (mac)[5] = b_[5]; \
} while (0)
@@ -0,0 +1,101 @@
// 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;
}
@@ -0,0 +1,10 @@
#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
@@ -1,14 +0,0 @@
#pragma once
// See https://mongoose.ws/docs/getting-started/build-environment/
#define MG_ARCH MG_ARCH_ARMGCC
#define MG_ENABLE_TCPIP 1
// Compile both drivers; main.c initialises both interfaces itself
#define MG_ENABLE_DRIVER_RW612 1 // Ethernet
#define MG_ENABLE_DRIVER_RW612_154 1 // 802.15.4 radio, 6LoWPAN
#define MG_ENABLE_TCPIP_DRIVER_INIT 0 // Do not start one in mg_mgr_init()
#define MG_ENABLE_CUSTOM_MILLIS 1
#define MG_ENABLE_CUSTOM_RANDOM 1
#define MG_TLS MG_TLS_BUILTIN
#define MG_ENABLE_TCPIP_TCPRTX 1 // Radio link can be lossy
@@ -0,0 +1,9 @@
# Nucleo-C5A3ZI Mongoose Integration
The `.ioc2` file in this directory is pre-configured. Note that after the
project generation, you should do the following to complete integraion:
1. Edit the linker script `.ld` file and increase heap and stack sizes to
0x20000 and 0x4000 respectively
2. Add Mongoose to the `main.c`, see https://mongoose.ws/docs/guides/stm32-tcpip-stack/#modify-mainc
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,46 @@
# Copyright (c) 2026 Cesanta Software Limited
# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/
MGDIR = ../../../..
CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion
CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include -Icmsis_c5/Include -DSTM32C5A3xx
CFLAGS += -mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard $(CFLAGS_EXTRA)
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nano.specs -lc -lgcc -Wl,--gc-sections
SOURCES = main.c hal.c
SOURCES += cmsis_c5/Source/startup_stm32c5a3xx.c
SOURCES += $(MGDIR)/mongoose.c
all build example: firmware.bin
firmware.elf: cmsis_core cmsis_c5 hal.h link.ld Makefile $(SOURCES) $(MGDIR)/mongoose.h mongoose_config.h
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@
firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
@echo
@echo "To flash, run 'make flash', or use STM32CubeProgrammer"
firmware.signed.bin: firmware.bin
test -f private.pem || node $(MGDIR)/resources/sign.js keygen
node $(MGDIR)/resources/sign.js sign firmware.bin
# 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 '
# If the board gets stuck: STM32_Programmer_CLI -c port=swd -e all mode=UR
flash: firmware.bin
STM32_Programmer_CLI -c port=swd -w firmware.elf mode=UR -rst
cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b v6.4.0 https://github.com/ARM-software/CMSIS_6 $@
cmsis_c5:
git clone -q -c advice.detachedHead=false --depth 1 -b 2.1.0 https://github.com/STMicroelectronics/stm32c5xx-dfp $@
clean:
rm -rf firmware.* cmsis_*
+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;
};
uint32_t SystemCoreClock = 160000000;
void SystemInit(void) { // Called automatically by startup code
hal_system_init(); // Enable FPU
hal_clock_init();
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
void ExitRun0Mode(void) {
}
// C library entry, called by the CMSIS startup code after it copies .data
// and clears .bss. See __cmsis_start() in cmsis_gcc_m.h
extern int main(void);
extern void __libc_init_array(void);
__attribute__((noreturn)) void _start(void) {
__libc_init_array();
main();
for (;;) (void) 0;
}
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) {
}
+165
View File
@@ -0,0 +1,165 @@
// Copyright (c) 2013-2026 Cesanta Software Limited
// Datasheet: RM0522, devboard manual: UM3616
// https://www.st.com/resource/en/reference_manual/rm0522-stm32c5-series-armbased-32bit-mcus-stmicroelectronics.pdf
// https://www.st.com/resource/en/user_manual/um3616-stm32c5-nucleo144-board-mb2310-stmicroelectronics.pdf
#pragma once
#include <stm32c5xx.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
extern bool hal_timer_expired(volatile uint64_t *t, uint64_t prd, uint64_t now);
extern uint64_t hal_get_tick(void);
extern size_t hal_ram_free(void);
extern size_t hal_ram_used(void);
extern uint32_t SystemCoreClock;
#define BIT(x) (1UL << (x))
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
#define PIN(bank, num) ((((bank) - 'A') << 8) | (num))
#define PINNO(pin) (pin & 255)
#define PINBANK(pin) (pin >> 8)
// Ethernet RMII pins and their alternate functions. UM3616 7.11, Table 9
#define HAL_ETH_PINS \
{PIN('A', 1), 10}, /* REF_CLK */ \
{PIN('C', 1), 10}, /* MDC */ \
{PIN('E', 12), 10}, /* MDIO */ \
{PIN('D', 1), 10}, /* CRS_DV */ \
{PIN('C', 4), 12}, /* RXD0 */ \
{PIN('C', 5), 13}, /* RXD1 */ \
{PIN('G', 11), 10}, /* TX_EN */ \
{PIN('G', 13), 10}, /* TXD0 */ \
{PIN('G', 12), 10} /* TXD1 */
// System clock (9.4.2, 9.4.8). HSI runs at 144 MHz, the maximum system clock:
// SYSCLK = HSIS, no PLL. hclk = SYSCLK, APB clocks = hclk. After reset, the
// system runs on HSIDIV3, 48 MHz. Flash latency for 144 MHz: 6.3.6, Table 20
#define SYS_FREQUENCY 144000000
#define AHB_FREQUENCY SYS_FREQUENCY
#define APB2_FREQUENCY AHB_FREQUENCY
#define APB1_FREQUENCY AHB_FREQUENCY
#define HAL_FLASH_LATENCY (4 | FLASH_ACR_WRHIGHFREQ_1) // 4 WS, WRHIGHFREQ 2
enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_MODE_AF, HAL_GPIO_MODE_ANALOG };
enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN };
enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_MEDIUM, HAL_GPIO_SPEED_HIGH, HAL_GPIO_SPEED_INSANE };
enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN };
#define HAL_GPIO(N) ((GPIO_TypeDef *) (GPIOA_BASE + 0x400 * (N)))
static GPIO_TypeDef *hal_gpio_bank(uint16_t pin) {
return HAL_GPIO(PINBANK(pin));
}
static inline void hal_gpio_toggle(uint16_t pin) {
GPIO_TypeDef *gpio = hal_gpio_bank(pin);
uint32_t mask = BIT(PINNO(pin));
gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0);
}
static inline int hal_gpio_read(uint16_t pin) {
return hal_gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0;
}
static inline void hal_gpio_write(uint16_t pin, bool val) {
GPIO_TypeDef *gpio = hal_gpio_bank(pin);
gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16);
}
static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type,
uint8_t speed, uint8_t pull, uint8_t af) {
GPIO_TypeDef *gpio = hal_gpio_bank(pin);
uint8_t n = (uint8_t) (PINNO(pin));
RCC->AHB2ENR |= BIT(PINBANK(pin)); // Enable GPIO clock
CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t) type) << n);
CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t) speed) << (n * 2));
CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t) pull) << (n * 2));
CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4),
((uint32_t) af) << ((n & 7) * 4));
CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t) mode) << (n * 2));
}
static inline void hal_gpio_input(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 0);
}
static inline void hal_gpio_output(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 0);
}
// USART1 kernel clock defaults to pclk2 (APB2), USART2,3 default to pclk1
// (APB1), see RCC_CCIPR1
static inline void hal_uart_init(USART_TypeDef *uart, uint16_t tx,
uint16_t rx, unsigned long baud) {
uint8_t af = 7; // Alternate function
uint32_t freq = 0; // Bus frequency. UART1 is on APB2, rest on APB1
if (uart == USART1) freq = APB2_FREQUENCY, RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
if (uart == USART2) freq = APB1_FREQUENCY, RCC->APB1LENR |= RCC_APB1LENR_USART2EN;
if (uart == USART3) freq = APB1_FREQUENCY, RCC->APB1LENR |= RCC_APB1LENR_USART3EN;
hal_gpio_init(tx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af);
hal_gpio_init(rx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af);
uart->CR1 = 0; // Disable this UART
uart->BRR = freq / baud; // Set baud rate
uart->CR1 = BIT(0) | BIT(2) | BIT(3); // Set UE, RE, TE
}
static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) {
uart->TDR = byte;
while ((uart->ISR & USART_ISR_TXE_TXFNF) == 0) (void) 0;
}
static inline void hal_uart_write_buf(USART_TypeDef *uart, char *buf, size_t len) {
while (len-- > 0) hal_uart_write_byte(uart, *(uint8_t *) buf++);
}
static inline int hal_uart_read_ready(USART_TypeDef *uart) {
return uart->ISR & BIT(5); // If RXNE bit is set, data is ready
}
static inline uint8_t hal_uart_read_byte(USART_TypeDef *uart) {
return (uint8_t) (uart->RDR & 255);
}
// RNG clock (rng_clk) is CK48, which is disabled after reset. Use HSIDIV3,
// 48 MHz. It stays enabled after the system clock switches to HSIS
static inline void hal_rng_init(void) {
CLRSET(RCC->CCIPR2, RCC_CCIPR2_CK48SEL_Msk, RCC_CCIPR2_CK48SEL_1);
RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; // Enable RNG AHB clock
RNG->CR |= RNG_CR_RNGEN; // Enable RNG
}
static inline uint32_t hal_rng_read(void) {
while ((RNG->SR & RNG_SR_DRDY) == 0) (void) 0;
return RNG->DR;
}
static inline void hal_ethernet_init(void) {
// Initialise Ethernet. Enable MAC GPIO pins, see UM3616 Table 9
uint16_t pins[][2] = {HAL_ETH_PINS};
for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) {
hal_gpio_init(pins[i][0], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_INSANE,
HAL_GPIO_PULL_NONE, (uint8_t) pins[i][1]);
}
NVIC_EnableIRQ(ETH1_IRQn); // Setup Ethernet IRQ handler
RCC->APB3ENR |= RCC_APB3ENR_SBSEN; // Enable SBS clock
CLRSET(SBS->PMCR, SBS_PMCR_ETH1_SEL_PHY_Msk, SBS_PMCR_ETH1_SEL_PHY_2); // RMII
RCC->AHB1ENR |= RCC_AHB1ENR_ETH1CKEN | RCC_AHB1ENR_ETH1EN |
RCC_AHB1ENR_ETH1TXEN | RCC_AHB1ENR_ETH1RXEN;
}
static inline void hal_clock_init(void) {
CLRSET(FLASH->ACR, FLASH_ACR_LATENCY_Msk | FLASH_ACR_WRHIGHFREQ_Msk,
HAL_FLASH_LATENCY);
while ((FLASH->ACR & FLASH_ACR_LATENCY_Msk) != (HAL_FLASH_LATENCY & 15)) (void) 0;
RCC->CR1 |= RCC_CR1_HSISON; // Enable HSIS, 144 MHz
while ((RCC->CR1 & RCC_CR1_HSISRDY) == 0) (void) 0; // Wait until ready
CLRSET(RCC->CFGR1, RCC_CFGR1_SW_Msk, RCC_CFGR1_SW_0); // SYSCLK = HSIS
while ((RCC->CFGR1 & RCC_CFGR1_SWS_Msk) != RCC_CFGR1_SWS_0) (void) 0;
SystemCoreClock = SYS_FREQUENCY; // Update SystemCoreClock global var
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
static inline void hal_system_init(void) {
SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU
__DSB();
__ISB();
}
@@ -0,0 +1,37 @@
ENTRY(Reset_Handler);
MEMORY {
flash(rx) : ORIGIN = 0x08000000, LENGTH = 1024k
sram(rwx) : ORIGIN = 0x20000000, LENGTH = 256k - 1k
health(rw) : ORIGIN = 0x2003fc00, LENGTH = 1k /* Retained health record */
}
__StackTop = ORIGIN(sram) + LENGTH(sram); /* End of RAM. stack points here */
_estack = __StackTop; /* Used by the health fault handler */
__StackLimit = __StackTop - 16384; /* Stack limit, MSPLIM. TLS needs > 4 KB */
SECTIONS {
.vectors : { KEEP(*(.vectors)) } > flash
.text : { *(.text* .text.*) } > flash
.rodata : { *(.rodata*) } > flash
/* Copy and zero tables for the CMSIS startup code, see __cmsis_start() */
.copy.table : {
__copy_table_start__ = .;
LONG(LOADADDR(.data)) LONG(ADDR(.data)) LONG(SIZEOF(.data) / 4)
__copy_table_end__ = .;
} > flash
.zero.table : {
__zero_table_start__ = .;
LONG(ADDR(.bss)) LONG(SIZEOF(.bss) / 4)
__zero_table_end__ = .;
} > flash
/* Note the .iram section, for functions copied to RAM. Required for MG_IRAM OTA support */
.data : { _sdata = .; *(.first_data) *(.iram .iram* .iram.*) *(.data SORT(.data.*)) . = ALIGN(4); _edata = .; } > sram AT > flash
_sidata = LOADADDR(.data);
.bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) . = ALIGN(4); _ebss = .; } > sram
_end = ALIGN(_ebss, 8); /* Heap starts after .bss */
/* Retained health record. Startup code must neither copy nor clear it. */
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > health
}
@@ -1,16 +1,16 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
#include "hal.h"
#include "mongoose.h"
#ifndef UART_DEBUG
#define UART_DEBUG USART3
#endif
#define UART_DEBUG USART2 // ST-LINK virtual COM port, UM3616 7.12
#define UART_DEBUG_TX_PIN PIN('A', 2)
#define UART_DEBUG_RX_PIN PIN('A', 3)
#define LED1 PIN(0, 12) // Green
#define LED2 PIN(0, 0) // Blue
#define LED3 PIN(0, 1) // Red
#define LED1 PIN('A', 5) // Green, on when high. UM3616 7.8
#define LED2 PIN('G', 1) // Red, on when low
#define LED3 PIN('G', 2) // Blue, on when low
static void log_fn(char ch, void *param) {
hal_uart_write_buf(param, &ch, 1);
@@ -18,8 +18,8 @@ static void log_fn(char ch, void *param) {
static void blink_task(void) {
static uint64_t blink_timer = 0;
if (hal_timer_expired(&blink_timer, 100, hal_get_tick())) {
// hal_gpio_toggle(LED1);
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
hal_gpio_toggle(LED2);
}
}
@@ -27,7 +27,6 @@ uint64_t mg_millis(void) {
return hal_get_tick();
}
#if MG_ENABLE_CUSTOM_RANDOM
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();
@@ -35,7 +34,6 @@ bool mg_random(void *buf, size_t len) {
}
return true;
}
#endif
// Self-signed P-256 certificate and key for the HTTPS listener. Created by:
// openssl ecparam -genkey -name prime256v1 -noout -out key.pem
@@ -65,7 +63,7 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
.key = mg_str(TLS_KEY)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
} 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());
@@ -82,80 +80,29 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
}
}
// Our address on the radio side, and the /64 prefix we advertise there.
// fd12:3456:789a:1::1, a unique local address, RFC 4193
#define RADIO_IP6 \
"\xfd\x12\x34\x56\x78\x9a\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01"
// NAT64 prefix advertised to radio nodes: 64:ff9b::/96, RFC 6052. Nodes reach
// IPv4 hosts as 64:ff9b::a.b.c.d, we translate to IPv4 on Ethernet
#define NAT64_PREFIX "\x00\x64\xff\x9b\x00\x00\x00\x00\x00\x00\x00\x00"
int main(void) {
MG_HEALTH_INIT();
hal_clock_init();
hal_rng_init();
hal_gpio_output(LED1);
hal_gpio_write(LED1, true);
hal_gpio_output(LED2);
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, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 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));
}
// Two interfaces, each on its own manager: Ethernet and 802.15.4
static struct mg_tcpip_driver_rw612_data eth_data = {
.mdc_cr = MG_DRIVER_MDC_CR, .phy_addr = MG_TCPIP_PHY_ADDR};
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,
.l2type = MG_TCPIP_L2_LOWPAN};
struct mg_mgr mgr_eth, mgr_radio;
uint8_t uid[FSL_OCOTP_UID_LENGTH], digest[32];
uint32_t uid_len = sizeof(uid);
// Stable addresses, made from the chip unique ID: neighbours remember
// them, e.g. radio nodes keep us as their default router
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, 8);
eth.mac[0] = radio.mac[0] = 0x02; // Locally administered, unicast
hal_rng_init();
hal_ethernet_init();
mg_mgr_init(&mgr_eth);
mg_tcpip_init(&mgr_eth, &eth);
radio.l2data.lowpan.channel = 15;
radio.l2data.lowpan.pan_id = 0xabcd;
memcpy(radio.ip6, RADIO_IP6, sizeof(radio.ip6)); // Static: we are router
memcpy(radio.prefix, RADIO_IP6, sizeof(radio.prefix));
radio.prefix_len = 64;
memcpy(radio.nat64, NAT64_PREFIX, sizeof(radio.nat64)); // We do NAT64
mg_mgr_init(&mgr_radio);
mg_tcpip_init(&mgr_radio, &radio);
mgr_radio.use_dns6 = true; // IPv6 only: no IPv4 over 6LoWPAN
hal_gpio_output(LED1);
hal_gpio_output(LED2);
hal_gpio_output(LED3);
// Route IPv6 between the two: radio nodes get addresses from RADIO_IP6/64,
// the Ethernet LAN gets a route to that prefix via us
mg_tcpip_route(&eth, &radio);
// Minimal web server on both interfaces
mg_http_listen(&mgr_eth, "http://0.0.0.0:80", http_ev_handler, NULL);
mg_http_listen(&mgr_eth, "https://0.0.0.0:443", http_ev_handler, NULL);
mg_http_listen(&mgr_radio, "http://[::]:80", http_ev_handler, NULL);
mg_http_listen(&mgr_radio, "https://[::]:443", http_ev_handler, NULL);
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_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_eth, 0);
mg_mgr_poll(&mgr_radio, 0);
mg_mgr_poll(&mgr, 0);
blink_task();
}
@@ -0,0 +1,32 @@
#pragma once
#include "hal.h"
// See https://mongoose.ws/docs/getting-started/build-environment/
#define MG_ARCH MG_ARCH_ARMGCC
#define MG_TLS MG_TLS_BUILTIN
#define MG_OTA MG_OTA_STM32C5
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_CUSTOM_MILLIS 1
#define MG_ENABLE_CUSTOM_RANDOM 1
#define MG_ENABLE_DRIVER_STM32C 1
// Construct MAC address from the MCU unique ID
#define MGUID ((uint32_t *) UID_BASE)
#define MG_SET_MAC_ADDRESS(mac) \
do { \
mac[0] = 2; \
mac[1] = MGUID[0] & 255; \
mac[2] = (MGUID[0] >> 10) & 255; \
mac[3] = (MGUID[0] >> 19) & 255; \
mac[4] = MGUID[1] & 255; \
mac[5] = MGUID[2] & 255; \
} while (0)
// Crash report support. Health record lives in the .mg_health region, see link.ld
#define MG_ENABLE_HEALTH 1
// mdash.net device management service support
#define MG_ENABLE_MDASH 0
#define MG_MDASH_KEY "..." // Set this - mDash device token
#define MG_FIRMWARE_VERSION "c5a3.1.0.0"