diff --git a/mongoose.c b/mongoose.c index 3b0f1d13..60777041 100644 --- a/mongoose.c +++ b/mongoose.c @@ -5770,6 +5770,70 @@ size_t mg_l2_driver_output(struct mg_tcpip_if *ifp, size_t len) { #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/l2_802154.c" +#endif + + + +#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 + #ifdef MG_ENABLE_LINES #line 1 "src/l2_eth.c" #endif @@ -12464,6 +12528,191 @@ bool mg_ota_end(void) { struct mg_flash *mg_flash = &s_mg_flash_mcxn; #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_mcxw71.c" +#endif + + + + +#if MG_OTA == MG_OTA_MCXW71 + +// MCXW71SRM, section 11.2: ROM flash API, 8 KB sectors, 16-byte phrases, +// 128-byte pages. Only the CM33 application flash is used; RF flash and IFR +// (including the write-once ROM configuration) are left alone. +struct mg_mcxw71_flash_config { + struct { + uint32_t base, size, blocks; + uint32_t ifr_base, ifr_size; + } mem[2]; +}; + +struct mg_mcxw71_flash_api { + uint32_t (*init)(struct mg_mcxw71_flash_config *); + uint32_t (*erase)(struct mg_mcxw71_flash_config *, void *, uint32_t, uint32_t, + uint32_t); + uint32_t (*phrase)(struct mg_mcxw71_flash_config *, void *, uint32_t, + uint32_t *, uint32_t); + uint32_t (*page)(struct mg_mcxw71_flash_config *, void *, uint32_t, + uint32_t *, uint32_t); +}; + +// The flash API pointer is the second entry in the ROM API table. +#define MG_MCXW71_FLASH_API \ + (*(const struct mg_mcxw71_flash_api *const *) 0x14816fe4U) +#define MG_MCXW71_ERASE_KEY 0x6b65666cU // FOUR_CHAR_CODE('l', 'f', 'e', 'k') + +static struct mg_mcxw71_flash_config s_mcxw71_config; +static size_t s_mcxw71_size, s_mcxw71_received, s_mcxw71_pending; +static uint32_t s_mcxw71_buf[4]; +static bool s_mcxw71_failed; + +static bool mg_mcxw71_write(void *, const void *, size_t); +static bool mg_mcxw71_swap(void); +static struct mg_flash s_mg_flash_mcxw71 = { + (void *) 0, 0x100000, 8192, 16, mg_mcxw71_write, mg_mcxw71_swap}; +struct mg_flash *mg_flash = &s_mg_flash_mcxw71; + +// Everything called while flash is busy, or after the application has been +// erased, must execute from RAM or ROM. Do not log or call libc here. +MG_IRAM static bool mg_mcxw71_write(void *addr, const void *buf, size_t len) { + uintptr_t dst = (uintptr_t) addr; + const volatile uint8_t *src = (const volatile uint8_t *) buf; + uint32_t irq, status = 0; + if (dst >= s_mg_flash_mcxw71.size || len > s_mg_flash_mcxw71.size - dst || + (dst % 16) != 0 || (len % 16) != 0) + return false; + + irq = __get_PRIMASK(); + __disable_irq(); + while (len > 0 && status == 0) { + uint32_t words[32]; // ROM needs a word-aligned source in RAM + volatile uint8_t *p = (volatile uint8_t *) words; + size_t i, n = (dst % 128) == 0 && len >= 128 ? 128 : 16; + for (i = 0; i < n; i++) p[i] = src[i]; + + // Match the MCUXpresso ROM wrappers' cache maintenance. Interrupts stay + // masked until the flash command completes and stale cache data is gone. + MCM->CPCR2 |= MCM_CPCR2_CCBC_MASK; + __DSB(); + __ISB(); + if ((dst % 8192) == 0) { + status = MG_MCXW71_FLASH_API->erase( + &s_mcxw71_config, FMU0, (uint32_t) dst, 8192, MG_MCXW71_ERASE_KEY); + } + if (status == 0) { + if (n == 128) { + status = MG_MCXW71_FLASH_API->page(&s_mcxw71_config, FMU0, + (uint32_t) dst, words, 128); + } else { + status = MG_MCXW71_FLASH_API->phrase(&s_mcxw71_config, FMU0, + (uint32_t) dst, words, 16); + } + } + MCM->CPCR2 |= MCM_CPCR2_CCBC_MASK; + __DSB(); + __ISB(); + dst += n, src += n, len -= n; + } + __set_PRIMASK(irq); + return status == 0; +} + +static bool mg_mcxw71_swap(void) { + // The common helper verifies the staged image before the RAM copy below. + // A failed write must never commit, even if all expected bytes arrived. + return !s_mcxw71_failed; +} + +MG_IRAM static void mg_mcxw71_apply(size_t size) { + size_t ofs, half = s_mg_flash_mcxw71.size / 2; + __disable_irq(); + SysTick->CTRL = 0; + for (ofs = 0; ofs < size; ofs += 8192) { + if (!mg_mcxw71_write((void *) (uintptr_t) ofs, + (const void *) (uintptr_t) (half + ofs), 8192)) { + // The application may already be erased. Stay in RAM for debugger + // recovery rather than return to partially overwritten flash. + for (;;) __NOP(); + } + } + __DSB(); + SCB->AIRCR = 0x05fa0004U; + __DSB(); + for (;;) __NOP(); +} + +bool mg_ota_begin(size_t new_firmware_size) { + uint32_t status; + if (s_mcxw71_size != 0 || new_firmware_size == 0) return false; + status = MG_MCXW71_FLASH_API->init(&s_mcxw71_config); + if (status != 0 || s_mcxw71_config.mem[0].base != 0 || + s_mcxw71_config.mem[0].size < 16384 || + (s_mcxw71_config.mem[0].size % 16384) != 0) { + MG_ERROR(("Flash init failed: %lu", (unsigned long) status)); + return false; + } + s_mg_flash_mcxw71.size = s_mcxw71_config.mem[0].size; + if (!mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxw71)) return false; + s_mcxw71_size = new_firmware_size; + s_mcxw71_received = s_mcxw71_pending = 0; + s_mcxw71_failed = false; + return true; +} + +bool mg_ota_write(const void *buf, size_t len) { + const uint8_t *p = (const uint8_t *) buf; + uint8_t *pending = (uint8_t *) s_mcxw71_buf; + if (s_mcxw71_size == 0 || s_mcxw71_failed) return false; + if (len > s_mcxw71_size - s_mcxw71_received || (len > 0 && buf == NULL)) { + s_mcxw71_failed = true; + return false; + } + s_mcxw71_received += len; + if (s_mcxw71_pending > 0) { + size_t n = 16 - s_mcxw71_pending; + if (n > len) n = len; + if (n > 0) memcpy(pending + s_mcxw71_pending, p, n); + s_mcxw71_pending += n; + if (n > 0) p += n; + len -= n; + if (s_mcxw71_pending == 16) { + s_mcxw71_failed = !mg_ota_flash_write(pending, 16, &s_mg_flash_mcxw71); + s_mcxw71_pending = 0; + } + } + if (!s_mcxw71_failed && len >= 16) { + size_t n = MG_ROUND_DOWN(len, 16); + s_mcxw71_failed = !mg_ota_flash_write(p, n, &s_mg_flash_mcxw71); + p += n, len -= n; + } + if (!s_mcxw71_failed && len > 0) { + memcpy(pending + s_mcxw71_pending, p, len); + s_mcxw71_pending += len; + } + return !s_mcxw71_failed; +} + +bool mg_ota_end(void) { + bool ok; + size_t size = s_mcxw71_size; + if (size == 0) return false; + if (s_mcxw71_received != size) s_mcxw71_failed = true; + if (!s_mcxw71_failed && s_mcxw71_pending > 0) { + s_mcxw71_failed = + !mg_ota_flash_write(s_mcxw71_buf, s_mcxw71_pending, &s_mg_flash_mcxw71); + } + ok = mg_ota_flash_end(&s_mg_flash_mcxw71); + s_mcxw71_size = s_mcxw71_pending = 0; + if (ok) { + MG_INFO(("Applying %lu bytes from staging flash", (unsigned long) size)); + mg_mcxw71_apply(size); + } + return false; +} + +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/ota_picosdk.c" #endif @@ -31442,6 +31691,152 @@ struct mg_tcpip_driver mg_tcpip_driver_imxrt = {mg_tcpip_driver_imxrt_init, #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/mcxw71.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \ + MG_ENABLE_DRIVER_MCXW71 + +// ZLL, 802.15.4 link layer. See MCXW71 reference manual and NXP +// ieee_802_15_4 PHY sources: PhyPacketProcessor.c, PhyPlmeData.c, PhyISR.c +struct mg_zll { + volatile uint32_t IRQSTS, PHY_CTRL, EVENT_TMR, TIMESTAMP, T1CMP, T2CMP, + T2PRIMECMP, T3CMP, T4CMP, PA_PWR, CHANNEL_NUM0, LQI_AND_RSSI, + MACSHORTADDRS0, MACLONGADDRS0_LSB, MACLONGADDRS0_MSB, RX_FRAME_FILTER, + CCA_LQI_CTRL, CCA2_CTRL, RESERVED_0, DSM_CTRL, RESERVED_1, + MACSHORTADDRS1, MACLONGADDRS1_LSB, MACLONGADDRS1_MSB, DUAL_PAN_CTRL, + CHANNEL_NUM1, SAM_CTRL, SAM_TABLE, SAM_MATCH, SAM_FREE_IDX, + SEQ_CTRL_STS, ACKDELAY, FILTERFAIL_CODE, RX_WTR_MARK, RESERVED_2, + SLOT_PRELOAD, SEQ_STATE, TMR_PRESCALE, LENIENCY_LSB, LENIENCY_MSB, + PART_ID, COEX_CTRL, COEX_PRIORITY, ENHACK_CTRL0; +}; + +// TrustZone: code built for the secure state (-mcmse) uses the secure alias +// of the radio registers, 0x5xxxxxxx. Otherwise, the non-secure alias +#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE & 2) +#define MGI_ZLL ((struct mg_zll *) 0x58A01000) +#define MGI_PKT_RAM ((volatile uint8_t *) 0x58A08000) +#else +#define MGI_ZLL ((struct mg_zll *) 0x48A01000) +#define MGI_PKT_RAM ((volatile uint8_t *) 0x48A08000) +#endif + +#define MGI_TX_PB MGI_PKT_RAM // Length byte (incl. FCS), then frame +#define MGI_RX_PB (MGI_PKT_RAM + 0x80) // Received frame, no length byte + +#define MGI_IRQSTS_SEQIRQ MG_BIT(0) +#define MGI_IRQSTS_RXIRQ MG_BIT(2) +#define MGI_IRQSTS_RX_LEN(x) (((x) >> 24) & 0x7f) // Frame length incl. FCS +#define MGI_PHY_CTRL_XCVSEQ 7U +#define MGI_PHY_CTRL_ALL_IRQ_MASKS 0x8007ff00U // Bits 8..18, 31: mask all IRQs +#define MGI_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 + +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; +} + +// Wait for the sequence to end, up to MGI_TX_TIMEOUT symbols +static bool mg_mcxw71_wait(void) { + uint32_t start = mg_mcxw71_symbols(); + while (((mg_mcxw71_symbols() - start) & 0xffffffU) < MGI_TX_TIMEOUT) { + if (MGI_ZLL->IRQSTS & MGI_IRQSTS_SEQIRQ) return true; + } + return false; +} + +// Stop the current sequence, wait for the sequencer to go idle. See PhyAbort() +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; + MGI_ZLL->PHY_CTRL &= ~MGI_PHY_CTRL_XCVSEQ; + while (MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) (void) 0; + } + MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; // Clear all flags, write 1 to clear +} + +// Start a new sequence: MGI_SEQ_RX or MGI_SEQ_TX +static void mg_mcxw71_start(uint32_t seq) { + MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; + MGI_ZLL->PHY_CTRL |= seq; +} + +static bool mg_mcxw71_init(struct mg_tcpip_if *ifp) { + struct mg_l2_802154_data *d = + (struct mg_l2_802154_data *) ifp->driver_data; + if (d == NULL || d->channel < 11 || d->channel > 26) { + MG_ERROR(("Set driver_data, channel 11..26")); + return false; + } + MGI_ZLL->SEQ_CTRL_STS |= MGI_SEQ_CTRL_NO_RX_RECYCLE; + MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS; // Mask all IRQs, we poll + MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; + MGI_ZLL->SAM_CTRL = 0; + MGI_ZLL->RX_FRAME_FILTER = MGI_FRAME_FILTER; + MGI_ZLL->TMR_PRESCALE = 5; // 16 us timebase, one symbol + MGI_ZLL->CHANNEL_NUM0 = d->channel; + MGI_ZLL->MACSHORTADDRS0 = (0xfffeU << 16) | d->pan_id; // Filter by PAN ID + MGI_ZLL->PA_PWR = 0x3f; // PA slices, max output power + mg_mcxw71_start(MGI_SEQ_RX); + return true; +} + +// Send one radio frame: MAC header and payload, no FCS +static size_t mg_mcxw71_send(const uint8_t *p, size_t len, + struct mg_tcpip_if *ifp) { + size_t i; + bool ok; + mg_mcxw71_idle(); + MGI_TX_PB[0] = (uint8_t) (len + 2); // FCS is added by hardware + for (i = 0; i < len; i++) MGI_TX_PB[i + 1] = p[i]; // 8-bit access only + mg_mcxw71_start(MGI_SEQ_TX); + ok = mg_mcxw71_wait(); + mg_mcxw71_idle(); + mg_mcxw71_start(MGI_SEQ_RX); + (void) ifp; + return ok ? len : 0; +} + +static size_t 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; + if ((irqsts & MGI_IRQSTS_SEQIRQ) == 0) return 0; // Still receiving + if (irqsts & MGI_IRQSTS_RXIRQ) { + n = MGI_IRQSTS_RX_LEN(irqsts); + n = n >= 2 ? n - 2 : 0; // Strip FCS + if (n > sizeof(f)) n = sizeof(f); + for (i = 0; i < n; i++) f[i] = MGI_RX_PB[i]; + } + mg_mcxw71_idle(); + mg_mcxw71_start(MGI_SEQ_RX); + return mg_l2_802154_rx(ifp, f, n, buf, len); +} + +static bool mg_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) { + (void) ifp, (void) s1; + return true; // Radio link is always up +} + +struct mg_tcpip_driver mg_tcpip_driver_mcxw71 = {mg_mcxw71_init, mg_mcxw71_tx, + mg_mcxw71_rx, mg_mcxw71_poll}; + +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/drivers/netc.c" #endif @@ -32873,6 +33268,460 @@ struct mg_tcpip_driver mg_tcpip_driver_rw612 = {mg_tcpip_driver_rw612_init, mg_tcpip_driver_rw612_poll}; #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/rw612_154.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \ + MG_ENABLE_DRIVER_RW612_154 + +// The 802.15.4 radio runs NXP combo firmware on CPU2, an OpenThread RCP. We +// are CPU3. Messages go through the IMU mailbox: IMU messages carry IMUMC +// messages, IMUMC messages carry HDLC framed Spinel. See NXP sources: +// mcuxsdk-core drivers/imu/fsl_imu.c, mcux-component +// imu_adapter/fsl_adapter_imu.c, connectivity framework +// platform/rw61x/fwk_platform_hdlc.c; OpenThread src/lib/spinel/spinel.h + +// IMU message FIFO registers, one block per direction. See fsl_imu.h +struct mg_rw612_imu { + volatile uint32_t WR_MSG, RD_MSG, STATUS, CNTL, RD_MSG_DBG; +}; +#define MGI_IMU ((struct mg_rw612_imu *) 0x443ff804) // CPU3 -> CPU2 +#define MGI_IMU_PEER ((struct mg_rw612_imu *) 0x443ff818) // CPU2 -> CPU3 +#define MGI_IMU_LOCKED MG_BIT(0) // STATUS: FIFO locked +#define MGI_IMU_ALMOST_FULL MG_BIT(1) // STATUS: FIFO almost full +#define MGI_IMU_EMPTY MG_BIT(3) // STATUS: FIFO empty +#define MGI_IMU_WR_PTR(s) (((s) >> 16) & 15U) +#define MGI_IMU_INT_CLR MG_BIT(0) // CNTL: clear message ready IRQ +#define MGI_IMU_FLUSH MG_BIT(16) // CNTL: flush send FIFO +#define MGI_IMU_WAIT_FOR_ACK MG_BIT(17) // CNTL: lock send FIFO +#define MGI_IMU_WATERMARK(x) ((uint32_t) (x) << 20) +#define MGI_IMU_WATERMARK_MASK (15U << 20) + +// IMU message: header, then payload words. See IMU_Msg_t +struct mg_rw612_msg { + uint8_t type, subtype, seq, len; // len: number of payload words + uint32_t payload[8]; +}; + +// Mailbox in SMU2 shared memory, at the address CPU2 firmware expects +struct mg_rw612_mbox { + uint32_t magic[2]; // Sync pattern: [0] CPU2, [1] us + struct mg_rw612_msg q[2][16]; // Message slots: [0] CPU2 -> us, [1] ours +}; +#define MGI_MBOX ((volatile struct mg_rw612_mbox *) 0x443c0000) +#define MGI_TXBUF 0x443c1508U // Our IMUMC buffers: 16 x 264 bytes +#define MGI_TXBUF_CNT 16 +#define MGI_TXBUF_LEN 264U +#define MGI_SLEEP_FLAG (*(volatile uint32_t *) 0x443c2588) +#define MGI_SYNC_MAGIC 0xabcdef89U + +// IMUMC message, carried in IMU payload words. See imumc_std_msg +struct mg_rw612_imumc { + uint32_t src, dst; // Endpoint addresses + uint16_t rfu, idx; // Reserved + uint16_t len; // Payload length, bytes + uint16_t flags; // Message flags + uint32_t data; // Payload buffer address +}; +#define MGI_EPT_LOCAL 10 // Our 802.15.4 endpoint +#define MGI_EPT_REMOTE 20 // Firmware 802.15.4 endpoint + +enum { MGI_MSG_CONTROL = 0, MGI_MSG_IMUMC = 8 }; // IMU message types +enum { // Control message subtypes + MGI_CTL_SYNC = 1, + MGI_CTL_SYNC_ACK = 2, + MGI_CTL_ACK_FOR_UNLOCK = 3, + MGI_CTL_CMD_BUF_ADDR = 6, + MGI_CTL_EPT_QUERY = 11, + MGI_CTL_EPT_QUERY_RSP = 12, + MGI_CTL_BUF_FREE = 13 +}; + +// Spinel commands, properties and statuses we use +enum { MGI_CMD_RESET = 1, MGI_CMD_GET = 2, MGI_CMD_SET = 3, MGI_CMD_IS = 6 }; +enum { + MGI_PROP_LAST_STATUS = 0, + MGI_PROP_NCP_VERSION = 2, + MGI_PROP_PHY_ENABLED = 0x20, + MGI_PROP_PHY_CHAN = 0x21, + MGI_PROP_PANID = 0x36, + MGI_PROP_RAW_STREAM = 0x37, + MGI_PROP_STREAM_RAW = 0x71 +}; +#define MGI_RESET_STACK 2 // RESET argument: reset the RCP stack only +#define MGI_STATUS_RESET_MIN 112 // LAST_STATUS: RCP has reset +#define MGI_STATUS_RESET_MAX 127 +#define MGI_TX_TIMEOUT_MS 200 // Wait for transmit done, incl. retries +#define MGI_RSP_TIMEOUT_MS 500 +#define MGI_RXQ_CNT 16 // A whole Ethernet frame, 15 radio frames + +// Driver state, one radio +struct mg_rw612 { + bool up; // IMU link synchronised + bool ept_ready; // IMUMC endpoints connected + bool reset; // RCP reported a reset + uint8_t seq; // IMU message sequence number + uint16_t free_bufs; // Free IMUMC buffers, bit per buffer + uint8_t hdlc[300]; // HDLC frame being received, unescaped + size_t hlen; // Bytes in hdlc + bool esc; // Previous byte was an escape + uint8_t tid; // Spinel transaction ID of the pending request + bool rsp; // Response to the pending request received + uint32_t rsp_status; // Its status, if LAST_STATUS, else 0 + char version[64]; // RCP version string + 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 +}; +static struct mg_rw612 s_rw612; + +static void mg_rw612_imu_send(uint8_t type, uint8_t subtype, + const uint32_t *payload, uint8_t len) { + volatile struct mg_rw612_msg *m; + uint64_t expire = mg_millis() + 100; + uint8_t i; + while ((MGI_IMU->STATUS & (MGI_IMU_ALMOST_FULL | MGI_IMU_LOCKED)) && + mg_millis() < expire) { + (void) 0; + } + m = &MGI_MBOX->q[1][MGI_IMU_WR_PTR(MGI_IMU->STATUS)]; + m->type = type, m->subtype = subtype, m->seq = s_rw612.seq++, m->len = len; + for (i = 0; i < len; i++) m->payload[i] = payload[i]; + MGI_IMU->WR_MSG = (uint32_t) (uintptr_t) m; +} + +static void mg_rw612_control(uint8_t subtype, uint32_t a, uint32_t b, + uint8_t len) { + uint32_t payload[2] = {a, b}; + mg_rw612_imu_send(MGI_MSG_CONTROL, subtype, payload, len); +} + +static void mg_rw612_queue_rx(const uint8_t *frame, size_t len) { + struct mg_rw612 *s = &s_rw612; + uint8_t next = (uint8_t) ((s->rxhead + 1) % MGI_RXQ_CNT); + if (next == s->rxtail || len > sizeof(s->rxq[0])) return; // Full: drop + memcpy(s->rxq[s->rxhead], frame, len); + s->rxlen[s->rxhead] = (uint8_t) len; + s->rxhead = next; +} + +// Spinel packed unsigned integer: 7 bits per byte, LSB first +static size_t mg_rw612_unpack(const uint8_t *p, size_t len, uint32_t *v) { + size_t i; + *v = 0; + for (i = 0; i < len && i < 4; i++) { + *v |= (uint32_t) (p[i] & 0x7f) << (7 * i); + if ((p[i] & 0x80) == 0) return i + 1; + } + return 0; +} + +// Handle a received Spinel frame: header, command, property, value +static void mg_rw612_spinel(const uint8_t *f, size_t len) { + struct mg_rw612 *s = &s_rw612; + uint32_t cmd, prop, status = 0; + size_t n, ofs = 1; + uint8_t tid = f[0] & 15; + if (len < 3 || (n = mg_rw612_unpack(f + ofs, len - ofs, &cmd)) == 0) return; + ofs += n; + if ((n = mg_rw612_unpack(f + ofs, len - ofs, &prop)) == 0) return; + ofs += n; + if (cmd != MGI_CMD_IS) return; + if (prop == MGI_PROP_STREAM_RAW && tid == 0 && len - ofs >= 2) { + size_t flen = (size_t) (f[ofs] | (f[ofs + 1] << 8)); // Incl. FCS + if (flen >= 2 && ofs + 2 + flen <= len) { + mg_rw612_queue_rx(f + ofs + 2, flen - 2); + } + } else if (prop == MGI_PROP_LAST_STATUS) { + mg_rw612_unpack(f + ofs, len - ofs, &status); + if (tid == 0 && status >= MGI_STATUS_RESET_MIN && + status <= MGI_STATUS_RESET_MAX) { + s->reset = true; + } + } else if (prop == MGI_PROP_NCP_VERSION) { + n = len - ofs < sizeof(s->version) - 1 ? len - ofs : sizeof(s->version) - 1; + memcpy(s->version, f + ofs, n); + s->version[n] = '\0'; + } + if (tid != 0 && tid == s->tid) s->rsp = true, s->rsp_status = status; +} + +// HDLC-lite: 0x7e delimits frames, 0x7d escapes, FCS-16 at the end +static uint16_t mg_rw612_fcs(uint16_t fcs, uint8_t c) { + int i; + fcs ^= c; + for (i = 0; i < 8; i++) fcs = (fcs & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1; + return fcs; +} + +static void mg_rw612_hdlc_rx(const uint8_t *p, size_t len) { + struct mg_rw612 *s = &s_rw612; + size_t i, j; + for (i = 0; i < len; i++) { + uint8_t c = p[i]; + if (c == 0x7e) { + if (s->hlen >= 3) { + uint16_t fcs = 0xffff; + for (j = 0; j < s->hlen; j++) fcs = mg_rw612_fcs(fcs, s->hdlc[j]); + if (fcs == 0xf0b8) mg_rw612_spinel(s->hdlc, s->hlen - 2); + } + s->hlen = 0, s->esc = false; + } else if (c == 0x7d) { + s->esc = true; + } else if (s->hlen < sizeof(s->hdlc)) { + s->hdlc[s->hlen++] = s->esc ? c ^ 0x20 : c; + s->esc = false; + } + } +} + +static void mg_rw612_control_rx(volatile struct mg_rw612_msg *m) { + struct mg_rw612 *s = &s_rw612; + switch (m->subtype) { + case MGI_CTL_ACK_FOR_UNLOCK: + MGI_IMU->CNTL &= ~MGI_IMU_WAIT_FOR_ACK; + break; + case MGI_CTL_SYNC: // As HAL_ImuCtrlHandler(): acknowledge twice + mg_rw612_control(MGI_CTL_SYNC_ACK, 0, 0, 0); + mg_rw612_control(MGI_CTL_SYNC_ACK, 0, 0, 0); + s->up = true; + break; + case MGI_CTL_SYNC_ACK: + case MGI_CTL_CMD_BUF_ADDR: + s->up = true; + break; + case MGI_CTL_EPT_QUERY: { // Firmware asks if our endpoint exists + bool ours = m->payload[0] == MGI_EPT_LOCAL; + mg_rw612_control(MGI_CTL_EPT_QUERY_RSP, m->payload[0], ours, 2); + if (ours) s->ept_ready = true; + break; + } + case MGI_CTL_EPT_QUERY_RSP: + if (m->payload[0] == MGI_EPT_REMOTE && m->payload[1] != 0) { + s->ept_ready = true; + } + break; + case MGI_CTL_BUF_FREE: { // Firmware is done with one of our buffers + uint32_t i = (m->payload[0] - MGI_TXBUF) / MGI_TXBUF_LEN; + if (i < MGI_TXBUF_CNT) s->free_bufs |= (uint16_t) MG_BIT(i); + break; + } + default: + break; + } +} + +// Process all received IMU messages. See HAL_ImuReceive() +static void mg_rw612_poll_imu(void) { + bool unlock = false; + while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) { + struct mg_rw612_msg m; + volatile struct mg_rw612_msg *p; + uint8_t i; + MGI_SLEEP_FLAG &= ~1U; + p = (volatile struct mg_rw612_msg *) (uintptr_t) MGI_IMU->RD_MSG; + if (MGI_IMU_PEER->STATUS & MGI_IMU_LOCKED) unlock = true; + if (p == NULL) continue; + m.type = p->type, m.subtype = p->subtype, m.len = p->len; + for (i = 0; i < 8; i++) m.payload[i] = p->payload[i]; + if (m.type == MGI_MSG_CONTROL) { + mg_rw612_control_rx(&m); + } else if (m.type == MGI_MSG_IMUMC) { + struct mg_rw612_imumc im; + memcpy(&im, m.payload, sizeof(im)); + if (im.dst == MGI_EPT_LOCAL) { + mg_rw612_hdlc_rx((const uint8_t *) (uintptr_t) im.data, im.len); + } + mg_rw612_control(MGI_CTL_BUF_FREE, im.data, 0, 1); + } + } + MGI_IMU->CNTL |= MGI_IMU_INT_CLR; + MGI_SLEEP_FLAG |= 1U; + if (unlock) mg_rw612_control(MGI_CTL_ACK_FOR_UNLOCK, 0, 0, 0); +} + +// Send bytes to the firmware endpoint, in IMUMC buffer sized chunks +static bool mg_rw612_imumc_send(const uint8_t *p, size_t len) { + struct mg_rw612 *s = &s_rw612; + while (len > 0) { + size_t n = len < MGI_TXBUF_LEN ? len : MGI_TXBUF_LEN; + uint64_t expire = mg_millis() + 100; + struct mg_rw612_imumc im; + uint32_t i; + while (s->free_bufs == 0) { + mg_rw612_poll_imu(); + if (mg_millis() > expire) return false; + } + for (i = 0; (s->free_bufs & MG_BIT(i)) == 0; i++) (void) 0; + s->free_bufs &= (uint16_t) ~MG_BIT(i); + memset(&im, 0, sizeof(im)); + im.src = MGI_EPT_LOCAL, im.dst = MGI_EPT_REMOTE, im.len = (uint16_t) n; + im.data = MGI_TXBUF + i * MGI_TXBUF_LEN; + memcpy((void *) (uintptr_t) im.data, p, n); + mg_rw612_imu_send(MGI_MSG_IMUMC, 0, (uint32_t *) &im, sizeof(im) / 4); + p += n, len -= n; + } + return true; +} + +// Send a Spinel frame, HDLC encoded, and wait for the response with the +// same transaction ID. Return false on timeout +static bool mg_rw612_request(uint8_t *f, size_t len, uint32_t timeout_ms) { + struct mg_rw612 *s = &s_rw612; + uint8_t buf[2 * 160 + 6]; + uint16_t fcs = 0xffff; + size_t i, n = 0; + uint64_t expire = mg_millis() + timeout_ms; + s->tid = (uint8_t) (s->tid % 15 + 1); + f[0] = (uint8_t) (0x80 | s->tid); // Header: flag, interface 0, TID + s->rsp = false; + buf[n++] = 0x7e; + for (i = 0; i < len + 2 && n + 3 < sizeof(buf); i++) { + uint8_t c; + if (i < len) { + c = f[i], fcs = mg_rw612_fcs(fcs, c); + } else { + c = (uint8_t) (i == len ? ~fcs & 0xff : ~fcs >> 8); + } + if (c == 0x7e || c == 0x7d || c == 0x11 || c == 0x13 || c == 0xf8) { + buf[n++] = 0x7d, c ^= 0x20; + } + buf[n++] = c; + } + buf[n++] = 0x7e; + if (!mg_rw612_imumc_send(buf, n)) return false; + while (!s->rsp && mg_millis() < expire) mg_rw612_poll_imu(); + return s->rsp; +} + +static bool mg_rw612_set(uint32_t prop, const uint8_t *val, size_t len) { + uint8_t f[16]; + if (len > sizeof(f) - 3) return false; + f[1] = MGI_CMD_SET, f[2] = (uint8_t) prop; // Properties used are < 0x80 + memcpy(f + 3, val, len); + return mg_rw612_request(f, len + 3, MGI_RSP_TIMEOUT_MS) && + s_rw612.rsp_status == 0; +} + +// Log an init error, return false +static bool mg_rw612_fail(const char *msg) { + MG_ERROR(("%s", msg)); + return false; +} + +static bool mg_rw612_154_init(struct mg_tcpip_if *ifp) { + struct mg_rw612 *s = &s_rw612; + struct mg_l2_802154_data *d = + (struct mg_l2_802154_data *) ifp->driver_data; + uint8_t f[8], one = 1, pan[2]; + uint64_t expire; + if (d == NULL || d->channel < 11 || d->channel > 26) { + MG_ERROR(("Set driver_data, channel 11..26")); + return false; + } + if (MGI_MBOX->magic[0] != MGI_SYNC_MAGIC) { + MG_ERROR(("Radio firmware is not running on CPU2")); + return false; + } + memset(s, 0, sizeof(*s)); + s->free_bufs = 0xffff; + // Reset our send FIFO, drop stale messages. See IMU_Init() + MGI_IMU->CNTL |= MGI_IMU_FLUSH; + MGI_IMU->CNTL &= ~(MGI_IMU_FLUSH | MGI_IMU_WAIT_FOR_ACK); + MGI_IMU->CNTL = (MGI_IMU->CNTL & ~MGI_IMU_WATERMARK_MASK) | + MGI_IMU_WATERMARK(15); + while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) (void) MGI_IMU->RD_MSG; + // Synchronise the IMU link, then connect the IMUMC endpoints + MGI_MBOX->magic[1] = MGI_SYNC_MAGIC; + mg_rw612_control(MGI_CTL_SYNC, 0, 0, 0); + expire = mg_millis() + 1000; + while (!s->up && mg_millis() < expire) mg_rw612_poll_imu(); + if (!s->up) return mg_rw612_fail("IMU sync timeout"); + expire = mg_millis() + 2000; + while (!s->ept_ready && mg_millis() < expire) { + uint64_t retry = mg_millis() + 100; + mg_rw612_control(MGI_CTL_EPT_QUERY, MGI_EPT_REMOTE, 0, 1); + while (!s->ept_ready && mg_millis() < retry) mg_rw612_poll_imu(); + } + if (!s->ept_ready) return mg_rw612_fail("IMUMC endpoint timeout"); + // Reset the RCP stack, wait for its reset status, read its version + f[1] = MGI_CMD_RESET, f[2] = MGI_RESET_STACK; + mg_rw612_request(f, 3, MGI_RSP_TIMEOUT_MS); + expire = mg_millis() + 1000; + while (!s->reset && mg_millis() < expire) mg_rw612_poll_imu(); + if (!s->reset) return mg_rw612_fail("RCP reset timeout"); + f[1] = MGI_CMD_GET, f[2] = MGI_PROP_NCP_VERSION; + mg_rw612_request(f, 3, MGI_RSP_TIMEOUT_MS); + MG_INFO(("RCP: %s", s->version)); + if (!mg_rw612_set(MGI_PROP_PHY_ENABLED, &one, 1)) { + return mg_rw612_fail("PHY enable failed"); + } + pan[0] = (uint8_t) 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; +} + +// 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; + f[1] = MGI_CMD_SET, f[2] = MGI_PROP_STREAM_RAW; + f[n++] = (uint8_t) (len + 2), f[n++] = 0; // PSDU length, incl. FCS + memcpy(f + n, p, len), n += len; + f[n++] = 0, f[n++] = 0; // FCS, filled in by the radio + f[n++] = d->channel; + f[n++] = 4; // Max CSMA backoffs + f[n++] = 0; // Max frame retries: broadcast frames are not acknowledged + f[n++] = 1; // CSMA-CA enabled + if (!mg_rw612_request(f, n, MGI_TX_TIMEOUT_MS)) { + MG_ERROR(("TX timeout")); + return 0; + } + if (s_rw612.rsp_status != 0) { + MG_VERBOSE(("TX failed, status %lu", (unsigned long) s_rw612.rsp_status)); + return 0; + } + return len; +} + +static size_t mg_rw612_154_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + return mg_l2_802154_tx(ifp, buf, len, mg_rw612_send); +} + +static size_t mg_rw612_154_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) { + struct mg_rw612 *s = &s_rw612; + size_t res = 0; + mg_rw612_poll_imu(); + while (s->rxtail != s->rxhead && res == 0) { + res = mg_l2_802154_rx(ifp, s->rxq[s->rxtail], s->rxlen[s->rxtail], buf, + len); + s->rxtail = (uint8_t) ((s->rxtail + 1) % MGI_RXQ_CNT); + } + return res; +} + +static bool mg_rw612_154_poll(struct mg_tcpip_if *ifp, bool s1) { + (void) ifp, (void) s1; + return s_rw612.up; +} + +struct mg_tcpip_driver mg_tcpip_driver_rw612_154 = { + mg_rw612_154_init, mg_rw612_154_tx, mg_rw612_154_rx, mg_rw612_154_poll}; + +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/drivers/same54.c" #endif diff --git a/mongoose.h b/mongoose.h index 251aaa20..8646fcaf 100644 --- a/mongoose.h +++ b/mongoose.h @@ -4227,6 +4227,7 @@ extern void mg_mqtt_poll(struct mg_mgr *); + // Supported OTA targets #define MG_OTA_NONE 0 // No OTA support #define MG_OTA_STM32H5 1 // STM32 H5 @@ -4242,6 +4243,7 @@ extern void mg_mqtt_poll(struct mg_mgr *); #define MG_OTA_RT1170 304 // IMXRT1170 #define MG_OTA_RT1180 305 // IMXRT1180 #define MG_OTA_MCXN 310 // MCXN947 +#define MG_OTA_MCXW71 311 // MCXW71 #define MG_OTA_RW612 320 // FRDM-RW612 #define MG_OTA_FLASH 900 // OTA via internal flash #define MG_OTA_ESP32 910 // ESP32 OTA implementation @@ -4271,6 +4273,19 @@ bool mg_ota_write(const void *buf, size_t len); // Ends the OTA process and commits the written firmware. bool mg_ota_end(void); +// Push-based OTA: mg_http_stream_body() callback that flashes the request +// +// ```c +// if (ev == MG_EV_HTTP_HDRS) { +// struct mg_http_message *hm = (struct mg_http_message *) ev_data; +// if (mg_match(hm->uri, mg_str("/ota"), NULL) && +// !mg_http_stream_body(c, hm, mg_http_ota_cb, NULL)) { +// mg_http_reply(c, 400, "", "POST firmware with Content-Length\n"); +// } +// } +// ``` +bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p); + // MG_OTA_ROLLBACK: swap firmware banks and reset. On flash-based OTA, the // default calls swap_fn() (no-op on single-bank) then resets. Override in // mongoose_config.h for platforms without mg_flash (ESP32, PicoSDK, etc.). @@ -4789,6 +4804,41 @@ extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi; extern struct mg_tcpip_driver mg_tcpip_driver_st67w6; extern struct mg_tcpip_driver mg_tcpip_driver_atcmd; extern struct mg_tcpip_driver mg_tcpip_driver_netc; +extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71; +extern struct mg_tcpip_driver mg_tcpip_driver_rw612_154; + +// IEEE 802.15.4 radio channel and PAN ID. Settable in mongoose_config.h +#ifndef MG_L2_802154_CHANNEL +#define MG_L2_802154_CHANNEL 15 // 11..26 +#endif +#ifndef MG_L2_802154_PAN_ID +#define MG_L2_802154_PAN_ID 0xabcd +#endif + +// driver_data for IEEE 802.15.4 radio drivers. The interface is Ethernet: +// each Ethernet frame is split into broadcast radio frames, and reassembled +// on the other side, see mg_l2_802154_tx() and mg_l2_802154_rx() +struct mg_l2_802154_data { + uint8_t channel; // User-set channel, 11..26 + uint16_t pan_id; // User-set PAN ID + uint8_t seq, tag; // MAC sequence number, tag of the last sent frame + uint8_t rxtag, rxnext; // Frame being reassembled: tag, next fragment + uint8_t rxsrc[8]; // Its sender + uint16_t rxlen; // Its bytes received so far + uint8_t rxbuf[1514]; // Reassembly buffer, one Ethernet frame +}; + +// Splits an Ethernet frame into radio frames, sends each by calling send(). +// Called by radio drivers' tx(). Return len, or 0 on error +size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len, + size_t (*send)(const uint8_t *, size_t, + struct mg_tcpip_if *)); + +// Adds a received radio frame f of n bytes to the frame being reassembled. +// Called by radio drivers' rx(). Return the Ethernet frame length, copied to +// buf, when it is complete, otherwise 0 +size_t mg_l2_802154_rx(struct mg_tcpip_if *ifp, const uint8_t *f, size_t n, + void *buf, size_t len); // SPI bus abstraction for SPI-attached network chips (e.g. W5500). // Populate and assign to the driver's driver_data field. @@ -5176,6 +5226,31 @@ struct mg_tcpip_driver_imxrt_data { #endif +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \ + MG_ENABLE_DRIVER_MCXW71 + +// NXP MCXW71 IEEE 802.15.4 radio, ZLL link layer, as an Ethernet interface: +// Ethernet frames are split into radio frames, see mg_l2_802154_tx(). +// The transceiver (XCVR) must be set up for 802.15.4 before mg_tcpip_init(), +// see hal_radio_init() in the FRDM-MCXW71 tutorial. +// driver_data: struct mg_l2_802154_data, channel and PAN ID set + +#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 + + #if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP && \ defined(MG_ENABLE_DRIVER_NETC) && MG_ENABLE_DRIVER_NETC @@ -5394,6 +5469,35 @@ struct mg_tcpip_driver_rw612_data { #endif +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \ + MG_ENABLE_DRIVER_RW612_154 + +// NXP RW612 IEEE 802.15.4 radio, as an Ethernet interface: Ethernet frames +// are split into radio frames, see mg_l2_802154_tx(). The radio runs on +// CPU2: NXP BLE/802.15.4 combo firmware, an OpenThread radio co-processor +// (RCP). The firmware must be loaded and running before mg_tcpip_init(), see +// the FRDM-RW612 eth_radio_bridge tutorial. The driver talks to it over the +// CPU2 <-> CPU3 IMU mailbox: IMUMC messages carrying HDLC framed Spinel. +// driver_data: struct mg_l2_802154_data, channel and PAN ID set + +#ifndef MG_TCPIP_DRIVER_INIT +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_l2_802154_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.channel = MG_L2_802154_CHANNEL; \ + driver_data_.pan_id = MG_L2_802154_PAN_ID; \ + mif_.driver = &mg_tcpip_driver_rw612_154; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: rw612_154, channel %d", MG_L2_802154_CHANNEL)); \ + } while (0) +#endif + +#endif + + #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && \ MG_ENABLE_DRIVER_SAME54 diff --git a/src/drivers/mcxw71.c b/src/drivers/mcxw71.c new file mode 100644 index 00000000..6cf9913b --- /dev/null +++ b/src/drivers/mcxw71.c @@ -0,0 +1,142 @@ +#include "net_builtin.h" + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \ + MG_ENABLE_DRIVER_MCXW71 + +// ZLL, 802.15.4 link layer. See MCXW71 reference manual and NXP +// ieee_802_15_4 PHY sources: PhyPacketProcessor.c, PhyPlmeData.c, PhyISR.c +struct mg_zll { + volatile uint32_t IRQSTS, PHY_CTRL, EVENT_TMR, TIMESTAMP, T1CMP, T2CMP, + T2PRIMECMP, T3CMP, T4CMP, PA_PWR, CHANNEL_NUM0, LQI_AND_RSSI, + MACSHORTADDRS0, MACLONGADDRS0_LSB, MACLONGADDRS0_MSB, RX_FRAME_FILTER, + CCA_LQI_CTRL, CCA2_CTRL, RESERVED_0, DSM_CTRL, RESERVED_1, + MACSHORTADDRS1, MACLONGADDRS1_LSB, MACLONGADDRS1_MSB, DUAL_PAN_CTRL, + CHANNEL_NUM1, SAM_CTRL, SAM_TABLE, SAM_MATCH, SAM_FREE_IDX, + SEQ_CTRL_STS, ACKDELAY, FILTERFAIL_CODE, RX_WTR_MARK, RESERVED_2, + SLOT_PRELOAD, SEQ_STATE, TMR_PRESCALE, LENIENCY_LSB, LENIENCY_MSB, + PART_ID, COEX_CTRL, COEX_PRIORITY, ENHACK_CTRL0; +}; + +// TrustZone: code built for the secure state (-mcmse) uses the secure alias +// of the radio registers, 0x5xxxxxxx. Otherwise, the non-secure alias +#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE & 2) +#define MGI_ZLL ((struct mg_zll *) 0x58A01000) +#define MGI_PKT_RAM ((volatile uint8_t *) 0x58A08000) +#else +#define MGI_ZLL ((struct mg_zll *) 0x48A01000) +#define MGI_PKT_RAM ((volatile uint8_t *) 0x48A08000) +#endif + +#define MGI_TX_PB MGI_PKT_RAM // Length byte (incl. FCS), then frame +#define MGI_RX_PB (MGI_PKT_RAM + 0x80) // Received frame, no length byte + +#define MGI_IRQSTS_SEQIRQ MG_BIT(0) +#define MGI_IRQSTS_RXIRQ MG_BIT(2) +#define MGI_IRQSTS_RX_LEN(x) (((x) >> 24) & 0x7f) // Frame length incl. FCS +#define MGI_PHY_CTRL_XCVSEQ 7U +#define MGI_PHY_CTRL_ALL_IRQ_MASKS 0x8007ff00U // Bits 8..18, 31: mask all IRQs +#define MGI_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 + +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; +} + +// Wait for the sequence to end, up to MGI_TX_TIMEOUT symbols +static bool mg_mcxw71_wait(void) { + uint32_t start = mg_mcxw71_symbols(); + while (((mg_mcxw71_symbols() - start) & 0xffffffU) < MGI_TX_TIMEOUT) { + if (MGI_ZLL->IRQSTS & MGI_IRQSTS_SEQIRQ) return true; + } + return false; +} + +// Stop the current sequence, wait for the sequencer to go idle. See PhyAbort() +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; + MGI_ZLL->PHY_CTRL &= ~MGI_PHY_CTRL_XCVSEQ; + while (MGI_ZLL->SEQ_STATE & MGI_SEQ_STATE_MASK) (void) 0; + } + MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; // Clear all flags, write 1 to clear +} + +// Start a new sequence: MGI_SEQ_RX or MGI_SEQ_TX +static void mg_mcxw71_start(uint32_t seq) { + MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; + MGI_ZLL->PHY_CTRL |= seq; +} + +static bool mg_mcxw71_init(struct mg_tcpip_if *ifp) { + struct mg_l2_802154_data *d = + (struct mg_l2_802154_data *) ifp->driver_data; + if (d == NULL || d->channel < 11 || d->channel > 26) { + MG_ERROR(("Set driver_data, channel 11..26")); + return false; + } + MGI_ZLL->SEQ_CTRL_STS |= MGI_SEQ_CTRL_NO_RX_RECYCLE; + MGI_ZLL->PHY_CTRL = MGI_PHY_CTRL_ALL_IRQ_MASKS; // Mask all IRQs, we poll + MGI_ZLL->IRQSTS = MGI_ZLL->IRQSTS; + MGI_ZLL->SAM_CTRL = 0; + MGI_ZLL->RX_FRAME_FILTER = MGI_FRAME_FILTER; + MGI_ZLL->TMR_PRESCALE = 5; // 16 us timebase, one symbol + MGI_ZLL->CHANNEL_NUM0 = d->channel; + MGI_ZLL->MACSHORTADDRS0 = (0xfffeU << 16) | d->pan_id; // Filter by PAN ID + MGI_ZLL->PA_PWR = 0x3f; // PA slices, max output power + mg_mcxw71_start(MGI_SEQ_RX); + return true; +} + +// Send one radio frame: MAC header and payload, no FCS +static size_t mg_mcxw71_send(const uint8_t *p, size_t len, + struct mg_tcpip_if *ifp) { + size_t i; + bool ok; + mg_mcxw71_idle(); + MGI_TX_PB[0] = (uint8_t) (len + 2); // FCS is added by hardware + for (i = 0; i < len; i++) MGI_TX_PB[i + 1] = p[i]; // 8-bit access only + mg_mcxw71_start(MGI_SEQ_TX); + ok = mg_mcxw71_wait(); + mg_mcxw71_idle(); + mg_mcxw71_start(MGI_SEQ_RX); + (void) ifp; + return ok ? len : 0; +} + +static size_t 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; + if ((irqsts & MGI_IRQSTS_SEQIRQ) == 0) return 0; // Still receiving + if (irqsts & MGI_IRQSTS_RXIRQ) { + n = MGI_IRQSTS_RX_LEN(irqsts); + n = n >= 2 ? n - 2 : 0; // Strip FCS + if (n > sizeof(f)) n = sizeof(f); + for (i = 0; i < n; i++) f[i] = MGI_RX_PB[i]; + } + mg_mcxw71_idle(); + mg_mcxw71_start(MGI_SEQ_RX); + return mg_l2_802154_rx(ifp, f, n, buf, len); +} + +static bool mg_mcxw71_poll(struct mg_tcpip_if *ifp, bool s1) { + (void) ifp, (void) s1; + return true; // Radio link is always up +} + +struct mg_tcpip_driver mg_tcpip_driver_mcxw71 = {mg_mcxw71_init, mg_mcxw71_tx, + mg_mcxw71_rx, mg_mcxw71_poll}; + +#endif diff --git a/src/drivers/mcxw71.h b/src/drivers/mcxw71.h new file mode 100644 index 00000000..06558ee8 --- /dev/null +++ b/src/drivers/mcxw71.h @@ -0,0 +1,25 @@ +#pragma once + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_MCXW71) && \ + MG_ENABLE_DRIVER_MCXW71 + +// NXP MCXW71 IEEE 802.15.4 radio, ZLL link layer, as an Ethernet interface: +// Ethernet frames are split into radio frames, see mg_l2_802154_tx(). +// The transceiver (XCVR) must be set up for 802.15.4 before mg_tcpip_init(), +// see hal_radio_init() in the FRDM-MCXW71 tutorial. +// driver_data: struct mg_l2_802154_data, channel and PAN ID set + +#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 diff --git a/src/drivers/rw612_154.c b/src/drivers/rw612_154.c new file mode 100644 index 00000000..c37ec5a9 --- /dev/null +++ b/src/drivers/rw612_154.c @@ -0,0 +1,450 @@ +#include "net_builtin.h" + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \ + MG_ENABLE_DRIVER_RW612_154 + +// The 802.15.4 radio runs NXP combo firmware on CPU2, an OpenThread RCP. We +// are CPU3. Messages go through the IMU mailbox: IMU messages carry IMUMC +// messages, IMUMC messages carry HDLC framed Spinel. See NXP sources: +// mcuxsdk-core drivers/imu/fsl_imu.c, mcux-component +// imu_adapter/fsl_adapter_imu.c, connectivity framework +// platform/rw61x/fwk_platform_hdlc.c; OpenThread src/lib/spinel/spinel.h + +// IMU message FIFO registers, one block per direction. See fsl_imu.h +struct mg_rw612_imu { + volatile uint32_t WR_MSG, RD_MSG, STATUS, CNTL, RD_MSG_DBG; +}; +#define MGI_IMU ((struct mg_rw612_imu *) 0x443ff804) // CPU3 -> CPU2 +#define MGI_IMU_PEER ((struct mg_rw612_imu *) 0x443ff818) // CPU2 -> CPU3 +#define MGI_IMU_LOCKED MG_BIT(0) // STATUS: FIFO locked +#define MGI_IMU_ALMOST_FULL MG_BIT(1) // STATUS: FIFO almost full +#define MGI_IMU_EMPTY MG_BIT(3) // STATUS: FIFO empty +#define MGI_IMU_WR_PTR(s) (((s) >> 16) & 15U) +#define MGI_IMU_INT_CLR MG_BIT(0) // CNTL: clear message ready IRQ +#define MGI_IMU_FLUSH MG_BIT(16) // CNTL: flush send FIFO +#define MGI_IMU_WAIT_FOR_ACK MG_BIT(17) // CNTL: lock send FIFO +#define MGI_IMU_WATERMARK(x) ((uint32_t) (x) << 20) +#define MGI_IMU_WATERMARK_MASK (15U << 20) + +// IMU message: header, then payload words. See IMU_Msg_t +struct mg_rw612_msg { + uint8_t type, subtype, seq, len; // len: number of payload words + uint32_t payload[8]; +}; + +// Mailbox in SMU2 shared memory, at the address CPU2 firmware expects +struct mg_rw612_mbox { + uint32_t magic[2]; // Sync pattern: [0] CPU2, [1] us + struct mg_rw612_msg q[2][16]; // Message slots: [0] CPU2 -> us, [1] ours +}; +#define MGI_MBOX ((volatile struct mg_rw612_mbox *) 0x443c0000) +#define MGI_TXBUF 0x443c1508U // Our IMUMC buffers: 16 x 264 bytes +#define MGI_TXBUF_CNT 16 +#define MGI_TXBUF_LEN 264U +#define MGI_SLEEP_FLAG (*(volatile uint32_t *) 0x443c2588) +#define MGI_SYNC_MAGIC 0xabcdef89U + +// IMUMC message, carried in IMU payload words. See imumc_std_msg +struct mg_rw612_imumc { + uint32_t src, dst; // Endpoint addresses + uint16_t rfu, idx; // Reserved + uint16_t len; // Payload length, bytes + uint16_t flags; // Message flags + uint32_t data; // Payload buffer address +}; +#define MGI_EPT_LOCAL 10 // Our 802.15.4 endpoint +#define MGI_EPT_REMOTE 20 // Firmware 802.15.4 endpoint + +enum { MGI_MSG_CONTROL = 0, MGI_MSG_IMUMC = 8 }; // IMU message types +enum { // Control message subtypes + MGI_CTL_SYNC = 1, + MGI_CTL_SYNC_ACK = 2, + MGI_CTL_ACK_FOR_UNLOCK = 3, + MGI_CTL_CMD_BUF_ADDR = 6, + MGI_CTL_EPT_QUERY = 11, + MGI_CTL_EPT_QUERY_RSP = 12, + MGI_CTL_BUF_FREE = 13 +}; + +// Spinel commands, properties and statuses we use +enum { MGI_CMD_RESET = 1, MGI_CMD_GET = 2, MGI_CMD_SET = 3, MGI_CMD_IS = 6 }; +enum { + MGI_PROP_LAST_STATUS = 0, + MGI_PROP_NCP_VERSION = 2, + MGI_PROP_PHY_ENABLED = 0x20, + MGI_PROP_PHY_CHAN = 0x21, + MGI_PROP_PANID = 0x36, + MGI_PROP_RAW_STREAM = 0x37, + MGI_PROP_STREAM_RAW = 0x71 +}; +#define MGI_RESET_STACK 2 // RESET argument: reset the RCP stack only +#define MGI_STATUS_RESET_MIN 112 // LAST_STATUS: RCP has reset +#define MGI_STATUS_RESET_MAX 127 +#define MGI_TX_TIMEOUT_MS 200 // Wait for transmit done, incl. retries +#define MGI_RSP_TIMEOUT_MS 500 +#define MGI_RXQ_CNT 16 // A whole Ethernet frame, 15 radio frames + +// Driver state, one radio +struct mg_rw612 { + bool up; // IMU link synchronised + bool ept_ready; // IMUMC endpoints connected + bool reset; // RCP reported a reset + uint8_t seq; // IMU message sequence number + uint16_t free_bufs; // Free IMUMC buffers, bit per buffer + uint8_t hdlc[300]; // HDLC frame being received, unescaped + size_t hlen; // Bytes in hdlc + bool esc; // Previous byte was an escape + uint8_t tid; // Spinel transaction ID of the pending request + bool rsp; // Response to the pending request received + uint32_t rsp_status; // Its status, if LAST_STATUS, else 0 + char version[64]; // RCP version string + 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 +}; +static struct mg_rw612 s_rw612; + +static void mg_rw612_imu_send(uint8_t type, uint8_t subtype, + const uint32_t *payload, uint8_t len) { + volatile struct mg_rw612_msg *m; + uint64_t expire = mg_millis() + 100; + uint8_t i; + while ((MGI_IMU->STATUS & (MGI_IMU_ALMOST_FULL | MGI_IMU_LOCKED)) && + mg_millis() < expire) { + (void) 0; + } + m = &MGI_MBOX->q[1][MGI_IMU_WR_PTR(MGI_IMU->STATUS)]; + m->type = type, m->subtype = subtype, m->seq = s_rw612.seq++, m->len = len; + for (i = 0; i < len; i++) m->payload[i] = payload[i]; + MGI_IMU->WR_MSG = (uint32_t) (uintptr_t) m; +} + +static void mg_rw612_control(uint8_t subtype, uint32_t a, uint32_t b, + uint8_t len) { + uint32_t payload[2] = {a, b}; + mg_rw612_imu_send(MGI_MSG_CONTROL, subtype, payload, len); +} + +static void mg_rw612_queue_rx(const uint8_t *frame, size_t len) { + struct mg_rw612 *s = &s_rw612; + uint8_t next = (uint8_t) ((s->rxhead + 1) % MGI_RXQ_CNT); + if (next == s->rxtail || len > sizeof(s->rxq[0])) return; // Full: drop + memcpy(s->rxq[s->rxhead], frame, len); + s->rxlen[s->rxhead] = (uint8_t) len; + s->rxhead = next; +} + +// Spinel packed unsigned integer: 7 bits per byte, LSB first +static size_t mg_rw612_unpack(const uint8_t *p, size_t len, uint32_t *v) { + size_t i; + *v = 0; + for (i = 0; i < len && i < 4; i++) { + *v |= (uint32_t) (p[i] & 0x7f) << (7 * i); + if ((p[i] & 0x80) == 0) return i + 1; + } + return 0; +} + +// Handle a received Spinel frame: header, command, property, value +static void mg_rw612_spinel(const uint8_t *f, size_t len) { + struct mg_rw612 *s = &s_rw612; + uint32_t cmd, prop, status = 0; + size_t n, ofs = 1; + uint8_t tid = f[0] & 15; + if (len < 3 || (n = mg_rw612_unpack(f + ofs, len - ofs, &cmd)) == 0) return; + ofs += n; + if ((n = mg_rw612_unpack(f + ofs, len - ofs, &prop)) == 0) return; + ofs += n; + if (cmd != MGI_CMD_IS) return; + if (prop == MGI_PROP_STREAM_RAW && tid == 0 && len - ofs >= 2) { + size_t flen = (size_t) (f[ofs] | (f[ofs + 1] << 8)); // Incl. FCS + if (flen >= 2 && ofs + 2 + flen <= len) { + mg_rw612_queue_rx(f + ofs + 2, flen - 2); + } + } else if (prop == MGI_PROP_LAST_STATUS) { + mg_rw612_unpack(f + ofs, len - ofs, &status); + if (tid == 0 && status >= MGI_STATUS_RESET_MIN && + status <= MGI_STATUS_RESET_MAX) { + s->reset = true; + } + } else if (prop == MGI_PROP_NCP_VERSION) { + n = len - ofs < sizeof(s->version) - 1 ? len - ofs : sizeof(s->version) - 1; + memcpy(s->version, f + ofs, n); + s->version[n] = '\0'; + } + if (tid != 0 && tid == s->tid) s->rsp = true, s->rsp_status = status; +} + +// HDLC-lite: 0x7e delimits frames, 0x7d escapes, FCS-16 at the end +static uint16_t mg_rw612_fcs(uint16_t fcs, uint8_t c) { + int i; + fcs ^= c; + for (i = 0; i < 8; i++) fcs = (fcs & 1) ? (fcs >> 1) ^ 0x8408 : fcs >> 1; + return fcs; +} + +static void mg_rw612_hdlc_rx(const uint8_t *p, size_t len) { + struct mg_rw612 *s = &s_rw612; + size_t i, j; + for (i = 0; i < len; i++) { + uint8_t c = p[i]; + if (c == 0x7e) { + if (s->hlen >= 3) { + uint16_t fcs = 0xffff; + for (j = 0; j < s->hlen; j++) fcs = mg_rw612_fcs(fcs, s->hdlc[j]); + if (fcs == 0xf0b8) mg_rw612_spinel(s->hdlc, s->hlen - 2); + } + s->hlen = 0, s->esc = false; + } else if (c == 0x7d) { + s->esc = true; + } else if (s->hlen < sizeof(s->hdlc)) { + s->hdlc[s->hlen++] = s->esc ? c ^ 0x20 : c; + s->esc = false; + } + } +} + +static void mg_rw612_control_rx(volatile struct mg_rw612_msg *m) { + struct mg_rw612 *s = &s_rw612; + switch (m->subtype) { + case MGI_CTL_ACK_FOR_UNLOCK: + MGI_IMU->CNTL &= ~MGI_IMU_WAIT_FOR_ACK; + break; + case MGI_CTL_SYNC: // As HAL_ImuCtrlHandler(): acknowledge twice + mg_rw612_control(MGI_CTL_SYNC_ACK, 0, 0, 0); + mg_rw612_control(MGI_CTL_SYNC_ACK, 0, 0, 0); + s->up = true; + break; + case MGI_CTL_SYNC_ACK: + case MGI_CTL_CMD_BUF_ADDR: + s->up = true; + break; + case MGI_CTL_EPT_QUERY: { // Firmware asks if our endpoint exists + bool ours = m->payload[0] == MGI_EPT_LOCAL; + mg_rw612_control(MGI_CTL_EPT_QUERY_RSP, m->payload[0], ours, 2); + if (ours) s->ept_ready = true; + break; + } + case MGI_CTL_EPT_QUERY_RSP: + if (m->payload[0] == MGI_EPT_REMOTE && m->payload[1] != 0) { + s->ept_ready = true; + } + break; + case MGI_CTL_BUF_FREE: { // Firmware is done with one of our buffers + uint32_t i = (m->payload[0] - MGI_TXBUF) / MGI_TXBUF_LEN; + if (i < MGI_TXBUF_CNT) s->free_bufs |= (uint16_t) MG_BIT(i); + break; + } + default: + break; + } +} + +// Process all received IMU messages. See HAL_ImuReceive() +static void mg_rw612_poll_imu(void) { + bool unlock = false; + while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) { + struct mg_rw612_msg m; + volatile struct mg_rw612_msg *p; + uint8_t i; + MGI_SLEEP_FLAG &= ~1U; + p = (volatile struct mg_rw612_msg *) (uintptr_t) MGI_IMU->RD_MSG; + if (MGI_IMU_PEER->STATUS & MGI_IMU_LOCKED) unlock = true; + if (p == NULL) continue; + m.type = p->type, m.subtype = p->subtype, m.len = p->len; + for (i = 0; i < 8; i++) m.payload[i] = p->payload[i]; + if (m.type == MGI_MSG_CONTROL) { + mg_rw612_control_rx(&m); + } else if (m.type == MGI_MSG_IMUMC) { + struct mg_rw612_imumc im; + memcpy(&im, m.payload, sizeof(im)); + if (im.dst == MGI_EPT_LOCAL) { + mg_rw612_hdlc_rx((const uint8_t *) (uintptr_t) im.data, im.len); + } + mg_rw612_control(MGI_CTL_BUF_FREE, im.data, 0, 1); + } + } + MGI_IMU->CNTL |= MGI_IMU_INT_CLR; + MGI_SLEEP_FLAG |= 1U; + if (unlock) mg_rw612_control(MGI_CTL_ACK_FOR_UNLOCK, 0, 0, 0); +} + +// Send bytes to the firmware endpoint, in IMUMC buffer sized chunks +static bool mg_rw612_imumc_send(const uint8_t *p, size_t len) { + struct mg_rw612 *s = &s_rw612; + while (len > 0) { + size_t n = len < MGI_TXBUF_LEN ? len : MGI_TXBUF_LEN; + uint64_t expire = mg_millis() + 100; + struct mg_rw612_imumc im; + uint32_t i; + while (s->free_bufs == 0) { + mg_rw612_poll_imu(); + if (mg_millis() > expire) return false; + } + for (i = 0; (s->free_bufs & MG_BIT(i)) == 0; i++) (void) 0; + s->free_bufs &= (uint16_t) ~MG_BIT(i); + memset(&im, 0, sizeof(im)); + im.src = MGI_EPT_LOCAL, im.dst = MGI_EPT_REMOTE, im.len = (uint16_t) n; + im.data = MGI_TXBUF + i * MGI_TXBUF_LEN; + memcpy((void *) (uintptr_t) im.data, p, n); + mg_rw612_imu_send(MGI_MSG_IMUMC, 0, (uint32_t *) &im, sizeof(im) / 4); + p += n, len -= n; + } + return true; +} + +// Send a Spinel frame, HDLC encoded, and wait for the response with the +// same transaction ID. Return false on timeout +static bool mg_rw612_request(uint8_t *f, size_t len, uint32_t timeout_ms) { + struct mg_rw612 *s = &s_rw612; + uint8_t buf[2 * 160 + 6]; + uint16_t fcs = 0xffff; + size_t i, n = 0; + uint64_t expire = mg_millis() + timeout_ms; + s->tid = (uint8_t) (s->tid % 15 + 1); + f[0] = (uint8_t) (0x80 | s->tid); // Header: flag, interface 0, TID + s->rsp = false; + buf[n++] = 0x7e; + for (i = 0; i < len + 2 && n + 3 < sizeof(buf); i++) { + uint8_t c; + if (i < len) { + c = f[i], fcs = mg_rw612_fcs(fcs, c); + } else { + c = (uint8_t) (i == len ? ~fcs & 0xff : ~fcs >> 8); + } + if (c == 0x7e || c == 0x7d || c == 0x11 || c == 0x13 || c == 0xf8) { + buf[n++] = 0x7d, c ^= 0x20; + } + buf[n++] = c; + } + buf[n++] = 0x7e; + if (!mg_rw612_imumc_send(buf, n)) return false; + while (!s->rsp && mg_millis() < expire) mg_rw612_poll_imu(); + return s->rsp; +} + +static bool mg_rw612_set(uint32_t prop, const uint8_t *val, size_t len) { + uint8_t f[16]; + if (len > sizeof(f) - 3) return false; + f[1] = MGI_CMD_SET, f[2] = (uint8_t) prop; // Properties used are < 0x80 + memcpy(f + 3, val, len); + return mg_rw612_request(f, len + 3, MGI_RSP_TIMEOUT_MS) && + s_rw612.rsp_status == 0; +} + +// Log an init error, return false +static bool mg_rw612_fail(const char *msg) { + MG_ERROR(("%s", msg)); + return false; +} + +static bool mg_rw612_154_init(struct mg_tcpip_if *ifp) { + struct mg_rw612 *s = &s_rw612; + struct mg_l2_802154_data *d = + (struct mg_l2_802154_data *) ifp->driver_data; + uint8_t f[8], one = 1, pan[2]; + uint64_t expire; + if (d == NULL || d->channel < 11 || d->channel > 26) { + MG_ERROR(("Set driver_data, channel 11..26")); + return false; + } + if (MGI_MBOX->magic[0] != MGI_SYNC_MAGIC) { + MG_ERROR(("Radio firmware is not running on CPU2")); + return false; + } + memset(s, 0, sizeof(*s)); + s->free_bufs = 0xffff; + // Reset our send FIFO, drop stale messages. See IMU_Init() + MGI_IMU->CNTL |= MGI_IMU_FLUSH; + MGI_IMU->CNTL &= ~(MGI_IMU_FLUSH | MGI_IMU_WAIT_FOR_ACK); + MGI_IMU->CNTL = (MGI_IMU->CNTL & ~MGI_IMU_WATERMARK_MASK) | + MGI_IMU_WATERMARK(15); + while ((MGI_IMU_PEER->STATUS & MGI_IMU_EMPTY) == 0) (void) MGI_IMU->RD_MSG; + // Synchronise the IMU link, then connect the IMUMC endpoints + MGI_MBOX->magic[1] = MGI_SYNC_MAGIC; + mg_rw612_control(MGI_CTL_SYNC, 0, 0, 0); + expire = mg_millis() + 1000; + while (!s->up && mg_millis() < expire) mg_rw612_poll_imu(); + if (!s->up) return mg_rw612_fail("IMU sync timeout"); + expire = mg_millis() + 2000; + while (!s->ept_ready && mg_millis() < expire) { + uint64_t retry = mg_millis() + 100; + mg_rw612_control(MGI_CTL_EPT_QUERY, MGI_EPT_REMOTE, 0, 1); + while (!s->ept_ready && mg_millis() < retry) mg_rw612_poll_imu(); + } + if (!s->ept_ready) return mg_rw612_fail("IMUMC endpoint timeout"); + // Reset the RCP stack, wait for its reset status, read its version + f[1] = MGI_CMD_RESET, f[2] = MGI_RESET_STACK; + mg_rw612_request(f, 3, MGI_RSP_TIMEOUT_MS); + expire = mg_millis() + 1000; + while (!s->reset && mg_millis() < expire) mg_rw612_poll_imu(); + if (!s->reset) return mg_rw612_fail("RCP reset timeout"); + f[1] = MGI_CMD_GET, f[2] = MGI_PROP_NCP_VERSION; + mg_rw612_request(f, 3, MGI_RSP_TIMEOUT_MS); + MG_INFO(("RCP: %s", s->version)); + if (!mg_rw612_set(MGI_PROP_PHY_ENABLED, &one, 1)) { + return mg_rw612_fail("PHY enable failed"); + } + pan[0] = (uint8_t) 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; +} + +// 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; + f[1] = MGI_CMD_SET, f[2] = MGI_PROP_STREAM_RAW; + f[n++] = (uint8_t) (len + 2), f[n++] = 0; // PSDU length, incl. FCS + memcpy(f + n, p, len), n += len; + f[n++] = 0, f[n++] = 0; // FCS, filled in by the radio + f[n++] = d->channel; + f[n++] = 4; // Max CSMA backoffs + f[n++] = 0; // Max frame retries: broadcast frames are not acknowledged + f[n++] = 1; // CSMA-CA enabled + if (!mg_rw612_request(f, n, MGI_TX_TIMEOUT_MS)) { + MG_ERROR(("TX timeout")); + return 0; + } + if (s_rw612.rsp_status != 0) { + MG_VERBOSE(("TX failed, status %lu", (unsigned long) s_rw612.rsp_status)); + return 0; + } + return len; +} + +static size_t mg_rw612_154_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + return mg_l2_802154_tx(ifp, buf, len, mg_rw612_send); +} + +static size_t mg_rw612_154_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) { + struct mg_rw612 *s = &s_rw612; + size_t res = 0; + mg_rw612_poll_imu(); + while (s->rxtail != s->rxhead && res == 0) { + res = mg_l2_802154_rx(ifp, s->rxq[s->rxtail], s->rxlen[s->rxtail], buf, + len); + s->rxtail = (uint8_t) ((s->rxtail + 1) % MGI_RXQ_CNT); + } + return res; +} + +static bool mg_rw612_154_poll(struct mg_tcpip_if *ifp, bool s1) { + (void) ifp, (void) s1; + return s_rw612.up; +} + +struct mg_tcpip_driver mg_tcpip_driver_rw612_154 = { + mg_rw612_154_init, mg_rw612_154_tx, mg_rw612_154_rx, mg_rw612_154_poll}; + +#endif diff --git a/src/drivers/rw612_154.h b/src/drivers/rw612_154.h new file mode 100644 index 00000000..acc67a9d --- /dev/null +++ b/src/drivers/rw612_154.h @@ -0,0 +1,29 @@ +#pragma once + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612_154) && \ + MG_ENABLE_DRIVER_RW612_154 + +// NXP RW612 IEEE 802.15.4 radio, as an Ethernet interface: Ethernet frames +// are split into radio frames, see mg_l2_802154_tx(). The radio runs on +// CPU2: NXP BLE/802.15.4 combo firmware, an OpenThread radio co-processor +// (RCP). The firmware must be loaded and running before mg_tcpip_init(), see +// the FRDM-RW612 eth_radio_bridge tutorial. The driver talks to it over the +// CPU2 <-> CPU3 IMU mailbox: IMUMC messages carrying HDLC framed Spinel. +// driver_data: struct mg_l2_802154_data, channel and PAN ID set + +#ifndef MG_TCPIP_DRIVER_INIT +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_l2_802154_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.channel = MG_L2_802154_CHANNEL; \ + driver_data_.pan_id = MG_L2_802154_PAN_ID; \ + mif_.driver = &mg_tcpip_driver_rw612_154; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: rw612_154, channel %d", MG_L2_802154_CHANNEL)); \ + } while (0) +#endif + +#endif diff --git a/src/l2_802154.c b/src/l2_802154.c new file mode 100644 index 00000000..ffceeedc --- /dev/null +++ b/src/l2_802154.c @@ -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 diff --git a/src/net_builtin.h b/src/net_builtin.h index 7df3b9b4..23aead26 100644 --- a/src/net_builtin.h +++ b/src/net_builtin.h @@ -154,6 +154,41 @@ extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi; extern struct mg_tcpip_driver mg_tcpip_driver_st67w6; extern struct mg_tcpip_driver mg_tcpip_driver_atcmd; extern struct mg_tcpip_driver mg_tcpip_driver_netc; +extern struct mg_tcpip_driver mg_tcpip_driver_mcxw71; +extern struct mg_tcpip_driver mg_tcpip_driver_rw612_154; + +// IEEE 802.15.4 radio channel and PAN ID. Settable in mongoose_config.h +#ifndef MG_L2_802154_CHANNEL +#define MG_L2_802154_CHANNEL 15 // 11..26 +#endif +#ifndef MG_L2_802154_PAN_ID +#define MG_L2_802154_PAN_ID 0xabcd +#endif + +// driver_data for IEEE 802.15.4 radio drivers. The interface is Ethernet: +// each Ethernet frame is split into broadcast radio frames, and reassembled +// on the other side, see mg_l2_802154_tx() and mg_l2_802154_rx() +struct mg_l2_802154_data { + uint8_t channel; // User-set channel, 11..26 + uint16_t pan_id; // User-set PAN ID + uint8_t seq, tag; // MAC sequence number, tag of the last sent frame + uint8_t rxtag, rxnext; // Frame being reassembled: tag, next fragment + uint8_t rxsrc[8]; // Its sender + uint16_t rxlen; // Its bytes received so far + uint8_t rxbuf[1514]; // Reassembly buffer, one Ethernet frame +}; + +// Splits an Ethernet frame into radio frames, sends each by calling send(). +// Called by radio drivers' tx(). Return len, or 0 on error +size_t mg_l2_802154_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len, + size_t (*send)(const uint8_t *, size_t, + struct mg_tcpip_if *)); + +// Adds a received radio frame f of n bytes to the frame being reassembled. +// Called by radio drivers' rx(). Return the Ethernet frame length, copied to +// buf, when it is complete, otherwise 0 +size_t mg_l2_802154_rx(struct mg_tcpip_if *ifp, const uint8_t *f, size_t n, + void *buf, size_t len); // SPI bus abstraction for SPI-attached network chips (e.g. W5500). // Populate and assign to the driver's driver_data field. diff --git a/src/ota.h b/src/ota.h index 27359930..69542883 100644 --- a/src/ota.h +++ b/src/ota.h @@ -26,6 +26,7 @@ #pragma once #include "arch.h" +#include "http.h" #include "net.h" // Supported OTA targets @@ -43,6 +44,7 @@ #define MG_OTA_RT1170 304 // IMXRT1170 #define MG_OTA_RT1180 305 // IMXRT1180 #define MG_OTA_MCXN 310 // MCXN947 +#define MG_OTA_MCXW71 311 // MCXW71 #define MG_OTA_RW612 320 // FRDM-RW612 #define MG_OTA_FLASH 900 // OTA via internal flash #define MG_OTA_ESP32 910 // ESP32 OTA implementation @@ -72,6 +74,19 @@ bool mg_ota_write(const void *buf, size_t len); // Ends the OTA process and commits the written firmware. bool mg_ota_end(void); +// Push-based OTA: mg_http_stream_body() callback that flashes the request +// +// ```c +// if (ev == MG_EV_HTTP_HDRS) { +// struct mg_http_message *hm = (struct mg_http_message *) ev_data; +// if (mg_match(hm->uri, mg_str("/ota"), NULL) && +// !mg_http_stream_body(c, hm, mg_http_ota_cb, NULL)) { +// mg_http_reply(c, 400, "", "POST firmware with Content-Length\n"); +// } +// } +// ``` +bool mg_http_ota_cb(struct mg_http_message *hm, struct mg_str *data, void **p); + // MG_OTA_ROLLBACK: swap firmware banks and reset. On flash-based OTA, the // default calls swap_fn() (no-op on single-bank) then resets. Override in // mongoose_config.h for platforms without mg_flash (ESP32, PicoSDK, etc.). diff --git a/src/ota_mcxw71.c b/src/ota_mcxw71.c new file mode 100644 index 00000000..19eed354 --- /dev/null +++ b/src/ota_mcxw71.c @@ -0,0 +1,181 @@ +#include "flash.h" +#include "log.h" +#include "ota.h" + +#if MG_OTA == MG_OTA_MCXW71 + +// MCXW71SRM, section 11.2: ROM flash API, 8 KB sectors, 16-byte phrases, +// 128-byte pages. Only the CM33 application flash is used; RF flash and IFR +// (including the write-once ROM configuration) are left alone. +struct mg_mcxw71_flash_config { + struct { + uint32_t base, size, blocks; + uint32_t ifr_base, ifr_size; + } mem[2]; +}; + +struct mg_mcxw71_flash_api { + uint32_t (*init)(struct mg_mcxw71_flash_config *); + uint32_t (*erase)(struct mg_mcxw71_flash_config *, void *, uint32_t, uint32_t, + uint32_t); + uint32_t (*phrase)(struct mg_mcxw71_flash_config *, void *, uint32_t, + uint32_t *, uint32_t); + uint32_t (*page)(struct mg_mcxw71_flash_config *, void *, uint32_t, + uint32_t *, uint32_t); +}; + +// The flash API pointer is the second entry in the ROM API table. +#define MG_MCXW71_FLASH_API \ + (*(const struct mg_mcxw71_flash_api *const *) 0x14816fe4U) +#define MG_MCXW71_ERASE_KEY 0x6b65666cU // FOUR_CHAR_CODE('l', 'f', 'e', 'k') + +static struct mg_mcxw71_flash_config s_mcxw71_config; +static size_t s_mcxw71_size, s_mcxw71_received, s_mcxw71_pending; +static uint32_t s_mcxw71_buf[4]; +static bool s_mcxw71_failed; + +static bool mg_mcxw71_write(void *, const void *, size_t); +static bool mg_mcxw71_swap(void); +static struct mg_flash s_mg_flash_mcxw71 = { + (void *) 0, 0x100000, 8192, 16, mg_mcxw71_write, mg_mcxw71_swap}; +struct mg_flash *mg_flash = &s_mg_flash_mcxw71; + +// Everything called while flash is busy, or after the application has been +// erased, must execute from RAM or ROM. Do not log or call libc here. +MG_IRAM static bool mg_mcxw71_write(void *addr, const void *buf, size_t len) { + uintptr_t dst = (uintptr_t) addr; + const volatile uint8_t *src = (const volatile uint8_t *) buf; + uint32_t irq, status = 0; + if (dst >= s_mg_flash_mcxw71.size || len > s_mg_flash_mcxw71.size - dst || + (dst % 16) != 0 || (len % 16) != 0) + return false; + + irq = __get_PRIMASK(); + __disable_irq(); + while (len > 0 && status == 0) { + uint32_t words[32]; // ROM needs a word-aligned source in RAM + volatile uint8_t *p = (volatile uint8_t *) words; + size_t i, n = (dst % 128) == 0 && len >= 128 ? 128 : 16; + for (i = 0; i < n; i++) p[i] = src[i]; + + // Match the MCUXpresso ROM wrappers' cache maintenance. Interrupts stay + // masked until the flash command completes and stale cache data is gone. + MCM->CPCR2 |= MCM_CPCR2_CCBC_MASK; + __DSB(); + __ISB(); + if ((dst % 8192) == 0) { + status = MG_MCXW71_FLASH_API->erase( + &s_mcxw71_config, FMU0, (uint32_t) dst, 8192, MG_MCXW71_ERASE_KEY); + } + if (status == 0) { + if (n == 128) { + status = MG_MCXW71_FLASH_API->page(&s_mcxw71_config, FMU0, + (uint32_t) dst, words, 128); + } else { + status = MG_MCXW71_FLASH_API->phrase(&s_mcxw71_config, FMU0, + (uint32_t) dst, words, 16); + } + } + MCM->CPCR2 |= MCM_CPCR2_CCBC_MASK; + __DSB(); + __ISB(); + dst += n, src += n, len -= n; + } + __set_PRIMASK(irq); + return status == 0; +} + +static bool mg_mcxw71_swap(void) { + // The common helper verifies the staged image before the RAM copy below. + // A failed write must never commit, even if all expected bytes arrived. + return !s_mcxw71_failed; +} + +MG_IRAM static void mg_mcxw71_apply(size_t size) { + size_t ofs, half = s_mg_flash_mcxw71.size / 2; + __disable_irq(); + SysTick->CTRL = 0; + for (ofs = 0; ofs < size; ofs += 8192) { + if (!mg_mcxw71_write((void *) (uintptr_t) ofs, + (const void *) (uintptr_t) (half + ofs), 8192)) { + // The application may already be erased. Stay in RAM for debugger + // recovery rather than return to partially overwritten flash. + for (;;) __NOP(); + } + } + __DSB(); + SCB->AIRCR = 0x05fa0004U; + __DSB(); + for (;;) __NOP(); +} + +bool mg_ota_begin(size_t new_firmware_size) { + uint32_t status; + if (s_mcxw71_size != 0 || new_firmware_size == 0) return false; + status = MG_MCXW71_FLASH_API->init(&s_mcxw71_config); + if (status != 0 || s_mcxw71_config.mem[0].base != 0 || + s_mcxw71_config.mem[0].size < 16384 || + (s_mcxw71_config.mem[0].size % 16384) != 0) { + MG_ERROR(("Flash init failed: %lu", (unsigned long) status)); + return false; + } + s_mg_flash_mcxw71.size = s_mcxw71_config.mem[0].size; + if (!mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxw71)) return false; + s_mcxw71_size = new_firmware_size; + s_mcxw71_received = s_mcxw71_pending = 0; + s_mcxw71_failed = false; + return true; +} + +bool mg_ota_write(const void *buf, size_t len) { + const uint8_t *p = (const uint8_t *) buf; + uint8_t *pending = (uint8_t *) s_mcxw71_buf; + if (s_mcxw71_size == 0 || s_mcxw71_failed) return false; + if (len > s_mcxw71_size - s_mcxw71_received || (len > 0 && buf == NULL)) { + s_mcxw71_failed = true; + return false; + } + s_mcxw71_received += len; + if (s_mcxw71_pending > 0) { + size_t n = 16 - s_mcxw71_pending; + if (n > len) n = len; + if (n > 0) memcpy(pending + s_mcxw71_pending, p, n); + s_mcxw71_pending += n; + if (n > 0) p += n; + len -= n; + if (s_mcxw71_pending == 16) { + s_mcxw71_failed = !mg_ota_flash_write(pending, 16, &s_mg_flash_mcxw71); + s_mcxw71_pending = 0; + } + } + if (!s_mcxw71_failed && len >= 16) { + size_t n = MG_ROUND_DOWN(len, 16); + s_mcxw71_failed = !mg_ota_flash_write(p, n, &s_mg_flash_mcxw71); + p += n, len -= n; + } + if (!s_mcxw71_failed && len > 0) { + memcpy(pending + s_mcxw71_pending, p, len); + s_mcxw71_pending += len; + } + return !s_mcxw71_failed; +} + +bool mg_ota_end(void) { + bool ok; + size_t size = s_mcxw71_size; + if (size == 0) return false; + if (s_mcxw71_received != size) s_mcxw71_failed = true; + if (!s_mcxw71_failed && s_mcxw71_pending > 0) { + s_mcxw71_failed = + !mg_ota_flash_write(s_mcxw71_buf, s_mcxw71_pending, &s_mg_flash_mcxw71); + } + ok = mg_ota_flash_end(&s_mg_flash_mcxw71); + s_mcxw71_size = s_mcxw71_pending = 0; + if (ok) { + MG_INFO(("Applying %lu bytes from staging flash", (unsigned long) size)); + mg_mcxw71_apply(size); + } + return false; +} + +#endif diff --git a/tutorials/nxp/frdm-mcxw71/minimal/Makefile b/tutorials/nxp/frdm-mcxw71/minimal/Makefile index 84c84ddb..b10b19da 100644 --- a/tutorials/nxp/frdm-mcxw71/minimal/Makefile +++ b/tutorials/nxp/frdm-mcxw71/minimal/Makefile @@ -2,7 +2,7 @@ MGDIR = ../../../.. -CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion +CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion CFLAGS += -g3 -Os -ffunction-sections -fdata-sections CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include @@ -10,9 +10,11 @@ CFLAGS += -Icmsis_mcxw71/MCXW/MCXW716C -Icmsis_mcxw71/MCXW/periph2 CFLAGS += -DCPU_MCXW716CMFTA CFLAGS += -D__START=main -D__STARTUP_CLEAR_BSS # For startup_MCXW716C.S CFLAGS += -mcpu=cortex-m33 -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CFLAGS_EXTRA) -LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections + +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lm -lgcc -Wl,--gc-sections SOURCES = main.c hal.c $(MGDIR)/mongoose.c +SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c SOURCES += cmsis_mcxw71/MCXW/MCXW716C/gcc/startup_MCXW716C.S all build: firmware.bin diff --git a/tutorials/nxp/frdm-mcxw71/minimal/hal.c b/tutorials/nxp/frdm-mcxw71/minimal/hal.c index 3fb9fa35..3578b226 100644 --- a/tutorials/nxp/frdm-mcxw71/minimal/hal.c +++ b/tutorials/nxp/frdm-mcxw71/minimal/hal.c @@ -2,6 +2,61 @@ // All rights reserved #include "hal.h" +#include "mongoose.h" + +// Initialise the transceiver the way NXP XCVR_Init() does it: start the RF +// oscillator, wait for the radio to wake up, enable its clocks and load the +// 802.15.4 register setup. Then select the legacy 802.15.4 demodulator and +// CCA from RX digital. See PhyHwInit() in NXP ieee_802_15_4 PHY sources, +// PhyPacketProcessor.c +bool hal_radio_init(void) { + uint32_t stat = RFMC_RF2P4GHZ_STAT_RST_STAT_MASK | + RFMC_RF2P4GHZ_STAT_LP_ACK_STAT_MASK; + size_t i, n = 1000000; + RFMC->CTRL &= ~RFMC_CTRL_RFMC_RST_MASK; // RFMC out of reset + RFMC->RF2P4GHZ_CTRL |= RFMC_RF2P4GHZ_CTRL_XO_EN_MASK; // Start RF oscillator + RFMC->RF2P4GHZ_CTRL &= + ~(RFMC_RF2P4GHZ_CTRL_LP_ENTER_MASK | RFMC_RF2P4GHZ_CTRL_RF_POR_MASK | + RFMC_RF2P4GHZ_CTRL_RST_MASK); // Release radio reset, no low power + while ((RFMC->RF2P4GHZ_STAT & stat) != 0 || + (RFMC->XO_STAT & RFMC_XO_STAT_XTAL_RDY_MASK) == 0) { + if (--n == 0) return false; // Radio did not wake up + } + RADIO_CTRL->LL_CTRL = RADIO_CTRL_LL_CTRL_ACTIVE_LL_MASK; + RFMC->RF2P4GHZ_CTRL |= + RFMC_RF2P4GHZ_CTRL_BLE_LP_EN_MASK | RFMC_RF2P4GHZ_CTRL_BLE_WKUP_MASK; + for (i = 0; i < sizeof(s_radio_regs) / sizeof(s_radio_regs[0]); i++) { + *s_radio_regs[i].reg = s_radio_regs[i].val; + } + XCVR_MISC->XCVR_CTRL &= ~XCVR_MISC_XCVR_CTRL_DEMOD_SEL_MASK; + XCVR_MISC->XCVR_CTRL |= XCVR_MISC_XCVR_CTRL_DEMOD_SEL(2); + XCVR_ZBDEMOD->CCA_LQI_SRC |= XCVR_ZBDEMOD_CCA_LQI_SRC_CCA1_FROM_RX_DIG(1); + return true; +} + +// Random bytes for Mongoose, MG_ENABLE_CUSTOM_RANDOM. Entropy is radio noise: +// receiver RSSI, sampled while the radio receives, mixed with SysTick by +// SHA-256. Good enough for random addresses and nonces, not a certified TRNG +bool mg_random(void *buf, size_t len) { + static uint32_t counter; + uint8_t digest[32], *p = (uint8_t *) buf; + size_t i, n; + while (len > 0) { + mg_sha256_ctx ctx; + mg_sha256_init(&ctx); + counter++; + mg_sha256_update(&ctx, (uint8_t *) &counter, sizeof(counter)); + for (i = 0; i < 64; i++) { + uint32_t sample[2] = {XCVR_RX_DIG->NB_RSSI_RES0, SysTick->VAL}; + mg_sha256_update(&ctx, (uint8_t *) sample, sizeof(sample)); + for (n = 0; n < 50; n++) __NOP(); // Let the RSSI update + } + mg_sha256_final(digest, &ctx); + n = len < sizeof(digest) ? len : sizeof(digest); + memcpy(p, digest, n), p += n, len -= n; + } + return true; +} // 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) { diff --git a/tutorials/nxp/frdm-mcxw71/minimal/hal.h b/tutorials/nxp/frdm-mcxw71/minimal/hal.h index adfd5ebe..778e78eb 100644 --- a/tutorials/nxp/frdm-mcxw71/minimal/hal.h +++ b/tutorials/nxp/frdm-mcxw71/minimal/hal.h @@ -13,13 +13,18 @@ #include #include +uint64_t hal_get_tick(void); +bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now); +size_t hal_ram_used(void); +size_t hal_ram_free(void); +bool hal_radio_init(void); // Set up the 2.4 GHz transceiver for 802.15.4 +extern uint32_t SystemCoreClock; // CPU clock, Hz. Set by hal_clock_init() + + #define BIT(x) (1UL << (x)) #define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) - #define SIRC_FREQUENCY 6000000UL // Slow IRC (FRO6M), Hz -extern uint32_t SystemCoreClock; // CPU clock, Hz. Set by hal_clock_init() - // MRCC peripheral clock source, MRCC_xx[MUX], see fsl_clock.h enum { MRCC_SRC_FRO6M = 2, MRCC_SRC_FRO192M = 3 }; @@ -30,10 +35,43 @@ static inline uint32_t hal_firc_freq(void) { return freqs[SCG0->FIRCCFG & SCG_FIRCCFG_RANGE_MASK]; } -// Work out the CPU clock from the SCG clock source and core divider. The -// clock is left as configured at reset +// Switch the system clock: source (2 SIRC, 3 FIRC), core and slow dividers +// as divider - 1. Wait until the switch is done. See CLOCK_SetRunModeSysClkConfig +static inline void hal_set_sysclk(uint32_t src, uint32_t divcore, + uint32_t divslow) { + SCG0->RCCR = SCG_RCCR_SCS(src) | SCG_RCCR_DIVCORE(divcore) | + SCG_RCCR_DIVSLOW(divslow); + while (((SCG0->CSR & SCG_CSR_SCS_MASK) >> SCG_CSR_SCS_SHIFT) != src) (void) 0; +} + +// Run the core at 96 MHz from FIRC, slow clock 24 MHz. Order as in NXP +// BOARD_BootClockRUN(), clock_config.c: raise the core voltage and memory +// timing first, then the clock. FIRC can only be changed when not in use +static inline void hal_clock_96mhz(void) { + while (SPC0->SC & SPC_SC_BUSY_MASK) (void) 0; + CLRSET(SPC0->ACTIVE_CFG, SPC_ACTIVE_CFG_CORELDO_VDD_LVL_MASK, + SPC_ACTIVE_CFG_CORELDO_VDD_LVL(2)); // Normal voltage + while (SPC0->SC & SPC_SC_BUSY_MASK) (void) 0; + CLRSET(FMU0->FCTRL, FMU_FCTRL_RWSC_MASK, FMU_FCTRL_RWSC(2)); // Wait states + CLRSET(SPC0->SRAMCTL, SPC_SRAMCTL_VSM_MASK, SPC_SRAMCTL_VSM(2)); // 1.1 V + SPC0->SRAMCTL |= SPC_SRAMCTL_REQ_MASK; + while ((SPC0->SRAMCTL & SPC_SRAMCTL_ACK_MASK) == 0) (void) 0; + SPC0->SRAMCTL &= ~SPC_SRAMCTL_REQ_MASK; + hal_set_sysclk(2, 0, 3); // Run from SIRC meanwhile + SCG0->FIRCCSR &= ~SCG_FIRCCSR_LK_MASK; // Unlock + SCG0->FIRCCSR = SCG_FIRCCSR_FIRCERR_MASK; // Disable, clear error + SCG0->FIRCCFG = SCG_FIRCCFG_RANGE(2); // 96 MHz + SCG0->FIRCCSR |= SCG_FIRCCSR_FIRCEN_MASK; + while ((SCG0->FIRCCSR & SCG_FIRCCSR_FIRCVLD_MASK) == 0) (void) 0; + hal_set_sysclk(3, 0, 3); // FIRC, core / 1, slow / 4 +} + +// Set the CPU clock to 96 MHz, then work out SystemCoreClock from the SCG +// clock source and core divider static inline void hal_clock_init(void) { - uint32_t csr = SCG0->CSR, freq = 0; + uint32_t csr, freq = 0; + hal_clock_96mhz(); + csr = SCG0->CSR; uint32_t src = (csr & SCG_CSR_SCS_MASK) >> SCG_CSR_SCS_SHIFT; uint32_t div = ((csr & SCG_CSR_DIVCORE_MASK) >> SCG_CSR_DIVCORE_SHIFT) + 1; if (src == 2) freq = SIRC_FREQUENCY; // Slow IRC @@ -108,7 +146,273 @@ static inline void hal_sys_init(void) { hal_wdog_disable(); } -uint64_t hal_get_tick(void); -bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now); -size_t hal_ram_used(void); -size_t hal_ram_free(void); +// 802.15.4 (O-QPSK 250 kbps) radio register setup for the MCXW71 transceiver. +// Values are taken from the NXP XCVR driver, release/25.06.00, configs/gen45: +// XCVR_RadioGenRegSetup() and XCVR_RadioGenRBMESetup() with +// xcvr_oqpsk_802p15p4_250kbps_full_config and xcvr_ble_uncoded_config. +// https://github.com/nxp-mcuxpresso/mcuxsdk-middleware-xcvr +// Copyright 2016 Freescale Semiconductor, Inc. +// Copyright 2018-2023 NXP +// SPDX-License-Identifier: BSD-3-Clause + +// One radio register write +struct w71_radio_reg { + volatile uint32_t *reg; // Register address + uint32_t val; // Value to write +}; + +// Register writes, in the order the NXP driver performs them +static const struct w71_radio_reg s_radio_regs[] = { + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[0].DMD_WAVE_REG0, 0x0420b3d3}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[0].DMD_WAVE_REG1, 0x31d78f00}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[0].DMD_WAVE_REG2, 0x00025a6d}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[1].DMD_WAVE_REG0, 0x0420b3d3}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[1].DMD_WAVE_REG1, 0x38d78f00}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[1].DMD_WAVE_REG2, 0x0000403c}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[2].DMD_WAVE_REG0, 0x3c00420b}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[2].DMD_WAVE_REG1, 0x082c8100}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[2].DMD_WAVE_REG2, 0x0003c004}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[3].DMD_WAVE_REG0, 0x3c00420b}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[3].DMD_WAVE_REG1, 0x0f2c8100}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[3].DMD_WAVE_REG2, 0x0001b5d3}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[4].DMD_WAVE_REG0, 0x0403ce35}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[4].DMD_WAVE_REG1, 0x31d78f00}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[4].DMD_WAVE_REG2, 0x00025a6d}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[5].DMD_WAVE_REG0, 0x0403ce35}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[5].DMD_WAVE_REG1, 0x38d78f00}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[5].DMD_WAVE_REG2, 0x0000403c}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[6].DMD_WAVE_REG0, 0x3ce35c6d}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[6].DMD_WAVE_REG1, 0x082c8100}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[6].DMD_WAVE_REG2, 0x0003c004}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[7].DMD_WAVE_REG0, 0x3ce35c6d}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[7].DMD_WAVE_REG1, 0x0f2c8100}, + {&XCVR_2P4GHZ_PHY->DEMOD_WAVE[7].DMD_WAVE_REG2, 0x0001b5d3}, + {&XCVR_2P4GHZ_PHY->DMD_CTRL0, 0x6d940101}, + {&XCVR_2P4GHZ_PHY->DMD_CTRL1, 0xa8049008}, + {&XCVR_2P4GHZ_PHY->FSK_CFG0, 0x15001502}, + {&XCVR_2P4GHZ_PHY->FSK_CFG1, 0x0f080020}, + {&XCVR_2P4GHZ_PHY->FSK_CFG2, 0x40000000}, + {&XCVR_2P4GHZ_PHY->FSK_FAD_CFG, 0x00000000}, + {&XCVR_2P4GHZ_PHY->FSK_FAD_CTRL, 0x00000000}, + {&XCVR_2P4GHZ_PHY->FSK_PD_CFG0, 0x0000ff0c}, + {&XCVR_2P4GHZ_PHY->FSK_PD_CFG1, 0x00000055}, + {&XCVR_2P4GHZ_PHY->FSK_PD_CFG2, 0x00f600f6}, + {&XCVR_2P4GHZ_PHY->FSK_PD_PH[0], 0x00030503}, + {&XCVR_2P4GHZ_PHY->FSK_PD_PH[1], 0x003d3b3d}, + {&XCVR_2P4GHZ_PHY->FSK_PT, 0x00000048}, + {&XCVR_2P4GHZ_PHY->LR_AA_CFG, 0x06020696}, + {&XCVR_2P4GHZ_PHY->LR_PD_CFG, 0x0001019a}, + {&XCVR_2P4GHZ_PHY->LR_PD_PH[0], 0x3d383028}, + {&XCVR_2P4GHZ_PHY->LR_PD_PH[1], 0x20283038}, + {&XCVR_2P4GHZ_PHY->LR_PD_PH[2], 0x03081018}, + {&XCVR_2P4GHZ_PHY->LR_PD_PH[3], 0x20181008}, + {&XCVR_2P4GHZ_PHY->MISC, 0x03ff00a0}, + {&XCVR_2P4GHZ_PHY->PREPHY_MISC, 0x00000c10}, + {&XCVR_2P4GHZ_PHY->RTT_CTRL, 0x00004166}, + {&XCVR_2P4GHZ_PHY->RTT_REF, 0x00806348}, + {&XCVR_2P4GHZ_PHY->SM_CFG, 0x00a02432}, + {&RADIO_CTRL->LL_CTRL, 0x00000001}, + {&XCVR_ANALOG->LDO_0, 0x04440400}, + {&XCVR_ANALOG->LDO_1, 0x00000005}, + {&XCVR_ANALOG->PLL, 0x00000040}, + {&XCVR_ANALOG->RX_0, 0x01000002}, + {&XCVR_ANALOG->RX_1, 0x00022008}, + {&XCVR_ANALOG->TX_DAC_PA, 0x01000000}, + {&XCVR_ANALOG->XO_DIST, 0x00000000}, + {&XCVR_MISC->IPS_FO_ADDR[0], 0x00000370}, + {&XCVR_MISC->IPS_FO_ADDR[1], 0x0000002c}, + {&XCVR_MISC->IPS_FO_DRS0_DATA[0], 0x00000000}, + {&XCVR_MISC->IPS_FO_DRS0_DATA[1], 0x00000000}, + {&XCVR_MISC->IPS_FO_DRS1_DATA[0], 0x00000000}, + {&XCVR_MISC->IPS_FO_DRS1_DATA[1], 0x00000000}, + {&XCVR_MISC->LDO_TRIM_0, 0x00020000}, + {&XCVR_MISC->LDO_TRIM_1, 0x00000000}, + {&XCVR_MISC->XCVR_CTRL, 0x00130180}, + {&XCVR_PLL_DIG->CHAN_MAP, 0x00040000}, + {&XCVR_PLL_DIG->CHAN_MAP_EXT, 0x00100000}, + {&XCVR_PLL_DIG->DATA_RATE_OVRD_CTRL1, 0x00000089}, + {&XCVR_PLL_DIG->DATA_RATE_OVRD_CTRL2, 0x00180000}, + {&XCVR_PLL_DIG->DELAY_MATCH, 0x00050102}, + {&XCVR_PLL_DIG->HPMCAL_CTRL, 0x00002000}, + {&XCVR_PLL_DIG->HPM_BUMP, 0x00440144}, + {&XCVR_PLL_DIG->HPM_CAL_TIMING, 0x00000000}, + {&XCVR_PLL_DIG->HPM_CTRL, 0x00840000}, + {&XCVR_PLL_DIG->HPM_SDM_RES, 0x00ff0000}, + {&XCVR_PLL_DIG->LOCK_DETECT, 0x0e1a1800}, + {&XCVR_PLL_DIG->LPM_CTRL, 0x88880a00}, + {&XCVR_PLL_DIG->LPM_SDM_CTRL1, 0x00260000}, + {&XCVR_PLL_DIG->MOD_CTRL, 0x80000000}, + {&XCVR_PLL_DIG->PLL_DATARATE_CTRL, 0x04126644}, + {&XCVR_PLL_DIG->TUNING_CAP_RX_CTRL, 0x00000000}, + {&XCVR_PLL_DIG->TUNING_CAP_TX_CTRL, 0x00ffffa3}, + {&XCVR_RX_DIG->ACQ_FILT_0_3, 0x737f0502}, + {&XCVR_RX_DIG->ACQ_FILT_0_3_DRS, 0x0f7f3a01}, + {&XCVR_RX_DIG->ACQ_FILT_10_11, 0x00ef0061}, + {&XCVR_RX_DIG->ACQ_FILT_10_11_DRS, 0x012b0021}, + {&XCVR_RX_DIG->ACQ_FILT_4_7, 0xf8221478}, + {&XCVR_RX_DIG->ACQ_FILT_4_7_DRS, 0x3c06617f}, + {&XCVR_RX_DIG->ACQ_FILT_8_9, 0x01d501bc}, + {&XCVR_RX_DIG->ACQ_FILT_8_9_DRS, 0x019401f3}, + {&XCVR_RX_DIG->AGC_CTRL, 0x587c8d19}, + {&XCVR_RX_DIG->AGC_CTRL_STAT, 0x0000872c}, + {&XCVR_RX_DIG->AGC_IDX0_GAIN_CFG, 0x000000f2}, + {&XCVR_RX_DIG->AGC_IDX0_GAIN_VAL, 0x000003fd}, + {&XCVR_RX_DIG->AGC_IDX0_THR, 0x0000001e}, + {&XCVR_RX_DIG->AGC_IDX10_GAIN_CFG, 0x00004a03}, + {&XCVR_RX_DIG->AGC_IDX10_GAIN_VAL, 0x000000eb}, + {&XCVR_RX_DIG->AGC_IDX10_THR, 0x0096001e}, + {&XCVR_RX_DIG->AGC_IDX11_GAIN_CFG, 0x00007e03}, + {&XCVR_RX_DIG->AGC_IDX11_GAIN_VAL, 0x000000fa}, + {&XCVR_RX_DIG->AGC_IDX11_THR, 0x00780000}, + {&XCVR_RX_DIG->AGC_IDX1_GAIN_CFG, 0x000001b2}, + {&XCVR_RX_DIG->AGC_IDX1_GAIN_VAL, 0x0000000b}, + {&XCVR_RX_DIG->AGC_IDX1_THR, 0x015e001e}, + {&XCVR_RX_DIG->AGC_IDX2_GAIN_CFG, 0x00000123}, + {&XCVR_RX_DIG->AGC_IDX2_GAIN_VAL, 0x00000026}, + {&XCVR_RX_DIG->AGC_IDX2_THR, 0x00c8001e}, + {&XCVR_RX_DIG->AGC_IDX3_GAIN_CFG, 0x00000113}, + {&XCVR_RX_DIG->AGC_IDX3_GAIN_VAL, 0x0000003c}, + {&XCVR_RX_DIG->AGC_IDX3_THR, 0x00c8001e}, + {&XCVR_RX_DIG->AGC_IDX4_GAIN_CFG, 0x00000083}, + {&XCVR_RX_DIG->AGC_IDX4_GAIN_VAL, 0x00000057}, + {&XCVR_RX_DIG->AGC_IDX4_THR, 0x00c8001e}, + {&XCVR_RX_DIG->AGC_IDX5_GAIN_CFG, 0x00000183}, + {&XCVR_RX_DIG->AGC_IDX5_GAIN_VAL, 0x0000006d}, + {&XCVR_RX_DIG->AGC_IDX5_THR, 0x00fa001e}, + {&XCVR_RX_DIG->AGC_IDX6_GAIN_CFG, 0x00000203}, + {&XCVR_RX_DIG->AGC_IDX6_GAIN_VAL, 0x00000087}, + {&XCVR_RX_DIG->AGC_IDX6_THR, 0x012c001e}, + {&XCVR_RX_DIG->AGC_IDX7_GAIN_CFG, 0x00000603}, + {&XCVR_RX_DIG->AGC_IDX7_GAIN_VAL, 0x000000a0}, + {&XCVR_RX_DIG->AGC_IDX7_THR, 0x012c001e}, + {&XCVR_RX_DIG->AGC_IDX8_GAIN_CFG, 0x00000e03}, + {&XCVR_RX_DIG->AGC_IDX8_GAIN_VAL, 0x000000b7}, + {&XCVR_RX_DIG->AGC_IDX8_THR, 0x012c001e}, + {&XCVR_RX_DIG->AGC_IDX9_GAIN_CFG, 0x00002203}, + {&XCVR_RX_DIG->AGC_IDX9_GAIN_VAL, 0x000000d2}, + {&XCVR_RX_DIG->AGC_IDX9_THR, 0x0096001e}, + {&XCVR_RX_DIG->AGC_MIS_GAIN_CFG, 0x00000001}, + {&XCVR_RX_DIG->AGC_THR_FAST, 0x0190000f}, + {&XCVR_RX_DIG->AGC_THR_FAST_DRS, 0x0190001e}, + {&XCVR_RX_DIG->AGC_THR_MIS, 0x00960028}, + {&XCVR_RX_DIG->AGC_TIMING0, 0x04102398}, + {&XCVR_RX_DIG->AGC_TIMING0_DRS, 0x40002000}, + {&XCVR_RX_DIG->AGC_TIMING1, 0x190242a7}, + {&XCVR_RX_DIG->AGC_TIMING1_DRS, 0x000002a7}, + {&XCVR_RX_DIG->AGC_TIMING2, 0xe000007f}, + {&XCVR_RX_DIG->AGC_TIMING2_DRS, 0x0000007f}, + {&XCVR_RX_DIG->CTRL0, 0x68063083}, + {&XCVR_RX_DIG->CTRL0_DRS, 0x000030c0}, + {&XCVR_RX_DIG->CTRL1, 0x00000180}, + {&XCVR_RX_DIG->DCOC_CTRL0, 0x04332d28}, + {&XCVR_RX_DIG->DCOC_CTRL0_DRS, 0x00000114}, + {&XCVR_RX_DIG->DCOC_CTRL1, 0x00000000}, + {&XCVR_RX_DIG->DEMOD_FILT_0_1, 0x01fe01fd}, + {&XCVR_RX_DIG->DEMOD_FILT_0_1_DRS, 0x00ff0003}, + {&XCVR_RX_DIG->DEMOD_FILT_2_4, 0x0ba20824}, + {&XCVR_RX_DIG->DEMOD_FILT_2_4_DRS, 0x124221ff}, + {&XCVR_RX_DIG->NB_RSSI_CTRL0, 0xed472234}, + {&XCVR_RX_DIG->NB_RSSI_CTRL1, 0x00000000}, + {&XCVR_RX_DIG->RCCAL_CTRL0, 0x0000003a}, + {&XCVR_RX_DIG->RSSI_GLOBAL_CTRL, 0x80008330}, + {&XCVR_RX_DIG->WB_RSSI_CTRL, 0x00223342}, + {&XCVR_TSM->CTRL, 0xff000000}, + {&XCVR_TSM->END_OF_SEQ, 0x5c5a7270}, + {&XCVR_TSM->FAST_CTRL1, 0x24006f00}, + {&XCVR_TSM->FAST_CTRL2, 0x66284208}, + {&XCVR_TSM->FAST_CTRL3, 0x18001800}, + {&XCVR_TSM->RECYCLE_COUNT, 0x00001258}, + {&XCVR_TSM->TIMING00, 0xffffffff}, + {&XCVR_TSM->TIMING01, 0xffffffff}, + {&XCVR_TSM->TIMING02, 0xffffffff}, + {&XCVR_TSM->TIMING03, 0xffffffff}, + {&XCVR_TSM->TIMING04, 0xffffffff}, + {&XCVR_TSM->TIMING05, 0xffffffff}, + {&XCVR_TSM->TIMING06, 0xffffffff}, + {&XCVR_TSM->TIMING07, 0xffffffff}, + {&XCVR_TSM->TIMING08, 0xffffffff}, + {&XCVR_TSM->TIMING09, 0x5700ffff}, + {&XCVR_TSM->TIMING10, 0x11081108}, + {&XCVR_TSM->TIMING11, 0x5b137113}, + {&XCVR_TSM->TIMING12, 0x5b19714f}, + {&XCVR_TSM->TIMING13, 0x572dffff}, + {&XCVR_TSM->TIMING14, 0x0000716f}, + {&XCVR_TSM->TIMING15, 0x5b58716f}, + {&XCVR_TSM->TIMING16, 0x59580000}, + {&XCVR_TSM->TIMING17, 0x5b580000}, + {&XCVR_TSM->TIMING18, 0x5b580000}, + {&XCVR_TSM->TIMING19, 0x11061106}, + {&XCVR_TSM->TIMING20, 0x11001100}, + {&XCVR_TSM->TIMING21, 0x19001100}, + {&XCVR_TSM->TIMING22, 0x58006f00}, + {&XCVR_TSM->TIMING23, 0x58006f00}, + {&XCVR_TSM->TIMING24, 0x5b007100}, + {&XCVR_TSM->TIMING25, 0x5c007200}, + {&XCVR_TSM->TIMING26, 0x5c007200}, + {&XCVR_TSM->TIMING27, 0x5c007200}, + {&XCVR_TSM->TIMING28, 0x5c007200}, + {&XCVR_TSM->TIMING29, 0xffff7200}, + {&XCVR_TSM->TIMING30, 0x5c00ffff}, + {&XCVR_TSM->TIMING31, 0x5b087108}, + {&XCVR_TSM->TIMING32, 0x1911ffff}, + {&XCVR_TSM->TIMING33, 0x23115a11}, + {&XCVR_TSM->TIMING34, 0x5b117111}, + {&XCVR_TSM->TIMING35, 0x5b117111}, + {&XCVR_TSM->TIMING36, 0x5b117111}, + {&XCVR_TSM->TIMING37, 0x5b117111}, + {&XCVR_TSM->TIMING38, 0x5b117111}, + {&XCVR_TSM->TIMING39, 0x5b2b7111}, + {&XCVR_TSM->TIMING40, 0xffff7111}, + {&XCVR_TSM->TIMING41, 0xffff7111}, + {&XCVR_TSM->TIMING42, 0x5b127112}, + {&XCVR_TSM->TIMING43, 0x5b12ffff}, + {&XCVR_TSM->TIMING44, 0x5b12ffff}, + {&XCVR_TSM->TIMING45, 0xffff7112}, + {&XCVR_TSM->TIMING46, 0xffff716f}, + {&XCVR_TSM->TIMING47, 0x5b137157}, + {&XCVR_TSM->TIMING48, 0x5b23715a}, + {&XCVR_TSM->TIMING49, 0x5b23715a}, + {&XCVR_TSM->TIMING50, 0x5b2bffff}, + {&XCVR_TSM->TIMING51, 0x5b2bffff}, + {&XCVR_TSM->TIMING52, 0xffffffff}, + {&XCVR_TSM->WU_LATENCY, 0x00060006}, + {&XCVR_TX_DIG->DATARATE_CONFIG_FILTER_CTRL, 0x00010000}, + {&XCVR_TX_DIG->DATARATE_CONFIG_FSK_CTRL, 0x08001800}, + {&XCVR_TX_DIG->DATARATE_CONFIG_GFSK_CTRL, 0x00000804}, + {&XCVR_TX_DIG->DATA_PADDING_CTRL, 0x00001012}, + {&XCVR_TX_DIG->DATA_PADDING_CTRL_1, 0x00001600}, + {&XCVR_TX_DIG->DATA_PADDING_CTRL_2, 0x08001800}, + {&XCVR_TX_DIG->FSK_CTRL, 0x04001c00}, + {&XCVR_TX_DIG->GFSK_COEFF_0_1, 0x00040001}, + {&XCVR_TX_DIG->GFSK_COEFF_2_3, 0x0028000d}, + {&XCVR_TX_DIG->GFSK_COEFF_4_5, 0x00c00063}, + {&XCVR_TX_DIG->GFSK_COEFF_6_7, 0x0176012c}, + {&XCVR_TX_DIG->GFSK_CTRL, 0x000003fd}, + {&XCVR_TX_DIG->IMAGE_FILTER_CTRL, 0x00000000}, + {&XCVR_TX_DIG->PA_CTRL, 0x00020126}, + {&XCVR_TX_DIG->PA_RAMP_TBL0, 0x06040201}, + {&XCVR_TX_DIG->PA_RAMP_TBL1, 0x14100c09}, + {&XCVR_TX_DIG->PA_RAMP_TBL2, 0x26211c18}, + {&XCVR_TX_DIG->PA_RAMP_TBL3, 0x3c38322c}, + {&XCVR_TX_DIG->SWITCH_TX_CTRL, 0x00000000}, + {&XCVR_TX_DIG->TXDIG_CTRL, 0x00000007}, + {&XCVR_2P4GHZ_PHY->DMD_CTRL1, 0x8c049008}, + {&RBME->CRCW_CFG, 0x00000001}, + {&RBME->CRCW_CFG2, 0x00000000}, + {&RBME->CRCW_CFG3, 0x00000403}, + {&RBME->CRC_INIT, 0x55555500}, + {&RBME->CRC_PHR_SZ, 0x00000002}, + {&RBME->CRC_POLY, 0x00065b00}, + {&RBME->CRC_XOR_OUT, 0x00000000}, + {&RBME->FCP_CFG, 0x00000000}, + {&RBME->FEC_BSZ_OV_B4SP, 0x00000000}, + {&RBME->FEC_CFG1, 0x00000000}, + {&RBME->FEC_CFG2, 0x00000000}, + {&RBME->FRAME_OVER_SZ, 0x00000000}, + {&RBME->NPAYL_OVER_SZ, 0x00000000}, + {&RBME->PKT_SZ, 0x00244000}, + {&RBME->SPREAD_CFG, 0x00000000}, + {&RBME->WHITEN_CFG, 0x00410705}, + {&RBME->WHITEN_POLY, 0x00000004}, + {&RBME->WHITEN_SZ_THR, 0x00000000}, +}; diff --git a/tutorials/nxp/frdm-mcxw71/minimal/link.ld b/tutorials/nxp/frdm-mcxw71/minimal/link.ld index bdd815db..9c78c8d5 100644 --- a/tutorials/nxp/frdm-mcxw71/minimal/link.ld +++ b/tutorials/nxp/frdm-mcxw71/minimal/link.ld @@ -1,7 +1,7 @@ /* MCXW716C memory map, see MCUXpresso SDK gcc/mcxw716_flash.ld */ ENTRY(Reset_Handler); MEMORY { - flash(rx) : ORIGIN = 0x00000000, LENGTH = 1024k + flash(rx) : ORIGIN = 0x00000000, LENGTH = 512k /* Upper half: OTA staging */ sram(rwx) : ORIGIN = 0x20000000, LENGTH = 112k } __StackTop = ORIGIN(sram) + LENGTH(sram); /* Stack points here */ @@ -16,6 +16,7 @@ SECTIONS { __data_start__ = .; _sdata = .; *(.first_data) + *(.iram .iram.*) /* OTA flash operations, copied to RAM by startup */ *(.data SORT(.data.*)) . = ALIGN(4); __data_end__ = .; diff --git a/tutorials/nxp/frdm-mcxw71/minimal/main.c b/tutorials/nxp/frdm-mcxw71/minimal/main.c index b374c74b..2cb7510e 100644 --- a/tutorials/nxp/frdm-mcxw71/minimal/main.c +++ b/tutorials/nxp/frdm-mcxw71/minimal/main.c @@ -1,5 +1,10 @@ // Copyright (c) 2026 Cesanta Software Limited // All rights reserved +// +// IPv4 over IEEE 802.15.4 on FRDM-MCXW71, Mongoose built-in TCP/IP stack. +// The radio carries Ethernet frames, channel 15, PAN ID 0xabcd. With the +// FRDM-RW612 eth_radio_bridge tutorial, gets an address from the LAN's DHCP +// server and is reachable from the LAN. Serves pages on ports 80 and 443 #include "hal.h" #include "mongoose.h" @@ -14,19 +19,71 @@ uint64_t mg_millis(void) { return hal_get_tick(); } +// Self-signed P-256 certificate and key for the HTTPS listener. Created by: +// openssl ecparam -genkey -name prime256v1 -noout -out key.pem +// openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg +#define TLS_CERT \ + "-----BEGIN CERTIFICATE-----\n" \ + "MIIBgjCCASegAwIBAgIUIJOq77Qn7lHbH6WseMemeqorR50wCgYIKoZIzj0EAwIw\n" \ + "FjEUMBIGA1UEAwwLbnVjbGVvLWg3MjMwHhcNMjYxMDEyMTUwMjE5WhcNMzYxMDA5\n" \ + "MTUwMjE5WjAWMRQwEgYDVQQDDAtudWNsZW8taDcyMzBZMBMGByqGSM49AgEGCCqG\n" \ + "SM49AwEHA0IABB6IQJNmzrsdQL+2BnZYS7GYOaj7sz8f6cjBmz3n8D9Em19XodlU\n" \ + "uy4ybIvyhr766lL0ERoTmMUivb/lGmzx9C+jUzBRMB0GA1UdDgQWBBQqxMuc4gj7\n" \ + "MTMzreWlRMNI3QMqTzAfBgNVHSMEGDAWgBQqxMuc4gj7MTMzreWlRMNI3QMqTzAP\n" \ + "BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQDY4xaJpuxq6b3rHK3b\n" \ + "lOIQyfkQX+orCRqQUkseplqpqwIhAKswZFNn+2zUNRRNY8+cHmdIwmdpsmxtoWYP\n" \ + "BCjX++LI\n" \ + "-----END CERTIFICATE-----\n" + +#define TLS_KEY \ + "-----BEGIN EC PRIVATE KEY-----\n" \ + "MHcCAQEEIJ/GBl0TlD52q9PMg1KWaNIdXqp8b/HyIsABmBpqC+O1oAoGCCqGSM49\n" \ + "AwEHoUQDQgAEHohAk2bOux1Av7YGdlhLsZg5qPuzPx/pyMGbPefwP0SbX1eh2VS7\n" \ + "LjJsi/KGvvrqUvQRGhOYxSK9v+UabPH0Lw==\n" \ + "-----END EC PRIVATE KEY-----\n" + +static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_ACCEPT && c->is_tls) { // Accepted on the https:// listener + struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT), + .key = mg_str(TLS_KEY)}; + mg_tls_init(c, &opts); + } else if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { + mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); + } else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) { + __asm volatile("udf #0"); // Trigger HardFault + } else { + mg_http_reply(c, 200, "", + "Hi from Mongoose!\n" + "tick: %llu\n" + "ram used: %zu\n" + "ram free: %zu\n", + hal_get_tick(), hal_ram_used(), hal_ram_free()); + } + } +} + int main(void) { hal_clock_init(); hal_uart_init(UART_DEBUG, 115200); mg_log_set_fn(log_fn, UART_DEBUG); + if (!hal_radio_init()) MG_ERROR(("Radio init failed")); + + struct mg_mgr mgr; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://0.0.0.0:80", http_ev_handler, NULL); + mg_http_listen(&mgr, "https://0.0.0.0:443", http_ev_handler, NULL); + mg_mqtt_init(&mgr); + MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000)); MG_INFO(("RAM used: %zu, free: %zu", hal_ram_used(), hal_ram_free())); + MG_INFO(("Crash record: %M", mg_print_crash_record)); for (;;) { - static uint64_t timer; - if (hal_timer_expired(&timer, 1000, hal_get_tick())) { - MG_INFO(("Hi from Mongoose, tick %llu", hal_get_tick())); - } + mg_mgr_poll(&mgr, 0); + mg_mqtt_poll(&mgr); } return 0; diff --git a/tutorials/nxp/frdm-mcxw71/minimal/mongoose_config.h b/tutorials/nxp/frdm-mcxw71/minimal/mongoose_config.h index 35e463c6..5d236e1e 100644 --- a/tutorials/nxp/frdm-mcxw71/minimal/mongoose_config.h +++ b/tutorials/nxp/frdm-mcxw71/minimal/mongoose_config.h @@ -1,7 +1,23 @@ #pragma once +#include "MCXW716C.h" -// See https://mongoose.ws/documentation/#build-options +// 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_MCXW71 -#define MG_ENABLE_TCPIP 1 // Built-in TCP/IP stack. No network driver yet +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_DRIVER_MCXW71 1 #define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_TCPIP_TCPRTX 1 // Link can be lossy, enable retransmit + +// Stable MAC, so the DHCP address stays the same across reboots: a hash of +// the 128-bit chip unique ID, MSCM UID, locally administered +#define MG_SET_MAC_ADDRESS(mac) \ + do { \ + uint8_t b_[32]; \ + mg_sha256(b_, (uint8_t *) MSCM->UID, sizeof(MSCM->UID)); \ + (mac)[0] = 2, (mac)[1] = b_[1], (mac)[2] = b_[2], (mac)[3] = b_[3]; \ + (mac)[4] = b_[4], (mac)[5] = b_[5]; \ + } while (0) diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/Makefile b/tutorials/nxp/frdm-rw612/eth_radio_bridge/Makefile new file mode 100644 index 00000000..6b09174d --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/Makefile @@ -0,0 +1,63 @@ +# Environment setup: https://mongoose.ws/documentation/tutorials/tools/ + +MGDIR = ../../../.. + +CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion +CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion +CFLAGS += -g3 -Os -ffunction-sections -fdata-sections +CFLAGS += -Wno-cast-function-type -Wno-old-style-declaration +CFLAGS += -Wno-unused-parameter +CFLAGS += -I. -I$(MGDIR) -Imongoose -Icmsis_core/CMSIS/Core/Include/ +CFLAGS += $(addprefix -Icmsis_rw612/devices/RW612/drivers/romapi/,bootloader nboot otp iap flexspi) +CFLAGS += -Icmsis_rw612/devices/RW612/ -Icmsis_rw612/devices/RW612/periph/ -Icmsis_rw612/devices/RW612/drivers/ +CFLAGS += -DCPU_RW612ETA2I -DCPU_RW612ETA2I_cm33_no_dsp +CFLAGS += -DBOOT_HEADER_ENABLE=1 +CFLAGS += -DRADIO_FW='"$(RADIO_FW)"' +CFLAGS += -mcpu=cortex-m33+nodsp -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CFLAGS_EXTRA) +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections + +SOURCES = main.c hal.c radio.c startup.c flexspi.c flash_config.c $(MGDIR)/mongoose.c +SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_clock.c +SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_trng.c +SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_reset.c +SOURCES += $(addprefix cmsis_rw612/devices/RW612/drivers/,fsl_power.c fsl_ocotp.c \ + romapi/bootloader/fsl_romapi.c romapi/nboot/fsl_romapi_nboot.c romapi/otp/fsl_romapi_otp.c) + +# NXP BLE/802.15.4 combo firmware for CPU2, linked into our image. NXP license: +# https://github.com/NXP/wifi_nb_fw/blob/nxp-v4.4.3/LICENSE.txt +RADIO_FW = rw61x_sb_ble_15d4_combo_a2.bin +RADIO_FW_URL = https://github.com/NXP/wifi_nb_fw/raw/nxp-v4.4.3/rw61x/$(RADIO_FW) +RADIO_FW_SHA256 = 2efac0d771e371377d57159c40f0909c7ecc6f6b950ce69ed2a3e02ac7eb686a + +all build: firmware.bin + +firmware.bin: firmware.elf + arm-none-eabi-objcopy -O binary $< $@ + arm-none-eabi-size --format=berkeley $< + +firmware.elf: cmsis_core cmsis_rw612 $(RADIO_FW) $(SOURCES) $(wildcard *.h) link.ld Makefile mongoose_config.h + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@ + +cmsis_core: + git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ + +$(RADIO_FW): + curl -sL $(RADIO_FW_URL) -o $@.tmp + echo "$(RADIO_FW_SHA256) $@.tmp" | shasum -a 256 -c - >/dev/null + mv $@.tmp $@ + +cmsis_rw612: + curl -sL https://mcuxpresso.nxp.com/cmsis_pack/repo/NXP.RW612_DFP.25.12.00.pack -o NXP.RW612_DFP.25.12.00.pack + unzip -q NXP.RW612_DFP.25.12.00.pack -d $@ + rm NXP.RW612_DFP.25.12.00.pack + +# Show all symbols sorted by size +show_symbols_sorted_by_size: firmware.bin + arm-none-eabi-readelf -sW firmware.elf | sed 1,3d | sed 's,^.*: ,,' | tr -s ' ' | cut -d ' ' -f 2,7 | sort -nk1 | grep -v '^0 ' + +flash: firmware.bin + (echo 'loadfile firmware.elf'; echo 'reset'; echo 'exit';) | JLinkExe -device RW612 -if SWD -speed 4000 -autoconnect 1 -NoGui 1 -ExitOnError 1 +# JRun --nogui --device RW612 --if SWD firmware.elf + +clean: + rm -rf firmware.* cmsis_* mongoose/mongoose.* $(RADIO_FW) diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/flash_config.c b/tutorials/nxp/frdm-rw612/eth_radio_bridge/flash_config.c new file mode 100644 index 00000000..e343febc --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/flash_config.c @@ -0,0 +1,86 @@ +/* + * Copyright 2021-2024 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "flash_config.h" + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.flash_config" +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +#if defined(BOOT_HEADER_ENABLE) && (BOOT_HEADER_ENABLE == 1) +#if defined(__ARMCC_VERSION) || defined(__GNUC__) +__attribute__((section(".flash_conf"), used)) +#elif defined(__ICCARM__) +#pragma location = ".flash_conf" +#endif +const fc_flexspi_nor_config_t flexspi_config = { + .memConfig = + { + .tag = FC_BLOCK_TAG, + .version = FC_BLOCK_VERSION, + .readSampleClkSrc = 1, + .csHoldTime = 3, + .csSetupTime = 3, + .deviceModeCfgEnable = 1, + .deviceModeSeq = {.seqNum = 1, .seqId = 2}, + .deviceModeArg = 0x02, + .configCmdEnable = 0, + .deviceType = 0x1, + .sflashPadType = kSerialFlash_4Pads, + .serialClkFreq = 5, + .sflashA1Size = 0x4000000U, + .sflashA2Size = 0, + .sflashB1Size = 0, + .sflashB2Size = 0, + .lookupTable = + { + /* Read */ + [0] = FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0xEC, FC_RADDR_SDR, FC_FLEXSPI_4PAD, 0x20), + [1] = FC_FLEXSPI_LUT_SEQ(FC_MODE8_SDR, FC_FLEXSPI_4PAD, 0xF0, FC_DUMMY_SDR, FC_FLEXSPI_4PAD, 0x04), + [2] = FC_FLEXSPI_LUT_SEQ(FC_READ_SDR, FC_FLEXSPI_4PAD, 0x04, FC_STOP_EXE, FC_FLEXSPI_1PAD, 0x00), + + /* Read Status */ + [4 * 1 + 0] = + FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x05, FC_READ_SDR, FC_FLEXSPI_1PAD, 0x04), + + /* Write Status */ + [4 * 2 + 0] = + FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x31, FC_WRITE_SDR, FC_FLEXSPI_1PAD, 0x01), + + /* Write Enable */ + [4 * 3 + 0] = + FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x06, FC_STOP_EXE, FC_FLEXSPI_1PAD, 0x00), + + /* Sector erase */ + [4 * 5 + 0] = + FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x21, FC_RADDR_SDR, FC_FLEXSPI_1PAD, 0x20), + + /* Block erase */ + [4 * 8 + 0] = + FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0xDC, FC_RADDR_SDR, FC_FLEXSPI_1PAD, 0x20), + + /* Page program */ + [4 * 9 + 0] = + FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0x34, FC_RADDR_SDR, FC_FLEXSPI_1PAD, 0x20), + [4 * 9 + 1] = + FC_FLEXSPI_LUT_SEQ(FC_WRITE_SDR, FC_FLEXSPI_4PAD, 0x00, FC_STOP_EXE, FC_FLEXSPI_1PAD, 0x00), + + /* chip erase */ + [4 * 11 + 0] = + FC_FLEXSPI_LUT_SEQ(FC_CMD_SDR, FC_FLEXSPI_1PAD, 0xC7, FC_STOP_EXE, FC_FLEXSPI_1PAD, 0x00), + }, + }, + .pageSize = 0x100, + .sectorSize = 0x1000, + .ipcmdSerialClkFreq = 0, + .blockSize = 0x10000, + .fcb_fill[0] = 0xFFFFFFFF, +}; +#endif /* BOOT_HEADER_ENABLE */ diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/flash_config.h b/tutorials/nxp/frdm-rw612/eth_radio_bridge/flash_config.h new file mode 100644 index 00000000..31cd29c8 --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/flash_config.h @@ -0,0 +1,221 @@ +/* + * Copyright 2021-2023 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __FLASH_CONFIG__ +#define __FLASH_CONFIG__ + +#include +#include +#include "fsl_common.h" + +/*! @name Driver version */ +/*@{*/ +/*! @brief FLASH_CONFIG driver version 2.0.0. */ +#define FSL_FLASH_CONFIG_DRIVER_VERSION (MAKE_VERSION(2, 0, 0)) +/*@}*/ + +/******************************************************************************* + * Definition + ******************************************************************************/ + +/* FLEXSPI memory config block related defintions */ +#define FC_BLOCK_TAG (0x42464346) +#define FC_BLOCK_VERSION (0x00000000) + +#define FC_CMD_SDR 0x01 +#define FC_CMD_DDR 0x21 +#define FC_RADDR_SDR 0x02 +#define FC_RADDR_DDR 0x22 +#define FC_CADDR_SDR 0x03 +#define FC_CADDR_DDR 0x23 +#define FC_MODE1_SDR 0x04 +#define FC_MODE1_DDR 0x24 +#define FC_MODE2_SDR 0x05 +#define FC_MODE2_DDR 0x25 +#define FC_MODE4_SDR 0x06 +#define FC_MODE4_DDR 0x26 +#define FC_MODE8_SDR 0x07 +#define FC_MODE8_DDR 0x27 +#define FC_WRITE_SDR 0x08 +#define FC_WRITE_DDR 0x28 +#define FC_READ_SDR 0x09 +#define FC_READ_DDR 0x29 +#define FC_LEARN_SDR 0x0A +#define FC_LEARN_DDR 0x2A +#define FC_DATSZ_SDR 0x0B +#define FC_DATSZ_DDR 0x2B +#define FC_DUMMY_SDR 0x0C +#define FC_DUMMY_DDR 0x2C +#define FC_DUMMY_RWDS_SDR 0x0D +#define FC_DUMMY_RWDS_DDR 0x2D +#define FC_JMP_ON_CS 0x1F +#define FC_STOP_EXE 0 + +#define FC_FLEXSPI_1PAD 0 +#define FC_FLEXSPI_2PAD 1 +#define FC_FLEXSPI_4PAD 2 +#define FC_FLEXSPI_8PAD 3 + +#define FC_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \ + FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) + +/* !@brief Data pad used in Read command */ +enum +{ + kSerialFlash_1Pads = 1, + kSerialFlash_2Pads = 2, + kSerialFlash_4Pads = 4, + kSerialFlash_8Pads = 8, +}; + +/* !@brief FLEXSPI clock configuration - In High speed boot mode mode */ +enum +{ + kFlexSpiSerialClk_30MHz = 1, + kFlexSpiSerialClk_50MHz = 2, + kFlexSpiSerialClk_60MHz = 3, + kFlexSpiSerialClk_80MHz = 4, + kFlexSpiSerialClk_100MHz = 5, + kFlexSpiSerialClk_120MHz = 6, + kFlexSpiSerialClk_133MHz = 7, + kFlexSpiSerialClk_166MHz = 8, + kFlexSpiSerialClk_200MHz = 9, +}; + +/* !@brief FLEXSPI clock configuration - In Normal boot SDR mode */ +enum +{ + kFlexSpiSerialClk_SDR_24MHz = 1, + kFlexSpiSerialClk_SDR_48MHz = 2, +}; + +/* !@brief FLEXSPI clock configuration - In Normal boot DDR mode */ +enum +{ + kFlexSpiSerialClk_DDR_48MHz = 1, +}; + +/* !@brief Misc feature bit definitions */ +enum +{ + kFlexSpiMiscOffset_DiffClkEnable = 0, /* !< Bit for Differential clock enable */ + kFlexSpiMiscOffset_WordAddressableEnable = 3, /* !< Bit for Word Addressable enable */ + kFlexSpiMiscOffset_SafeConfigFreqEnable = 4, /* !< Bit for Safe Configuration Frequency enable */ + kFlexSpiMiscOffset_DdrModeEnable = 6, /* !< Bit for DDR clock confiuration indication. */ +}; + +/* !@brief Flash Configuration Command Type */ +enum +{ + kDeviceConfigCmdType_Generic, /* !< Generic command, for example: configure dummy cycles, drive strength, etc */ + kDeviceConfigCmdType_QuadEnable, /* !< Quad Enable command */ + kDeviceConfigCmdType_Spi2Xpi, /* !< Switch from SPI to DPI/QPI/OPI mode */ + kDeviceConfigCmdType_Xpi2Spi, /* !< Switch from DPI/QPI/OPI to SPI mode */ + kDeviceConfigCmdType_Spi2NoCmd, /* !< Switch to 0-4-4/0-8-8 mode */ + kDeviceConfigCmdType_Reset, /* !< Reset device command */ +}; + +typedef struct _fc_flexspi_dll_time +{ + uint8_t time_100ps; /* !< Data valid time, in terms of 100ps */ + uint8_t delay_cells; /* !< Data valid time, in terms of delay cells */ +} fc_flexspi_dll_time_t; + +/* !@brief FlexSPI LUT Sequence structure */ +typedef struct _fc_flexspi_lut_seq +{ + uint8_t seqNum; /* !< Sequence Number, valid number: 1-16 */ + uint8_t seqId; /* !< Sequence Index, valid number: 0-15 */ + uint16_t reserved; +} fc_flexspi_lut_seq_t; + +/* !@brief FlexSPI Memory Configuration Block */ +typedef struct _fc_flexspi_mem_config +{ + uint32_t tag; /* !< [0x000-0x003] Tag, fixed value 0x42464346UL */ + uint32_t version; /* !< [0x004-0x007] Version,[31:24] -'V', [23:16] - Major, [15:8] - Minor, [7:0] - bugfix */ + uint32_t reserved0; /* !< [0x008-0x00b] Reserved for future use */ + uint8_t readSampleClkSrc; /* !< [0x00c-0x00c] Read Sample Clock Source, valid value: 0/1/3 */ + uint8_t csHoldTime; /* !< [0x00d-0x00d] CS hold time, default value: 3 */ + uint8_t csSetupTime; /* !< [0x00e-0x00e] CS setup time, default value: 3 */ + uint8_t columnAddressWidth; /* !< [0x00f-0x00f] Column Address with, for HyperBus protocol, it is fixed to 3, For + Serial NAND, need to refer to datasheet */ + uint8_t deviceModeCfgEnable; /* !< [0x010-0x010] Device Mode Configure enable flag, 1 - Enable, 0 - Disable */ + uint8_t deviceModeType; /* !< [0x011-0x011] Specify the configuration command type:Quad Enable, DPI/QPI/OPI switch, + Generic configuration, etc. */ + uint16_t waitTimeCfgCommands; /* !< [0x012-0x013] Wait time for all configuration commands, unit: 100us, Used for + DPI/QPI/OPI switch or reset command */ + fc_flexspi_lut_seq_t deviceModeSeq; /* !< [0x014-0x017] Device mode sequence info, [7:0] - LUT sequence id, [15:8] - + LUt sequence number, [31:16] Reserved */ + uint32_t deviceModeArg; /* !< [0x018-0x01b] Argument/Parameter for device configuration */ + uint8_t configCmdEnable; /* !< [0x01c-0x01c] Configure command Enable Flag, 1 - Enable, 0 - Disable */ + uint8_t configModeType[3]; /* !< [0x01d-0x01f] Configure Mode Type, similar as deviceModeTpe */ + fc_flexspi_lut_seq_t configCmdSeqs[3]; /* !< [0x020-0x02b] Sequence info for Device Configuration command, similar + as deviceModeSeq */ + uint32_t reserved1; /* !< [0x02c-0x02f] Reserved for future use */ + uint32_t configCmdArgs[3]; /* !< [0x030-0x03b] Arguments/Parameters for device Configuration commands */ + uint32_t reserved2; /* !< [0x03c-0x03f] Reserved for future use */ + uint32_t controllerMiscOption; /* !< [0x040-0x043] Controller Misc Options, see Misc feature bit definitions for + more details */ + uint8_t deviceType; /* !< [0x044-0x044] Device Type: See Flash Type Definition for more details */ + uint8_t sflashPadType; /* !< [0x045-0x045] Serial Flash Pad Type: 1 - Single, 2 - Dual, 4 - Quad, 8 - Octal */ + uint8_t serialClkFreq; /* !< [0x046-0x046] Serial Flash Frequencey, device specific definitions, See System Boot + Chapter for more details */ + uint8_t lutCustomSeqEnable; /* !< [0x047-0x047] LUT customization Enable, it is required if the program/erase cannot + be done using 1 LUT sequence, currently, only applicable to HyperFLASH */ + uint32_t reserved3[2]; /* !< [0x048-0x04f] Reserved for future use */ + uint32_t sflashA1Size; /* !< [0x050-0x053] Size of Flash connected to A1 */ + uint32_t sflashA2Size; /* !< [0x054-0x057] Size of Flash connected to A2 */ + uint32_t sflashB1Size; /* !< [0x058-0x05b] Size of Flash connected to B1 */ + uint32_t sflashB2Size; /* !< [0x05c-0x05f] Size of Flash connected to B2 */ + uint32_t csPadSettingOverride; /* !< [0x060-0x063] CS pad setting override value */ + uint32_t sclkPadSettingOverride; /* !< [0x064-0x067] SCK pad setting override value */ + uint32_t dataPadSettingOverride; /* !< [0x068-0x06b] data pad setting override value */ + uint32_t dqsPadSettingOverride; /* !< [0x06c-0x06f] DQS pad setting override value */ + uint32_t timeoutInMs; /* !< [0x070-0x073] Timeout threshold for read status command */ + uint32_t commandInterval; /* !< [0x074-0x077] CS deselect interval between two commands */ + fc_flexspi_dll_time_t dataValidTime[2]; /* !< [0x078-0x07b] CLK edge to data valid time for PORT A and PORT B */ + uint16_t busyOffset; /* !< [0x07c-0x07d] Busy offset, valid value: 0-31 */ + uint16_t busyBitPolarity; /* !< [0x07e-0x07f] Busy flag polarity, 0 - busy flag is 1 when flash device is busy, 1 - + busy flag is 0 when flash device is busy */ + uint32_t lookupTable[64]; /* !< [0x080-0x17f] Lookup table holds Flash command sequences */ + fc_flexspi_lut_seq_t lutCustomSeq[12]; /* !< [0x180-0x1af] Customizable LUT Sequences */ + uint32_t reserved4[4]; /* !< [0x1b0-0x1bf] Reserved for future use */ +} fc_flexspi_mem_config_t; +/* + * Serial NOR configuration block + */ +typedef struct _fc_flexspi_nor_config +{ +#if defined(__ARMCC_VERSION) || defined(__ICCARM__) + uint8_t padding[0x400]; /* !< Padding for MDK and IAR */ +#endif + fc_flexspi_mem_config_t memConfig; /* !< Common memory configuration info via FlexSPI */ + uint32_t pageSize; /* !< Page size of Serial NOR */ + uint32_t sectorSize; /* !< Sector size of Serial NOR */ + uint8_t ipcmdSerialClkFreq; /* !< Clock frequency for IP command */ + uint8_t isUniformBlockSize; /* !< Sector/Block size is the same */ + uint8_t isDataOrderSwapped; /* !< Data order (D0, D1, D2, D3) is swapped (D1,D0, D3, D2) */ + uint8_t reserved0[1]; /* !< Reserved for future use */ + uint8_t serialNorType; /* !< Serial NOR Flash type: 0/1/2/3 */ + uint8_t needExitNoCmdMode; /* !< Need to exit NoCmd mode before other IP command */ + uint8_t halfClkForNonReadCmd; /* !< Half the Serial Clock for non-read command: true/false */ + uint8_t needRestoreNoCmdMode; /* !< Need to Restore NoCmd mode after IP commmand execution */ + uint32_t blockSize; /* !< Block size */ + uint32_t flashStateCtx; /* !< Flash State Context */ + uint32_t reserve2[10]; /* !< Reserved for future use */ + uint32_t fcb_fill[0x280]; /* !< Fill */ +} fc_flexspi_nor_config_t; +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __FLASH_CONFIG__ */ diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/flexspi.c b/tutorials/nxp/frdm-rw612/eth_radio_bridge/flexspi.c new file mode 100644 index 00000000..568d5861 --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/flexspi.c @@ -0,0 +1,82 @@ +#include "RW612.h" +#include "fsl_flexspi.h" // peripheral structures + +__attribute__((section(".iram"))) static void deinit_flash(FLEXSPI_Type *base) { + CLKCTL0->PSCCTL0_SET = + CLKCTL0_PSCCTL0_SET_FLEXSPI0_MASK; // Enable FLEXSPI clock again + base->MCR0 &= ~FLEXSPI_MCR0_MDIS_MASK; // Enable FLEXSPI module + // Wait until FLEXSPI is not busy + while (!((base->STS0 & FLEXSPI_STS0_ARBIDLE_MASK) && + (base->STS0 & FLEXSPI_STS0_SEQIDLE_MASK))) + (void)0; + base->MCR0 |= + FLEXSPI_MCR0_MDIS_MASK; // Disable module during the reset procedure +} + +__attribute__((section(".iram"))) static void init_flash(FLEXSPI_Type *base) { + uint32_t status; + uint32_t lastStatus; + uint32_t retry; + + // Loopback from DQS pad can maximize RD board flash speed. + if ((base->MCR0 & FLEXSPI_MCR0_RXCLKSRC_MASK) != FLEXSPI_MCR0_RXCLKSRC(1)) { + base->MCR0 = + (base->MCR0 & ~FLEXSPI_MCR0_RXCLKSRC_MASK) | FLEXSPI_MCR0_RXCLKSRC(1); + } + base->DLLCR[0] = 0x1U; + base->MCR0 &= ~FLEXSPI_MCR0_MDIS_MASK; // Enable FLEXSPI module + base->MCR0 |= FLEXSPI_MCR0_SWRESET_MASK; + while (base->MCR0 & FLEXSPI_MCR0_SWRESET_MASK) + (void)0; + + // Need to wait DLL locked if DLL enabled + if (0U != (base->DLLCR[0] & FLEXSPI_DLLCR_DLLEN_MASK)) { + lastStatus = base->STS2; + retry = 10; + // Wait slave delay line locked and slave reference delay line locked. + do { + status = base->STS2; + if ((status & + (FLEXSPI_STS2_AREFLOCK_MASK | FLEXSPI_STS2_ASLVLOCK_MASK)) == + (FLEXSPI_STS2_AREFLOCK_MASK | FLEXSPI_STS2_ASLVLOCK_MASK)) { + // Locked + retry = 100; + break; + } else if (status == lastStatus) { + retry--; + } else { + retry = 10; + lastStatus = status; + } + } while (retry > 0); + // According to ERR011377, need to delay at least 100 NOPs to ensure the DLL + // is locked + for (; retry > 0U; retry--) + (void)0; + } +} + +__attribute__((section(".iram"))) void +set_flexspi_clock(FLEXSPI_Type *base, uint32_t src, uint32_t divider) { + if ((CLKCTL0->FLEXSPIFCLKSEL != CLKCTL0_FLEXSPIFCLKSEL_SEL(src)) || + ((CLKCTL0->FLEXSPIFCLKDIV & CLKCTL0_FLEXSPIFCLKDIV_DIV_MASK) != + (divider - 1))) { + // Always deinit FLEXSPI and init FLEXSPI for the flash to make sure the + // flash works correctly after the FLEXSPI root clock changed as the default + // FLEXSPI configuration may does not work for the new root clock frequency. + deinit_flash(base); + + // Disable clock before changing clock source + CLKCTL0->PSCCTL0_CLR = CLKCTL0_PSCCTL0_CLR_FLEXSPI0_MASK; + // Update flexspi clock. + CLKCTL0->FLEXSPIFCLKSEL = CLKCTL0_FLEXSPIFCLKSEL_SEL(src); + CLKCTL0->FLEXSPIFCLKDIV |= + CLKCTL0_FLEXSPIFCLKDIV_RESET_MASK; // Reset the divider counter + CLKCTL0->FLEXSPIFCLKDIV = CLKCTL0_FLEXSPIFCLKDIV_DIV(divider - 1); + while ((CLKCTL0->FLEXSPIFCLKDIV) & CLKCTL0_FLEXSPIFCLKDIV_REQFLAG_MASK) + (void)0; + // Enable FLEXSPI clock again + CLKCTL0->PSCCTL0_SET = CLKCTL0_PSCCTL0_SET_FLEXSPI0_MASK; + init_flash(base); + } +} diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/hal.c b/tutorials/nxp/frdm-rw612/eth_radio_bridge/hal.c new file mode 100644 index 00000000..7ce75e72 --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/hal.c @@ -0,0 +1,133 @@ +// 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 = SYS_FREQUENCY; +int hal_radio_err; // Result of hal_radio_init() +void SystemInit(void) { // Called automatically by startup code + hal_system_init(); // Enable FPU + hal_radio_err = hal_radio_init(); // At the boot clock, see radio.c + hal_clock_init(); + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + +void ExitRun0Mode(void) { +} + +struct stat; +__attribute__((weak)) int _fstat(int fd, struct stat *st) { + (void) fd, (void) st; + return -1; +} + +extern unsigned char _end[]; // End of data section, start of heap. See link.ld +static unsigned char *s_current_heap_end = _end; + +size_t hal_ram_used(void) { + return (size_t) (s_current_heap_end - _end); +} + +size_t hal_ram_free(void) { + unsigned char endofstack; + return (size_t) (&endofstack - s_current_heap_end); +} + +void *_sbrk(int incr) { + unsigned char *prev_heap; + unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256); + prev_heap = s_current_heap_end; + // Check how much space we got from the heap end to the stack end + if (s_current_heap_end + incr > heap_end) return (void *) -1; + s_current_heap_end += incr; + return prev_heap; +} + +__attribute__((weak)) int _open(const char *path) { + (void) path; + return -1; +} + +__attribute__((weak)) int _close(int fd) { + (void) fd; + return -1; +} + +__attribute__((weak)) int _isatty(int fd) { + (void) fd; + return 1; +} + +__attribute__((weak)) int _lseek(int fd, int ptr, int dir) { + (void) fd, (void) ptr, (void) dir; + return 0; +} + +__attribute__((weak)) void _exit(int status) { + (void) status; + for (;;) asm volatile("BKPT #0"); +} + +__attribute__((weak)) void _kill(int pid, int sig) { + (void) pid, (void) sig; +} + +__attribute__((weak)) int _getpid(void) { + return -1; +} + +__attribute__((weak)) int _write(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _read(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _link(const char *a, const char *b) { + (void) a, (void) b; + return -1; +} + +__attribute__((weak)) int _unlink(const char *a) { + (void) a; + return -1; +} + +__attribute__((weak)) int _stat(const char *path, struct stat *st) { + (void) path, (void) st; + return -1; +} + +__attribute__((weak)) int mkdir(const char *path, int mode) { + (void) path, (void) mode; + return -1; +} + +__attribute__((weak)) void _init(void) { +} diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/hal.h b/tutorials/nxp/frdm-rw612/eth_radio_bridge/hal.h new file mode 100644 index 00000000..57eb3490 --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/hal.h @@ -0,0 +1,366 @@ +// Copyright (c) 2023 Cesanta Software Limited +// All rights reserved + +#pragma once + +#include +#include +#include +#include + +#include "RW612.h" +#include "fsl_clock.h" +#include "fsl_ocotp.h" +#include "fsl_power.h" +#include "fsl_io_mux.h" +#include "fsl_reset.h" +#include "fsl_trng.h" + +int hal_radio_init(void); // Start the 802.15.4 radio firmware. 0: success +extern int hal_radio_err; // hal_radio_init() result, called by SystemInit() + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) +#define PIN(bank, num) ((bank << 8) | (num)) +#define PINNO(pin) (pin & 255) +#define PINBANK(pin) (pin >> 8) + +void hal_init(void); +size_t hal_ram_free(void); +size_t hal_ram_used(void); +bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now); +uint64_t hal_get_tick(void); + +static inline void spin(volatile uint32_t count) { + while (count--) + (void)0; +} + +#define SYS_FREQUENCY 260000000UL +#define FRG_PLL_CLKDIV 13 + +enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT }; +enum { + HAL_GPIO_TYPE_NOPULL_WEAKEST = 0x0, + HAL_GPIO_TYPE_NOPULL_WEAK = 0x4, + HAL_GPIO_TYPE_NOPULL_STRONG = 0x8, + HAL_GPIO_TYPE_NOPULL_STRONGEST = 0xC, + HAL_GPIO_TYPE_PULLUP_WEAKEST = 0x1, + HAL_GPIO_TYPE_PULLUP_WEAK = 0x5, + HAL_GPIO_TYPE_PULLUP_STRONG = 0x9, + HAL_GPIO_TYPE_PULLUP_STRONGEST = 0xD, + HAL_GPIO_TYPE_PULLDOWN_WEAKEST = 0x2, + HAL_GPIO_TYPE_PULLDOWN_WEAK = 0x6, + HAL_GPIO_TYPE_PULLDOWN_STRONG = 0xA, + HAL_GPIO_TYPE_PULLDOWN_STRONGEST = 0xE, + HAL_GPIO_TYPE_NOPULL = HAL_GPIO_TYPE_NOPULL_STRONGEST, + HAL_GPIO_TYPE_PULLUP = HAL_GPIO_TYPE_PULLUP_STRONGEST, + HAL_GPIO_TYPE_PULLDOWN = HAL_GPIO_TYPE_PULLDOWN_STRONGEST, +}; + +// Wrapper structure over the IO_MUX_* macros in fsl_io_mux.h +// To use those macros with our gpio functions, +// wrap them inside this structure as shown in the example from the +// ethernet_init function. +typedef struct +{ + uint32_t pinLowMask, pinHighMask, gpioFcSetMask, + gpioFcClrMask, fselSetMask, fselClrMask, ctimerSetMask, + ctimerClrMask, sctimerSetMask, sctimerClrMask; +} io_mux_config_t; + +static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type, + io_mux_config_t *mux_config) { + uint8_t bank = PINBANK(pin); + uint8_t pin_no = PINNO(pin); + if (mode == HAL_GPIO_MODE_INPUT) { + GPIO->DIR[bank] &= ~(1 << pin_no); + } else { + GPIO->DIR[bank] |= (1 << pin_no); + } + + // configure pull + pin_no += bank * 32; + uint32_t shift = (pin_no % 16) * 2; + uint32_t pull_val = (uint32_t)type & 3; + uint32_t drive_val = ((uint32_t)type >> 2) & 3; + volatile uint32_t *pull_reg, *drive_reg; + if (pin_no < 22 || pin_no > 27) { + pull_reg = &SOCCTRL->PAD_PU_PD_EN0 + pin_no / 16; + drive_reg = &SOCCTRL->SR_CONFIG0 + pin_no / 16; + } else { + pull_reg = &AON_SOC_CIU->PAD_PU_PD_EN1; + drive_reg = &AON_SOC_CIU->SR_CONFIG1; + } + *pull_reg = (*pull_reg & ~(3 << shift)) | (pull_val << shift); + *drive_reg = (*drive_reg & ~(3 << shift)) | (drive_val << shift); + + if (mux_config) { + IO_MUX_SetPinMux(mux_config->pinLowMask, mux_config->pinHighMask, + mux_config->gpioFcSetMask, mux_config->gpioFcClrMask, + mux_config->fselSetMask, mux_config->fselClrMask, + mux_config->ctimerSetMask, mux_config->ctimerClrMask, + mux_config->sctimerSetMask, mux_config->sctimerClrMask + ); + } else { + // if no mux config is specified, assume default gpio settings and + // no special function and just enable the IO + IO_MUX_SetPinMux(bank ? 0 : BIT(PINNO(pin)), !bank ? 0 : BIT(PINNO(pin)), + 1 << 16, 0, 0, 0, 0, 0, 0, 0); + } +} + +static inline void hal_gpio_input(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_TYPE_PULLUP_STRONGEST, NULL); +} +static inline void hal_gpio_output(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_TYPE_NOPULL_STRONGEST, NULL); +} + +static inline bool hal_gpio_read(uint16_t pin) { + return GPIO->PIN[PINBANK(pin)] & (1 << PINNO(pin)); +} + +static inline void hal_gpio_write(uint16_t pin, bool value) { + if (value) { + GPIO->SET[PINBANK(pin)] |= (1 << PINNO(pin)); + } else { + GPIO->CLR[PINBANK(pin)] |= (1 << PINNO(pin)); + } +} + +static inline void hal_gpio_toggle(uint16_t pin) { + hal_gpio_read(pin) ? hal_gpio_write(pin, false) : hal_gpio_write(pin, true); +} + +#if defined(__ZEPHYR__) +#undef MG_HAL_DISABLE_SYSTEM_INIT +#define MG_HAL_DISABLE_SYSTEM_INIT 1 +#undef MG_HAL_DISABLE_NEWLIB +#define MG_HAL_DISABLE_NEWLIB 1 +#undef MG_HAL_DISABLE_CLOCK +#define MG_HAL_DISABLE_CLOCK 1 +#undef MG_HAL_DISABLE_RANDOM +#define MG_HAL_DISABLE_RANDOM 1 +#undef MG_HAL_DISABLE_MILLIS +#define MG_HAL_DISABLE_MILLIS 1 +#undef MG_HAL_DISABLE_UART +#define MG_HAL_DISABLE_UART 1 +#undef MG_HAL_DISABLE_ETHERNET +#define MG_HAL_DISABLE_ETHERNET 1 +#undef MG_HAL_SYSTICK +#define MG_HAL_SYSTICK MG_HAL_SYSTICK_NONE +#endif + +static inline void hal_uart_init(USART_Type *uart, unsigned long baud) { + if (uart == USART0) { + IO_MUX_SetPinMux(IO_MUX_FC0_USART_DATA); + } else if (uart == USART1) { + IO_MUX_SetPinMux(IO_MUX_FC1_USART_DATA); + } else if (uart == USART2) { + IO_MUX_SetPinMux(IO_MUX_FC2_USART_DATA); + } else if (uart == USART3) { + IO_MUX_SetPinMux(IO_MUX_FC3_USART_DATA); + } else if (uart == USART14) { + IO_MUX_SetPinMux(IO_MUX_FC14_USART_DATA); + } else { + return; + } + + uint32_t uart_index = ((uint32_t)uart - USART0_BASE) / 0x1000; + + // set FRG clk + CLKCTL1->FLEXCOMM[uart_index].FRGCLKSEL = kCLOCK_FrgPllDiv; + CLKCTL1->FLEXCOMM[uart_index].FRGCTL = + (CLKCTL1_FRGCTL_MULT(0) | CLKCTL1_FRGCTL_DIV(255)); + CLOCK_AttachClk(CLKCTL1_TUPLE_MUXA(0x508 + uart_index * 0x20, 4)); + + // set FLEXCOMM to USART mode + CLOCK_EnableClock(CLK_GATE_DEFINE(CLK_CTL1_PSCCTL0, uart_index + 8)); + uint32_t mask = BIT(uart_index + 8); + RSTCTL1->PRSTCTL0_SET = mask; + while ((RSTCTL1->PRSTCTL0 & mask) == 0) + spin(1); + RSTCTL1->PRSTCTL0_CLR = mask; + while ((RSTCTL1->PRSTCTL0 & mask) == mask) + spin(1); + ((FLEXCOMM_Type *)uart)->PSELID = 1; + + uart->FIFOCFG |= USART_FIFOCFG_EMPTYTX_MASK | + USART_FIFOCFG_ENABLETX_MASK; // empty and enable txFIFO + uart->CFG = BIT(2) | BIT(0); // 8 bit data length and enable + + // compute baud + uint32_t uart_freq = SYS_FREQUENCY / FRG_PLL_CLKDIV; + uint32_t best_diff = (uint32_t)-1, best_osr = 0xF, best_brg = (uint32_t)-1; + uint32_t osr, brg, diff, current_baud; + for (osr = 15; osr >= 4; osr--) { + brg = (((uart_freq * 10) / ((osr + 1) * baud)) - 5) / 10; + if (brg > 0xFFFF) + continue; + current_baud = uart_freq / ((osr + 1) * (brg + 1)); + diff = + (baud < current_baud) ? (current_baud - baud) : (baud - current_baud); + if (diff < best_diff) { + best_diff = diff; + best_osr = osr; + best_brg = brg; + } + } + uart->OSR = best_osr; + uart->BRG = best_brg; +} + +static inline void hal_uart_write_byte(USART_Type *uart, uint8_t byte) { + while ((uart->FIFOSTAT & USART_FIFOSTAT_TXNOTFULL_MASK) == 0) + spin(1); + uart->FIFOWR = byte; +} +static inline void hal_uart_write_buf(USART_Type *uart, char *buf, size_t len) { + while (len-- > 0) + hal_uart_write_byte(uart, *(uint8_t *)buf++); +} + +static inline void hal_rng_init(void) { + trng_config_t config; + TRNG_GetDefaultConfig(&config); + TRNG_Init(TRNG, &config); +} + +static inline uint32_t hal_rng_read(void) { + uint32_t value; + TRNG_GetRandomData(TRNG, &value, sizeof(value)); + return value; +} + +static inline void hal_ethernet_init(void) { + CLOCK_EnableClock(kCLOCK_TddrMciEnetClk); + hal_gpio_init(PIN(0, 25), HAL_GPIO_MODE_OUTPUT, IO_MUX_PinConfigPullUpDriveWeak, + &(io_mux_config_t){ IO_MUX_ENET_CLK }); // ENET_CLK on GPIO_25 + IO_MUX_SetPinMux( + IO_MUX_ENET_RX); // ENET_RX on pin GPIO_62, GPIO_63, GPIO_22, GPIO_23 + IO_MUX_SetPinMux(IO_MUX_ENET_TX); // ENET_TX on pin GPIO_58, GPIO_59, GPIO_60 + IO_MUX_SetPinMux(IO_MUX_ENET_MDIO); // ENET_MDIO on pin GPIO_56, GPIO_57 + + // reset ENET peripheral + RSTCTL0->PRSTCTL1_SET = BIT(25); + while ((RSTCTL0->PRSTCTL1 & BIT(25)) == 0) + spin(1); + RSTCTL0->PRSTCTL1_CLR = BIT(25); + while ((RSTCTL0->PRSTCTL1 & BIT(25)) == BIT(25)) + spin(1); + RSTCTL0->PRSTCTL1_SET = BIT(26); + while ((RSTCTL0->PRSTCTL1 & BIT(26)) == 0) + spin(1); + RSTCTL0->PRSTCTL1_CLR = BIT(26); + while ((RSTCTL0->PRSTCTL1 & BIT(26)) == BIT(26)) + spin(1); + + // Enable clocks + CLOCK_EnableClock(kCLOCK_EnetIpg); + CLOCK_EnableClock(kCLOCK_EnetIpgS); + + NVIC_EnableIRQ(ENET_IRQn); +} +extern void set_flexspi_clock(FLEXSPI_Type *base, uint32_t src, + uint32_t divider); +static inline void hal_clock_init(void) { + const clock_avpll_config_t avpllConfig_BOARD_BootClockRUN = { + .ch1Freq = + kCLOCK_AvPllChFreq12p288m, // AVPLL channel frequency 12.288 MHz + .ch2Freq = kCLOCK_AvPllChFreq64m, // AVPLL channel frequency 64 MHz + .enableCali = true, // AVPLL calibration is enabled + }; + // As NXP BOARD_BootClockRUN(): the ROM leaves the ELS glitch detector and + // voltage sensors armed, trimmed for the boot clock. Disable them before + // changing the clock, or the ROM resets the chip when it next uses ELS + POWER_DisableGDetVSensors(); + if ((PMU->CAU_SLP_CTRL & PMU_CAU_SLP_CTRL_SOC_SLP_RDY_MASK) == 0) { + CLOCK_EnableClock( + kCLOCK_RefClkCauSlp); // Enable the CAU sleep clock for PMU + } + if ((SYSCTL2->SOURCE_CLK_GATE & SYSCTL2_SOURCE_CLK_GATE_REFCLK_SYS_CG_MASK) != + 0) { + CLOCK_EnableClock(kCLOCK_RefClkSys); // Enable the REFCLK_SYS clock + } + CLOCK_InitT3RefClk(kCLOCK_T3MciIrc48m); // Initialize T3 PLL and enable + // outputs that are not clock gated + CLOCK_EnableClock( + kCLOCK_T3PllMciIrcClk); // Enable FFRO - T3 PLL 48/60 MHz IRC clock output + CLOCK_EnableClock( + kCLOCK_T3PllMci256mClk); // Enable T3 PLL 256 MHz clock output + + // Move FLEXSPI clock source to T3 256M / 4 to avoid instruction/data fetch + // issue in XIP when updating PLL and main clock. + set_flexspi_clock(FLEXSPI, 6, 4); + CLOCK_AttachClk(kSYSOSC_to_MAIN_CLK); // Set core clock to safe system + // oscillator for initialization + CLOCK_SetClkDiv(kCLOCK_DivSysCpuAhbClk, + 1); // Set system CPU AHB clock divider to 1 + CLOCK_InitTcpuRefClk( + 3120000000UL, + kCLOCK_TcpuFlexspiDiv10); // Initialize TCPU PLL and enable outputs + CLOCK_EnableClock(kCLOCK_TcpuMciClk); // Enable TCPU PLL MCI clock output + CLOCK_InitTddrRefClk( + kCLOCK_TddrFlexspiDiv10); // Initialize TDDR PLL and enable outputs + CLOCK_EnableClock( + kCLOCK_TddrMciFlexspiClk); // Enable TDDR PLL FlexSPI clock output + CLOCK_InitAvPll(&avpllConfig_BOARD_BootClockRUN); // Initialize AVPLL and + // enable both channels + CLOCK_AttachClk(kRC32K_to_CLK32K); // Switch CLK32K to RC32K + CLOCK_AttachClk( + kSYSTICK_DIV_to_SYSTICK_CLK); // Switch SYSTICK_CLK to SYSTICK_DIV + CLOCK_SetClkDiv(kCLOCK_DivAudioPllClk, 1U); // Set AUDIOPLLCLKDIV divider to 1 + CLOCK_SetClkDiv(kCLOCK_DivPllFrgClk, + FRG_PLL_CLKDIV); // Set FRGPLLCLKDIV divider + CLOCK_SetClkDiv(kCLOCK_DivMainPllClk, 1U); // Set MAINPLLCLKDIV divider to 1 + CLOCK_SetClkDiv(kCLOCK_DivAux0PllClk, 1U); // Set AUX0PLLCLKDIV divider to 1 + CLOCK_SetClkDiv(kCLOCK_DivSystickClk, 1U); // Set SYSTICKFCLKDIV divider to 1 + CLOCK_SetClkDiv(kCLOCK_DivPmuFclk, 5U); // Set PMUFCLKDIV divider to 5 + CLOCK_AttachClk( + kMAIN_PLL_to_MAIN_CLK); // Select the main clock source for system clock + set_flexspi_clock(FLEXSPI, 2U, 2U); // set_flexspi_clock + + CLOCK_EnableClock(kCLOCK_HsGpio0); + RSTCTL1->PRSTCTL1_SET = BIT(0); // Reset GPIO_0 + while ((RSTCTL1->PRSTCTL1 & BIT(0)) == 0) + spin(1); + RSTCTL1->PRSTCTL1_CLR = BIT(0); + while ((RSTCTL1->PRSTCTL1 & BIT(0)) == BIT(0)) + spin(1); + + CLOCK_EnableClock(kCLOCK_HsGpio1); + RSTCTL1->PRSTCTL1_SET = BIT(1); // Reset GPIO_1 + while ((RSTCTL1->PRSTCTL1 & BIT(1)) == 0) + spin(1); + RSTCTL1->PRSTCTL1_CLR = BIT(1); + while ((RSTCTL1->PRSTCTL1 & BIT(1)) == BIT(1)) + spin(1); +} + +#if defined(__MCUXPRESSO) +#undef MG_HAL_DISABLE_SYSTEM_INIT +#define MG_HAL_DISABLE_SYSTEM_INIT 1 +#undef MG_HAL_SBRK_NONE +#define MG_HAL_SBRK_NONE 1 +#endif + +static inline void hal_system_init(void) { + SCB->CPACR |= + (3UL << 0 * 2) | (3UL << 1 * 2) | // Enable PowerQuad (CPO/CP1) + (3UL << 10 * 2) | (3UL << 11 * 2); // Enable FPU + SCB->NSACR |= ((3UL << 0) | (3UL << 10)); + CACHE64_CTRL0->CCR = CACHE64_CTRL_CCR_INVW1_MASK | CACHE64_CTRL_CCR_INVW0_MASK; + CACHE64_CTRL0->CCR |= CACHE64_CTRL_CCR_GO_MASK; + while ((CACHE64_CTRL0->CCR & CACHE64_CTRL_CCR_GO_MASK) != 0) spin(1); + // Enable cache, enable write buffer + CACHE64_CTRL0->CCR = (CACHE64_CTRL_CCR_ENWRBUF_MASK | CACHE64_CTRL_CCR_ENCACHE_MASK); + + // Set whole FlexSPI0 space to write through + CACHE64_POLSEL0->REG0_TOP = 0x07FFFC00U; + CACHE64_POLSEL0->REG1_TOP = 0x0U; + CACHE64_POLSEL0->POLSEL = 0x1U; + + __DSB(); + __ISB(); +} diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/link.ld b/tutorials/nxp/frdm-rw612/eth_radio_bridge/link.ld new file mode 100644 index 00000000..f644e1db --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/link.ld @@ -0,0 +1,38 @@ +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x08000000, LENGTH = 0x4000000 + sram(rwx) : ORIGIN = 0x20000000, LENGTH = 0x123000 /* Top is reserved, as in NXP wireless linker scripts */ +} +__StackTop = ORIGIN(sram) + LENGTH(sram); + +SECTIONS { + .boot_hdr : ALIGN(4) { FILL(0xFF); . = 0x400; __FLASH_BASE = .; KEEP(*(.flash_conf)) . = 0x1000; } > flash + .vectors : { FILL(256) KEEP(*(.isr_vector)) } > flash + .text : { *(.text.* *.text.*) } > flash + .rodata : { *(.rodata*) } > flash + + .iram : { + _siram = .; + *(.iram) + *(CodeQuickAccess) /* NXP SDK drivers, e.g. fsl_power.c */ + *(DataQuickAccess) + _eiram = .; + } > sram AT > flash + _siiram = LOADADDR(.iram); + + .data : { + _sdata = .; + *(.first_data) + *(.data SORT(.data.*)) + _edata = .; + } > sram AT > flash + _sidata = LOADADDR(.data); + + .bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) _ebss = .; } > sram + . = ALIGN(8); + _end = .; + + _image_start = LOADADDR(.text); + _image_end = LOADADDR(.data) + SIZEOF(.data); + _image_size = _image_end - _image_start; +} diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/main.c b/tutorials/nxp/frdm-rw612/eth_radio_bridge/main.c new file mode 100644 index 00000000..ca18b830 --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/main.c @@ -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 = ð_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(ð.recv_queue, eth_queue, sizeof(eth_queue)); + if (!eth.driver->init(ð)) 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(ð.recv_queue, &p)) > 0) { // Ethernet -> radio + if (n >= 14 && for_radio((uint8_t *) p)) radio.driver->tx(p, n, &radio); + mg_queue_del(ð.recv_queue, n); + } + if ((n = radio.driver->rx(frame, sizeof(frame), &radio)) >= 14) { + learn(frame + 6); // Radio -> Ethernet + eth.driver->tx(frame, n, ð); + } + if (mg_timer_expired(&timer, 1000, mg_millis())) { // Ethernet link + bool up = eth.driver->poll(ð, true); + eth.state = up ? MG_TCPIP_STATE_UP : MG_TCPIP_STATE_DOWN; + } + } + + return 0; +} diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/mongoose_config.h b/tutorials/nxp/frdm-rw612/eth_radio_bridge/mongoose_config.h new file mode 100644 index 00000000..c190ccf5 --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/mongoose_config.h @@ -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 diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/radio.c b/tutorials/nxp/frdm-rw612/eth_radio_bridge/radio.c new file mode 100644 index 00000000..5a5ad9f5 --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/radio.c @@ -0,0 +1,236 @@ +// Load NXP BLE/802.15.4 combo firmware to CPU2 and start it. +// Port of NXP mcux-component conn_fwloader: fsl_loader_utils.c (SB3 loader, +// load_service_monolithic) and nboot_hal.c (manifest parameters). +// Copyright 2024 NXP +// SPDX-License-Identifier: BSD-3-Clause + +#include "hal.h" +#include "fsl_ocotp.h" +#include "fsl_power.h" +#include "fsl_romapi_nboot.h" +#include "fsl_romapi_otp.h" +#include "fsl_kb_api.h" +#include "fsl_sbloader_v3.h" + +// The firmware image, linked into our flash. Signed SB3 container, see +// https://github.com/NXP/wifi_nb_fw, rw61x/rw61x_sb_ble_15d4_combo_a2.bin +__asm__( + ".section .rodata.radio_fw,\"a\"\n" + ".balign 16\n" + "radio_fw:\n" + ".incbin \"" RADIO_FW "\"\n" + ".balign 4\n" + "radio_fw_end:\n" + ".previous\n"); +extern const uint8_t radio_fw[], radio_fw_end[]; + +#define CIU2_RST_SW3 (*(volatile uint32_t *) 0x4424011cU) +#define CIU2_RST_SW3_CPU2_RST BIT(4) +#define CPU2_MAGIC (*(volatile uint32_t *) 0x443c0000U) // Set by firmware +#define CPU2_MAGIC_VALUE 0xabcdef89U +#define OTP_LCS_FUSE_IDX 45U // Life cycle state, and its redundant copy +#define OTP_PCK_BLOB_FUSE_IDX 116U // Part common key blob, 48 bytes + +// SB3 loader state. See fsl_ldr_Context_v3_t +struct sb3 { + nboot_context_t nboot; // ROM nboot context + uint8_t buf[MAX(NBOOT_SB3_MANIFEST_MAX_SIZE_IN_BYTES, + NBOOT_SB3_BLOCK_MAX_SIZE_IN_BYTES)]; // Block being read + uint32_t buf_size; // Bytes needed for the current block + uint32_t buf_pos; // Bytes in buf so far + bool header_done; // Manifest (block 0) processed + uint32_t data_offset; // Data offset within a block + uint32_t blocks; // Data blocks processed + uint8_t *data; // Data of the current block + uint32_t pos; // Position in data + bool in_section; // Inside a data section + uint32_t section_done; // Bytes of the section handled + sb3_section_header_t section; + bool in_range; // Inside a data range + uint32_t range_gap; // Padding after the range, 16 byte alignment + sb3_data_range_header_t range; + bool has_expansion; // Range expansion read + bool executed; // Execute command seen: firmware loaded +}; +static struct sb3 s_sb3; + +static uint32_t antipole(uint32_t v) { + return (v & 0xffffU) | (~(v & 0xffffU) << 16); +} + +// Manifest parameters: root of trust for NXP signed images, rev A2 chips. +// See nboot_hal_get_sb3_manifest_params(), nboot_hal.c +static bool manifest_params(nboot_sb3_load_manifest_parms_t *p) { + static const uint32_t rkh_a2[8] = {0xE7C7E9BB, 0x12C8C535, 0x37E61148, + 0x2BE7F18C, 0x8F0E3094, 0xB2BA7F32, + 0xEC9B4ECB, 0xAD9FC941}; + uint32_t i, lcs = OCOTP->OTP_SHADOW[OTP_LCS_FUSE_IDX]; + memset(p, 0, sizeof(*p)); + if ((lcs & 0xff) != ((lcs >> 8) & 0xff)) return false; // Corrupt LCS + for (i = 0; i < NBOOT_ROOT_CERT_COUNT; i++) { + p->soc_RoTNVM.soc_rootKeyRevocation[i] = kNBOOT_RootKey_Enabled; + } + memcpy(p->soc_RoTNVM.soc_rkh, rkh_a2, sizeof(rkh_a2)); + p->soc_RoTNVM.soc_numberOfRootKeys = 1; + for (i = 1; i < NBOOT_ROOT_CERT_COUNT; i++) { + p->soc_RoTNVM.soc_rootKeyUsage[i] = kNBOOT_RootKeyUsage_Unused; + } + p->soc_RoTNVM.soc_rootKeyTypeAndLength = kNBOOT_RootKey_Ecdsa_P256; + p->soc_RoTNVM.soc_lifecycle = (nboot_soc_lifecycle_t) antipole(lcs & 0xff); + for (i = 0; i < sizeof(p->pckBlob) / 4; i++) { + if (OCOTP_OtpFuseRead(OTP_PCK_BLOB_FUSE_IDX + i, + (uint32_t *) &p->pckBlob[4 * i]) != kStatus_Success) { + return false; + } + } + return true; +} + +// Manifest (block 0): verify, learn the block layout. See ldr_DoHeader_v3() +static bool sb3_header(struct sb3 *s) { + nboot_sb3_header_t *h = (nboot_sb3_header_t *) s->buf; + nboot_sb3_load_manifest_parms_t parms; + if (s->buf_size == sizeof(nboot_sb3_header_t)) { // Header only so far + s->buf_size = h->imageTotalLength; + s->buf_pos = sizeof(nboot_sb3_header_t); + return s->buf_size <= sizeof(s->buf); + } + if (!manifest_params(&parms)) return false; + // Protected status: the status code is in the low 32 bits + if ((nboot_status_t) nboot_sb3_load_manifest(&s->nboot, (uint32_t *) h, + &parms) != kStatus_NBOOT_Success) { + return false; + } + s->data_offset = 4 + h->certificateBlockOffset - sizeof(nboot_sb3_header_t); + s->buf_size = s->data_offset + NBOOT_SB3_CHUNK_SIZE_IN_BYTES; + s->header_done = true; + return true; +} + +// Copy (part of) the current load range to its destination +static void sb3_load(struct sb3 *s) { + uint32_t avail = NBOOT_SB3_CHUNK_SIZE_IN_BYTES - s->pos; + uint32_t n = s->range.length < avail ? s->range.length : avail; + memcpy((void *) s->range.startAddress, &s->data[s->pos], n); + s->pos += n, s->section_done += n; + s->range.startAddress += n, s->range.length -= n; + if (s->range.length == 0) { // Range done, skip alignment padding + s->in_range = false; + s->pos += s->range_gap, s->section_done += s->range_gap; + } +} + +// Process one decrypted data block. See ldr_DoBlock() +static bool sb3_block(struct sb3 *s) { + while (s->pos < NBOOT_SB3_CHUNK_SIZE_IN_BYTES) { + if (!s->in_section) { // New section + memcpy(&s->section, &s->data[s->pos], sizeof(s->section)); + if (s->section.sectionType == kSectionNone) break; // Rest is unused + if (s->section.sectionType != kSectionDataRange) return false; + s->in_section = true, s->section_done = 0, s->in_range = false; + s->pos += sizeof(sb3_section_header_t); + } + if (!s->in_range) { // New range + memcpy(&s->range, &s->data[s->pos], sizeof(s->range)); + if (s->range.tag != SB3_DATA_RANGE_HEADER_TAG) return false; + s->in_range = true, s->has_expansion = false, s->range_gap = 0; + s->pos += sizeof(sb3_data_range_header_t); + s->section_done += sizeof(sb3_data_range_header_t); + if (s->range.cmd == kSB3_CmdLoad) { + s->range_gap = (SB3_DATA_ALIGNMENT_SIZE_IN_BYTE - + (s->range.length & 15U)) & 15U; + } + } + if (s->range.cmd == kSB3_CmdLoad) { + if (!s->has_expansion) { + if (s->pos == NBOOT_SB3_CHUNK_SIZE_IN_BYTES) break; // Next block + s->has_expansion = true; + s->pos += sizeof(sb3_data_range_expansion_t); + s->section_done += sizeof(sb3_data_range_expansion_t); + } + if (s->pos < NBOOT_SB3_CHUNK_SIZE_IN_BYTES) sb3_load(s); + } else if (s->range.cmd == kSB3_CmdExecute) { + s->executed = true; + return true; + } else { + s->in_range = false; // Other commands: nothing to do + } + if (s->section_done == s->section.length) s->in_section = false; + if (s->section_done > s->section.length) return false; + } + s->pos = 0; + return true; +} + +// Feed image bytes, process complete blocks. See fsl_sbloader_pump() +static bool sb3_pump(struct sb3 *s, const uint8_t *p, uint32_t len) { + while (len > 0 && !s->executed) { + uint32_t n = s->buf_size - s->buf_pos; + if (n > len) n = len; + memcpy(&s->buf[s->buf_pos], p, n); + s->buf_pos += n, p += n, len -= n; + if (s->buf_pos < s->buf_size) break; + s->buf_pos = 0; + if (!s->header_done) { + if (!sb3_header(s)) return false; + } else { + if (s->blocks >= s->nboot.totalBlocks) return false; + if ((nboot_status_t) nboot_sb3_load_block( + &s->nboot, (uint32_t *) s->buf) != kStatus_NBOOT_Success) { + return false; + } + s->blocks++; + s->data = &s->buf[s->data_offset]; + if (!sb3_block(s)) return false; + } + } + return true; +} + +// Authenticate the image with the ROM, copy it to CPU2 memory. +// See loader_process_sb_file() +static bool sb3_process(const uint8_t *img, size_t len) { + struct sb3 *s = &s_sb3; + bool ok = false; + memset(s, 0, sizeof(*s)); + s->buf_size = sizeof(nboot_sb3_header_t); + // Start ELS only if it is off: the ROM keeps keys in it after boot + if ((CLKCTL0->PSCCTL0 & CLKCTL0_PSCCTL0_ELS_MASK) == 0 || + (CLKCTL0->PSCCTL1 & CLKCTL0_PSCCTL1_ELS_APB_MASK) == 0 || + (RSTCTL0->PRSTCTL0 & RSTCTL0_PRSTCTL0_ELS_MASK) != 0) { + CLOCK_EnableClock(kCLOCK_Els); + CLOCK_EnableClock(kCLOCK_ElsApb); + RESET_PeripheralReset(kELS_RST_SHIFT_RSTn); + } + if (nboot_context_init(&s->nboot) == kStatus_NBOOT_Success) { + ok = sb3_pump(s, img, (uint32_t) len) && s->executed; + (void) nboot_context_deinit(&s->nboot); + } + return ok; +} + +// Load the radio firmware to CPU2 and start it. See sb3_fw_reset() and +// load_service_monolithic(), fsl_loader_utils.c. Call before changing the +// boot clock: the ROM trims the ELS glitch detector for it, and after the +// clock change ELS reports glitch events. Interrupts may be off. +// Return 0 on success, or the failed step: 1 chip revision, 2 OTP, 3 image +// load, 4 firmware start +int hal_radio_init(void) { + uint32_t n = 100000000; // Firmware start timeout, loop iterations + bool ok; + if ((SOCCTRL->CHIP_INFO & 15U) != 2U) return 1; // Image is for rev A2 + POWER_PowerOffBle(); + spin(100000); + if (otp_init(0) != kStatus_Success) return 2; + POWER_PowerOnBle(); + spin(100000); + CPU2_MAGIC = 0; + ok = sb3_process(radio_fw, (size_t) (radio_fw_end - radio_fw)); + (void) otp_deinit(); + if (!ok) return 3; + CIU2_RST_SW3 |= CIU2_RST_SW3_CPU2_RST; + while (CPU2_MAGIC != CPU2_MAGIC_VALUE) { + if (--n == 0) return 4; // Firmware did not start + } + return 0; +} diff --git a/tutorials/nxp/frdm-rw612/eth_radio_bridge/startup.c b/tutorials/nxp/frdm-rw612/eth_radio_bridge/startup.c new file mode 100644 index 00000000..31b6ebee --- /dev/null +++ b/tutorials/nxp/frdm-rw612/eth_radio_bridge/startup.c @@ -0,0 +1,338 @@ +#include "RW612.h" + +void Reset_Handler(void); // Defined below +void Dummy_Handler(void); // Defined below +void SysTick_Handler(void); // Defined in main.c +void SystemInit(void); // Defined in main.c, called by reset handler +void __StackTop(void); // Defined in link.ld +void _image_size(void); + +#define WEAK __attribute__((weak, alias("Default_Handler"))) + +void Default_Handler(void); +WEAK void NMI_Handler(void); +WEAK void HardFault_Handler(void); +WEAK void MemManage_Handler(void); +WEAK void BusFault_Handler(void); +WEAK void UsageFault_Handler(void); +WEAK void SecureFault_Handler(void); +WEAK void SVC_Handler(void); +WEAK void DebugMon_Handler(void); +WEAK void PendSV_Handler(void); +WEAK void SysTick_Handler(void); +WEAK void IntDefaultHandler(void); +WEAK void WDT0_IRQHandler(void); +WEAK void DMA0_IRQHandler(void); +WEAK void GPIO_INTA_IRQHandler(void); +WEAK void GPIO_INTB_IRQHandler(void); +WEAK void PIN_INT0_IRQHandler(void); +WEAK void PIN_INT1_IRQHandler(void); +WEAK void PIN_INT2_IRQHandler(void); +WEAK void PIN_INT3_IRQHandler(void); +WEAK void UTICK_IRQHandler(void); +WEAK void MRT_IRQHandler(void); +WEAK void CTIMER0_IRQHandler(void); +WEAK void CTIMER1_IRQHandler(void); +WEAK void SCT0_IRQHandler(void); +WEAK void CTIMER3_IRQHandler(void); +WEAK void FLEXCOMM0_IRQHandler(void); +WEAK void FLEXCOMM1_IRQHandler(void); +WEAK void FLEXCOMM2_IRQHandler(void); +WEAK void FLEXCOMM3_IRQHandler(void); +WEAK void Reserved34_IRQHandler(void); +WEAK void Reserved35_IRQHandler(void); +WEAK void FLEXCOMM14_IRQHandler(void); +WEAK void Reserved37_IRQHandler(void); +WEAK void Reserved38_IRQHandler(void); +WEAK void GFMRT_IRQHandler(void); +WEAK void Reserved40_IRQHandler(void); +WEAK void DMIC_IRQHandler(void); +WEAK void WKDEEPSLEEP_IRQHandler(void); +WEAK void HYPERVISOR_IRQHandler(void); +WEAK void SECUREVIOLATION_IRQHandler(void); +WEAK void HWVAD_IRQHandler(void); +WEAK void Reserved46_IRQHandler(void); +WEAK void Reserved47_IRQHandler(void); +WEAK void RTC_IRQHandler(void); +WEAK void Reserved49_IRQHandler(void); +WEAK void Reserved50_IRQHandler(void); +WEAK void PIN_INT4_IRQHandler(void); +WEAK void PIN_INT5_IRQHandler(void); +WEAK void PIN_INT6_IRQHandler(void); +WEAK void PIN_INT7_IRQHandler(void); +WEAK void CTIMER2_IRQHandler(void); +WEAK void CTIMER4_IRQHandler(void); +WEAK void OS_EVENT_TIMER_IRQHandler(void); +WEAK void FLEXSPI_IRQHandler(void); +WEAK void Reserved59_IRQHandler(void); +WEAK void Reserved60_IRQHandler(void); +WEAK void Reserved61_IRQHandler(void); +WEAK void SDIO_IRQHandler(void); +WEAK void SGPIO_INTA_IRQHandler(void); +WEAK void SGPIO_INTB_IRQHandler(void); +WEAK void Reserved65_IRQHandler(void); +WEAK void USB_IRQHandler(void); +WEAK void Reserved67_IRQHandler(void); +WEAK void Reserved68_IRQHandler(void); +WEAK void Reserved69_IRQHandler(void); +WEAK void DMA1_IRQHandler(void); +WEAK void PUF_IRQHandler(void); +WEAK void POWERQUAD_IRQHandler(void); +WEAK void Reserved73_IRQHandler(void); +WEAK void Reserved74_IRQHandler(void); +WEAK void Reserved75_IRQHandler(void); +WEAK void Reserved76_IRQHandler(void); +WEAK void LCD_IRQHandler(void); +WEAK void CAPTIMER_IRQHandler(void); +WEAK void Reserved79_IRQHandler(void); +WEAK void W2MWKUP_DONE0_IRQHandler(void); +WEAK void W2MWKUP_DONE1_IRQHandler(void); +WEAK void W2MWKUP_DONE2_IRQHandler(void); +WEAK void W2MWKUP_DONE3_IRQHandler(void); +WEAK void W2MWKUP_DONE4_IRQHandler(void); +WEAK void W2MWKUP_DONE5_IRQHandler(void); +WEAK void W2MWKUP_DONE6_IRQHandler(void); +WEAK void W2MWKUP_DONE7_IRQHandler(void); +WEAK void W2MWKUP0_IRQHandler(void); +WEAK void W2MWKUP1_IRQHandler(void); +WEAK void WL_MCI_INT0_IRQHandler(void); +WEAK void WL_MCI_INT1_IRQHandler(void); +WEAK void WL_MCI_INT2_IRQHandler(void); +WEAK void WL_MCI_INT3_IRQHandler(void); +WEAK void WL_MCI_INT4_IRQHandler(void); +WEAK void WL_MCI_INT5_IRQHandler(void); +WEAK void WL_MCI_INT6_IRQHandler(void); +WEAK void WL_MCI_INT7_IRQHandler(void); +WEAK void B2MWKUP_DONE0_IRQHandler(void); +WEAK void B2MWKUP_DONE1_IRQHandler(void); +WEAK void B2MWKUP_DONE2_IRQHandler(void); +WEAK void B2MWKUP_DONE3_IRQHandler(void); +WEAK void B2MWKUP_DONE4_IRQHandler(void); +WEAK void B2MWKUP_DONE5_IRQHandler(void); +WEAK void B2MWKUP_DONE6_IRQHandler(void); +WEAK void B2MWKUP_DONE7_IRQHandler(void); +WEAK void B2MWKUP0_IRQHandler(void); +WEAK void B2MWKUP1_IRQHandler(void); +WEAK void BLE_MCI_INT0_IRQHandler(void); +WEAK void BLE_MCI_INT1_IRQHandler(void); +WEAK void BLE_MCI_INT2_IRQHandler(void); +WEAK void BLE_MCI_INT3_IRQHandler(void); +WEAK void BLE_MCI_INT4_IRQHandler(void); +WEAK void BLE_MCI_INT5_IRQHandler(void); +WEAK void BLE_MCI_INT6_IRQHandler(void); +WEAK void BLE_MCI_INT7_IRQHandler(void); +WEAK void PIN0_INT_IRQHandler(void); +WEAK void PIN1_INT_IRQHandler(void); +WEAK void ELS_IRQHandler(void); +WEAK void ELS_GDET_IRQHandler(void); +WEAK void ELS_GDET_UM_IRQHandler(void); +WEAK void PKC_INT_IRQHandler(void); +WEAK void PKC_ERR_IRQHandler(void); +WEAK void CDOG_INT_IRQHandler(void); +WEAK void GAU_DAC_IRQHandler(void); +WEAK void GAU_ACOMP_WKUP_IRQHandler(void); +WEAK void GAU_ACOMP_IRQHandler(void); +WEAK void GAU_ADC1_IRQHandler(void); +WEAK void GAU_ADC0_IRQHandler(void); +WEAK void USIM_IRQHandler(void); +WEAK void OTP_IRQHandler(void); +WEAK void ENET_IRQHandler(void); +WEAK void ENET_TIMER_IRQHandler(void); +WEAK void PMIP_IRQHandler(void); +WEAK void PMIP_CHANGE_IRQHandler(void); +WEAK void ITRC_IRQHandler(void); +WEAK void Reserved136_IRQHandler(void); +WEAK void Reserved137_IRQHandler(void); +WEAK void Reserved138_IRQHandler(void); +WEAK void TRNG_IRQHandler(void); +WEAK void ACC_C_INT_IRQHandler(void); +WEAK void ACC_S_INT_IRQHandler(void); +WEAK void WACC_IRQHandler(void); +WEAK void BACC_IRQHandler(void); +WEAK void GDMA_IRQHandler(void); + + +#define WEAK_ALIAS __attribute__((weak, alias("Default_Handler"))) + + +__attribute__((section(".vectors"))) void (*const tab[16 + 156])(void) = { + &__StackTop, // The initial stack pointer + Reset_Handler, // The reset handler + NMI_Handler, // NMI Handler + HardFault_Handler, // Hard Fault Handler + MemManage_Handler, // MPU Fault Handler + BusFault_Handler, // Bus Fault Handler + UsageFault_Handler, // Usage Fault Handler + SecureFault_Handler, // Secure Fault Handler + &_image_size, // Image size + 0, // Image type + 0, // Reserved + SVC_Handler, // SVCall Handler + DebugMon_Handler, // Debug Monitor Handler + (void (*)())tab, // Image load address + PendSV_Handler, // PendSV Handler + SysTick_Handler, // SysTick Handler + + // Chip Level - RW612 + WDT0_IRQHandler, // 16 : Windowed watchdog timer 0 (WDT0)(Cortex-M33 watchdog) + DMA0_IRQHandler, // 17 : Direct memory access (DMA) controller 0 (secure or Cortex-M33 DMA) + GPIO_INTA_IRQHandler, // 18 : GPIO interrupt A + GPIO_INTB_IRQHandler, // 19 : GPIO interrupt B + PIN_INT0_IRQHandler, // 20 : Pin interrupt 0 or pattern match engine slice 0 + PIN_INT1_IRQHandler, // 21 : Pin interrupt 1 or pattern match engine slice 1 + PIN_INT2_IRQHandler, // 22 : Pin interrupt 2 or pattern match engine slice 2 + PIN_INT3_IRQHandler, // 23 : Pin interrupt 3 or pattern match engine slice 3 + UTICK_IRQHandler, // 24 : Micro-tick Timer (UTICK) + MRT_IRQHandler, // 25 : Multi-Rate Timer (MRT). Global MRT interrupts + CTIMER0_IRQHandler, // 26 : Standard counter/timer CTIMER0 + CTIMER1_IRQHandler, // 27 : Standard counter/timer CTIMER1 + SCT0_IRQHandler, // 28 : SCTimer/PWM + CTIMER3_IRQHandler, // 29 : Standard counter/timer CTIMER3 + FLEXCOMM0_IRQHandler, // 30 : Flexcomm Interface 0 (USART, SPI, I2C, I2S) + FLEXCOMM1_IRQHandler, // 31 : Flexcomm Interface 1 (USART, SPI, I2C, I2S) + FLEXCOMM2_IRQHandler, // 32 : Flexcomm Interface 2 (USART, SPI, I2C, I2S) + FLEXCOMM3_IRQHandler, // 33 : Flexcomm Interface 3 (USART, SPI, I2C, I2S) + Reserved34_IRQHandler, // 34 : Reserved interrupt + Reserved35_IRQHandler, // 35 : Reserved interrupt + FLEXCOMM14_IRQHandler, // 36 : Flexcomm Interface 14 (USART, SPI, I2C, I2S) + Reserved37_IRQHandler, // 37 : Reserved interrupt + Reserved38_IRQHandler, // 38 : Reserved interrupt + GFMRT_IRQHandler, // 39 : Free Multi-rate timer (GFMRT). Global MRT interrupts + Reserved40_IRQHandler, // 40 : Reserved interrupt + DMIC_IRQHandler, // 41 : Digital microphone (DMIC) and DMIC subsystem + WKDEEPSLEEP_IRQHandler, // 42 : Wake-up from deep sleep + HYPERVISOR_IRQHandler, // 43 : Hypervisor service software interrupt + SECUREVIOLATION_IRQHandler, // 44 : Secure violation + HWVAD_IRQHandler, // 45 : Hardware Voice Activity Detector + Reserved46_IRQHandler, // 46 : Reserved interrupt + Reserved47_IRQHandler, // 47 : Reserved interrupt + RTC_IRQHandler, // 48 : RTC alarm and wake-up + Reserved49_IRQHandler, // 49 : Reserved interrupt + Reserved50_IRQHandler, // 50 : Reserved interrupt + PIN_INT4_IRQHandler, // 51 : Pin interrupt 4 or pattern match engine slice 4 + PIN_INT5_IRQHandler, // 52 : Pin interrupt 5 or pattern match engine slice 5 + PIN_INT6_IRQHandler, // 53 : Pin interrupt 6 or pattern match engine slice 6 + PIN_INT7_IRQHandler, // 54 : Pin interrupt 7 or pattern match engine slice 7 + CTIMER2_IRQHandler, // 55 : Standard counter/timer CTIMER2 + CTIMER4_IRQHandler, // 56 : Standard counter/timer CTIMER4 + OS_EVENT_TIMER_IRQHandler, // 57 : OS event timer 0 + FLEXSPI_IRQHandler, // 58 : FLEXSPI interface + Reserved59_IRQHandler, // 59 : Reserved interrupt + Reserved60_IRQHandler, // 60 : Reserved interrupt + Reserved61_IRQHandler, // 61 : Reserved interrupt + SDIO_IRQHandler, // 62 : The secure digital interface + SGPIO_INTA_IRQHandler, // 63 : Secure GPIO interrupt A + SGPIO_INTB_IRQHandler, // 64 : Secure GPIO interrupt B + Reserved65_IRQHandler, // 65 : Reserved interrupt + USB_IRQHandler, // 66 : High-speed USB device/host + Reserved67_IRQHandler, // 67 : Reserved interrupt + Reserved68_IRQHandler, // 68 : Reserved interrupt + Reserved69_IRQHandler, // 69 : Reserved interrupt + DMA1_IRQHandler, // 70 : DMA controller 1 (non-secure or HiFi 4 DMA) + PUF_IRQHandler, // 71 : Physical Unclonable Function + POWERQUAD_IRQHandler, // 72 : PowerQuad math coprocessor + Reserved73_IRQHandler, // 73 : Reserved interrupt + Reserved74_IRQHandler, // 74 : Reserved interrupt + Reserved75_IRQHandler, // 75 : Reserved interrupt + Reserved76_IRQHandler, // 76 : Reserved interrupt + LCD_IRQHandler, // 77 : LCDIC + CAPTIMER_IRQHandler, // 78 : Capture timer + Reserved79_IRQHandler, // 79 : Reserved interrupt + W2MWKUP_DONE0_IRQHandler, // 80 : Wi-Fi to MCU, wakeup done 0 + W2MWKUP_DONE1_IRQHandler, // 81 : Wi-Fi to MCU, wakeup done 1 + W2MWKUP_DONE2_IRQHandler, // 82 : Wi-Fi to MCU, wakeup done 2 + W2MWKUP_DONE3_IRQHandler, // 83 : Wi-Fi to MCU, wakeup done 3 + W2MWKUP_DONE4_IRQHandler, // 84 : Wi-Fi to MCU, wakeup done 4 + W2MWKUP_DONE5_IRQHandler, // 85 : Wi-Fi to MCU, wakeup done 5 + W2MWKUP_DONE6_IRQHandler, // 86 : Wi-Fi to MCU, wakeup done 6 + W2MWKUP_DONE7_IRQHandler, // 87 : Wi-Fi to MCU, wakeup done 7 + W2MWKUP0_IRQHandler, // 88 : Wi-Fi to MCU, wakeup signal 0 + W2MWKUP1_IRQHandler, // 89 : Wi-Fi to MCU, wakueup signal 1 + WL_MCI_INT0_IRQHandler, // 90 : Wi-Fi to MCU interrupt 0 + WL_MCI_INT1_IRQHandler, // 91 : Reserved for Wi-Fi to MCU + WL_MCI_INT2_IRQHandler, // 92 : Reserved for Wi-Fi to MCU + WL_MCI_INT3_IRQHandler, // 93 : Reserved for Wi-Fi to MCU + WL_MCI_INT4_IRQHandler, // 94 : Reserved for Wi-Fi to MCU + WL_MCI_INT5_IRQHandler, // 95 : Reserved for Wi-Fi to MCU + WL_MCI_INT6_IRQHandler, // 96 : Reserved for Wi-Fi to MCU + WL_MCI_INT7_IRQHandler, // 97 : Reserved for Wi-Fi to MCU + B2MWKUP_DONE0_IRQHandler, // 98 : Bluetooth LE/802.15.4 radio to MCU, wakeup done 0 + B2MWKUP_DONE1_IRQHandler, // 99 : Bluetooth LE/802.15.4 radio to MCU, wakeup done 1 + B2MWKUP_DONE2_IRQHandler, // 100: Bluetooth LE/802.15.4 radio to MCU, wakeup done 2 + B2MWKUP_DONE3_IRQHandler, // 101: Bluetooth LE/802.15.4 radio to MCU, wakeup done 3 + B2MWKUP_DONE4_IRQHandler, // 102: Bluetooth LE/802.15.4 radio to MCU, wakeup done 4 + B2MWKUP_DONE5_IRQHandler, // 103: Bluetooth LE/802.15.4 radio to MCU, wakeup done 5 + B2MWKUP_DONE6_IRQHandler, // 104: Bluetooth LE/802.15.4 radio to MCU, wakeup done 6 + B2MWKUP_DONE7_IRQHandler, // 105: Bluetooth LE/802.15.4 radio to MCU, wakeup done 7 + B2MWKUP0_IRQHandler, // 106: Bluetooth LE/802.15.4 radio to MCU, wakeup signal 0 + B2MWKUP1_IRQHandler, // 107: Bluetooth LE/802.15.4 radio to MCU, wakeup signal 1 + BLE_MCI_INT0_IRQHandler, // 108: Bluetooth LE/802.15.4 radio to MCU interrupt 0 + BLE_MCI_INT1_IRQHandler, // 109: Reserved for Bluetooth LE/802.15.4 radio to MCU + BLE_MCI_INT2_IRQHandler, // 110: Reserved for Bluetooth LE/802.15.4 radio to MCU + BLE_MCI_INT3_IRQHandler, // 111: Reserved for Bluetooth LE/802.15.4 radio to MCU + BLE_MCI_INT4_IRQHandler, // 112: Reserved for Bluetooth LE/802.15.4 radio to MCU + BLE_MCI_INT5_IRQHandler, // 113: Reserved for Bluetooth LE/802.15.4 radio to MCU + BLE_MCI_INT6_IRQHandler, // 114: Reserved for Bluetooth LE/802.15.4 radio to MCU + BLE_MCI_INT7_IRQHandler, // 115: Reserved for Bluetooth LE/802.15.4 radio to MCU + PIN0_INT_IRQHandler, // 116: From AON GPIO + PIN1_INT_IRQHandler, // 117: From AON GPIO + ELS_IRQHandler, // 118: EdgeLock subsystem (ELS) + ELS_GDET_IRQHandler, // 119: ELS IRQ line for GDET error + ELS_GDET_UM_IRQHandler, // 120: ELS un-gated latched error + PKC_INT_IRQHandler, // 121: Public key crypto-processor (PKC) interrupt + PKC_ERR_IRQHandler, // 122: PKC error + CDOG_INT_IRQHandler, // 123: Code watch dog timmer interrupt + GAU_DAC_IRQHandler, // 124: General analog unit (GAU) digital to analog converter (DAC) + GAU_ACOMP_WKUP_IRQHandler, // 125: GAU analog comparator (ACOMP) wake-up + GAU_ACOMP_IRQHandler, // 126: GAU analog comparator + GAU_ADC1_IRQHandler, // 127: GAU analog to digital converter 1 (ADC1) + GAU_ADC0_IRQHandler, // 128: GAU analog to digital converter 0 (ADC0) + USIM_IRQHandler, // 129: Universal subscriber identity module (USIM) interface + OTP_IRQHandler, // 130: One time programmable (OTP) memory interrupt + ENET_IRQHandler, // 131: Etheret interrupt + ENET_TIMER_IRQHandler, // 132: Ethernet timer interrupt + PMIP_IRQHandler, // 133: Power management IP (PMIP) + PMIP_CHANGE_IRQHandler, // 134: PMIP change from 1 to 0 + ITRC_IRQHandler, // 135: Intrusion and tamper response controller (ITRC) interrupt request + Reserved136_IRQHandler, // 136: Reserved interrupt + Reserved137_IRQHandler, // 137: Reserved interrupt + Reserved138_IRQHandler, // 138: Reserved interrupt + TRNG_IRQHandler, // 139: TRNG interrupt request + ACC_C_INT_IRQHandler, // 140: AHB memory access checker - Cortex-M33 code bus + ACC_S_INT_IRQHandler, // 141: AHB memory access checker - Cortex-M33 sys bus + WACC_IRQHandler, // 142: Wi-Fi accessed during power off + BACC_IRQHandler, // 143: Bluetooth LE/802.15.4 radio accessed during power off + GDMA_IRQHandler, // 144: General purpose direct memory access (GDMA) interrupt +}; + +extern unsigned char _end[]; // End of data section, start of heap. See link.ld +__attribute__((naked, noreturn)) void Reset_Handler(void) { + __asm("cpsid i"); // Disable interrupts + // Config VTOR & MSPLIM register + __asm("LDR R0, =0xE000ED08 \n" + "STR %0, [R0] \n" + "LDR R1, [%0] \n" + "MSR MSP, R1 \n" + "MSR MSPLIM, %1 \n" + : + : "r"(tab), "r"(_end) + : "r0", "r1"); + extern long _sbss, _ebss, _sdata, _edata, _sidata, _siram, _siiram, _eiram; + for (long *dst = &_sbss; dst < &_ebss; dst++) *dst = 0; + for (long *dst = &_sdata, *src = &_sidata; dst < &_edata;) *dst++ = *src++; + for (long *dst = &_siram, *src = &_siiram; dst < &_eiram;) *dst++ = *src++; + SystemInit(); + __asm("cpsie i"); // Reenable interrupts + // Call main() + extern void main(void); + main(); + for (;;) (void) 0; // Infinite loop +} + +void SystemCoreClockUpdate (void) { + +} + +void Default_Handler(void) { + for (;;) (void) 0; +}