diff --git a/.github/workflows/stm32n6-build.yml b/.github/workflows/stm32n6-build.yml new file mode 100644 index 00000000..828afa59 --- /dev/null +++ b/.github/workflows/stm32n6-build.yml @@ -0,0 +1,32 @@ +name: STM32N6 Build + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +jobs: + stm32n6_build: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - name: Install ARM toolchain + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y gcc-arm-none-eabi + + - name: Build STM32N6 echo firmware + run: | + set -euo pipefail + make -C src/port/stm32n6 CC=arm-none-eabi-gcc OBJCOPY=arm-none-eabi-objcopy + + - name: Verify binary + run: | + set -euo pipefail + test -f src/port/stm32n6/app.bin + arm-none-eabi-size src/port/stm32n6/app.elf diff --git a/Makefile b/Makefile index 7bdbb111..5cd157ba 100644 --- a/Makefile +++ b/Makefile @@ -119,6 +119,8 @@ CPPCHECK_FLAGS=--enable=warning,performance,portability,missingInclude \ --suppress=comparePointers:src/port/stm32h563/syscalls.c \ --suppress=comparePointers:src/port/stm32h753/startup.c \ --suppress=comparePointers:src/port/stm32h753/syscalls.c \ + --suppress=comparePointers:src/port/stm32n6/startup.c \ + --suppress=comparePointers:src/port/stm32n6/syscalls.c \ --suppress=comparePointers:src/port/va416xx/startup.c \ --suppress=comparePointers:src/port/va416xx/syscalls.c \ --disable=style \ diff --git a/src/port/stm32/stm32_eth.c b/src/port/stm32/stm32_eth.c index c57c3825..ae49c05b 100644 --- a/src/port/stm32/stm32_eth.c +++ b/src/port/stm32/stm32_eth.c @@ -1,6 +1,6 @@ /* stm32_eth.c * - * Common Ethernet MAC/PHY driver for STM32H5 and STM32H7. + * Common Ethernet MAC/PHY driver for STM32H5, STM32H7 and STM32N6. * * Copyright (C) 2024-2026 wolfSSL Inc. * @@ -11,8 +11,8 @@ #include "config.h" #include "stm32_eth.h" -#if !defined(STM32H5) && !defined(STM32H7) -#error "Define STM32H5 or STM32H7 for stm32_eth.c" +#if !defined(STM32H5) && !defined(STM32H7) && !defined(STM32N6) +#error "Define STM32H5, STM32H7 or STM32N6 for stm32_eth.c" #endif #if defined(STM32H5) @@ -25,23 +25,39 @@ #elif defined(STM32H7) #define ETH_BASE 0x40028000UL #define STM32_ETH_NEEDS_MDIO_DELAY 1 +#elif defined(STM32N6) +#define ETH_BASE 0x58036000UL /* ETH1 on AHB5, secure alias */ +#define STM32_ETH_NEEDS_MDIO_DELAY 1 /* M55 at 600MHz needs stabilization */ #endif +/* MDIO clock divider: CR field in ETH_MACMDIOAR. + * CR=0: 20-35MHz, CR=1: 35-60MHz, CR=2: 60-100MHz, + * CR=3: 100-150MHz, CR=4: 150-250MHz, CR=5: 250-300MHz + * HCLK 150-250MHz -> CR=4 */ +#define MDIO_CR_VALUE 4U + #define ETH_REG(offset) (*(volatile uint32_t *)(ETH_BASE + (offset))) #define ETH_TPDR ETH_REG(0x1180U) /* MAC registers */ #define ETH_MACCR ETH_REG(0x0000U) -#define ETH_MACPFR ETH_REG(0x0004U) +#define ETH_MACECR ETH_REG(0x0004U) /* Extended Configuration */ +#define ETH_MACPFR ETH_REG(0x0008U) /* Packet Filter */ +#define ETH_MACWTR ETH_REG(0x000CU) /* Watchdog Timeout */ +#define ETH_MACQ0TXFCR ETH_REG(0x0070U) /* TX Flow Control Q0 */ +#define ETH_MACRXFCR ETH_REG(0x0090U) /* RX Flow Control */ +#define ETH_MAC1USTCR ETH_REG(0x00DCU) #define ETH_MACA0HR ETH_REG(0x0300U) #define ETH_MACA0LR ETH_REG(0x0304U) -#if defined(STM32H7) +#if defined(STM32H7) || defined(STM32N6) #define ETH_MACRXQC0R ETH_REG(0x00A0U) #endif /* MTL registers */ -#define ETH_MTLTXQOMR ETH_REG(0x0D00U) -#define ETH_MTLRXQOMR ETH_REG(0x0D30U) +#define ETH_MTLOMR ETH_REG(0x0C00U) /* MTL Operation Mode */ +#define ETH_MTLTXQOMR ETH_REG(0x0D00U) /* MTL TX Q0 Operation Mode */ +#define ETH_MTLRXQOMR ETH_REG(0x0D30U) /* MTL RX Q0 Operation Mode */ +#define ETH_MTLRXQDMAMR ETH_REG(0x0C30U) /* RX Queue to DMA Mapping */ /* DMA registers */ #define ETH_DMAMR ETH_REG(0x1000U) @@ -56,14 +72,37 @@ #define ETH_DMACTXRLR ETH_REG(0x112CU) #define ETH_DMACRXRLR ETH_REG(0x1130U) #define ETH_DMACSR ETH_REG(0x1160U) +#define ETH_DMACIER ETH_REG(0x1134U) /* DMA CH0 Interrupt Enable */ +#define ETH_DMACIER_NIE (1U << 15) /* Normal Interrupt Summary Enable */ +#define ETH_DMACIER_AIE (1U << 14) /* Abnormal Interrupt Summary Enable */ +#define ETH_DMACIER_RBUE (1U << 7) /* RX Buffer Unavailable Enable */ +#define ETH_DMACIER_RIE (1U << 6) /* Receive Interrupt Enable */ +#define ETH_DMACIER_TIE (1U << 0) /* Transmit Interrupt Enable */ #define ETH_MACMDIOAR ETH_REG(0x0200U) #define ETH_MACMDIODR ETH_REG(0x0204U) +/* N6 DMA Channel 1 registers (offset 0x80 from CH0) */ +#if defined(STM32N6) +#define ETH_DMAC1CR ETH_REG(0x1180U) +#define ETH_DMAC1TXCR ETH_REG(0x1184U) +#define ETH_DMAC1RXCR ETH_REG(0x1188U) +#define ETH_DMAC1TXDLAR ETH_REG(0x1194U) +#define ETH_DMAC1RXDLAR ETH_REG(0x119CU) +#define ETH_DMAC1TXDTPR ETH_REG(0x11A0U) +#define ETH_DMAC1RXDTPR ETH_REG(0x11A8U) +#define ETH_DMAC1TXRLR ETH_REG(0x11ACU) +#define ETH_DMAC1RXRLR ETH_REG(0x11B0U) +#define ETH_DMAC1SR ETH_REG(0x11E0U) +#define ETH_DMAC1IER ETH_REG(0x11B4U) /* DMA CH1 Interrupt Enable */ +#define ETH_MTLTXQ1OMR ETH_REG(0x0D40U) +#endif + /* MAC control bits */ #define ETH_MACCR_RE (1U << 0) #define ETH_MACCR_TE (1U << 1) #define ETH_MACCR_DM (1U << 13) #define ETH_MACCR_FES (1U << 14) +#define ETH_MACCR_PS (1U << 15) /* Port Select: 1=MII/RMII, 0=GMII */ /* DMA bits */ #define ETH_DMAMR_SWR (1U << 0) @@ -85,9 +124,14 @@ #define ETH_MTLTXQOMR_TSF (1U << 1) #define ETH_MTLTXQOMR_TXQEN_SHIFT 2 #define ETH_MTLTXQOMR_TXQEN_ENABLE (2U << ETH_MTLTXQOMR_TXQEN_SHIFT) -#define ETH_MTLTXQOMR_MASK 0x00000072U +#define ETH_MTLTXQOMR_TQS_SHIFT 16 /* TX Queue Size */ +#define ETH_MTLTXQOMR_TQS_2048 (0x7U << ETH_MTLTXQOMR_TQS_SHIFT) /* 2KB */ +#define ETH_MTLTXQOMR_MASK 0x00070072U #define ETH_MTLRXQOMR_RSF (1U << 5) -#define ETH_MTLRXQOMR_MASK 0x0000007BU +#define ETH_MTLRXQOMR_RQS_SHIFT 20 /* RX Queue Size */ +#define ETH_MTLRXQOMR_RQS_4096 (0xFU << ETH_MTLRXQOMR_RQS_SHIFT) /* 4KB */ +#define ETH_MTLRXQOMR_DEHF (1U << 6) /* Drop Error Half Frame */ +#define ETH_MTLRXQOMR_MASK 0x00F0007FU /* MDIO bits */ #define ETH_MACMDIOAR_MB (1U << 0) @@ -123,12 +167,18 @@ #define PHY_ANAR_DEFAULT 0x01E1U -/* DMA descriptor structure */ +/* DMA descriptor structure. + * On N6 GMAC v5.20, DSL=1 skips 1 doubleword (8 bytes) between + * 16-byte descriptors -> stride = 24 bytes. HAL uses 6-field struct + * (DESC0-3 + BackupAddr0/1). */ struct eth_desc { volatile uint32_t des0; volatile uint32_t des1; volatile uint32_t des2; volatile uint32_t des3; +#if defined(STM32N6) + volatile uint32_t _pad[2]; /* DSL=1: 8-byte skip (1 doubleword) */ +#endif }; /* Descriptor bits */ @@ -154,7 +204,7 @@ struct eth_desc { #define DMA_TPBL 32U #define DMA_RPBL 32U -#if defined(STM32H7) +#if defined(STM32H7) || defined(STM32N6) #define ETH_SECTION __attribute__((section(".eth_buffers"))) #elif defined(STM32H5) #if TZEN_ENABLED @@ -164,11 +214,20 @@ struct eth_desc { #endif #endif +/* DMA buffer address — use secure alias (0x34), matching RISAF SEC=1. */ +#define ETH_DMA_ADDR(ptr) ((uint32_t)(ptr)) + static struct eth_desc rx_ring[RX_DESC_COUNT] __attribute__((aligned(32))) ETH_SECTION; static struct eth_desc tx_ring[TX_DESC_COUNT] __attribute__((aligned(32))) ETH_SECTION; static uint8_t rx_buffers[RX_DESC_COUNT][RX_BUF_SIZE] __attribute__((aligned(32))) ETH_SECTION; static uint8_t tx_buffers[TX_DESC_COUNT][TX_BUF_SIZE] __attribute__((aligned(32))) ETH_SECTION; -static uint8_t rx_staging_buffer[RX_BUF_SIZE] __attribute__((aligned(32))); + +#if defined(STM32N6) +/* DMA Channel 1 descriptor rings and buffers */ +static struct eth_desc rx_ring1[RX_DESC_COUNT] __attribute__((aligned(32))) ETH_SECTION; +static struct eth_desc tx_ring1[TX_DESC_COUNT] __attribute__((aligned(32))) ETH_SECTION; +static uint8_t rx_buffers1[RX_DESC_COUNT][RX_BUF_SIZE] __attribute__((aligned(32))) ETH_SECTION; +#endif static uint32_t rx_idx; static uint32_t tx_idx; @@ -183,13 +242,22 @@ static void eth_mdio_write(uint32_t phy, uint32_t reg, uint16_t value); #if STM32_ETH_NEEDS_MDIO_DELAY static void eth_delay(uint32_t count) { - for (volatile uint32_t i = 0; i < count; i++) { } + volatile uint32_t i; + for (i = 0; i < count; i++) { } } #endif static int eth_hw_reset(void) { - uint32_t timeout = 1000000U; + uint32_t timeout; + +#if defined(STM32N6) + /* On N6, SWR requires REF_CLK from PHY. Use a very long timeout + * (HAL uses 1 second). Do NOT manually clear SWR — it corrupts DMA. */ + timeout = 50000000U; /* ~80ms at 600MHz */ +#else + timeout = 1000000U; +#endif ETH_DMAMR |= ETH_DMAMR_SWR; while ((ETH_DMAMR & ETH_DMAMR_SWR) && (timeout > 0U)) { @@ -202,28 +270,75 @@ static int eth_hw_reset(void) eth_delay(400000); /* ~1ms at 400MHz SYSCLK */ /* Pre-configure MDIO clock divider before PHY access. */ - ETH_MACMDIOAR = (4U << ETH_MACMDIOAR_CR_SHIFT); + ETH_MACMDIOAR = (MDIO_CR_VALUE << ETH_MACMDIOAR_CR_SHIFT); #endif return 0; } +#if !defined(STM32N6) static void eth_trigger_tx(void) { ETH_TPDR = 0U; __asm volatile ("dsb sy" ::: "memory"); } +#endif static void eth_config_mac(const uint8_t mac[6]) { - uint32_t maccr = ETH_MACCR; - maccr &= ~(ETH_MACCR_DM | ETH_MACCR_FES); - maccr |= ETH_MACCR_DM | ETH_MACCR_FES; + uint32_t maccr; + + /* Build MACCR value to match HAL defaults: + * - AutomaticPadCRCStrip = ENABLE (bit 20) + * - CRCStripTypePacket = ENABLE (bit 21) + * - ChecksumOffload = ENABLE (bit 27) + * - Watchdog = ENABLE (bit 19 = 0) + * - Jabber = ENABLE (bit 17 = 0) + * - InterPacketGap = 96bit (bits 26:24 = 0) + * - DuplexMode = Full (bit 13) + * - Speed = 100Mbps (bit 14) + * - PortSelect = MII/RMII (bit 15) for N6 + */ + maccr = (1U << 20) | /* ACS - Auto Pad/CRC Strip */ + (1U << 21) | /* CST - CRC Strip Type */ + (1U << 27) | /* IPC - Checksum Offload */ + ETH_MACCR_DM | ETH_MACCR_FES; + +#if defined(STM32N6) + /* PS=1 selects MII/RMII port (vs GMII). + * SARC=0: don't modify source address (wolfIP constructs full frame). */ + maccr |= ETH_MACCR_PS; + /* Configure 1µs tick counter — required for MAC internal timing. + * Value = (HCLK_Hz / 1000000) - 1. With PLL: HCLK ~200MHz -> 199. */ + ETH_MAC1USTCR = 199U; +#endif ETH_MACCR = maccr; -#if defined(STM32H7) - /* Enable RX Queue 0 for DCB/Generic traffic. */ - ETH_MACRXQC0R = (2U << 0); + /* Default packet filter: unicast (our MAC) + broadcast. + * Promiscuous mode disabled to avoid flooding wolfIP with irrelevant traffic. */ + ETH_MACPFR = 0; + + /* Configure MACECR (Extended Config) - HAL defaults: + * - GiantPacketSizeLimit = 0x618 (1560 bytes) + * - CRCCheckingRxPackets = ENABLE (bit 16 = 0) + */ + ETH_MACECR = 0x618U; /* Giant packet size limit */ + + /* Configure MACWTR (Watchdog Timeout) - HAL defaults: + * - WatchdogTimeout = 2KB (bits 3:0 = 0) + * - ProgrammableWatchdog = DISABLE (bit 8 = 0) + */ + ETH_MACWTR = 0x0U; + + /* Configure flow control - HAL defaults: disabled */ + ETH_MACQ0TXFCR = (1U << 7); /* Zero Quanta Pause disabled (DZPQ=1) */ + ETH_MACRXFCR = 0x0U; + +#if defined(STM32N6) + /* Enable both RX queues — N6 requires both DMA channels active */ + ETH_MACRXQC0R = 0x0Au; +#elif defined(STM32H7) + ETH_MACRXQC0R = 0x02u; #endif ETH_MACA0HR = ((uint32_t)mac[5] << 8) | (uint32_t)mac[4]; @@ -259,15 +374,40 @@ static void eth_config_speed_duplex(void) static void eth_config_mtl(void) { - uint32_t txqomr = ETH_MTLTXQOMR; - uint32_t rxqomr = ETH_MTLRXQOMR; + uint32_t txqomr; + uint32_t rxqomr; + + /* Configure MTL Operation Mode (MTLOMR) - HAL defaults: + * - TxSchedulingAlgorithm = Strict Priority (bits 6:5 = 0) + * - ReceiveArbitrationAlgorithm = Strict Priority (bit 8 = 0) + * - TransmitStatus = ENABLE (bit 1 = 0, DTXSTS=0) + */ + /* CubeN6 sets 0x60: bits 6:5 = Strict Priority arbitration */ + ETH_MTLOMR = 0x00000060u; + +#if defined(STM32N6) + /* Map RX Queue 0 to DMA Channel 0 (MTLRXQDMAMR) */ + ETH_MTLRXQDMAMR = 0x0U; /* Q0 -> CH0, Q1 -> CH1 */ +#endif + /* Configure TX Queue 0 Operation Mode: + * - TSF = Transmit Store and Forward (bit 1) + * - TXQEN = Enabled (bits 3:2 = 2) + * - TQS = 2KB (bits 22:16 = 0x7) + */ + txqomr = ETH_MTLTXQOMR; txqomr &= ~ETH_MTLTXQOMR_MASK; - txqomr |= (ETH_MTLTXQOMR_TSF | ETH_MTLTXQOMR_TXQEN_ENABLE); + txqomr |= ETH_MTLTXQOMR_TSF | ETH_MTLTXQOMR_TXQEN_ENABLE | ETH_MTLTXQOMR_TQS_2048; ETH_MTLTXQOMR = txqomr; + /* Configure RX Queue 0 Operation Mode: + * - RSF = Receive Store and Forward (bit 5) + * - RQS = 4KB (bits 23:20 = 0xF) + * - DEHF = Drop Error Half Frames (bit 6 = 0, disabled by HAL) + */ + rxqomr = ETH_MTLRXQOMR; rxqomr &= ~ETH_MTLRXQOMR_MASK; - rxqomr |= ETH_MTLRXQOMR_RSF; + rxqomr |= ETH_MTLRXQOMR_RSF | ETH_MTLRXQOMR_RQS_4096; ETH_MTLRXQOMR = rxqomr; } @@ -275,78 +415,184 @@ static void eth_init_desc(void) { uint32_t i; - /* Step 1: Clear all descriptors (like HAL does). */ + /* Clear all descriptors (HAL flow: init empty, arm later via eth_start). */ for (i = 0; i < TX_DESC_COUNT; i++) { - tx_ring[i].des0 = 0; - tx_ring[i].des1 = 0; - tx_ring[i].des2 = 0; - tx_ring[i].des3 = 0; + tx_ring[i].des0 = 0; tx_ring[i].des1 = 0; + tx_ring[i].des2 = 0; tx_ring[i].des3 = 0; } for (i = 0; i < RX_DESC_COUNT; i++) { - rx_ring[i].des0 = 0; - rx_ring[i].des1 = 0; - rx_ring[i].des2 = 0; - rx_ring[i].des3 = 0; + rx_ring[i].des0 = 0; rx_ring[i].des1 = 0; + rx_ring[i].des2 = 0; rx_ring[i].des3 = 0; } rx_idx = 0; tx_idx = 0; - /* Step 2: Configure DMA registers. */ + /* Configure DMA descriptor registers with EMPTY descriptors. */ __asm volatile ("dsb sy" ::: "memory"); - ETH_DMACTXDLAR = (uint32_t)&tx_ring[0]; - ETH_DMACRXDLAR = (uint32_t)&rx_ring[0]; + ETH_DMACTXDLAR = ETH_DMA_ADDR(&tx_ring[0]); + ETH_DMACRXDLAR = ETH_DMA_ADDR(&rx_ring[0]); ETH_DMACTXRLR = TX_DESC_COUNT - 1U; ETH_DMACRXRLR = RX_DESC_COUNT - 1U; - ETH_DMACTXDTPR = (uint32_t)&tx_ring[0]; - ETH_DMACRXDTPR = (uint32_t)&rx_ring[RX_DESC_COUNT - 1U]; + ETH_DMACTXDTPR = ETH_DMA_ADDR(&tx_ring[0]); + ETH_DMACRXDTPR = ETH_DMA_ADDR(&rx_ring[RX_DESC_COUNT - 1U]); __asm volatile ("dsb sy" ::: "memory"); - /* Step 3: Now set buffer addresses and OWN bit. */ + /* TX descriptors: set buffer addresses (no OWN, host owns them). */ + for (i = 0; i < TX_DESC_COUNT; i++) { + *(volatile uint32_t *)&tx_ring[i].des0 = ETH_DMA_ADDR(tx_buffers[i]); + } + /* RX descriptors: do NOT set OWN yet — will be armed by eth_start + * via ETH_UpdateDescriptor-style flow (matching CubeN6 HAL). */ + +#if defined(STM32N6) + /* Initialize DMA Channel 1 descriptors (N6 has 2 channels) */ for (i = 0; i < TX_DESC_COUNT; i++) { - *(volatile uint32_t *)&tx_ring[i].des0 = (uint32_t)tx_buffers[i]; + tx_ring1[i].des0 = 0; tx_ring1[i].des1 = 0; + tx_ring1[i].des2 = 0; tx_ring1[i].des3 = 0; } for (i = 0; i < RX_DESC_COUNT; i++) { - *(volatile uint32_t *)&rx_ring[i].des0 = (uint32_t)rx_buffers[i]; - *(volatile uint32_t *)&rx_ring[i].des3 = ETH_RDES3_OWN | ETH_RDES3_IOC | ETH_RDES3_BUF1V; + rx_ring1[i].des0 = 0; rx_ring1[i].des1 = 0; + rx_ring1[i].des2 = 0; rx_ring1[i].des3 = 0; } - - /* Data synchronization barrier before updating tail pointer. */ __asm volatile ("dsb sy" ::: "memory"); - __asm volatile ("isb sy" ::: "memory"); - /* Step 4: Update tail pointer to signal DMA that descriptors are ready. */ - ETH_DMACRXDTPR = (uint32_t)&rx_ring[RX_DESC_COUNT - 1U]; + ETH_DMAC1TXDLAR = ETH_DMA_ADDR(&tx_ring1[0]); + ETH_DMAC1RXDLAR = ETH_DMA_ADDR(&rx_ring1[0]); + ETH_DMAC1TXRLR = TX_DESC_COUNT - 1U; + ETH_DMAC1RXRLR = RX_DESC_COUNT - 1U; + ETH_DMAC1TXDTPR = ETH_DMA_ADDR(&tx_ring1[0]); + ETH_DMAC1RXDTPR = ETH_DMA_ADDR(&rx_ring1[RX_DESC_COUNT - 1U]); +#endif + /* Final barrier. */ __asm volatile ("dsb sy" ::: "memory"); } #define ETH_DMACCR_DSL_0BIT (0x00000000u) +/* DMA System Bus Mode Register bits */ +#define ETH_DMASBMR_RX_OSR_LIMIT_3 (0x3U << 16) /* RX Outstanding requests limit */ +#define ETH_DMASBMR_TX_OSR_LIMIT_3 (0x3U << 24) /* TX Outstanding requests limit */ +#define ETH_DMASBMR_BLEN4 (1U << 1) /* AXI Burst Length 4 */ + static void eth_config_dma(void) { - ETH_DMASBMR = ETH_DMASBMR_FB | ETH_DMASBMR_AAL; - /* Set DSL=0 for 16-byte descriptors (no skip). */ + /* Configure DMA System Bus Mode (DMASBMR) - HAL defaults: + * - AddressAlignedBeats = ENABLE (bit 12) + * - BurstMode = Fixed (bit 0) + * - RxOSRLimit = 3 (bits 17:16) + * - TxOSRLimit = 3 (bits 25:24) + * - AXIBLENMaxSize = 4 (bit 1) + */ + /* DMA System Bus Mode (matching CubeN6) */ + ETH_DMASBMR = 0x03031003u; + + /* DMACCR: DSL=1 (skip 1 doubleword = 8 bytes between 16-byte descriptors, + * stride = 24 bytes matching HAL ETH_DMADescTypeDef). MSS=536. */ +#if defined(STM32N6) + ETH_DMACCR = 0x40218u; +#else ETH_DMACCR = ETH_DMACCR_DSL_0BIT; +#endif + + /* Configure RX DMA: + * - RBSZ = buffer size + * - RPBL = 32 beat burst + */ ETH_DMACRXCR = ((RX_BUF_SIZE & ETH_RDES3_PL_MASK) << ETH_DMACRXCR_RBSZ_SHIFT) | ETH_DMACRXCR_RPBL(DMA_RPBL); + + /* Configure TX DMA: + * - TPBL = 32 beat burst + * - OSF = Operate on Second Frame (bit 4) - N6 does NOT use OSF (per CubeN6/Oryx) + */ +#if defined(STM32N6) + ETH_DMACTXCR = ETH_DMACTXCR_TPBL(DMA_TPBL); + + /* Configure DMA Channel 1 (N6 has 2 TX queues / DMA channels) */ + ETH_DMAC1CR = 0x40218u; /* DSL=1, MSS=536 — same as CH0 */ + ETH_DMAC1RXCR = ((RX_BUF_SIZE & ETH_RDES3_PL_MASK) << ETH_DMACRXCR_RBSZ_SHIFT) | + ETH_DMACRXCR_RPBL(DMA_RPBL); + ETH_DMAC1TXCR = ETH_DMACTXCR_TPBL(DMA_TPBL); + + /* MTL TX Queue 1: same as Q0 */ + ETH_MTLTXQ1OMR = ETH_MTLTXQOMR_TSF | ETH_MTLTXQOMR_TXQEN_ENABLE | + ETH_MTLTXQOMR_TQS_2048; + + /* Map RX Q1 -> DMA CH1 */ + ETH_MTLRXQDMAMR = (1u << 8); /* Q1MDMACH = 1 */ +#else ETH_DMACTXCR = ETH_DMACTXCR_OSF | ETH_DMACTXCR_TPBL(DMA_TPBL); +#endif } #define ETH_DMACSR_TPS (1U << 1) #define ETH_DMACSR_RPS (1U << 8) +static void eth_arm_rx_descriptors(void) +{ + uint32_t i; + /* Arm RX descriptors: set buffer address + OWN bit (HAL ETH_UpdateDescriptor flow). + * Must be done AFTER DMA is started. */ + for (i = 0; i < RX_DESC_COUNT; i++) { + rx_ring[i].des0 = ETH_DMA_ADDR(rx_buffers[i]); + rx_ring[i].des1 = 0; + rx_ring[i].des2 = 0; + __asm volatile ("dsb sy" ::: "memory"); + rx_ring[i].des3 = ETH_RDES3_OWN | ETH_RDES3_IOC | ETH_RDES3_BUF1V; + } + __asm volatile ("dmb sy" ::: "memory"); + ETH_DMACRXDTPR = ETH_DMA_ADDR(&rx_ring[RX_DESC_COUNT - 1U]); +} + static void eth_start(void) { +#if defined(STM32N6) + uint32_t j; +#endif ETH_MACCR |= ETH_MACCR_TE | ETH_MACCR_RE; ETH_MTLTXQOMR |= ETH_MTLTXQOMR_FTQ; ETH_DMACTXCR |= ETH_DMACTXCR_ST; ETH_DMACRXCR |= ETH_DMACRXCR_SR; - /* Clear TX and RX process stopped flags. */ - ETH_DMACSR = ETH_DMACSR_TPS | ETH_DMACSR_RPS; +#if defined(STM32N6) + /* N6 has 2 DMA channels. Both must be started for MAC RX to work. + * CubeN6 configures both channels identically. */ + ETH_MTLTXQ1OMR |= ETH_MTLTXQOMR_FTQ; + ETH_DMAC1TXCR |= ETH_DMACTXCR_ST; + ETH_DMAC1RXCR |= ETH_DMACRXCR_SR; + ETH_DMAC1SR = 0xFFFF; /* Clear all status */ + __asm volatile ("dsb sy" ::: "memory"); + ETH_DMAC1RXDTPR = ETH_DMA_ADDR(&rx_ring1[RX_DESC_COUNT - 1U]); +#endif + + /* Enable DMA interrupt flags (matching CubeN6 HAL_ETH_Start_IT). + * Even in polled mode, some GMAC implementations need these enabled + * for the DMA to process descriptors. */ + ETH_DMACIER = ETH_DMACIER_NIE | ETH_DMACIER_AIE | ETH_DMACIER_RBUE | + ETH_DMACIER_RIE | ETH_DMACIER_TIE; +#if defined(STM32N6) + ETH_DMAC1IER = ETH_DMACIER_NIE | ETH_DMACIER_AIE | ETH_DMACIER_RBUE | + ETH_DMACIER_RIE | ETH_DMACIER_TIE; +#endif + + /* Clear TX and RX process stopped flags + RBU (bit 7). */ + ETH_DMACSR = ETH_DMACSR_TPS | ETH_DMACSR_RPS | (1U << 7); __asm volatile ("dsb sy" ::: "memory"); - /* Write tail pointer to start RX DMA. */ - ETH_DMACRXDTPR = (uint32_t)&rx_ring[RX_DESC_COUNT - 1U]; + + /* Arm RX descriptors AFTER DMA is started (HAL_ETH_Start_IT flow). + * This tells the DMA about available descriptors via tail pointer. */ + eth_arm_rx_descriptors(); +#if defined(STM32N6) + for (j = 0; j < RX_DESC_COUNT; j++) { + rx_ring1[j].des0 = ETH_DMA_ADDR(rx_buffers1[j]); + rx_ring1[j].des1 = 0; rx_ring1[j].des2 = 0; + __asm volatile ("dsb sy" ::: "memory"); + rx_ring1[j].des3 = ETH_RDES3_OWN | ETH_RDES3_IOC | ETH_RDES3_BUF1V; + } + __asm volatile ("dmb sy" ::: "memory"); + ETH_DMAC1RXDTPR = ETH_DMA_ADDR(&rx_ring1[RX_DESC_COUNT - 1U]); +#endif } static void eth_stop(void) @@ -370,8 +616,7 @@ static uint16_t eth_mdio_read(uint32_t phy, uint32_t reg) { uint32_t cfg; eth_mdio_wait_ready(); - /* CR = 4 for HCLK 150-250MHz range. */ - cfg = (4U << ETH_MACMDIOAR_CR_SHIFT) | + cfg = (MDIO_CR_VALUE << ETH_MACMDIOAR_CR_SHIFT) | (reg << ETH_MACMDIOAR_RDA_SHIFT) | (phy << ETH_MACMDIOAR_PA_SHIFT) | (ETH_MACMDIOAR_GOC_READ << ETH_MACMDIOAR_GOC_SHIFT); @@ -385,8 +630,7 @@ static void eth_mdio_write(uint32_t phy, uint32_t reg, uint16_t value) uint32_t cfg; eth_mdio_wait_ready(); ETH_MACMDIODR = (uint32_t)value; - /* CR = 4 for HCLK 150-250MHz range. */ - cfg = (4U << ETH_MACMDIOAR_CR_SHIFT) | + cfg = (MDIO_CR_VALUE << ETH_MACMDIOAR_CR_SHIFT) | (reg << ETH_MACMDIOAR_RDA_SHIFT) | (phy << ETH_MACMDIOAR_PA_SHIFT) | (ETH_MACMDIOAR_GOC_WRITE << ETH_MACMDIOAR_GOC_SHIFT); @@ -414,7 +658,10 @@ static void eth_phy_init(void) if (phy_addr < 0) { phy_addr = eth_detect_phy(); - if (phy_addr < 0) phy_addr = 0; + if (phy_addr < 0) { + phy_addr = 0; + return; /* No PHY found — skip init to avoid long timeouts */ + } } /* Reset PHY. */ @@ -445,6 +692,8 @@ static void eth_phy_init(void) } while ((bsr & PHY_BSR_LINK_STATUS) == 0U && --timeout != 0U); } +#define ETH_DMACSR_RBU (1U << 7) + static int eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) { struct eth_desc *desc; @@ -454,6 +703,14 @@ static int eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) (void)dev; rx_poll_count++; + /* Recover from RBU (Receive Buffer Unavailable) — DMA stops after + * exhausting all descriptors. Clear the status and kick the DMA. */ + if (ETH_DMACSR & ETH_DMACSR_RBU) { + ETH_DMACSR = ETH_DMACSR_RBU; /* W1C */ + __asm volatile ("dsb sy" ::: "memory"); + ETH_DMACRXDTPR = ETH_DMA_ADDR(&rx_ring[RX_DESC_COUNT - 1U]); + } + desc = &rx_ring[rx_idx]; if (desc->des3 & ETH_RDES3_OWN) return 0; @@ -463,15 +720,20 @@ static int eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) if ((status & (ETH_RDES3_FS | ETH_RDES3_LS)) == (ETH_RDES3_FS | ETH_RDES3_LS)) { frame_len = status & ETH_RDES3_PL_MASK; if (frame_len > len) frame_len = len; - memcpy(rx_staging_buffer, rx_buffers[rx_idx], frame_len); - memcpy(frame, rx_staging_buffer, frame_len); + if (frame_len > 0) { + memcpy(frame, rx_buffers[rx_idx], frame_len); + } } - /* Reinitialize descriptor. */ + /* Reinitialize descriptor — must restore des0 (buffer address) since + * DMA writeback overwrites it with timestamp data. */ + desc->des0 = ETH_DMA_ADDR(rx_buffers[rx_idx]); desc->des1 = 0; + desc->des2 = 0; + __asm volatile ("dsb sy" ::: "memory"); desc->des3 = ETH_RDES3_OWN | ETH_RDES3_IOC | ETH_RDES3_BUF1V; __asm volatile ("dsb sy" ::: "memory"); - ETH_DMACRXDTPR = (uint32_t)desc; + ETH_DMACRXDTPR = ETH_DMA_ADDR(desc); rx_idx = (rx_idx + 1U) % RX_DESC_COUNT; return (int)frame_len; @@ -497,7 +759,7 @@ static int eth_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) if (dma_len > len) memset(tx_buffers[tx_idx] + len, 0, dma_len - len); /* Setup descriptor. */ - desc->des0 = (uint32_t)tx_buffers[tx_idx]; + desc->des0 = ETH_DMA_ADDR(tx_buffers[tx_idx]); desc->des1 = 0; desc->des2 = (dma_len & ETH_TDES2_B1L_MASK); __asm volatile ("dsb sy" ::: "memory"); @@ -508,10 +770,14 @@ static int eth_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) __asm volatile ("dsb sy" ::: "memory"); ETH_DMACSR = ETH_DMACSR_TBU; +#if !defined(STM32N6) + /* TPDR (0x1180) is a separate TX poll register on H5/H7. + * On N6, offset 0x1180 is DMAC1CR — writing 0 would clobber CH1 config. */ if (tx_idx == 0U) eth_trigger_tx(); +#endif next_idx = (tx_idx + 1U) % TX_DESC_COUNT; - ETH_DMACTXDTPR = (uint32_t)&tx_ring[next_idx]; + ETH_DMACTXDTPR = ETH_DMA_ADDR(&tx_ring[next_idx]); tx_idx = next_idx; return (int)len; @@ -527,6 +793,11 @@ static void stm32_eth_generate_mac(uint8_t mac[6]) mac[3] = 0x33; mac[4] = 0x44; mac[5] = 0x55; +#elif defined(STM32N6) + mac[2] = 0xCC; + mac[3] = 0xDD; + mac[4] = 0x55; + mac[5] = 0x66; #else mac[2] = 0xAA; mac[3] = 0xBB; @@ -541,51 +812,21 @@ void stm32_eth_get_stats(uint32_t *polls, uint32_t *pkts) if (pkts) *pkts = rx_pkt_count; } + uint32_t stm32_eth_get_rx_des3(void) { return rx_ring[0].des3; } -uint32_t stm32_eth_get_rx_des0(void) -{ - return rx_ring[0].des0; -} - -uint32_t stm32_eth_get_rx_ring_addr(void) -{ - return (uint32_t)&rx_ring[0]; -} - uint32_t stm32_eth_get_dmacsr(void) { - /* Clear RBU by writing 1 to bit 7. */ uint32_t val = ETH_DMACSR; if (val & 0x80) { - ETH_DMACSR = 0x80; + ETH_DMACSR = 0x80; /* Clear RBU (W1C) */ } return val; } -uint32_t stm32_eth_get_rx_tail(void) -{ - return ETH_DMACRXDTPR; -} - -void stm32_eth_kick_rx(void) -{ - uint32_t i; - /* Reinitialize all RX descriptors and kick DMA. */ - for (i = 0; i < RX_DESC_COUNT; i++) { - *(volatile uint32_t *)&rx_ring[i].des0 = (uint32_t)rx_buffers[i]; - *(volatile uint32_t *)&rx_ring[i].des1 = 0; - *(volatile uint32_t *)&rx_ring[i].des2 = 0; - *(volatile uint32_t *)&rx_ring[i].des3 = ETH_RDES3_OWN | ETH_RDES3_IOC | ETH_RDES3_BUF1V; - } - __asm volatile ("dsb sy" ::: "memory"); - __asm volatile ("isb sy" ::: "memory"); - ETH_DMACRXDTPR = (uint32_t)&rx_ring[RX_DESC_COUNT - 1U]; -} - int stm32_eth_init(struct wolfIP_ll_dev *ll, const uint8_t *mac) { uint8_t local_mac[6]; @@ -610,8 +851,8 @@ int stm32_eth_init(struct wolfIP_ll_dev *ll, const uint8_t *mac) } eth_config_mac(mac); eth_config_mtl(); + eth_config_dma(); /* DMA mode config BEFORE descriptor setup (per CubeN6 HAL) */ eth_init_desc(); - eth_config_dma(); eth_phy_init(); eth_config_speed_duplex(); eth_start(); diff --git a/src/port/stm32/stm32_eth.h b/src/port/stm32/stm32_eth.h index fb702eff..989d39e8 100644 --- a/src/port/stm32/stm32_eth.h +++ b/src/port/stm32/stm32_eth.h @@ -1,6 +1,6 @@ /* stm32_eth.h * - * Common STM32H5/STM32H7 Ethernet driver interface. + * Common STM32H5/STM32H7/STM32N6 Ethernet driver interface. * * Copyright (C) 2024-2026 wolfSSL Inc. * @@ -15,10 +15,6 @@ int stm32_eth_init(struct wolfIP_ll_dev *ll, const uint8_t *mac); void stm32_eth_get_stats(uint32_t *polls, uint32_t *pkts); uint32_t stm32_eth_get_rx_des3(void); -uint32_t stm32_eth_get_rx_des0(void); -uint32_t stm32_eth_get_rx_ring_addr(void); uint32_t stm32_eth_get_dmacsr(void); -uint32_t stm32_eth_get_rx_tail(void); -void stm32_eth_kick_rx(void); #endif /* WOLFIP_STM32_ETH_H */ diff --git a/src/port/stm32n6/Makefile b/src/port/stm32n6/Makefile new file mode 100644 index 00000000..ed0f0ef9 --- /dev/null +++ b/src/port/stm32n6/Makefile @@ -0,0 +1,66 @@ +CC ?= arm-none-eabi-gcc +OBJCOPY ?= arm-none-eabi-objcopy + +ROOT := ../../.. + +# Base compiler flags — Cortex-M55 soft-float +CFLAGS := -mcpu=cortex-m55 -mthumb -mfloat-abi=soft -Os -ffreestanding -fdata-sections -ffunction-sections +CFLAGS += -g -ggdb -Wall -Wextra -Werror +CFLAGS += -I. -I$(ROOT) -I$(ROOT)/src -I$(ROOT)/src/port -I$(ROOT)/src/port/stm32 +CFLAGS += -DSTM32N6 -DTZEN_ENABLED=0 +EXTRA_CFLAGS ?= +CFLAGS += $(EXTRA_CFLAGS) + +# Optional: DEBUG_FAULT=1 enables verbose HardFault handler with register dump +ifdef DEBUG_FAULT +CFLAGS += -DDEBUG_FAULT +endif + +LDSCRIPT := target.ld +LDFLAGS := -nostdlib -T $(LDSCRIPT) -Wl,-gc-sections + +# Source files +SRCS := startup.c ivt.c syscalls.c +SRCS += main.c $(ROOT)/src/port/stm32/stm32_eth.c $(ROOT)/src/wolfip.c + +OBJS := $(patsubst %.c,%.o,$(SRCS)) + +all: app.bin + @echo "Built STM32N6 wolfIP echo server" + +app.elf: $(OBJS) $(LDSCRIPT) + $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -Wl,--start-group -lc -lm -lgcc -lnosys -Wl,--end-group -o $@ + +app.bin: app.elf + $(OBJCOPY) -O binary $< $@ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f *.o app.elf app.bin + rm -f $(ROOT)/src/*.o + rm -f $(ROOT)/src/port/*.o + rm -f $(ROOT)/src/port/stm32/*.o + +size: app.elf + @echo "=== Memory Usage ===" + @arm-none-eabi-size app.elf + +flash: app.bin + bash flash.sh + +monitor: + picocom -b 115200 /dev/ttyACM0 + +debug: app.elf + openocd -f openocd.cfg & + arm-none-eabi-gdb app.elf \ + -ex "target remote :3333" \ + -ex "monitor reset init" \ + -ex "load" \ + -ex "monitor mww 0xE000ED08 0x34000000" \ + -ex "break main" \ + -ex "continue" + +.PHONY: all clean size flash monitor debug diff --git a/src/port/stm32n6/README.md b/src/port/stm32n6/README.md new file mode 100644 index 00000000..685e03aa --- /dev/null +++ b/src/port/stm32n6/README.md @@ -0,0 +1,256 @@ +# wolfIP STM32N6 Port + +This port provides a bare-metal wolfIP TCP/IP stack implementation for the +STM32N6 microcontroller (NUCLEO-N657X0-Q board). The STM32N6 is ST's first +Cortex-M55 microcontroller, running at 600 MHz (up to 800 MHz) with Helium +vector extensions, targeting high-performance edge AI workloads. + +## Features + +- **TCP/IP Stack**: Full wolfIP stack with TCP, UDP, ICMP, ARP +- **600 MHz Cortex-M55**: PLL1 configured for 600 MHz CPU clock +- **Ethernet**: RMII interface with LAN8742 PHY, dual DMA channels +- **Bare Metal**: No HAL, no CMSIS, no RTOS — fully self-contained +- **JTAG-to-SRAM**: Loads directly into AXISRAM via OpenOCD (no flash required) + +## Hardware Requirements + +- NUCLEO-N657X0-Q development board (STM32N657X0H, MB1940) +- Ethernet cable connected to a switch or directly to host PC +- USB cable for ST-Link programming and debug UART + +## About the STM32N6 + +The STM32N6 is built around the Arm Cortex-M55 core running at 600 MHz, +with support for up to 800 MHz at Voltage Scale 0. Unlike most STM32 parts, +the N6 has no internal flash — all firmware resides on external NOR flash +(XSPI2) or is loaded directly into on-chip SRAM. The device provides over +4.2 MB of on-chip SRAM across multiple regions (AXISRAM1-6, AHBSRAM, DTCM). + +Key differences from STM32H5/H7: +- **GMAC v5.20**: Dual-channel DMA with 24-byte descriptor stride (DSL=1) +- **RISAF firewall**: Memory access requires explicit RISAF region configuration +- **RIMC/RISC**: ETH DMA bus master identity (CID) must match RISAF grants +- **SAU/IDAU**: Security attribution affects DMA memory access paths +- **No internal flash**: Code runs from SRAM (JTAG load) or external XSPI flash + +## Clock Configuration + +``` +HSI 64 MHz -> PLL1 (M=4, N=75) -> VCO 1200 MHz -> PDIV1=1 -> 1200 MHz + IC1 /2 = 600 MHz -> CPU + IC2 /3 = 400 MHz -> AXI bus + IC6 /4 = 300 MHz -> System bus C + IC11 /3 = 400 MHz -> System bus D +AHB prescaler /2 -> HCLK = 300 MHz +USART1 kernel clock: IC9 -> HSI 64 MHz +``` + +## Pin Configuration (NUCLEO-N657X0-Q RMII) + +| Pin | Function | Description | +|------|--------------|--------------------| +| PF4 | ETH_MDIO | PHY management | +| PG11 | ETH_MDC | PHY clock | +| PF7 | ETH_REF_CLK | 50 MHz reference | +| PF10 | ETH_CRS_DV | Carrier sense | +| PF11 | ETH_TX_EN | Transmit enable | +| PF12 | ETH_TXD0 | Transmit data 0 | +| PF13 | ETH_TXD1 | Transmit data 1 | +| PF14 | ETH_RXD0 | Receive data 0 | +| PF15 | ETH_RXD1 | Receive data 1 | +| PE5 | USART1_TX | Debug output | +| PE6 | USART1_RX | Debug input | +| PO1 | LED LD1 | Green heartbeat | + +All Ethernet pins use AF11. USART1 uses AF7. + +## Building + +### Prerequisites + +- ARM GCC toolchain (`arm-none-eabi-gcc`) +- OpenOCD (STMicroelectronics fork with STM32N6 support) + +```bash +# Ubuntu/Debian +sudo apt install gcc-arm-none-eabi +``` + +### Build + +```bash +cd src/port/stm32n6 +CC=arm-none-eabi-gcc make +``` + +This produces `app.elf` and `app.bin` for loading into AXISRAM via JTAG. + +### Memory Usage + +```bash +make size +``` + +| Configuration | Code + Data | BSS (static RAM) | +|---------------|-------------|------------------| +| TCP Echo only | ~25 KB | ~146 KB | + +## Flashing + +The N6 port loads firmware directly into AXISRAM1 (0x34000000) via JTAG. +No flash programming is needed. + +```bash +make flash +# or +bash flash.sh +``` + +The `flash.sh` script: +1. Resets the CPU via OpenOCD +2. Loads `app.bin` to AXISRAM1 at 0x34000000 +3. Sets VTOR, stack pointer, and entry point +4. Resumes execution + +## Serial Console + +Connect to the ST-Link VCP at 115200 baud: + +```bash +picocom -b 115200 /dev/ttyACM0 +# or +make monitor +``` + +### Expected Boot Output + +``` + RIMC_ATTR6 (ETH1): 0x00000301 +Initializing Ethernet MAC... + PHY link: UP, PHY addr: 0x00000000 + MAC: 02:11:CC:DD:55:66 +Setting IP configuration: + IP: 192.168.12.11 + Mask: 255.255.255.0 + GW: 192.168.12.1 +Creating TCP socket on port 7... +Entering main loop. Ready for connections! + TCP Echo: port 7 +``` + +## Network Configuration + +The port uses static IP by default (DHCP disabled). + +| Setting | Value | +|-------------|-----------------| +| IP Address | 192.168.12.11 | +| Subnet Mask | 255.255.255.0 | +| Gateway | 192.168.12.1 | + +Configure your host PC to be on the same subnet: + +```bash +sudo ip addr add 192.168.12.1/24 dev eth0 +sudo ip link set eth0 up +``` + +Replace `eth0` with your Ethernet interface name. + +## Testing + +### Ping + +```bash +ping 192.168.12.11 +``` + +Expected: sub-millisecond replies (~0.15 ms RTT). + +### TCP Echo (Port 7) + +```bash +echo "Hello STM32N6!" | nc -q1 -w2 192.168.12.11 7 +``` + +Expected: `Hello STM32N6!` echoed back. + +## Memory Map + +| Region | Address | Size | Usage | +|-----------|--------------|--------|---------------------------------------| +| AXISRAM1 | 0x34000000 | 512 KB | Code, data, BSS, heap, stack | +| AXISRAM2 | 0x341F8000 | 32 KB | ETH DMA descriptors + buffers | + +ETH DMA buffers are placed at the end of AXISRAM2 (matching the CubeN6 HAL +descriptor placement). The MPU marks this region as Normal Non-cacheable for +DMA coherency. + +## N6-Specific Initialization + +The STM32N6 requires several security and bus fabric configurations that +other STM32 ports do not need: + +### SAU (Security Attribution Unit) +All SAU regions are cleared and `ALLNS=1` is set, making all memory +non-secure. This matches the CubeN6 SystemInit behavior. + +### RISAF3 (AXISRAM2 Firewall) +The default RISAF3 base region covers only 4 KB. The ETH DMA buffers at +offset 0xF8000 are outside this range and would be blocked. The port extends +RISAF3 REG0 to cover the full 1 MB of AXISRAM2 with all CIDs granted +read+write access (`SEC=1` for secure-alias compatibility). + +### RIMC (Resource Isolation Master Control) +The ETH DMA bus master is configured with `CID=1` (matching the CPU), +`SEC=1`, `PRIV=1`. This matches the CubeN6 HAL `RISAF_Config()`. + +### RISC (Resource Isolation Slave Control) +The ETH1 peripheral is marked as Secure + Privileged in the RIFSC slave +security registers. + +### RCC ETH Reset +A full RCC peripheral reset of ETH1 is performed before MAC initialization +to ensure a clean state. + +## Debugging + +### GDB + +```bash +make debug +``` + +This starts OpenOCD and connects GDB with the firmware loaded. + +### HardFault Handler + +The port includes a detailed HardFault handler that prints register state +via UART (PC, LR, R0-R3, R12, xPSR, HFSR, CFSR, BFAR, MMFAR). The handler +only accesses UART if `uart_ready` is set, preventing double-faults during +early boot. + +## File Structure + +``` +stm32n6/ + Makefile Build system + README.md This file + config.h wolfIP configuration (static IP, socket counts) + target.ld Linker script (AXISRAM1 code + AXISRAM2 ETH buffers) + openocd.cfg OpenOCD configuration for NUCLEO-N657X0-Q + flash.sh JTAG load script (SRAM execution) + startup.c Cortex-M55 startup code + ivt.c Interrupt vector table + syscalls.c Newlib syscall stubs + main.c Application: clocks, GPIO, UART, SAU, RISAF, RIMC, ETH + ../stm32/stm32_eth.c Shared Ethernet MAC/PHY driver (H5, H7, N6) + ../stm32/stm32_eth.h Shared Ethernet driver header +``` + +## License + +Copyright (C) 2026 wolfSSL Inc. + +This project is licensed under GPLv3. See the wolfIP LICENSE file for details. diff --git a/src/port/stm32n6/config.h b/src/port/stm32n6/config.h new file mode 100644 index 00000000..a703e556 --- /dev/null +++ b/src/port/stm32n6/config.h @@ -0,0 +1,66 @@ +/* config.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#ifndef WOLF_CONFIG_H +#define WOLF_CONFIG_H + +#ifndef CONFIG_IPFILTER +#define CONFIG_IPFILTER 0 +#endif + +#define ETHERNET +#define LINK_MTU 1536 + +#define MAX_TCPSOCKETS 4 +#define MAX_UDPSOCKETS 2 +#define MAX_ICMPSOCKETS 1 +#define RXBUF_SIZE (LINK_MTU * 2) +#define TXBUF_SIZE (LINK_MTU * 2) + +#define MAX_NEIGHBORS 16 + +#ifndef WOLFIP_MAX_INTERFACES +#define WOLFIP_MAX_INTERFACES 1 +#endif + +#ifndef WOLFIP_ENABLE_FORWARDING +#define WOLFIP_ENABLE_FORWARDING 0 +#endif + +#ifndef WOLFIP_ENABLE_LOOPBACK +#define WOLFIP_ENABLE_LOOPBACK 0 +#endif + +#ifndef WOLFIP_ENABLE_DHCP +#define WOLFIP_ENABLE_DHCP 0 +#endif + +/* Static IP configuration */ +#define WOLFIP_IP "192.168.12.11" +#define WOLFIP_NETMASK "255.255.255.0" +#define WOLFIP_GW "192.168.12.1" + +#if WOLFIP_ENABLE_DHCP +#define DHCP +#define DHCP_DISCOVER_RETRIES 1 +#define DHCP_REQUEST_RETRIES 1 +#endif + +#endif /* WOLF_CONFIG_H */ diff --git a/src/port/stm32n6/flash.sh b/src/port/stm32n6/flash.sh new file mode 100755 index 00000000..2a8d445f --- /dev/null +++ b/src/port/stm32n6/flash.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Flash wolfIP to STM32N6 AXISRAM via OpenOCD +set -e +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +BIN="${SCRIPT_DIR}/app.bin" +CFG="${SCRIPT_DIR}/openocd.cfg" + +[ -f "$BIN" ] || { echo "app.bin not found. Run 'make' first."; exit 1; } + +# Extract initial SP (word 0) and entry point (word 1) from vector table +INIT_SP=$(od -A n -t x4 -N 4 "$BIN" | awk '{print "0x"$1}') +ENTRY=$(od -A n -j 4 -t x4 -N 4 "$BIN" | awk '{print "0x"$1}') +ENTRY_THUMB=$(printf "0x%08x" $(( ENTRY | 1 ))) + +echo "Loading app.bin to AXISRAM1 (0x34000000)" +echo " SP: ${INIT_SP}, Entry: ${ENTRY_THUMB}" + +# Try normal reset init first. If it fails (CPU in LOCKUP), try recovery +# via AP0 AIRCR write, then retry. +openocd -f "$CFG" -c " + reset init; + load_image ${BIN} 0x34000000 bin; + reg msplim_s 0x00000000; + reg psplim_s 0x00000000; + reg msp ${INIT_SP}; + mww 0xE000ED08 0x34000000; + mww 0xE000ED28 0xFFFFFFFF; + resume ${ENTRY_THUMB}; + shutdown +" 2>&1 +STATUS=$? + +if [ $STATUS -ne 0 ]; then + echo "" + echo "*** Normal flash failed (CPU may be in LOCKUP state) ***" + echo "Please press the RESET button on the NUCLEO board, then re-run this script." + echo "" + echo "If the board is unrecoverable, unplug and replug the USB cable." + exit 1 +fi + +echo "Done. Monitor UART: picocom -b 115200 /dev/ttyACM0" diff --git a/src/port/stm32n6/ivt.c b/src/port/stm32n6/ivt.c new file mode 100644 index 00000000..a8d0b793 --- /dev/null +++ b/src/port/stm32n6/ivt.c @@ -0,0 +1,69 @@ +/* ivt.c + * + * Cortex-M55 interrupt vector table for STM32N6. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#include + +extern void Reset_Handler(void); +extern unsigned long _estack; + +static void default_handler(void) +{ + while (1) { } +} + +void NMI_Handler(void) __attribute__((weak, alias("default_handler"))); +void HardFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void MemManage_Handler(void) __attribute__((weak, alias("default_handler"))); +void BusFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void UsageFault_Handler(void) __attribute__((weak, alias("default_handler"))); +void SecureFault_Handler(void)__attribute__((weak, alias("default_handler"))); +void SVC_Handler(void) __attribute__((weak, alias("default_handler"))); +void DebugMon_Handler(void) __attribute__((weak, alias("default_handler"))); +void PendSV_Handler(void) __attribute__((weak, alias("default_handler"))); +void SysTick_Handler(void) __attribute__((weak, alias("default_handler"))); + +/* Repeat macros for filling IRQ entries portably */ +#define DH (uint32_t)&default_handler +#define DH5 DH, DH, DH, DH, DH +#define DH10 DH5, DH5 +#define DH50 DH10, DH10, DH10, DH10, DH10 +#define DH200 DH50, DH50, DH50, DH50 + +/* Cortex-M55 vector table: 16 system + 200 IRQs (ETH1_IRQn = 179) */ +__attribute__((section(".isr_vector"))) +const uint32_t vector_table[16 + 200] = { + (uint32_t)&_estack, + (uint32_t)&Reset_Handler, + (uint32_t)&NMI_Handler, + (uint32_t)&HardFault_Handler, + (uint32_t)&MemManage_Handler, + (uint32_t)&BusFault_Handler, + (uint32_t)&UsageFault_Handler, + (uint32_t)&SecureFault_Handler, + 0, 0, 0, + (uint32_t)&SVC_Handler, + (uint32_t)&DebugMon_Handler, + 0, + (uint32_t)&PendSV_Handler, + (uint32_t)&SysTick_Handler, + DH200 +}; diff --git a/src/port/stm32n6/main.c b/src/port/stm32n6/main.c new file mode 100644 index 00000000..54ac0511 --- /dev/null +++ b/src/port/stm32n6/main.c @@ -0,0 +1,842 @@ +/* main.c + * + * STM32N6 (NUCLEO-N657X0-Q, Cortex-M55) wolfIP echo server. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#include +#include +#include "config.h" +#include "wolfip.h" +#include "stm32_eth.h" + +/* Forward declarations */ +static void uart_puts(const char *s); +static void delay(uint32_t count); + +/* RCC Reset and Clock Control (secure bus) */ +#define RCC_BASE 0x56028000UL + +/* Control: direct read / set (+0x800) / clear (+0x1000) */ +#define RCC_CR (*(volatile uint32_t *)(RCC_BASE + 0x000u)) +#define RCC_CSR (*(volatile uint32_t *)(RCC_BASE + 0x800u)) +#define RCC_CCR (*(volatile uint32_t *)(RCC_BASE + 0x1000u)) +#define RCC_CR_HSION (1u << 3) +#define RCC_CR_PLL1ON (1u << 8) + +/* Status ready flags */ +#define RCC_SR (*(volatile uint32_t *)(RCC_BASE + 0x04u)) +#define RCC_SR_HSIRDY (1u << 3) +#define RCC_SR_PLL1RDY (1u << 8) + +/* Clock switching */ +#define RCC_CFGR1 (*(volatile uint32_t *)(RCC_BASE + 0x20u)) +#define RCC_CFGR1_CPUSW_SHIFT 16u +#define RCC_CFGR1_CPUSW_MASK (0x3u << 16u) +#define RCC_CFGR1_CPUSWS_SHIFT 20u +#define RCC_CFGR1_CPUSWS_MASK (0x3u << 20u) +#define RCC_CFGR1_SYSSW_SHIFT 24u +#define RCC_CFGR1_SYSSW_MASK (0x3u << 24u) +#define RCC_CFGR1_SYSSWS_SHIFT 28u +#define RCC_CFGR1_SYSSWS_MASK (0x3u << 28u) + +/* AHB/APB prescalers */ +#define RCC_CFGR2 (*(volatile uint32_t *)(RCC_BASE + 0x24u)) +#define RCC_CFGR2_HPRE_SHIFT 20u +#define RCC_CFGR2_HPRE_MASK (0x7u << 20u) + +/* PLL1 configuration */ +#define RCC_PLL1CFGR1 (*(volatile uint32_t *)(RCC_BASE + 0x80u)) +#define RCC_PLL1CFGR1_SEL_MASK (0x7u << 28u) +#define RCC_PLL1CFGR1_DIVM_MASK (0x3Fu << 20u) +#define RCC_PLL1CFGR1_DIVN_MASK (0xFFFu << 8u) +#define RCC_PLL1CFGR1_BYP (1u << 27u) +#define RCC_PLL1CFGR1_SEL_HSI (0x0u << 28u) +#define RCC_PLL1CFGR1_DIVM_SHIFT 20u +#define RCC_PLL1CFGR1_DIVN_SHIFT 8u + +#define RCC_PLL1CFGR2 (*(volatile uint32_t *)(RCC_BASE + 0x84u)) + +#define RCC_PLL1CFGR3 (*(volatile uint32_t *)(RCC_BASE + 0x88u)) +#define RCC_PLL1CFGR3_PDIV1_SHIFT 27u +#define RCC_PLL1CFGR3_PDIV2_SHIFT 24u +#define RCC_PLL1CFGR3_MODSSDIS (1u << 2u) +#define RCC_PLL1CFGR3_MODSSRST (1u << 0u) +#define RCC_PLL1CFGR3_PDIVEN (1u << 30u) + +/* IC dividers */ +#define RCC_IC1CFGR (*(volatile uint32_t *)(RCC_BASE + 0xC4u)) +#define RCC_IC2CFGR (*(volatile uint32_t *)(RCC_BASE + 0xC8u)) +#define RCC_IC6CFGR (*(volatile uint32_t *)(RCC_BASE + 0xD8u)) +#define RCC_IC11CFGR (*(volatile uint32_t *)(RCC_BASE + 0xECu)) +#define RCC_ICCFGR_INT_SHIFT 16u +#define RCC_ICCFGR_SEL_PLL1 (0x0u << 28u) + +/* Divider enable direct / set (+0x800) / clear (+0x1000) */ +#define RCC_DIVENR (*(volatile uint32_t *)(RCC_BASE + 0x240u)) +#define RCC_DIVENSR (*(volatile uint32_t *)(RCC_BASE + 0xA40u)) +#define RCC_DIVENCR (*(volatile uint32_t *)(RCC_BASE + 0x1240u)) +#define RCC_DIVENR_IC1EN (1u << 0u) +#define RCC_DIVENR_IC2EN (1u << 1u) +#define RCC_DIVENR_IC6EN (1u << 5u) +#define RCC_DIVENR_IC11EN (1u << 10u) + +/* Peripheral clock enable SET registers (write 1s to set bits) */ +#define RCC_AHB2ENSR (*(volatile uint32_t *)(RCC_BASE + 0xA54u)) +#define RCC_AHB3ENSR (*(volatile uint32_t *)(RCC_BASE + 0xA58u)) +#define RCC_AHB4ENSR (*(volatile uint32_t *)(RCC_BASE + 0xA5Cu)) +#define RCC_AHB5ENSR (*(volatile uint32_t *)(RCC_BASE + 0xA60u)) +#define RCC_APB2ENSR (*(volatile uint32_t *)(RCC_BASE + 0xA6Cu)) +#define RCC_MEMENSR (*(volatile uint32_t *)(RCC_BASE + 0xA4Cu)) + +/* Clock configuration for ETH PHY interface selection */ +#define RCC_CCIPR2 (*(volatile uint32_t *)(RCC_BASE + 0x148u)) +#define RCC_CCIPR2_ETH1SEL_RMII (0x4u << 16u) /* bit 18 = RMII mode */ + +/* Peripheral reset */ +#define RCC_AHB5RSTR (*(volatile uint32_t *)(RCC_BASE + 0x220u)) + +/* GPIO secure bus addresses */ +#define GPIOE_BASE 0x56021000UL +#define GPIOF_BASE 0x56021400UL +#define GPIOG_BASE 0x56021800UL +#define GPIOO_BASE 0x56023800UL + +#define GPIO_MODER(base) (*(volatile uint32_t *)((base) + 0x00u)) +#define GPIO_OSPEEDR(base) (*(volatile uint32_t *)((base) + 0x08u)) +#define GPIO_PUPDR(base) (*(volatile uint32_t *)((base) + 0x0Cu)) +#define GPIO_ODR(base) (*(volatile uint32_t *)((base) + 0x14u)) +#define GPIO_BSRR(base) (*(volatile uint32_t *)((base) + 0x18u)) +#define GPIO_AFRL(base) (*(volatile uint32_t *)((base) + 0x20u)) +#define GPIO_AFRH(base) (*(volatile uint32_t *)((base) + 0x24u)) + +/* PWR Power Control */ +#define PWR_BASE 0x56024800UL +#define PWR_SVMCR1 (*(volatile uint32_t *)(PWR_BASE + 0x34u)) +#define PWR_SVMCR2 (*(volatile uint32_t *)(PWR_BASE + 0x38u)) +#define PWR_SVMCR3 (*(volatile uint32_t *)(PWR_BASE + 0x3Cu)) +#define PWR_SVMCR1_VDDIO4SV (1u << 8u) +#define PWR_SVMCR2_VDDIO5SV (1u << 8u) +#define PWR_SVMCR3_VDDIO2SV (1u << 8u) +#define PWR_SVMCR3_VDDIO3SV (1u << 9u) + +/* USART1 PE5 (TX) / PE6 (RX), AF7 */ +#define USART1_BASE 0x52001000UL +#define USART1_CR1 (*(volatile uint32_t *)(USART1_BASE + 0x00u)) +#define USART1_CR2 (*(volatile uint32_t *)(USART1_BASE + 0x04u)) +#define USART1_CR3 (*(volatile uint32_t *)(USART1_BASE + 0x08u)) +#define USART1_BRR (*(volatile uint32_t *)(USART1_BASE + 0x0Cu)) +#define USART1_ISR (*(volatile uint32_t *)(USART1_BASE + 0x1Cu)) +#define USART1_TDR (*(volatile uint32_t *)(USART1_BASE + 0x28u)) + +/* SCB Cortex-M55 cache control */ +#define SCB_CCR (*(volatile uint32_t *)(0xE000ED14UL)) +#define SCB_CCR_IC (1u << 17u) +#define SCB_CCR_DC (1u << 16u) +#define SCB_ICIALLU (*(volatile uint32_t *)(0xE000EF50UL)) + +/* MPU ARMv8.1-M Memory Protection Unit */ +#define MPU_TYPE (*(volatile uint32_t *)(0xE000ED90UL)) +#define MPU_CTRL (*(volatile uint32_t *)(0xE000ED94UL)) +#define MPU_RNR (*(volatile uint32_t *)(0xE000ED98UL)) +#define MPU_RBAR (*(volatile uint32_t *)(0xE000ED9CUL)) +#define MPU_RLAR (*(volatile uint32_t *)(0xE000EDA0UL)) +#define MPU_MAIR0 (*(volatile uint32_t *)(0xE000EDC0UL)) +#define MPU_MAIR1 (*(volatile uint32_t *)(0xE000EDC4UL)) + +/* Fault registers */ +#define SCB_HFSR (*(volatile uint32_t *)0xE000ED2CUL) +#define SCB_CFSR (*(volatile uint32_t *)0xE000ED28UL) +#define SCB_BFAR (*(volatile uint32_t *)0xE000ED38UL) +#define SCB_MMFAR (*(volatile uint32_t *)0xE000ED34UL) + +/* Barrier macros */ +#define DSB() __asm volatile ("dsb sy" ::: "memory") +#define ISB() __asm volatile ("isb sy" ::: "memory") +#define DMB() __asm volatile ("dmb sy" ::: "memory") + +/* LED — LD1 green on PO1 */ +#define LED1_PORT GPIOO_BASE +#define LED1_PIN 1u +#define LED1_RCC_BIT 14u /* RCC_AHB4ENR bit for GPIOO */ + +#define ECHO_PORT 7 +#define RX_BUF_SIZE 1024 + +static struct wolfIP *IPStack; +static int listen_fd = -1; +static int client_fd = -1; +static uint8_t rx_buf[RX_BUF_SIZE]; + +#ifdef DEBUG_FAULT +/* HardFault Handler use only prints if uart_ready (avoids double-fault on + * unclocked peripherals which would cause CPU LOCKUP). */ +static volatile int uart_ready = 0; + +#define FAULT_USART1_ISR (*(volatile uint32_t *)(USART1_BASE + 0x1Cu)) +#define FAULT_USART1_TDR (*(volatile uint32_t *)(USART1_BASE + 0x28u)) + +static void fault_uart_putc(char c) +{ + while ((FAULT_USART1_ISR & (1u << 7)) == 0) { } + FAULT_USART1_TDR = (uint32_t)c; +} + +static void fault_uart_puts(const char *s) +{ + while (*s) { + if (*s == '\n') fault_uart_putc('\r'); + fault_uart_putc(*s++); + } +} + +static void fault_uart_puthex(uint32_t val) +{ + const char hex[] = "0123456789ABCDEF"; + int i; + fault_uart_puts("0x"); + for (i = 28; i >= 0; i -= 4) + fault_uart_putc(hex[(val >> i) & 0xF]); +} + +void hard_fault_handler_c(uint32_t *frame) +{ + if (uart_ready) { + fault_uart_puts("\n\n*** HARD FAULT ***\n"); + fault_uart_puts(" PC: "); fault_uart_puthex(frame[6]); fault_uart_puts("\n"); + fault_uart_puts(" LR: "); fault_uart_puthex(frame[5]); fault_uart_puts("\n"); + fault_uart_puts(" R0: "); fault_uart_puthex(frame[0]); fault_uart_puts("\n"); + fault_uart_puts(" R1: "); fault_uart_puthex(frame[1]); fault_uart_puts("\n"); + fault_uart_puts(" R2: "); fault_uart_puthex(frame[2]); fault_uart_puts("\n"); + fault_uart_puts(" R3: "); fault_uart_puthex(frame[3]); fault_uart_puts("\n"); + fault_uart_puts(" R12: "); fault_uart_puthex(frame[4]); fault_uart_puts("\n"); + fault_uart_puts(" xPSR: "); fault_uart_puthex(frame[7]); fault_uart_puts("\n"); + fault_uart_puts(" HFSR: "); fault_uart_puthex(SCB_HFSR); fault_uart_puts("\n"); + fault_uart_puts(" CFSR: "); fault_uart_puthex(SCB_CFSR); fault_uart_puts("\n"); + if (SCB_CFSR & 0x00008200u) { + fault_uart_puts(" BFAR: "); fault_uart_puthex(SCB_BFAR); fault_uart_puts("\n"); + } + if (SCB_CFSR & 0x00000082u) { + fault_uart_puts(" MMFAR:"); fault_uart_puthex(SCB_MMFAR); fault_uart_puts("\n"); + } + } + while (1) { } +} + +void HardFault_Handler(void) __attribute__((naked)); +void HardFault_Handler(void) +{ + __asm volatile( + "tst lr, #4 \n" + "ite eq \n" + "mrseq r0, msp \n" + "mrsne r0, psp \n" + "b hard_fault_handler_c \n" + ); +} +#endif /* DEBUG_FAULT */ + +/* Clock: HSI 64MHz -> PLL1 (M=4,N=75) -> 1200MHz VCO + * IC1/2=600MHz CPU, IC2/3=400MHz AXI, IC6/4=300MHz, IC11/3=400MHz + * AHB/2 -> HCLK 300MHz */ +static void clock_config(void) +{ + uint32_t reg; + + /* Enable HSI 64 MHz */ + RCC_CSR = RCC_CR_HSION; + while (!(RCC_SR & RCC_SR_HSIRDY)) + ; + + /* Disable PLL1 before reconfiguring */ + RCC_CCR = RCC_CR_PLL1ON; + while (RCC_SR & RCC_SR_PLL1RDY) + ; + + /* PLL1: HSI / 4 * 75 = 1200 MHz VCO. + * Clear BYP for Boot ROM leaves it set. */ + reg = RCC_PLL1CFGR1; + reg &= ~(RCC_PLL1CFGR1_SEL_MASK | RCC_PLL1CFGR1_DIVM_MASK | + RCC_PLL1CFGR1_DIVN_MASK | RCC_PLL1CFGR1_BYP); + reg |= RCC_PLL1CFGR1_SEL_HSI | + (4u << RCC_PLL1CFGR1_DIVM_SHIFT) | + (75u << RCC_PLL1CFGR1_DIVN_SHIFT); + RCC_PLL1CFGR1 = reg; + + RCC_PLL1CFGR2 = 0; /* no fractional */ + + /* PDIV1=1, PDIV2=1 -> PLL output = VCO = 1200 MHz */ + RCC_PLL1CFGR3 = (1u << RCC_PLL1CFGR3_PDIV1_SHIFT) | + (1u << RCC_PLL1CFGR3_PDIV2_SHIFT) | + RCC_PLL1CFGR3_MODSSDIS | + RCC_PLL1CFGR3_MODSSRST | + RCC_PLL1CFGR3_PDIVEN; + + /* Enable PLL1, wait for lock */ + RCC_CSR = RCC_CR_PLL1ON; + while (!(RCC_SR & RCC_SR_PLL1RDY)) + ; + + /* Configure IC dividers: disable -> configure -> re-enable */ + RCC_DIVENCR = RCC_DIVENR_IC1EN; + RCC_IC1CFGR = RCC_ICCFGR_SEL_PLL1 | ((2u - 1u) << RCC_ICCFGR_INT_SHIFT); + RCC_DIVENSR = RCC_DIVENR_IC1EN; + + RCC_DIVENCR = RCC_DIVENR_IC2EN; + RCC_IC2CFGR = RCC_ICCFGR_SEL_PLL1 | ((3u - 1u) << RCC_ICCFGR_INT_SHIFT); + RCC_DIVENSR = RCC_DIVENR_IC2EN; + + RCC_DIVENCR = RCC_DIVENR_IC6EN; + RCC_IC6CFGR = RCC_ICCFGR_SEL_PLL1 | ((4u - 1u) << RCC_ICCFGR_INT_SHIFT); + RCC_DIVENSR = RCC_DIVENR_IC6EN; + + RCC_DIVENCR = RCC_DIVENR_IC11EN; + RCC_IC11CFGR = RCC_ICCFGR_SEL_PLL1 | ((3u - 1u) << RCC_ICCFGR_INT_SHIFT); + RCC_DIVENSR = RCC_DIVENR_IC11EN; + + /* AHB prescaler /2 -> HCLK = 300 MHz */ + reg = RCC_CFGR2; + reg &= ~RCC_CFGR2_HPRE_MASK; + reg |= (1u << RCC_CFGR2_HPRE_SHIFT); + RCC_CFGR2 = reg; + + /* Switch CPU to IC1 system bus to IC2/IC6/IC11 */ + reg = RCC_CFGR1; + reg &= ~(RCC_CFGR1_CPUSW_MASK | RCC_CFGR1_SYSSW_MASK); + reg |= (0x3u << RCC_CFGR1_CPUSW_SHIFT) | + (0x3u << RCC_CFGR1_SYSSW_SHIFT); + RCC_CFGR1 = reg; + while ((RCC_CFGR1 & RCC_CFGR1_CPUSWS_MASK) != + (0x3u << RCC_CFGR1_CPUSWS_SHIFT)) + ; + while ((RCC_CFGR1 & RCC_CFGR1_SYSSWS_MASK) != + (0x3u << RCC_CFGR1_SYSSWS_SHIFT)) + ; +} + +/* For power mark VDDIO supplies valid */ +static void pwr_enable_io_supply(void) +{ + /* Enable PWR peripheral clock */ + RCC_AHB4ENSR = (1u << 18u); /* PWREN */ + DMB(); + PWR_SVMCR1 |= PWR_SVMCR1_VDDIO4SV; + PWR_SVMCR2 |= PWR_SVMCR2_VDDIO5SV; + PWR_SVMCR3 |= (1u << 4u) | PWR_SVMCR3_VDDIO2SV | PWR_SVMCR3_VDDIO3SV | + (1u << 12u) | (1u << 20u); /* Match CubeN6: 0x00101310 */ + DMB(); +} + +/* Cache */ +static void icache_enable(void) +{ + DSB(); ISB(); + SCB_ICIALLU = 0; + DSB(); ISB(); + SCB_CCR |= SCB_CCR_IC; + DSB(); ISB(); +} + +static void dcache_enable(void) +{ + DSB(); + SCB_CCR |= SCB_CCR_DC; + DSB(); ISB(); +} + +/* MPU for mark ETH DMA buffers (AXISRAM2) as non-cacheable */ +extern uint32_t _eth_start; +extern uint32_t _eth_end; + +static void mpu_configure_eth_nocache(void) +{ + uint32_t base = (uint32_t)&_eth_start & ~0x1Fu; /* Align down to 32 */ + uint32_t limit = ((uint32_t)&_eth_end + 0x1Fu) & ~0x1Fu; /* Align up */ + + /* Disable MPU */ + MPU_CTRL = 0; + DSB(); + + /* Region 0: ETH DMA descriptors + buffers, Normal Non-cacheable. */ + MPU_RNR = 0; + MPU_RBAR = base | (1u << 1u) | (1u << 0u); /* AP=RW, XN=1 */ + MPU_RLAR = ((limit - 1u) & ~0x1Fu) | (2u << 1u) | 1u; /* AttrIdx=2, EN=1 */ + + /* MAIR0: Attr2 (bits [23:16]) = 0x44 -> Normal, Non-cacheable */ + MPU_MAIR0 = (0x44u << 16u); + + /* Enable MPU + PRIVDEFENA (default map for other regions) */ + MPU_CTRL = 5u; + DSB(); ISB(); +} + +static void delay(uint32_t count) +{ + volatile uint32_t i; + for (i = 0; i < count; i++) { } +} + +static void led_init(void) +{ + uint32_t moder; + + /* Enable GPIOO clock */ + RCC_AHB4ENSR = (1u << LED1_RCC_BIT); + delay(100); + + /* Set PO1 as output */ + moder = GPIO_MODER(LED1_PORT); + moder &= ~(3u << (LED1_PIN * 2u)); + moder |= (1u << (LED1_PIN * 2u)); + GPIO_MODER(LED1_PORT) = moder; +} + +static void led_on(void) +{ + GPIO_BSRR(LED1_PORT) = (1u << LED1_PIN); +} + +static void led_toggle(void) +{ + GPIO_ODR(LED1_PORT) ^= (1u << LED1_PIN); +} + +/* UART USART1 on PE5/PE6 (AF7), 115200 baud via IC9 64MHz kernel clock */ +static void uart_init(void) +{ + uint32_t moder, afr; + + /* Enable GPIOE + USART1 clocks */ + RCC_AHB4ENSR = (1u << 4u); /* GPIOEEN */ + RCC_APB2ENSR = (1u << 4u); /* USART1EN */ + delay(100); + + /* PE5 + PE6 -> AF mode */ + moder = GPIO_MODER(GPIOE_BASE); + moder &= ~((3u << (5u * 2u)) | (3u << (6u * 2u))); + moder |= (2u << (5u * 2u)) | (2u << (6u * 2u)); + GPIO_MODER(GPIOE_BASE) = moder; + + /* High speed */ + GPIO_OSPEEDR(GPIOE_BASE) |= (3u << (5u * 2u)) | (3u << (6u * 2u)); + + /* AF7 for PE5 and PE6 (both in AFRL since pins < 8) */ + afr = GPIO_AFRL(GPIOE_BASE); + afr &= ~((0xFu << (5u * 4u)) | (0xFu << (6u * 4u))); + afr |= (7u << (5u * 4u)) | (7u << (6u * 4u)); + GPIO_AFRL(GPIOE_BASE) = afr; + + /* Configure USART1: 115200 baud using IC9 kernel clock (64 MHz). + * BRR = 64000000 / 115200 = 556 (0x22C), matching CubeN6. */ + USART1_CR1 = 0; + USART1_CR2 = 0; + USART1_CR3 = 0; + USART1_BRR = 64000000u / 115200u; /* 556 */ + DSB(); + delay(1000); + USART1_CR1 = (1u << 0u) | (1u << 2u) | (1u << 3u); /* UE + RE + TE */ + delay(1000); +} + +static void uart_putc(char c) +{ + volatile uint32_t timeout = 100000u; + while ((USART1_ISR & (1u << 7u)) == 0 && --timeout) { } + if (timeout == 0) return; /* USART kernel clock not running */ + USART1_TDR = (uint32_t)c; +} + +static void uart_puts(const char *s) +{ + while (*s) { + if (*s == '\n') uart_putc('\r'); + uart_putc(*s++); + } +} + +static void uart_puthex(uint32_t val) +{ + const char hex[] = "0123456789ABCDEF"; + int i; + uart_puts("0x"); + for (i = 28; i >= 0; i -= 4) { + uart_putc(hex[(val >> i) & 0xF]); + } +} + +static void uart_puthex8(uint8_t val) +{ + const char hex[] = "0123456789ABCDEF"; + uart_putc(hex[(val >> 4) & 0xF]); + uart_putc(hex[val & 0xF]); +} + +static void uart_putdec(uint32_t val) +{ + char buf[12]; + int i = 0; + if (val == 0) { + uart_putc('0'); + return; + } + while (val > 0 && i < 11) { + buf[i++] = '0' + (val % 10); + val /= 10; + } + while (i > 0) { + uart_putc(buf[--i]); + } +} + +static void uart_putip4(ip4 ip) +{ + uart_putdec((ip >> 24) & 0xFF); + uart_putc('.'); + uart_putdec((ip >> 16) & 0xFF); + uart_putc('.'); + uart_putdec((ip >> 8) & 0xFF); + uart_putc('.'); + uart_putdec(ip & 0xFF); +} + +/* STM32N6 hardware RNG (TRNG) */ +#define RNG_BASE_ADDR 0x54020000UL /* AHB3 offset 0x0000 (secure) */ +#define RNG_CR (*(volatile uint32_t *)(RNG_BASE_ADDR + 0x00u)) +#define RNG_SR (*(volatile uint32_t *)(RNG_BASE_ADDR + 0x04u)) +#define RNG_DR (*(volatile uint32_t *)(RNG_BASE_ADDR + 0x08u)) + +static int rng_initialized = 0; + +static void rng_init(void) +{ + RCC_AHB3ENSR = (1u << 0u); /* RNGEN clock */ + DSB(); + delay(100); + RNG_CR = (1u << 2u); /* RNGEN=1 */ + rng_initialized = 1; +} + +uint32_t wolfIP_getrandom(void) +{ + uint32_t timeout = 100000u; + if (!rng_initialized) + rng_init(); + while (!(RNG_SR & 1u) && --timeout) { } /* Wait for DRDY */ + if (timeout == 0) + return 0; + return RNG_DR; +} + +/* SYSCFG I/O compensation for VDDIO3 (Ethernet GPIOF pins) */ +#define SYSCFG_BASE 0x56008000UL +#define SYSCFG_VDDIO3CCCR (*(volatile uint32_t *)(SYSCFG_BASE + 0x5Cu)) +#define SYSCFG_VDDIO3CCSR (*(volatile uint32_t *)(SYSCFG_BASE + 0x60u)) + +/* RCC clock for SYSCFG APB4ENR2 at offset 0x278, bit 0 = SYSCFGEN */ +#define RCC_APB4ENR2 (*(volatile uint32_t *)(RCC_BASE + 0x278u)) +#define RCC_APB4ENSR2 (*(volatile uint32_t *)(RCC_BASE + 0xA78u)) + +/* Ethernet GPIO RMII pins (AF11) on GPIOF, MDC on PG11 */ +static void gpio_eth_pin(uint32_t base, uint32_t pin) +{ + uint32_t moder, ospeedr, afr; + uint32_t pos2 = pin * 2u; + + /* Alternate function mode (0b10) */ + moder = GPIO_MODER(base); + moder &= ~(3u << pos2); + moder |= (2u << pos2); + GPIO_MODER(base) = moder; + + /* High speed (0b10) match CubeN6. Very High (0b11) + * causes issues on N6. */ + ospeedr = GPIO_OSPEEDR(base); + ospeedr &= ~(3u << pos2); + ospeedr |= (2u << pos2); + GPIO_OSPEEDR(base) = ospeedr; + + /* AF11 for Ethernet */ + if (pin < 8u) { + afr = GPIO_AFRL(base); + afr &= ~(0xFu << (pin * 4u)); + afr |= (11u << (pin * 4u)); + GPIO_AFRL(base) = afr; + } else { + afr = GPIO_AFRH(base); + afr &= ~(0xFu << ((pin - 8u) * 4u)); + afr |= (11u << ((pin - 8u) * 4u)); + GPIO_AFRH(base) = afr; + } +} + +static void eth_gpio_init(void) +{ + volatile uint32_t *ramcfg_sram2_cr = (volatile uint32_t *)0x52023080u; + volatile uint32_t *rimc_attr6 = (volatile uint32_t *)(0x54024000u + 0xC28u); + + /* Enable GPIO clocks: F and G */ + RCC_AHB4ENSR = (1u << 5u) | (1u << 6u); /* GPIOFEN + GPIOGEN */ + delay(1000); + + /* Power on AXISRAM2 for ETH DMA buffers. + * AXISRAM2-6 are powered down by default after boot ROM. */ + RCC_AHB2ENSR = (1u << 12u); /* RAMCFGEN */ + RCC_MEMENSR = (1u << 1u); /* AXISRAM2EN */ + delay(100); + *ramcfg_sram2_cr &= ~(1u << 20u); /* Clear SRAMSD -> power on */ + DSB(); + delay(10000); + + /* RISAF3 (AXISRAM2): extend base region to full 1MB, all CIDs R+W, SEC=1 */ + *(volatile uint32_t *)0x54028044u = 0x00000000u; /* STARTR = 0 */ + *(volatile uint32_t *)0x54028048u = 0x000FFFFFu; /* ENDR = 1MB-1 */ + *(volatile uint32_t *)0x5402804Cu = 0x000F000Fu; /* CIDCFGR: CID0-3 R+W */ + *(volatile uint32_t *)0x54028040u = 0x00000101u; /* CFGR: BREN=1, SEC=1 */ + DSB(); + + /* RIFSC: ETH1 DMA as CID=1/SEC/PRIV, ETH1 peripheral as SEC+PRIV */ + RCC_AHB3ENSR = (1u << 9u); /* RIFSCEN */ + delay(100); + *rimc_attr6 = 0x1u | (1u << 8u) | (1u << 9u); /* CID=1, SEC, PRIV */ + *(volatile uint32_t *)(0x54024000u + 0x14u) |= (1u << 28u); /* ETH1 SEC */ + *(volatile uint32_t *)(0x54024000u + 0x34u) |= (1u << 28u); /* ETH1 PRIV */ + DSB(); + uart_puts(" RIMC_ATTR6 (ETH1): "); + uart_puthex(*rimc_attr6); + uart_puts("\n"); + + /* VDDIO3 compensation cells per Errata ES0620 */ + RCC_APB4ENSR2 = (1u << 0u); /* SYSCFGEN */ + delay(100); + SYSCFG_VDDIO3CCCR = 0x00000287u; + DSB(); + + /* Configure RMII pins (AF11) NUCLEO-N657X0-Q pinout */ + gpio_eth_pin(GPIOF_BASE, 4); /* MDIO */ + gpio_eth_pin(GPIOG_BASE, 11); /* MDC */ + gpio_eth_pin(GPIOF_BASE, 7); /* REF_CLK */ + gpio_eth_pin(GPIOF_BASE, 10); /* CRS_DV */ + gpio_eth_pin(GPIOF_BASE, 11); /* TX_EN */ + gpio_eth_pin(GPIOF_BASE, 12); /* TXD0 */ + gpio_eth_pin(GPIOF_BASE, 13); /* TXD1 */ + gpio_eth_pin(GPIOF_BASE, 14); /* RXD0 */ + gpio_eth_pin(GPIOF_BASE, 15); /* RXD1 */ +} + +/* TCP echo callback */ +static void echo_cb(int fd, uint16_t event, void *arg) +{ + struct wolfIP *s = (struct wolfIP *)arg; + int ret; + + if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { + client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd > 0) { + wolfIP_register_callback(s, client_fd, echo_cb, s); + } + return; + } + + if ((fd == client_fd) && (event & CB_EVENT_READABLE)) { + ret = wolfIP_sock_recvfrom(s, client_fd, rx_buf, sizeof(rx_buf), 0, NULL, NULL); + if (ret > 0) { + (void)wolfIP_sock_sendto(s, client_fd, rx_buf, (uint32_t)ret, 0, NULL, 0); + } else if (ret == 0) { + wolfIP_sock_close(s, client_fd); + client_fd = -1; + } + } + + if ((fd == client_fd) && (event & CB_EVENT_CLOSED)) { + wolfIP_sock_close(s, client_fd); + client_fd = -1; + } +} + +int main(void) +{ + struct wolfIP_ll_dev *ll; + struct wolfIP_sockaddr_in addr; + uint64_t tick = 0; + int ret; + volatile uint32_t *sau_ctrl = (volatile uint32_t *)0xE000EDD0u; + volatile uint32_t *sau_rnr = (volatile uint32_t *)0xE000EDD8u; + volatile uint32_t *sau_rbar = (volatile uint32_t *)0xE000EDDCu; + volatile uint32_t *sau_rlar = (volatile uint32_t *)0xE000EDE0u; + volatile uint32_t *ic9cfgr = (volatile uint32_t *)(RCC_BASE + 0xE4u); + volatile uint32_t *ccipr13 = (volatile uint32_t *)(RCC_BASE + 0x174u); + ip4 ip, nm, gw; + int i; +#ifdef DEBUG_BLINK + int blink; +#endif + + /* SAU ALLNS=1: all memory Non-Secure (required for ETH DMA access). + * Note: disables TrustZone isolation. Production builds with TZEN=1 + * should configure SAU regions to restrict DMA to ETH buffer memory only. */ + *sau_ctrl = 0; + DSB(); + for (i = 0; i < 8; i++) { + *sau_rnr = (uint32_t)i; + *sau_rbar = 0; + *sau_rlar = 0; + } + *sau_ctrl = 2u; /* ALLNS=1, ENABLE=0 -> all memory Non-Secure */ + DSB(); ISB(); + + pwr_enable_io_supply(); + + led_init(); + led_on(); + + /* PLL + IC dividers must be up before UART boot ROM leaves IC dividers + * disabled, so APB2 has no clock source and USART1 kernel clock is dead. */ + clock_config(); + +#ifdef DEBUG_BLINK + /* Blink LED 3x fast to indicate PLL locked */ + for (blink = 0; blink < 3; blink++) { + led_toggle(); + delay(500000); + led_toggle(); + delay(500000); + } +#endif + + /* Enable IC9 divider (USART1 kernel clock source on N6). + * CubeN6 uses IC9CFGR=0x30000000 (SEL=3=HSI, INT=0=div1) -> 64 MHz. + * Then CCIPR13 USART1SEL=2 selects ic9_ck as USART1 kernel clock. */ + RCC_DIVENCR = (1u << 8u); /* IC9 disable */ + *ic9cfgr = 0x30000000u; /* SEL=3 (HSI), INT=0 (div 1) -> 64 MHz */ + RCC_DIVENSR = (1u << 8u); /* IC9 enable */ + DSB(); + *ccipr13 = (*ccipr13 & ~0x7u) | 0x2u; /* USART1SEL = ic9_ck */ + DSB(); + + uart_init(); +#ifdef DEBUG_FAULT + uart_ready = 1; +#endif + + icache_enable(); + mpu_configure_eth_nocache(); + dcache_enable(); + + /* Initialize wolfIP stack */ + wolfIP_init_static(&IPStack); + + /* ETH init sequence matching CubeN6 HAL MspInit order: + * 1. Enable ETH clocks (AHB5) + * 2. Set RMII mode (CCIPR2) + * 3. Configure GPIO + RIMC + compensation + * CubeN6 HAL: MspInit enables clocks -> HAL_ETH_Ini + * sets CCIPR2 -> SWR -> config */ + + /* Step 1: Enable Ethernet clocks */ + RCC_AHB5ENSR = (1u << 22u) | (1u << 23u) | (1u << 24u); + DSB(); + delay(10000); + + /* Step 1b: RCC peripheral reset of ETH1 — deeper than SWR */ + RCC_AHB5RSTR = (1u << 22u); /* Assert ETH1MAC reset */ + DSB(); + delay(10000); + RCC_AHB5RSTR = 0; /* Release reset */ + DSB(); + delay(10000); + + /* Step 2: Select RMII mode (external REF_CLK from PHY). + * RCC_CCIPR2 fields (from Zephyr stm32n6_clock.h): + * ETH1PTP_SEL [1:0] = 0 (default) + * ETH1CLK_SEL [13:12]= 0 (default, bus clock) + * ETH1_SEL [18:16]= 4 (RMII mode) + * ETH1REFCLK_SEL [20] = 0 (external REF_CLK from PHY) + * ETH1GTXCLK_SEL [24] = 0 (default) */ + RCC_CCIPR2 |= RCC_CCIPR2_ETH1SEL_RMII; + DSB(); + delay(10000); + + /* Step 3: GPIO and peripheral setup (AFTER clocks + RMII selection) */ + eth_gpio_init(); + delay(100000); /* Wait for PHY REF_CLK */ + + /* Initialize Ethernet MAC + PHY */ + uart_puts("Initializing Ethernet MAC...\n"); + ll = wolfIP_getdev(IPStack); + ret = stm32_eth_init(ll, NULL); + if (ret < 0) { + uart_puts(" ERROR: stm32_eth_init failed ("); + uart_puthex((uint32_t)ret); + uart_puts(")\n"); + } else { + uart_puts(" PHY link: "); + uart_puts((ret & 0x100) ? "UP" : "DOWN"); + uart_puts(", PHY addr: "); + uart_puthex(ret & 0xFF); + uart_puts("\n MAC: "); + { + int mi; + for (mi = 0; mi < 6; mi++) { + if (mi > 0) uart_puts(":"); + uart_puthex8(ll->mac[mi]); + } + } + uart_puts("\n"); + } + + /* Static IP configuration */ + ip = atoip4(WOLFIP_IP); + nm = atoip4(WOLFIP_NETMASK); + gw = atoip4(WOLFIP_GW); + uart_puts("Setting IP configuration:\n"); + uart_puts(" IP: "); + uart_putip4(ip); + uart_puts("\n Mask: "); + uart_putip4(nm); + uart_puts("\n GW: "); + uart_putip4(gw); + uart_puts("\n"); + wolfIP_ipconfig_set(IPStack, ip, nm, gw); + + /* TCP echo server on port 7 */ + uart_puts("Creating TCP socket on port 7...\n"); + listen_fd = wolfIP_sock_socket(IPStack, AF_INET, IPSTACK_SOCK_STREAM, 0); + wolfIP_register_callback(IPStack, listen_fd, echo_cb, IPStack); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = ee16(ECHO_PORT); + addr.sin_addr.s_addr = 0; + (void)wolfIP_sock_bind(IPStack, listen_fd, (struct wolfIP_sockaddr *)&addr, + sizeof(addr)); + (void)wolfIP_sock_listen(IPStack, listen_fd, 1); + + uart_puts("Entering main loop. Ready for connections!\n"); + uart_puts(" TCP Echo: port 7\n"); + + for (;;) { + (void)wolfIP_poll(IPStack, tick++); + + /* Toggle LED every ~256K iterations as heartbeat */ + if ((tick & 0x3FFFFu) == 0) { + led_toggle(); + } + } + return 0; +} diff --git a/src/port/stm32n6/openocd.cfg b/src/port/stm32n6/openocd.cfg new file mode 100644 index 00000000..c68117da --- /dev/null +++ b/src/port/stm32n6/openocd.cfg @@ -0,0 +1,5 @@ +source [find interface/stlink.cfg] +transport select swd +set CHIPNAME stm32n6x +source [find target/stm32n6x.cfg] +init diff --git a/src/port/stm32n6/startup.c b/src/port/stm32n6/startup.c new file mode 100644 index 00000000..53cecea1 --- /dev/null +++ b/src/port/stm32n6/startup.c @@ -0,0 +1,47 @@ +/* startup.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#include + +extern uint32_t _sidata; +extern uint32_t _sdata; +extern uint32_t _edata; +extern uint32_t _sbss; +extern uint32_t _ebss; +extern void __libc_init_array(void); + +int main(void); + +void Reset_Handler(void) +{ + uint32_t *src; + uint32_t *dst; + + src = &_sidata; + for (dst = &_sdata; dst < &_edata; ) { + *dst++ = *src++; + } + for (dst = &_sbss; dst < &_ebss; ) { + *dst++ = 0u; + } + __libc_init_array(); + (void)main(); + while (1) { } +} diff --git a/src/port/stm32n6/syscalls.c b/src/port/stm32n6/syscalls.c new file mode 100644 index 00000000..5d0f1e29 --- /dev/null +++ b/src/port/stm32n6/syscalls.c @@ -0,0 +1,141 @@ +/* syscalls.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#include +#include +#include +#include +#include +#include + +extern uint32_t _ebss; +extern uint32_t _heap_limit; + +static char *heap_end; + +int _write(int file, const char *ptr, int len) +{ + (void)file; + (void)ptr; + return len; +} + +int _close(int file) +{ + (void)file; + return -1; +} + +int _fstat(int file, struct stat *st) +{ + (void)file; + if (st == 0) { + errno = EINVAL; + return -1; + } + st->st_mode = S_IFCHR; + return 0; +} + +int _isatty(int file) +{ + (void)file; + return 1; +} + +int _lseek(int file, int ptr, int dir) +{ + (void)file; + (void)ptr; + (void)dir; + return 0; +} + +int _read(int file, char *ptr, int len) +{ + (void)file; + (void)ptr; + (void)len; + return 0; +} + +void *_sbrk(ptrdiff_t incr) +{ + char *prev; + if (heap_end == 0) { + heap_end = (char *)&_ebss; + } + prev = heap_end; + if ((heap_end + incr) >= (char *)&_heap_limit) { + errno = ENOMEM; + return (void *)-1; + } + heap_end += incr; + return prev; +} + +int _gettimeofday(struct timeval *tv, void *tzvp) +{ + (void)tzvp; + if (tv == 0) { + errno = EINVAL; + return -1; + } + tv->tv_sec = 0; + tv->tv_usec = 0; + return 0; +} + +time_t time(time_t *t) +{ + if (t != 0) { + *t = 0; + } + return 0; +} + +void _exit(int status) +{ + (void)status; + while (1) { + __asm volatile("wfi"); + } +} + +int _kill(int pid, int sig) +{ + (void)pid; + (void)sig; + errno = EINVAL; + return -1; +} + +int _getpid(void) +{ + return 1; +} + +void _init(void) +{ +} + +void _fini(void) +{ +} diff --git a/src/port/stm32n6/target.ld b/src/port/stm32n6/target.ld new file mode 100644 index 00000000..53531ad9 --- /dev/null +++ b/src/port/stm32n6/target.ld @@ -0,0 +1,106 @@ +/* STM32N6 Linker Script — AXISRAM execution via JTAG + * + * Memory Map (all execution from SRAM, no flash): + * AXISRAM1: 512K @ 0x34000000 — code + rodata + data + bss + heap + stack + * + * Note: Only AXISRAM1 is guaranteed accessible after boot ROM. + * Other regions (AXISRAM2-6) may need explicit RCC enable. + * ETH DMA buffers are placed in AXISRAM2 at 0x341F8000, + * with MPU marking them non-cacheable. + */ + +MEMORY +{ + SRAM (rwx) : ORIGIN = 0x34000000, LENGTH = 512K + AXISRAM2 (rwx) : ORIGIN = 0x341F8000, LENGTH = 32K +} + +_estack = ORIGIN(SRAM) + LENGTH(SRAM); +_sidata = LOADADDR(.data); + +/* Heap and stack sizes */ +_Min_Heap_Size = 0x4000; /* 16KB heap */ +_Min_Stack_Size = 0x4000; /* 16KB stack */ +_heap_limit = _estack - _Min_Stack_Size; + +SECTIONS +{ + .isr_vector : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) + . = ALIGN(4); + } > SRAM + + .text : + { + . = ALIGN(4); + *(.text*) + *(.rodata*) + *(.ARM.extab* .gnu.linkonce.armextab.*) + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + *(.glue_7) + *(.glue_7t) + *(.eh_frame) + . = ALIGN(4); + } > SRAM + + .preinit_array : + { + __preinit_array_start = .; + KEEP(*(.preinit_array*)) + __preinit_array_end = .; + } > SRAM + + .init_array : + { + __init_array_start = .; + KEEP(*(.init_array*)) + __init_array_end = .; + } > SRAM + + .fini_array : + { + __fini_array_start = .; + KEEP(*(.fini_array*)) + __fini_array_end = .; + } > SRAM + + .data : + { + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; + } > SRAM + + .bss (NOLOAD) : + { + . = ALIGN(4); + _sbss = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + } > SRAM + + .eth_buffers (NOLOAD) : + { + . = ALIGN(32); + _eth_start = .; + *(.eth_buffers*) + . = ALIGN(32); + _eth_end = .; + } > AXISRAM2 + + ._user_heap_stack (NOLOAD) : + { + . = ALIGN(8); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + . = . + _Min_Heap_Size; + . = . + _Min_Stack_Size; + . = ALIGN(8); + } > SRAM +}