diff --git a/src/port/nxp_enetc/README.md b/src/port/nxp_enetc/README.md new file mode 100644 index 00000000..e5108130 --- /dev/null +++ b/src/port/nxp_enetc/README.md @@ -0,0 +1,50 @@ +# wolfIP NXP ENETC port + +Ethernet driver for the NXP ENETC integrated-endpoint MAC found on Layerscape parts (lead target: LS1028A, Cortex-A72, AArch64, little-endian). ENETC appears as PCI functions on an internal ECAM root complex (bus 0); this driver discovers the port over ECAM, runs one RX and one TX buffer-descriptor ring, brings up the MAC + PHY, and exposes the wolfIP poll/send callbacks. + +## Model + +The board (DDR, clocks, MMU, UART, PCIe/ECAM) is brought up by the boot stage (wolfBoot). This driver runs in the booted application: it enumerates the ENETC port and the shared MDIO controller over ECAM, programs the station-interface (SI) and port registers, sets up the BD rings in memory, and polls them. There is no interrupt path. + +It is consumed by the wolfBoot test-app (which links wolfIP core + this driver + the TFTP client). `nxp_enetc.c` is the reusable driver; board parameters live in `nxp_enetc_board.h`. + +## Files + +- `nxp_enetc.h` - public API + PCI/ECAM, SI, port, BD-ring and MDIO register map. +- `nxp_enetc.c` - driver: ECAM config access + BAR setup, MDIO clause-22, PHY detect/reset/autoneg, SGMII PCS / RGMII interface select, BD-ring datapath, wolfIP poll/send callbacks. +- `nxp_enetc_board.h` - board parameters (ECAM base, port/MDIO function index, PHY address, interface mode). All overridable from CFLAGS. +- `config.h` - wolfIP compile-time configuration for this port. + +## Board parameters + +All overridable from CFLAGS. Defaults target the LS1028A-RDB (port 0, external SGMII PHY at MDIO address 0x2 reached through the shared MDIO PF). `nxp_enetc_init()` scans the MDIO bus, so a board with the PHY elsewhere is still detected. + +| Define | LS1028A value | Notes | +|---|---|---| +| `NXP_ENETC_ECAM_BASE` | `0x01F0000000` | ECAM config space (bus 0) | +| `NXP_ENETC_IERB_BASE` | `0x01F0800000` | IERB (persistent MAC address) | +| `NXP_ENETC_PORT_FN` | `0` | ECAM function for the ENETC port (fn0 = port0 SGMII) | +| `NXP_ENETC_MDIO_FN` | `3` | ECAM function for the shared MDIO controller | +| `NXP_ENETC_PHY_ADDR` | `0x2` | external PHY on port0 (probed first, then bus scan) | +| `NXP_ENETC_IF_SGMII` | `1` | `1` = SGMII (configures the internal PCS), `0` = RGMII | + +On LS1028A the ENETC ports are: fn0 = port0 (external SGMII PHY on the RDB), fn1 = port1, fn2 = port2 (internal 2.5G to the Felix L2 switch), fn6 = port3, fn3 = the shared MDIO controller. Port0 is the wire-facing path with a real PHY; the internal ports need switch configuration to reach a jack. + +## Endianness and cache + +ENETC registers and buffer descriptors are little-endian, matching the AArch64 host, so register/BD access needs no byte swap. wolfBoot runs the LS1028A target with the MMU/D-cache effectively off, so DMA memory is non-cacheable and the driver needs only a `dmb` barrier between BD/buffer stores and the ring doorbell (no clean/invalidate). If the MMU is later enabled with cacheable DMA memory, add `dc civac` (TX) / `dc ivac` (RX) maintenance around the rings. + +## Status + +Build-validated (compiles clean for AArch64 LE in both SGMII and RGMII modes; BD structures verified to be exactly 16 bytes). The polled `eth_poll`/`eth_send` ring datapath (producer/consumer index, wrap, ring-full and length-clamp bookkeeping) is NOT yet exercised by any host or hardware test. Hardware bring-up on the LS1028A-RDB is pending board availability. The bring-up sequence (ECAM discovery, BAR enable, SI/port enable, ring setup, PCS/PHY) follows the U-Boot ENETC driver. Hardware-verify items are noted in the source: the per-PF BAR0 reset value (read live, fallback-assigned only if zero), whether the SI accepts the port `PSIPMAR` directly or only via the IERB (the driver writes both), and DMA address translation (assumed 1:1 with the MMU off). + +`nxp_enetc_init()` populates the `struct wolfIP_ll_dev` (mac/ifname/mtu/poll/send). `eth_poll()`/`eth_send()` move frames through the BD rings with `dmb` ordering. + +## Build + +The driver is built by its consumer (the wolfBoot test-app) with the AArch64 cross-compiler, e.g.: + +``` +aarch64-linux-gnu-gcc -I -Isrc/port/nxp_enetc \ + -c src/port/nxp_enetc/nxp_enetc.c +``` diff --git a/src/port/nxp_enetc/config.h b/src/port/nxp_enetc/config.h new file mode 100644 index 00000000..14db1e90 --- /dev/null +++ b/src/port/nxp_enetc/config.h @@ -0,0 +1,68 @@ +/* config.h + * + * wolfIP configuration for the NXP ENETC port (LS1028A / Layerscape). + * + * 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 +#define TXBUF_SIZE LINK_MTU + +#define MAX_NEIGHBORS 8 +#define WOLFIP_ARP_PENDING_MAX 2 + +#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 1 +#endif + +/* Static IP fallback (used when DHCP is disabled or times out). */ +#define WOLFIP_IP "192.168.1.10" +#define WOLFIP_NETMASK "255.255.255.0" +#define WOLFIP_GW "192.168.1.1" +#define WOLFIP_STATIC_DNS_IP "8.8.8.8" + +#if WOLFIP_ENABLE_DHCP +#define DHCP +#endif + +#endif /* WOLF_CONFIG_H */ diff --git a/src/port/nxp_enetc/nxp_enetc.c b/src/port/nxp_enetc/nxp_enetc.c new file mode 100644 index 00000000..54337eac --- /dev/null +++ b/src/port/nxp_enetc/nxp_enetc.c @@ -0,0 +1,573 @@ +/* nxp_enetc.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 + * + * NXP ENETC ethernet driver for wolfIP (LS1028A and other Layerscape parts). + * + * ENETC is a PCIe integrated endpoint on an internal ECAM root complex. The + * driver discovers the ethernet port and the shared MDIO controller over + * ECAM (bus 0), sets up one RX and one TX buffer-descriptor ring in memory, + * brings up the MAC + (SGMII PCS or RGMII) interface and the external PHY, + * and exposes the polled wolfIP poll/send callbacks. AArch64, little-endian. + * Ported from the U-Boot ENETC driver (drivers/net/fsl_enetc.c). + */ + +#include +#include +#include "config.h" +#include "nxp_enetc.h" + +/* Local byte-loop mem helpers. The driver is linked into bare-metal images + * (e.g. the wolfBoot test-app) whose startup performs no PLT/GOT fixup, so + * we avoid external libc calls. */ +static void *enetc_memcpy(void *dst, const void *src, uint32_t n) +{ + uint8_t *d = (uint8_t *)dst; + const uint8_t *s = (const uint8_t *)src; + while (n--) + *d++ = *s++; + return dst; +} +static void enetc_memset(void *dst, int c, uint32_t n) +{ + uint8_t *d = (uint8_t *)dst; + while (n--) + *d++ = (uint8_t)c; +} + +/* ---- Register access (LE, no swap on AArch64) ------------------------ */ +static inline uint32_t rd32(uintptr_t a) +{ + return *(volatile uint32_t *)a; +} +static inline void wr32(uintptr_t a, uint32_t v) +{ + *(volatile uint32_t *)a = v; +} +/* Data memory barrier: order BD/buffer stores before the doorbell write so + * the bus-master DMA sees them. wolfBoot runs the ls1028a target with the + * MMU/D-cache off, so DMA memory is non-cacheable and no clean/invalidate + * is needed -- only ordering. If the MMU is later enabled with cacheable + * DMA memory, add dc civac (TX) / dc ivac (RX) around the rings here. */ +#if defined(__aarch64__) +static inline void enetc_dmb(void) +{ + __asm__ __volatile__("dmb sy" ::: "memory"); +} +#else +static inline void enetc_dmb(void) +{ + __asm__ __volatile__("" ::: "memory"); +} +#endif + +/* Optional debug log hook (set by the application). */ +static void (*g_log)(const char *) = 0; +void nxp_enetc_set_log(void (*log_fn)(const char *)) { g_log = log_fn; } +#define ELOG(s) do { if (g_log) g_log(s); } while (0) + +/* ---- PCI ECAM config access (bus 0) ---------------------------------- */ +static uintptr_t pci_cfg_addr(uint32_t fn, uint32_t off) +{ + return (uintptr_t)((uint64_t)NXP_ENETC_ECAM_BASE + + (((uint64_t)fn & 0x7U) << 12) + (off & 0xFFFU)); +} +static uint32_t pci_cfg_rd32(uint32_t fn, uint32_t off) +{ + return rd32(pci_cfg_addr(fn, off & ~3U)); +} +static void pci_cfg_wr32(uint32_t fn, uint32_t off, uint32_t val) +{ + wr32(pci_cfg_addr(fn, off & ~3U), val); +} +static uint16_t pci_cfg_rd16(uint32_t fn, uint32_t off) +{ + uint32_t w = pci_cfg_rd32(fn, off); + return (uint16_t)((off & 2U) ? (w >> 16) : (w & 0xFFFFU)); +} + +/* Enable PCI memory space (and optionally bus-master) for a function. */ +static void pci_enable(uint32_t fn, int master) +{ + uint32_t w = pci_cfg_rd32(fn, PCI_CFG_COMMAND); + uint32_t cmd = (w & 0xFFFFU) | PCI_CMD_MEMORY; + if (master) + cmd |= PCI_CMD_MASTER; + pci_cfg_wr32(fn, PCI_CFG_COMMAND, (w & 0xFFFF0000UL) | (cmd & 0xFFFFU)); +} + +/* Resolve a function's 64-bit BAR0. The IERB normally programs these from + * reset, so reading the live BAR is the primary path. If a BAR reads back + * unassigned (zero) we fall back to a fabricated address within the ENETC + * BAR window and set *fabricated so the caller can warn -- on real silicon + * a zero BAR is unexpected and the fabricated address may alias unrelated + * memory, so this fallback is a hardware-verify item. */ +static uint64_t pci_bar0(uint32_t fn, int *fabricated) +{ + uint32_t lo = pci_cfg_rd32(fn, PCI_CFG_BAR0); + uint32_t hi = pci_cfg_rd32(fn, PCI_CFG_BAR1); + uint64_t bar = (((uint64_t)hi) << 32) | (lo & PCI_BAR_MEM_MASK); + + if (fabricated != NULL) + *fabricated = 0; + if (bar == 0) { + bar = (uint64_t)NXP_ENETC_BAR_WINDOW + + ((uint64_t)fn * (uint64_t)NXP_ENETC_BAR_STRIDE); + pci_cfg_wr32(fn, PCI_CFG_BAR0, (uint32_t)(bar & PCI_BAR_MEM_MASK)); + pci_cfg_wr32(fn, PCI_CFG_BAR1, (uint32_t)(bar >> 32)); + if (fabricated != NULL) + *fabricated = 1; + } + return bar; +} + +/* ---- Driver state ---------------------------------------------------- */ +static uintptr_t si_base; /* ENETC port station-interface BAR0 */ +static uintptr_t port_base; /* si_base + ENETC_PORT_REGS_OFF */ +static uintptr_t ext_mdio; /* external MDIO regs (MDIO PF BAR0 + 0x1C00) */ +static uintptr_t pcs_mdio; /* internal PCS MDIO (port_base + 0x8030) */ +static uintptr_t tx_ring_reg;/* TX ring register block (si_base + 0x8000) */ +static uintptr_t rx_ring_reg;/* RX ring register block (si_base + 0x8100) */ +static uint32_t tx_prod; +static uint32_t rx_idx; +static int32_t phy_addr = -1; + +#define ENETC_PM_IMDIO_BASE 0x8030UL + +static struct enetc_rx_bd rx_bd_ring[ENETC_RX_RING_SIZE] + __attribute__((aligned(ENETC_BD_ALIGN))); +static struct enetc_tx_bd tx_bd_ring[ENETC_TX_RING_SIZE] + __attribute__((aligned(ENETC_BD_ALIGN))); +static uint8_t rx_buf_pool[ENETC_RX_RING_SIZE][ENETC_MAX_BUF_LEN] + __attribute__((aligned(ENETC_BUF_ALIGN))); +static uint8_t tx_buf_pool[ENETC_TX_RING_SIZE][ENETC_MAX_BUF_LEN] + __attribute__((aligned(ENETC_BUF_ALIGN))); + +/* ---- PHY (clause 22) register map ------------------------------------ */ +#define PHY_BMCR 0x00U +#define PHY_BMSR 0x01U +#define PHY_ID1 0x02U +#define PHY_ID2 0x03U +#define PHY_ANAR 0x04U + +#define BMCR_RESET (1U << 15) +#define BMCR_AUTONEG_EN (1U << 12) +#define BMCR_POWER_DOWN (1U << 11) +#define BMCR_ISOLATE (1U << 10) +#define BMCR_RESTART_ANEG (1U << 9) +#define BMSR_LINK (1U << 2) +#define BMSR_ANEG_COMPLETE (1U << 5) +#define ANAR_DEFAULT 0x01E1U +#define MDIO_TIMEOUT 1000000U + +/* ---- MDIO clause-22 (controller base parameterized) ------------------ */ +static int mdio_wait_bsy(uintptr_t mbase) +{ + uint32_t t = MDIO_TIMEOUT; + while ((rd32(mbase + ENETC_MDIO_CFG) & ENETC_MDIO_CFG_BSY) && --t) { + } + return t ? 0 : -1; +} + +static uint16_t mdio_read_base(uintptr_t mbase, uint32_t phy, uint32_t reg) +{ + wr32(mbase + ENETC_MDIO_CFG, ENETC_MDIO_CFG_C22); + if (mdio_wait_bsy(mbase) != 0) + return 0xFFFFU; + wr32(mbase + ENETC_MDIO_CTL, ENETC_MDIO_CTL_READ | ENETC_MDIO_CTL_ADDR(phy, reg)); + if (mdio_wait_bsy(mbase) != 0) + return 0xFFFFU; + if (rd32(mbase + ENETC_MDIO_CFG) & ENETC_MDIO_CFG_RD_ER) + return 0xFFFFU; + return (uint16_t)(rd32(mbase + ENETC_MDIO_DATA) & 0xFFFFU); +} + +static int mdio_write_base(uintptr_t mbase, uint32_t phy, uint32_t reg, + uint16_t val) +{ + wr32(mbase + ENETC_MDIO_CFG, ENETC_MDIO_CFG_C22); + if (mdio_wait_bsy(mbase) != 0) + return -1; + wr32(mbase + ENETC_MDIO_CTL, ENETC_MDIO_CTL_ADDR(phy, reg)); + if (mdio_wait_bsy(mbase) != 0) + return -1; + wr32(mbase + ENETC_MDIO_DATA, (uint32_t)val); + if (mdio_wait_bsy(mbase) != 0) + return -1; + return 0; +} + +/* External PHY accessors use the shared MDIO PF controller. */ +static uint16_t phy_read(uint32_t phy, uint32_t reg) +{ + return mdio_read_base(ext_mdio, phy, reg); +} +static int phy_write(uint32_t phy, uint32_t reg, uint16_t val) +{ + return mdio_write_base(ext_mdio, phy, reg, val); +} + +/* ---- PHY discovery / link -------------------------------------------- */ +static int32_t phy_detect(void) +{ + uint32_t addr; + uint16_t id; + + id = phy_read(NXP_ENETC_PHY_ADDR, PHY_ID1); + if (id != 0xFFFFU && id != 0x0000U) + return (int32_t)NXP_ENETC_PHY_ADDR; + for (addr = 0; addr < 32U; addr++) { + id = phy_read(addr, PHY_ID1); + if (id != 0xFFFFU && id != 0x0000U) + return (int32_t)addr; + } + return -1; +} + +int nxp_enetc_phy_addr(void) +{ + return (int)phy_addr; +} + +uint16_t nxp_enetc_phy_read(uint8_t reg) +{ + if (phy_addr < 0) + return 0xFFFFU; + return phy_read((uint32_t)phy_addr, reg); +} + +uint32_t nxp_enetc_link_up(void) +{ + uint16_t bsr; + if (phy_addr < 0) + return 0; + bsr = phy_read((uint32_t)phy_addr, PHY_BMSR); + bsr = phy_read((uint32_t)phy_addr, PHY_BMSR); + return (bsr & BMSR_LINK) ? 1U : 0U; +} + +/* 0 = success, <0 = hard failure (reset stuck / MDIO error), 1 = autoneg + * incomplete (soft; link likely down -- caller reads live link state). */ +static int phy_init(uint32_t phy) +{ + uint32_t timeout; + uint16_t ctrl, bsr; + + if (phy_write(phy, PHY_BMCR, BMCR_RESET) != 0) + return -1; + timeout = MDIO_TIMEOUT; + do { + ctrl = phy_read(phy, PHY_BMCR); + } while ((ctrl & BMCR_RESET) && --timeout); + if (timeout == 0) + return -1; + + if (phy_write(phy, PHY_ANAR, ANAR_DEFAULT) != 0) + return -1; + + ctrl &= ~(BMCR_POWER_DOWN | BMCR_ISOLATE); + ctrl |= BMCR_AUTONEG_EN | BMCR_RESTART_ANEG; + if (phy_write(phy, PHY_BMCR, ctrl) != 0) + return -1; + + timeout = MDIO_TIMEOUT; + do { + bsr = phy_read(phy, PHY_BMSR); + } while (!(bsr & BMSR_ANEG_COMPLETE) && --timeout); + if (timeout == 0) + return 1; + return 0; +} + +/* ---- SGMII PCS (internal MDIO, PCS at phy addr 0) -------------------- */ +#if NXP_ENETC_IF_SGMII +#define PCS_PHY_ADDR 0U +#define PCS_CR 0x00U +#define PCS_DEV_ABILITY 0x04U +#define PCS_LINK_TIMER1 0x12U +#define PCS_LINK_TIMER2 0x13U +#define PCS_IF_MODE 0x14U +#define PCS_CR_DEF_VAL 0x0140U +#define PCS_CR_RESET_AN 0x1200U +#define PCS_DEV_ABILITY_SGMII 0x4001U +#define PCS_IF_MODE_SGMII 0x0001U +#define PCS_IF_MODE_SGMII_AN 0x0002U +#define PCS_LINK_TIMER1_VAL 0x06A0U +#define PCS_LINK_TIMER2_VAL 0x0003U + +static void enetc_init_sgmii(void) +{ + mdio_write_base(pcs_mdio, PCS_PHY_ADDR, PCS_IF_MODE, + PCS_IF_MODE_SGMII | PCS_IF_MODE_SGMII_AN); + mdio_write_base(pcs_mdio, PCS_PHY_ADDR, PCS_DEV_ABILITY, + PCS_DEV_ABILITY_SGMII); + mdio_write_base(pcs_mdio, PCS_PHY_ADDR, PCS_LINK_TIMER1, + PCS_LINK_TIMER1_VAL); + mdio_write_base(pcs_mdio, PCS_PHY_ADDR, PCS_LINK_TIMER2, + PCS_LINK_TIMER2_VAL); + mdio_write_base(pcs_mdio, PCS_PHY_ADDR, PCS_CR, + PCS_CR_DEF_VAL | PCS_CR_RESET_AN); +} +#endif /* NXP_ENETC_IF_SGMII */ + +#if !NXP_ENETC_IF_SGMII +/* For RGMII, force the MAC to the (assumed gigabit/full) link and disable + * the unreliable in-band signalling. */ +static void enetc_init_rgmii(void) +{ + uint32_t v = rd32(port_base + ENETC_PM_IF_MODE); + v &= ~ENETC_PM_IF_MODE_AN_ENA; + v &= ~ENETC_PM_IFM_SSP_MASK; + v |= ENETC_PM_IFM_SSP_1000; + v |= ENETC_PM_IF_MODE_FULL_DPX; + wr32(port_base + ENETC_PM_IF_MODE, v); +} +#endif /* !NXP_ENETC_IF_SGMII */ + +/* ---- MAC / SI bring-up ----------------------------------------------- */ +static void enetc_set_mac(const uint8_t *mac) +{ + static const int8_t devfn_to_pf[7] = { 0, 1, 2, -1, -1, -1, 3 }; + uint32_t a0, a1; + int pf; + + a0 = ((uint32_t)mac[3] << 24) | ((uint32_t)mac[2] << 16) | + ((uint32_t)mac[1] << 8) | (uint32_t)mac[0]; + a1 = ((uint32_t)mac[5] << 8) | (uint32_t)mac[4]; + + /* Immediate (port) MAC address. */ + wr32(port_base + ENETC_PSIPMAR0, a0); + wr32(port_base + ENETC_PSIPMAR1, a1); + + /* Persistent (IERB) MAC address for the matching PF. */ + pf = (NXP_ENETC_PORT_FN < 7U) ? devfn_to_pf[NXP_ENETC_PORT_FN] : -1; + if (pf >= 0) { + uintptr_t ie = (uintptr_t)((uint64_t)NXP_ENETC_IERB_BASE + 0x8000UL + + (uint32_t)pf * 0x100UL); + wr32(ie + 0x00UL, a0); + wr32(ie + 0x04UL, a1); + } +} + +static void enetc_enable_si_port(void) +{ + wr32(port_base + ENETC_PSICFGR0, + ENETC_PSICFGR_SET_TXBDR(1) | ENETC_PSICFGR_SET_RXBDR(1)); + wr32(port_base + ENETC_PM_MAXFRM, LINK_MTU); + wr32(port_base + ENETC_PM_CC, ENETC_PM_CC_RX_TX_EN); + wr32(port_base + ENETC_PMR, ENETC_PMR_SI0_EN); + wr32(si_base + ENETC_SICAR0, ENETC_SICAR_VALUE); + wr32(si_base + ENETC_SIMR, ENETC_SIMR_EN); +} + +/* ---- BD ring setup --------------------------------------------------- */ +static void enetc_setup_tx_bdr(void) +{ + uint64_t base = (uint64_t)(uintptr_t)tx_bd_ring; + uint32_t i; + + for (i = 0; i < ENETC_TX_RING_SIZE; i++) { + tx_bd_ring[i].addr = 0; + tx_bd_ring[i].buf_len = 0; + tx_bd_ring[i].frm_len = 0; + tx_bd_ring[i].err_csum = 0; + tx_bd_ring[i].flags = 0; + } + wr32(tx_ring_reg + ENETC_TBBAR0, (uint32_t)(base & 0xFFFFFFFFUL)); + wr32(tx_ring_reg + ENETC_TBBAR1, (uint32_t)(base >> 32)); + wr32(tx_ring_reg + ENETC_TBLENR, ENETC_TX_RING_SIZE); + wr32(tx_ring_reg + ENETC_TBCIR, 0); + wr32(tx_ring_reg + ENETC_TBPIR, 0); + wr32(tx_ring_reg + ENETC_TBMR, ENETC_TBMR_EN); + tx_prod = 0; +} + +static void enetc_setup_rx_bdr(void) +{ + uint64_t base = (uint64_t)(uintptr_t)rx_bd_ring; + uint64_t buf; + uint32_t i; + + for (i = 0; i < ENETC_RX_RING_SIZE; i++) { + buf = (uint64_t)(uintptr_t)&rx_buf_pool[i][0]; + rx_bd_ring[i].addr_lo = (uint32_t)(buf & 0xFFFFFFFFUL); + rx_bd_ring[i].addr_hi = (uint32_t)(buf >> 32); + rx_bd_ring[i].buf_len = 0; + rx_bd_ring[i].vlan_opt = 0; + rx_bd_ring[i].lstatus = 0; + } + wr32(rx_ring_reg + ENETC_RBBAR0, (uint32_t)(base & 0xFFFFFFFFUL)); + wr32(rx_ring_reg + ENETC_RBBAR1, (uint32_t)(base >> 32)); + wr32(rx_ring_reg + ENETC_RBLENR, ENETC_RX_RING_SIZE); + wr32(rx_ring_reg + ENETC_RBBSR, ENETC_MAX_BUF_LEN); + wr32(rx_ring_reg + ENETC_RBCIR, 0); + wr32(rx_ring_reg + ENETC_RBPIR, 0); + enetc_dmb(); + wr32(rx_ring_reg + ENETC_RBMR, ENETC_RBMR_EN); + rx_idx = 0; +} + +/* ---- wolfIP poll/send callbacks -------------------------------------- */ +static int eth_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + struct enetc_tx_bd *bd = &tx_bd_ring[tx_prod]; + uint32_t ci, tries; + (void)dev; + + if (len == 0 || len > ENETC_MAX_BUF_LEN) + return -1; + + ci = rd32(tx_ring_reg + ENETC_TBCIR) & ENETC_BDR_IDX_MASK; + if (((tx_prod + 1U) % ENETC_TX_RING_SIZE) == ci) + return -2; /* ring full */ + + enetc_memcpy(tx_buf_pool[tx_prod], frame, len); + if (len < 60U) { + enetc_memset(tx_buf_pool[tx_prod] + len, 0, 60U - len); + len = 60U; + } + + bd->addr = (uint64_t)(uintptr_t)tx_buf_pool[tx_prod]; + bd->buf_len = (uint16_t)len; + bd->frm_len = (uint16_t)len; + bd->err_csum = 0; + bd->flags = ENETC_TXBD_FLAGS_F; + enetc_dmb(); + + tx_prod = (tx_prod + 1U) % ENETC_TX_RING_SIZE; + wr32(tx_ring_reg + ENETC_TBPIR, tx_prod); + + /* Wait (bounded) for HW to consume the BD so the buffer is reusable. */ + tries = ENETC_POLL_TRIES; + while (--tries && + (tx_prod != (rd32(tx_ring_reg + ENETC_TBCIR) & ENETC_BDR_IDX_MASK))) { + } + return tries ? (int)len : -1; +} + +static int eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + struct enetc_rx_bd *bd = &rx_bd_ring[rx_idx]; + uint64_t buf; + uint32_t status, flen, err; + (void)dev; + + enetc_dmb(); + status = bd->lstatus; + if (!ENETC_RXBD_LSTATUS_READY(status)) + return 0; + + err = ENETC_RXBD_LSTATUS_ERR(status) ? 1U : 0U; + flen = bd->buf_len; + if (flen > ENETC_MAX_BUF_LEN) + flen = ENETC_MAX_BUF_LEN; + if (flen > len) + flen = len; + if (!err && flen > 0U) + enetc_memcpy(frame, rx_buf_pool[rx_idx], flen); + + /* Recycle the BD: restore the buffer address, clear the writeback. */ + buf = (uint64_t)(uintptr_t)&rx_buf_pool[rx_idx][0]; + bd->addr_lo = (uint32_t)(buf & 0xFFFFFFFFUL); + bd->addr_hi = (uint32_t)(buf >> 32); + bd->buf_len = 0; + bd->vlan_opt = 0; + bd->lstatus = 0; + enetc_dmb(); + + rx_idx = (rx_idx + 1U) % ENETC_RX_RING_SIZE; + wr32(rx_ring_reg + ENETC_RBCIR, rx_idx); + + return err ? 0 : (int)flen; +} + +/* ---- Public init ----------------------------------------------------- */ +int nxp_enetc_init(struct wolfIP_ll_dev *ll, const uint8_t *mac) +{ + static const uint8_t default_mac[6] = { + 0x02, 0x11, 0x20, 0x28, 0x00, 0x01 + }; + uint64_t port_bar, mdio_bar; + uint16_t id1, bsr; + int fab_port = 0, fab_mdio = 0; + + if (ll == NULL) + return -1; + if (mac == NULL) + mac = default_mac; + + enetc_memcpy(ll->mac, mac, 6); + enetc_memset(ll->ifname, 0, sizeof(ll->ifname)); + enetc_memcpy(ll->ifname, "eth0", 4); + ll->mtu = LINK_MTU; + ll->poll = eth_poll; + ll->send = eth_send; + + /* Confirm the ENETC port function is present over ECAM. */ + ELOG("ecam"); + if (pci_cfg_rd16(NXP_ENETC_PORT_FN, PCI_CFG_VENDOR_ID) != ENETC_PCI_VENDOR_ID) + return -3; + + /* Resolve BARs and enable the functions. A fabricated (fallback) BAR is + * unexpected on real silicon -- warn but proceed (hardware-verify). */ + ELOG("bar"); + port_bar = pci_bar0(NXP_ENETC_PORT_FN, &fab_port); + pci_enable(NXP_ENETC_PORT_FN, 1); + mdio_bar = pci_bar0(NXP_ENETC_MDIO_FN, &fab_mdio); + pci_enable(NXP_ENETC_MDIO_FN, 0); + if (fab_port || fab_mdio) + ELOG("bar-fallback"); + + si_base = (uintptr_t)port_bar; + port_base = si_base + ENETC_PORT_REGS_OFF; + ext_mdio = (uintptr_t)mdio_bar + ENETC_MDIO_REGS_OFF; + pcs_mdio = port_base + ENETC_PM_IMDIO_BASE; + tx_ring_reg = si_base + ENETC_BDR_BASE(ENETC_BDR_TX, 0); + rx_ring_reg = si_base + ENETC_BDR_BASE(ENETC_BDR_RX, 0); + + ELOG("si"); + enetc_enable_si_port(); + enetc_set_mac(mac); + + ELOG("rings"); + enetc_setup_tx_bdr(); + enetc_setup_rx_bdr(); + + ELOG("iface"); +#if NXP_ENETC_IF_SGMII + enetc_init_sgmii(); +#else + enetc_init_rgmii(); +#endif + + /* Bring up the external PHY over the shared MDIO. */ + ELOG("phy"); + phy_addr = phy_detect(); + if (phy_addr < 0) + return 0x100; /* datapath up, PHY not found */ + + if (phy_init((uint32_t)phy_addr) < 0) + return -5; /* hard PHY-init failure */ + id1 = phy_read((uint32_t)phy_addr, PHY_ID1); + bsr = phy_read((uint32_t)phy_addr, PHY_BMSR); + + return (int)(((uint32_t)(id1 & 0xFF00U) << 8) | + ((bsr & BMSR_LINK) ? 0x100U : 0U) | + ((uint32_t)phy_addr & 0xFFU)); +} diff --git a/src/port/nxp_enetc/nxp_enetc.h b/src/port/nxp_enetc/nxp_enetc.h new file mode 100644 index 00000000..bcac189d --- /dev/null +++ b/src/port/nxp_enetc/nxp_enetc.h @@ -0,0 +1,182 @@ +/* nxp_enetc.h + * + * NXP ENETC ethernet driver for wolfIP (LS1028A and other Layerscape parts + * with the integrated-endpoint ENETC MAC). AArch64, little-endian. + * + * ENETC is a PCIe integrated endpoint: the MAC, its station interface (SI) + * register space, and a shared MDIO controller appear as PCI functions on + * an internal ECAM root complex (bus 0). This driver discovers the port + * over ECAM, sets up one RX and one TX buffer-descriptor ring in memory, + * brings up the MAC + PHY, and exposes the wolfIP poll/send callbacks. + * + * Board parameters (ECAM base, port/function index, PHY address, interface + * mode) live in nxp_enetc_board.h and are overridable from CFLAGS. + * + * 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 WOLFIP_NXP_ENETC_H +#define WOLFIP_NXP_ENETC_H + +#include +#include "wolfip.h" +#include "nxp_enetc_board.h" + +/* ---- PCI / ECAM ------------------------------------------------------ */ +/* Generic ECAM (bus 0): cfg addr = ECAM_CFG_BASE + (fn << 12) + reg. */ +#define ENETC_PCI_VENDOR_ID 0x1957U /* Freescale/NXP */ +#define ENETC_PCI_DEV_ETH 0xE100U +#define ENETC_PCI_DEV_MDIO 0xEE01U + +#define PCI_CFG_VENDOR_ID 0x00U +#define PCI_CFG_DEVICE_ID 0x02U +#define PCI_CFG_COMMAND 0x04U +#define PCI_CFG_BAR0 0x10U +#define PCI_CFG_BAR1 0x14U /* high dword of 64-bit BAR0 */ +#define PCI_CMD_MEMORY 0x0002U +#define PCI_CMD_MASTER 0x0004U +#define PCI_BAR_MEM_MASK 0xFFFFFFF0UL + +/* ENETC port registers sit ENETC_PORT_REGS_OFF above the SI BAR0 base. */ +#define ENETC_PORT_REGS_OFF 0x10000UL +/* MDIO function register block within its BAR0. */ +#define ENETC_MDIO_REGS_OFF 0x1C00UL + +/* ---- Station interface (SI) global registers (BAR0 + off) ------------ */ +#define ENETC_SIMR 0x000UL +#define ENETC_SIMR_EN (1UL << 31) +#define ENETC_SICAR0 0x040UL +/* Snoop/coherent DMA AXI cache attributes (rd cfg | wr cfg), per RM. */ +#define ENETC_SICAR_VALUE 0x27276767UL + +/* ---- Port registers (PORT = BAR0 + 0x10000) ------------------------- */ +#define ENETC_PMR 0x0000UL +#define ENETC_PMR_SI0_EN (1UL << 16) +#define ENETC_PSICFGR0 0x0940UL +#define ENETC_PSICFGR_SET_TXBDR(n) ((uint32_t)(n) & 0xFFU) +#define ENETC_PSICFGR_SET_RXBDR(n) (((uint32_t)(n) & 0xFFU) << 16) +#define ENETC_PSIPMAR0 0x0100UL /* MAC bytes [0..3] */ +#define ENETC_PSIPMAR1 0x0104UL /* MAC bytes [4..5] */ +#define ENETC_PM_CC 0x8008UL /* MAC command/config */ +#define ENETC_PM_CC_RX_TX_EN 0x00008813UL +#define ENETC_PM_MAXFRM 0x8014UL +#define ENETC_PM_IF_MODE 0x8300UL +#define ENETC_PM_IF_MODE_AN_ENA (1UL << 15) +#define ENETC_PM_IF_MODE_RG (2UL << 0) /* RGMII interface select */ +#define ENETC_PM_IF_MODE_FULL_DPX (1UL << 6) +#define ENETC_PM_IFM_SSP_1000 (2UL << 13) +#define ENETC_PM_IFM_SSP_MASK (3UL << 13) + +/* ---- BD ring registers ----------------------------------------------- */ +/* ring block = SI + 0x8000 + type*0x100 + n*0x200; type: TX=0, RX=1 */ +#define ENETC_BDR_BASE(type, n) (0x8000UL + ((uint32_t)(type) * 0x100UL) + \ + ((uint32_t)(n) * 0x200UL)) +#define ENETC_BDR_TX 0U +#define ENETC_BDR_RX 1U +#define ENETC_BDR_IDX_MASK 0xFFFFU + +/* RX ring register offsets (within a ring block). */ +#define ENETC_RBMR 0x00UL +#define ENETC_RBMR_EN (1UL << 31) +#define ENETC_RBBSR 0x08UL /* buffer size */ +#define ENETC_RBCIR 0x0CUL /* consumer index (SW) */ +#define ENETC_RBBAR0 0x10UL /* ring base lo */ +#define ENETC_RBBAR1 0x14UL /* ring base hi */ +#define ENETC_RBPIR 0x18UL /* producer index (HW) */ +#define ENETC_RBLENR 0x20UL /* ring length (#BDs) */ + +/* TX ring register offsets. */ +#define ENETC_TBMR 0x00UL +#define ENETC_TBMR_EN (1UL << 31) +#define ENETC_TBBAR0 0x10UL /* ring base lo */ +#define ENETC_TBBAR1 0x14UL /* ring base hi */ +#define ENETC_TBPIR 0x18UL /* producer index (SW) */ +#define ENETC_TBCIR 0x1CUL /* consumer index (HW) */ +#define ENETC_TBLENR 0x20UL /* ring length (#BDs) */ + +/* ---- Internal MDIO (shared MDIO PF, regs at BAR0 + 0x1C00) ----------- */ +#define ENETC_MDIO_CFG 0x00UL +#define ENETC_MDIO_CTL 0x04UL +#define ENETC_MDIO_DATA 0x08UL +#define ENETC_MDIO_STAT 0x0CUL +#define ENETC_MDIO_CFG_C22 0x00809508UL +#define ENETC_MDIO_CFG_BSY (1UL << 0) +#define ENETC_MDIO_CFG_RD_ER (1UL << 1) +#define ENETC_MDIO_CTL_READ (1UL << 15) +#define ENETC_MDIO_CTL_ADDR(phy, reg) ((((uint32_t)(phy) & 0x1FU) << 5) | \ + ((uint32_t)(reg) & 0x1FU)) + +/* ---- Buffer descriptors (little-endian, 16 bytes) ------------------- */ +struct enetc_tx_bd { + volatile uint64_t addr; + volatile uint16_t buf_len; + volatile uint16_t frm_len; + volatile uint16_t err_csum; + volatile uint16_t flags; +}; +#define ENETC_TXBD_FLAGS_F (1U << 15) /* final BD of the frame */ + +/* RX BD (16 bytes). SW view: a 64-bit buffer address in the first 8 bytes. + * HW overwrites the BD with the writeback view: csum/parse/rss in the first + * 8 bytes, then buf_len (received length) at offset 8 and a 32-bit status + * word (lstatus) at offset 12. Within lstatus: READY = bit 30, FINAL = + * bit 31, and the error byte is bits 16-23 -- matching the U-Boot ENETC + * driver's ENETC_RXBD_STATUS_R / _ERRORS (drivers/net/fsl_enetc.c). We write + * the address as lo/hi 32-bit halves and read back buf_len + lstatus. */ +struct enetc_rx_bd { + volatile uint32_t addr_lo; /* SW: buffer addr lo / WB: csum+parse */ + volatile uint32_t addr_hi; /* SW: buffer addr hi / WB: rss hash */ + volatile uint16_t buf_len; /* WB: received length (offset 8) */ + volatile uint16_t vlan_opt; /* WB: vlan (offset 10) */ + volatile uint32_t lstatus; /* WB: status word (offset 12) */ +}; +#define ENETC_RXBD_LSTATUS_READY(s) (((s) >> 30) & 1U) /* bit 30 */ +#define ENETC_RXBD_LSTATUS_FINAL(s) (((s) >> 31) & 1U) /* bit 31 */ +#define ENETC_RXBD_LSTATUS_ERR(s) (((s) >> 16) & 0xFFU) /* bits 16-23 */ + +/* ---- Ring / buffer sizing ------------------------------------------- */ +#define ENETC_BD_ALIGN 128U /* BD array alignment */ +#define ENETC_BUF_ALIGN 64U /* RX/TX buffer alignment */ +#define ENETC_RX_RING_SIZE 8U /* must be a multiple of 8 */ +#define ENETC_TX_RING_SIZE 8U +#define ENETC_MAX_BUF_LEN 2048U +#define ENETC_POLL_TRIES 32000U + +/* ---- Public API ------------------------------------------------------ */ + +/* Discover the ENETC port over ECAM, set up the BD rings, bring up the + * MAC + PHY, and populate ll with the MAC address, interface name and + * poll/send callbacks. Pass mac=NULL to use the built-in default address. + * Returns a status word encoding the detected PHY id high byte, link bit + * and PHY address, or a negative value on hard failure. */ +int nxp_enetc_init(struct wolfIP_ll_dev *ll, const uint8_t *mac); + +/* Detected PHY MDIO address, or -1 if no external PHY was found. */ +int nxp_enetc_phy_addr(void); + +/* MDIO clause-22 read of the detected PHY (0xFFFF on error / no PHY). */ +uint16_t nxp_enetc_phy_read(uint8_t reg); + +/* Non-zero when the PHY reports link up. */ +uint32_t nxp_enetc_link_up(void); + +/* Optional debug log hook: the driver calls it at bring-up phase + * boundaries. Pass NULL (default) to disable. */ +void nxp_enetc_set_log(void (*log_fn)(const char *msg)); + +#endif /* WOLFIP_NXP_ENETC_H */ diff --git a/src/port/nxp_enetc/nxp_enetc_board.h b/src/port/nxp_enetc/nxp_enetc_board.h new file mode 100644 index 00000000..3a5f17ab --- /dev/null +++ b/src/port/nxp_enetc/nxp_enetc_board.h @@ -0,0 +1,81 @@ +/* nxp_enetc_board.h + * + * Board parameters for the NXP ENETC ethernet driver (nxp_enetc.c). + * Defaults target the NXP LS1028A (port 0, external SGMII PHY). + * + * Every value here is overridable from CFLAGS or a consumer header so the + * same driver can serve other Layerscape ENETC boards. + * + * 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 WOLFIP_NXP_ENETC_BOARD_H +#define WOLFIP_NXP_ENETC_BOARD_H + +/* ECAM configuration-space base (generic ECAM, bus 0). LS1028A reset + * default per the reference manual. wolfBoot keeps the default CCSR/ECAM + * layout (hal/nxp_ls1028a.h ECAMCFG_BASE). */ +#ifndef NXP_ENETC_ECAM_BASE +#define NXP_ENETC_ECAM_BASE 0x01F0000000ULL +#endif + +/* IERB register space base. On LS1028A the persistent (Linux-visible) MAC + * address is programmed in the IERB; we also write the port PSIPMAR for the + * immediate bring-up. hal/nxp_ls1028a.h ECAMREG_BASE. */ +#ifndef NXP_ENETC_IERB_BASE +#define NXP_ENETC_IERB_BASE 0x01F0800000ULL +#endif + +/* ECAM PCI function for the ENETC port to use. On LS1028A: + * fn0 = port0 (external SGMII PHY on the RDB), + * fn1 = port1, fn2 = port2 (internal 2.5G to Felix switch), + * fn6 = port3. Port 0 is the wire-facing path with a real PHY. */ +#ifndef NXP_ENETC_PORT_FN +#define NXP_ENETC_PORT_FN 0U +#endif + +/* ECAM PCI function for the shared ENETC MDIO controller (LS1028A fn3). */ +#ifndef NXP_ENETC_MDIO_FN +#define NXP_ENETC_MDIO_FN 3U +#endif + +/* External PHY MDIO address. LS1028A-RDB wires the port0 SGMII PHY at + * address 0x2. nxp_enetc_init() also scans the bus, so a board with the + * PHY elsewhere is still detected. */ +#ifndef NXP_ENETC_PHY_ADDR +#define NXP_ENETC_PHY_ADDR 0x2U +#endif + +/* MAC-to-PHY interface: 1 = SGMII / in-band (default, no MAC speed force), + * 0 = RGMII (the driver forces 1000/full on the MAC). */ +#ifndef NXP_ENETC_IF_SGMII +#define NXP_ENETC_IF_SGMII 1 +#endif + +/* Fallback BAR0 window used ONLY if a function's BAR0 reads back + * unassigned (the IERB normally programs these from reset; reading the + * live BAR is the primary path). LS1028A ENETC PF BARs live in the + * 0x01F8000000 region. Verify on hardware before relying on the fallback. */ +#ifndef NXP_ENETC_BAR_WINDOW +#define NXP_ENETC_BAR_WINDOW 0x01F8000000ULL +#endif +#ifndef NXP_ENETC_BAR_STRIDE +#define NXP_ENETC_BAR_STRIDE 0x00040000ULL +#endif + +#endif /* WOLFIP_NXP_ENETC_BOARD_H */ diff --git a/src/port/nxp_etsec/README.md b/src/port/nxp_etsec/README.md new file mode 100644 index 00000000..4116cf95 --- /dev/null +++ b/src/port/nxp_etsec/README.md @@ -0,0 +1,47 @@ +# wolfIP NXP eTSEC (Gianfar) port + +Ethernet driver for the NXP eTSEC (Enhanced Three-Speed Ethernet Controller, a.k.a. Gianfar) MAC on big-endian PowerPC QorIQ/PQ parts (P1020/P1021/P2020, e500v2). Brings up one eTSEC in polled mode and exposes the wolfIP poll/send callbacks. This is the stock-P1021RDB ethernet path (eTSEC + RGMII PHYs / VSC7385 switch). + +## Model + +The board (DDR, clocks, UART, L1/L2, TLBs, LAWs) is brought up by the boot stage (wolfBoot). This driver runs in the booted application: soft-reset the eTSEC, configure the MAC and interface, set up one RX and one TX BD ring in DRAM, bring up the external PHY (or use a fixed link), and poll the rings. + +## Files + +- `nxp_etsec.h` - public API + eTSEC / MDIO / BD register map. +- `nxp_etsec.c` - driver: BE register access, MDIO clause-22, PHY bring-up, MAC/interface config, BD-ring datapath, wolfIP poll/send callbacks. +- `nxp_etsec_board.h` - board parameters (CCSRBAR, eTSEC index, interface mode, PHY address, fixed-link). All overridable from CFLAGS. +- `config.h` - wolfIP compile-time configuration for this port. + +## Board parameters + +| Define | Default | Notes | +|---|---|---| +| `NXP_ETSEC_CCSRBAR` | `0xFFE00000` | CCSRBAR as relocated by wolfBoot (NOT the 0xFF700000 reset default) | +| `NXP_ETSEC_INDEX` | `0` | 0=eTSEC1, 1=eTSEC2, 2=eTSEC3 | +| `NXP_ETSEC_IF_SGMII` | `0` | 0=RGMII, 1=SGMII (configures the internal TBI/SerDes) | +| `NXP_ETSEC_FIXED_LINK` | `1` | 1=skip PHY autoneg, force NXP_ETSEC_SPEED (eTSEC1->VSC7385 uplink) | +| `NXP_ETSEC_SPEED` | `1000` | forced/expected speed in Mbps | +| `NXP_ETSEC_PHY_ADDR` | `0x1` | external PHY (probed first, then bus scan); P1021RDB uses 0/1/2 | +| `NXP_ETSEC_TBIPA` | `0x1F` | internal TBI MDIO address (kept off the external range) | + +eTSEC register block: eTSEC1 = CCSRBAR + 0x24000, step 0x1000. The MDIO management block for all eTSECs lives in eTSEC1's window at +0x520; the per-eTSEC internal TBI is reached through that eTSEC's own +0x520 window at the TBIPA address. + +## P1021RDB wiring + +eTSEC1 is RGMII to the VSC7385 5-port switch (a fixed link, no external MDIO PHY -- the switch is eLBC-attached and its firmware is loaded by the boot stage); use the default `NXP_ETSEC_FIXED_LINK=1`. eTSEC2/eTSEC3 are SGMII to external PHYs (MDIO addresses 0x1/0x2 by convention) when the SerDes protocol selects SGMII on those lanes. All external MDIO goes through eTSEC1's window. + +## Cache coherency + +e500v2 has no I/O cache snooping and wolfBoot maps low DDR cacheable-but-non-coherent (MAS2_G, no M bit), so the driver does explicit `dcbf` cache maintenance (32-byte lines) around the BD rings and packet buffers before/after DMA. + +## Status + +Build-validated (compiles clean for e500 BE in RGMII fixed-link, RGMII+PHY, and SGMII+PHY configurations; BD struct verified to be 8 bytes). The polled `eth_poll`/`eth_send` ring datapath (index/wrap/ring-full and length-clamp bookkeeping) is NOT yet exercised by any host or hardware test. Hardware bring-up on the P1021RDB is pending board availability. The bring-up follows the U-Boot eTSEC driver (drivers/net/tsec.c, fsl_mdio.c). Hardware-verify items flagged inline: the eTSEC129 RX-init erratum workaround (early P1021 silicon) is not implemented; the SGMII TBI register values in `configure_serdes()` should be confirmed; and the exact P1021RDB PHY addresses/interface come from the board DTS/schematic. + +## Build + +``` +powerpc-linux-gnu-gcc -mcpu=e500mc -mbig-endian -I -Isrc/port/nxp_etsec \ + -c src/port/nxp_etsec/nxp_etsec.c +``` diff --git a/src/port/nxp_etsec/config.h b/src/port/nxp_etsec/config.h new file mode 100644 index 00000000..37ac38dc --- /dev/null +++ b/src/port/nxp_etsec/config.h @@ -0,0 +1,68 @@ +/* config.h + * + * wolfIP configuration for the NXP eTSEC (Gianfar) port (P1020/P1021/P2020). + * + * 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 +#define TXBUF_SIZE LINK_MTU + +#define MAX_NEIGHBORS 8 +#define WOLFIP_ARP_PENDING_MAX 2 + +#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 1 +#endif + +/* Static IP fallback (used when DHCP is disabled or times out). */ +#define WOLFIP_IP "192.168.1.10" +#define WOLFIP_NETMASK "255.255.255.0" +#define WOLFIP_GW "192.168.1.1" +#define WOLFIP_STATIC_DNS_IP "8.8.8.8" + +#if WOLFIP_ENABLE_DHCP +#define DHCP +#endif + +#endif /* WOLF_CONFIG_H */ diff --git a/src/port/nxp_etsec/nxp_etsec.c b/src/port/nxp_etsec/nxp_etsec.c new file mode 100644 index 00000000..09dab341 --- /dev/null +++ b/src/port/nxp_etsec/nxp_etsec.c @@ -0,0 +1,525 @@ +/* nxp_etsec.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 + * + * NXP eTSEC (Gianfar) ethernet driver for wolfIP. + * + * Brings up one eTSEC in polled mode: soft reset, MAC config, BD rings in + * DRAM, MDIO clause-22 + external PHY (or a fixed link), and the polled + * poll/send datapath. Ported from the U-Boot eTSEC driver (drivers/net/ + * tsec.c, fsl_mdio.c). e500v2, big-endian. + */ + +#include +#include +#include "config.h" +#include "nxp_etsec.h" + +/* Local byte-loop mem helpers (bare-metal, no PLT/GOT fixup). */ +static void *etsec_memcpy(void *dst, const void *src, uint32_t n) +{ + uint8_t *d = (uint8_t *)dst; + const uint8_t *s = (const uint8_t *)src; + while (n--) + *d++ = *s++; + return dst; +} +static void etsec_memset(void *dst, int c, uint32_t n) +{ + uint8_t *d = (uint8_t *)dst; + while (n--) + *d++ = (uint8_t)c; +} + +/* ---- Big-endian register access (sync/twi/isync like wolfBoot) ------- */ +#if defined(__powerpc__) || defined(__PPC__) || defined(__ppc__) +static inline uint32_t rd32(uintptr_t a) +{ + uint32_t r; + __asm__ __volatile__("sync;\n" "lwz %0,0(%1);\n" "twi 0,%0,0;\n" "isync" + : "=r"(r) : "r"(a) : "memory"); + return r; +} +static inline void wr32(uintptr_t a, uint32_t v) +{ + __asm__ __volatile__("sync;\n" "stw %0,0(%1)" : : "r"(v), "r"(a) : "memory"); +} +static inline void etsec_sync(void) +{ + __asm__ __volatile__("sync" ::: "memory"); +} +/* e500v2 data cache line = 32 bytes; low DDR is cacheable-non-coherent, so + * flush (dcbf) BDs/buffers around DMA. */ +static void dcache_flush(const void *p, uint32_t len) +{ + uintptr_t a = (uintptr_t)p & ~31UL; + uintptr_t end = (uintptr_t)p + len; + for (; a < end; a += 32) + __asm__ __volatile__("dcbf 0,%0" : : "r"(a) : "memory"); + etsec_sync(); +} +#else +static inline uint32_t rd32(uintptr_t a) { return *(volatile uint32_t *)a; } +static inline void wr32(uintptr_t a, uint32_t v) { *(volatile uint32_t *)a = v; } +static inline void etsec_sync(void) { } +static void dcache_flush(const void *p, uint32_t len) { (void)p; (void)len; } +#endif + +#define dcache_inval(p, l) dcache_flush((p), (l)) + +/* Optional debug log hook. */ +static void (*g_log)(const char *) = 0; +void nxp_etsec_set_log(void (*log_fn)(const char *)) { g_log = log_fn; } +#define TLOG(s) do { if (g_log) g_log(s); } while (0) + +/* ---- Driver state ---------------------------------------------------- */ +static uintptr_t reg_base; /* selected eTSEC register block */ +static uintptr_t mdio_base; /* external MDIO (eTSEC1 + 0x520) */ +static uintptr_t local_mdio; /* selected eTSEC's own MDIO (+0x520) */ +static uint32_t rx_idx; +static uint32_t tx_idx; +static int32_t phy_addr = -1; + +static struct etsec_bd rx_bd_ring[ETSEC_RX_RING_SIZE] + __attribute__((aligned(ETSEC_BD_ALIGN))); +static struct etsec_bd tx_bd_ring[ETSEC_TX_RING_SIZE] + __attribute__((aligned(ETSEC_BD_ALIGN))); +static uint8_t rx_buf_pool[ETSEC_RX_RING_SIZE][ETSEC_MAX_BUF_LEN] + __attribute__((aligned(ETSEC_BUF_ALIGN))); +static uint8_t tx_buf_pool[ETSEC_TX_RING_SIZE][ETSEC_MAX_BUF_LEN] + __attribute__((aligned(ETSEC_BUF_ALIGN))); + +/* ---- PHY (clause 22) register map ------------------------------------ */ +#define PHY_BMCR 0x00U +#define PHY_BMSR 0x01U +#define PHY_ID1 0x02U +#define PHY_ID2 0x03U +#define PHY_ANAR 0x04U +#define BMCR_RESET (1U << 15) +#define BMCR_AUTONEG_EN (1U << 12) +#define BMCR_POWER_DOWN (1U << 11) +#define BMCR_ISOLATE (1U << 10) +#define BMCR_RESTART_ANEG (1U << 9) +#define BMSR_LINK (1U << 2) +#define BMSR_ANEG_COMPLETE (1U << 5) +#define ANAR_DEFAULT 0x01E1U +#define MDIO_TIMEOUT 1000000U + +/* ---- MDIO clause-22 (controller base parameterized) ------------------ */ +static void mdio_setup(uintptr_t mbase) +{ + uint32_t t = MDIO_TIMEOUT; + wr32(mbase + MIIM_CFG, MIIMCFG_RESET_MGMT); + wr32(mbase + MIIM_CFG, MIIMCFG_INIT_VALUE); + while ((rd32(mbase + MIIM_IND) & MIIMIND_BUSY) && --t) { + } +} + +static uint16_t mdio_read_base(uintptr_t mbase, uint32_t phy, uint32_t reg) +{ + uint32_t t = MDIO_TIMEOUT; + wr32(mbase + MIIM_ADD, ((phy & 0x1FU) << MIIMADD_PHY_SHIFT) | (reg & 0x1FU)); + wr32(mbase + MIIM_COM, 0); + etsec_sync(); + wr32(mbase + MIIM_COM, MIIMCOM_READ_CYCLE); + while ((rd32(mbase + MIIM_IND) & (MIIMIND_NOTVALID | MIIMIND_BUSY)) && --t) { + } + if (t == 0) + return 0xFFFFU; + return (uint16_t)(rd32(mbase + MIIM_STAT) & 0xFFFFU); +} + +#if !NXP_ETSEC_FIXED_LINK || NXP_ETSEC_IF_SGMII +static int mdio_write_base(uintptr_t mbase, uint32_t phy, uint32_t reg, + uint16_t val) +{ + uint32_t t = MDIO_TIMEOUT; + wr32(mbase + MIIM_ADD, ((phy & 0x1FU) << MIIMADD_PHY_SHIFT) | (reg & 0x1FU)); + wr32(mbase + MIIM_CON, (uint32_t)val); + etsec_sync(); + while ((rd32(mbase + MIIM_IND) & MIIMIND_BUSY) && --t) { + } + return t ? 0 : -1; +} +#endif + +static uint16_t phy_read(uint32_t phy, uint32_t reg) +{ + return mdio_read_base(mdio_base, phy, reg); +} +#if !NXP_ETSEC_FIXED_LINK +static int phy_write(uint32_t phy, uint32_t reg, uint16_t val) +{ + return mdio_write_base(mdio_base, phy, reg, val); +} +#endif + +/* ---- PHY discovery / link -------------------------------------------- */ +#if !NXP_ETSEC_FIXED_LINK +static int32_t phy_detect(void) +{ + uint32_t addr; + uint16_t id; + + id = phy_read(NXP_ETSEC_PHY_ADDR, PHY_ID1); + if (id != 0xFFFFU && id != 0x0000U) + return (int32_t)NXP_ETSEC_PHY_ADDR; + for (addr = 0; addr < 32U; addr++) { + id = phy_read(addr, PHY_ID1); + if (id != 0xFFFFU && id != 0x0000U) + return (int32_t)addr; + } + return -1; +} +#endif + +int nxp_etsec_phy_addr(void) { return (int)phy_addr; } + +uint16_t nxp_etsec_phy_read(uint8_t reg) +{ + if (phy_addr < 0) + return 0xFFFFU; + return phy_read((uint32_t)phy_addr, reg); +} + +uint32_t nxp_etsec_link_up(void) +{ + uint16_t bsr; +#if NXP_ETSEC_FIXED_LINK + return 1U; +#endif + if (phy_addr < 0) + return 0; + bsr = phy_read((uint32_t)phy_addr, PHY_BMSR); + bsr = phy_read((uint32_t)phy_addr, PHY_BMSR); + return (bsr & BMSR_LINK) ? 1U : 0U; +} + +#if !NXP_ETSEC_FIXED_LINK +/* 0 = success, <0 = hard failure (reset stuck / MDIO error), 1 = autoneg + * incomplete (soft; link likely down -- caller reads live link state). */ +static int phy_init(uint32_t phy) +{ + uint32_t timeout; + uint16_t ctrl, bsr; + + if (phy_write(phy, PHY_BMCR, BMCR_RESET) != 0) + return -1; + timeout = MDIO_TIMEOUT; + do { + ctrl = phy_read(phy, PHY_BMCR); + } while ((ctrl & BMCR_RESET) && --timeout); + if (timeout == 0) + return -1; + + if (phy_write(phy, PHY_ANAR, ANAR_DEFAULT) != 0) + return -1; + ctrl &= ~(BMCR_POWER_DOWN | BMCR_ISOLATE); + ctrl |= BMCR_AUTONEG_EN | BMCR_RESTART_ANEG; + if (phy_write(phy, PHY_BMCR, ctrl) != 0) + return -1; + + timeout = MDIO_TIMEOUT; + do { + bsr = phy_read(phy, PHY_BMSR); + } while (!(bsr & BMSR_ANEG_COMPLETE) && --timeout); + if (timeout == 0) + return 1; + return 0; +} +#endif + +#if NXP_ETSEC_IF_SGMII +/* Configure the internal TBI/SerDes for SGMII via the eTSEC's own (local) + * MDIO at the TBIPA address. VERIFY the TBI register values on hardware. */ +static void configure_serdes(void) +{ + wr32(reg_base + ETSEC_TBIPA, NXP_ETSEC_TBIPA); + mdio_write_base(local_mdio, NXP_ETSEC_TBIPA, 0x00, 0x8000); /* TBI_CR reset */ + mdio_write_base(local_mdio, NXP_ETSEC_TBIPA, 0x04, 0x4001); /* TBI_ANA SGMII */ + mdio_write_base(local_mdio, NXP_ETSEC_TBIPA, 0x11, 0x0020); /* TBICON clk */ + mdio_write_base(local_mdio, NXP_ETSEC_TBIPA, 0x00, 0x1140); /* aneg+1G+FD */ +} +#endif + +/* ---- MAC bring-up ---------------------------------------------------- */ +static void mac_reset(void) +{ + wr32(reg_base + ETSEC_MACCFG1, MACCFG1_SOFT_RESET); + etsec_sync(); + wr32(reg_base + ETSEC_MACCFG1, 0); +} + +static void mac_set_addr(const uint8_t *mac) +{ + uint32_t a1, a2; + a1 = ((uint32_t)mac[5] << 24) | ((uint32_t)mac[4] << 16) | + ((uint32_t)mac[3] << 8) | (uint32_t)mac[2]; + a2 = ((uint32_t)mac[1] << 24) | ((uint32_t)mac[0] << 16); + wr32(reg_base + ETSEC_MACSTNADDR1, a1); + wr32(reg_base + ETSEC_MACSTNADDR2, a2); +} + +static void init_registers(void) +{ + uint32_t i; + + wr32(reg_base + ETSEC_IEVENT, IEVENT_INIT_CLEAR); + wr32(reg_base + ETSEC_IMASK, 0); /* polled: mask all */ + for (i = 0; i < 8U; i++) { + wr32(reg_base + ETSEC_IADDR0 + i * 4U, 0); + wr32(reg_base + ETSEC_GADDR0 + i * 4U, 0); + } + wr32(reg_base + ETSEC_MRBLR, ETSEC_MAX_BUF_LEN); + wr32(reg_base + ETSEC_MINFLR, MINFLR_INIT_SETTINGS); + wr32(reg_base + ETSEC_ATTR, ATTR_INIT_SETTINGS); + wr32(reg_base + ETSEC_ATTRELI, 0); + wr32(reg_base + ETSEC_MAXFRM, ETSEC_MAX_FRAME_LEN); + wr32(reg_base + ETSEC_RCTRL, 0); + wr32(reg_base + ETSEC_TCTRL, 0); +} + +/* Program MACCFG2/ECNTRL for the link speed and interface. */ +static void adjust_link(uint32_t speed, int full_duplex) +{ + uint32_t ecntrl = rd32(reg_base + ETSEC_ECNTRL) & ~ECNTRL_R100; + uint32_t cfg2 = rd32(reg_base + ETSEC_MACCFG2) & + ~(MACCFG2_IF_MASK | MACCFG2_FULL_DUPLEX); + + if (full_duplex) + cfg2 |= MACCFG2_FULL_DUPLEX; + if (speed >= 1000U) { + cfg2 |= MACCFG2_GMII; + } + else { + cfg2 |= MACCFG2_MII; + if (speed >= 100U) + ecntrl |= ECNTRL_R100; + } + wr32(reg_base + ETSEC_ECNTRL, ecntrl); + wr32(reg_base + ETSEC_MACCFG2, cfg2); +} + +static void setup_bd_rings(void) +{ + uint32_t i; + + for (i = 0; i < ETSEC_RX_RING_SIZE; i++) { + rx_bd_ring[i].status = RXBD_EMPTY; + rx_bd_ring[i].length = 0; + rx_bd_ring[i].bufptr = (uint32_t)(uintptr_t)&rx_buf_pool[i][0]; + } + rx_bd_ring[ETSEC_RX_RING_SIZE - 1].status = RXBD_EMPTY | RXBD_WRAP; + for (i = 0; i < ETSEC_TX_RING_SIZE; i++) { + tx_bd_ring[i].status = 0; + tx_bd_ring[i].length = 0; + tx_bd_ring[i].bufptr = 0; + } + tx_bd_ring[ETSEC_TX_RING_SIZE - 1].status = TXBD_WRAP; + dcache_flush(rx_bd_ring, sizeof(rx_bd_ring)); + dcache_flush(tx_bd_ring, sizeof(tx_bd_ring)); + /* Clean+invalidate the RX buffers before the DMA owns them so a later + * write-back of a dirty line cannot clobber a DMA-written frame. */ + dcache_flush(rx_buf_pool, sizeof(rx_buf_pool)); + + wr32(reg_base + ETSEC_TBASE, (uint32_t)(uintptr_t)tx_bd_ring); + wr32(reg_base + ETSEC_RBASE, (uint32_t)(uintptr_t)rx_bd_ring); + rx_idx = 0; + tx_idx = 0; +} + +/* ---- wolfIP poll/send callbacks -------------------------------------- */ +static int eth_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + struct etsec_bd *bd = &tx_bd_ring[tx_idx]; + uint16_t wrap; + uint32_t tries; + (void)dev; + + if (len == 0 || len > ETSEC_MAX_BUF_LEN) + return -1; + + dcache_inval(bd, sizeof(*bd)); + if (bd->status & TXBD_READY) + return -2; /* ring full */ + + etsec_memcpy(tx_buf_pool[tx_idx], frame, len); + if (len < 60U) { + etsec_memset(tx_buf_pool[tx_idx] + len, 0, 60U - len); + len = 60U; + } + dcache_flush(tx_buf_pool[tx_idx], len); + + wrap = bd->status & TXBD_WRAP; + bd->bufptr = (uint32_t)(uintptr_t)tx_buf_pool[tx_idx]; + bd->length = (uint16_t)len; + etsec_sync(); + bd->status = wrap | TXBD_READY | TXBD_PADCRC | TXBD_LAST | TXBD_CRC; + dcache_flush(bd, sizeof(*bd)); + + /* doorbell: clear TX halt */ + wr32(reg_base + ETSEC_TSTAT, TSTAT_CLEAR_THALT); + + /* wait (bounded) for the BD to be consumed so the buffer is reusable */ + tries = ETSEC_POLL_TRIES; + do { + dcache_inval(bd, sizeof(*bd)); + } while ((bd->status & TXBD_READY) && --tries); + + tx_idx = (tx_idx + 1U) % ETSEC_TX_RING_SIZE; + return tries ? (int)len : -1; +} + +static int eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + struct etsec_bd *bd = &rx_bd_ring[rx_idx]; + uint16_t status, wrap, flen = 0; + int err; + (void)dev; + + dcache_inval(bd, sizeof(*bd)); + status = bd->status; + if (status & RXBD_EMPTY) { + /* recover from an out-of-buffers stall */ + if (rd32(reg_base + ETSEC_IEVENT) & IEVENT_BSY) { + wr32(reg_base + ETSEC_IEVENT, IEVENT_BSY); + wr32(reg_base + ETSEC_RSTAT, RSTAT_CLEAR_RHALT); + } + return 0; + } + + err = (status & RXBD_STATS) ? 1 : 0; + if (!err) { + flen = bd->length; /* includes 4-byte FCS */ + if (flen >= 4U) + flen -= 4U; + else + flen = 0; + if (flen > ETSEC_MAX_BUF_LEN) + flen = ETSEC_MAX_BUF_LEN; + if (flen > len) + flen = (uint16_t)len; + if (flen > 0) { + dcache_inval(rx_buf_pool[rx_idx], flen); + etsec_memcpy(frame, rx_buf_pool[rx_idx], flen); + } + } + + /* recycle the BD */ + wrap = status & RXBD_WRAP; + bd->length = 0; + etsec_sync(); + bd->status = wrap | RXBD_EMPTY; + dcache_flush(bd, sizeof(*bd)); + + /* re-arm RX in case the controller halted on an empty ring */ + wr32(reg_base + ETSEC_RSTAT, RSTAT_CLEAR_RHALT); + + rx_idx = (rx_idx + 1U) % ETSEC_RX_RING_SIZE; + return err ? 0 : (int)flen; +} + +/* ---- Public init ----------------------------------------------------- */ +int nxp_etsec_init(struct wolfIP_ll_dev *ll, const uint8_t *mac) +{ + static const uint8_t default_mac[6] = { + 0x02, 0x11, 0x20, 0x10, 0x21, 0x01 + }; + uint32_t v; + + if (ll == NULL) + return -1; + if (mac == NULL) + mac = default_mac; + + etsec_memcpy(ll->mac, mac, 6); + etsec_memset(ll->ifname, 0, sizeof(ll->ifname)); + etsec_memcpy(ll->ifname, "eth0", 4); + ll->mtu = LINK_MTU; + ll->poll = eth_poll; + ll->send = eth_send; + + reg_base = ETSEC_BASE(NXP_ETSEC_INDEX); + mdio_base = ETSEC_BASE(0) + ETSEC_MDIO_OFFSET; /* eTSEC1 window */ + local_mdio = reg_base + ETSEC_MDIO_OFFSET; + + TLOG("reset"); + mac_reset(); + + /* MAC + interface base config. */ + TLOG("maccfg"); + wr32(reg_base + ETSEC_MACCFG2, MACCFG2_INIT_SETTINGS); + v = ECNTRL_INIT_SETTINGS; +#if NXP_ETSEC_IF_SGMII + v |= ECNTRL_SGMII_MODE; +#else + v |= ECNTRL_REDUCED_MODE; /* RGMII */ +#endif + wr32(reg_base + ETSEC_ECNTRL, v); + mac_set_addr(mac); + + TLOG("regs"); + init_registers(); + wr32(reg_base + ETSEC_TBIPA, NXP_ETSEC_TBIPA); + + TLOG("rings"); + setup_bd_rings(); + + /* Enable MAC + DMA, then release the rings. */ + TLOG("enable"); + v = rd32(reg_base + ETSEC_MACCFG1); + wr32(reg_base + ETSEC_MACCFG1, v | MACCFG1_RX_EN | MACCFG1_TX_EN); + v = rd32(reg_base + ETSEC_DMACTRL); + wr32(reg_base + ETSEC_DMACTRL, v | DMACTRL_INIT_SETTINGS); + wr32(reg_base + ETSEC_TSTAT, TSTAT_CLEAR_THALT); + wr32(reg_base + ETSEC_RSTAT, RSTAT_CLEAR_RHALT); + v = rd32(reg_base + ETSEC_DMACTRL); + wr32(reg_base + ETSEC_DMACTRL, v & ~(DMACTRL_GRS | DMACTRL_GTS)); + + /* MDIO + PHY (or fixed link). */ + TLOG("mdio"); + mdio_setup(mdio_base); +#if NXP_ETSEC_IF_SGMII + if (local_mdio != mdio_base) + mdio_setup(local_mdio); + configure_serdes(); +#endif + +#if NXP_ETSEC_FIXED_LINK + adjust_link(NXP_ETSEC_SPEED, 1); + return (int)0x1FF; /* datapath up, fixed link */ +#else + { + uint16_t id1, bsr; + phy_addr = phy_detect(); + if (phy_addr < 0) { + adjust_link(NXP_ETSEC_SPEED, 1); + return 0x100; /* datapath up, PHY not found */ + } + if (phy_init((uint32_t)phy_addr) < 0) + return -5; /* hard PHY-init failure */ + id1 = phy_read((uint32_t)phy_addr, PHY_ID1); + bsr = phy_read((uint32_t)phy_addr, PHY_BMSR); + adjust_link(NXP_ETSEC_SPEED, 1); + return (int)(((uint32_t)(id1 & 0xFF00U) << 8) | + ((bsr & BMSR_LINK) ? 0x100U : 0U) | + ((uint32_t)phy_addr & 0xFFU)); + } +#endif +} diff --git a/src/port/nxp_etsec/nxp_etsec.h b/src/port/nxp_etsec/nxp_etsec.h new file mode 100644 index 00000000..a8e38da9 --- /dev/null +++ b/src/port/nxp_etsec/nxp_etsec.h @@ -0,0 +1,182 @@ +/* nxp_etsec.h + * + * NXP eTSEC (Enhanced Three-Speed Ethernet Controller / Gianfar) driver for + * wolfIP. Targets the eTSEC MAC on big-endian PowerPC QorIQ/PQ parts + * (P1020/P1021/P2020, e500v2). Brings up one eTSEC in polled mode and + * exposes the wolfIP poll/send callbacks. + * + * Board parameters (CCSRBAR, eTSEC index, interface mode, PHY address) live + * in nxp_etsec_board.h and are overridable from CFLAGS. + * + * 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 WOLFIP_NXP_ETSEC_H +#define WOLFIP_NXP_ETSEC_H + +#include +#include "wolfip.h" +#include "nxp_etsec_board.h" + +/* ---- eTSEC block layout (offsets from CCSRBAR) ---------------------- */ +/* eTSEC1=0x24000, eTSEC2=0x25000, eTSEC3=0x26000 (step 0x1000). */ +#define ETSEC_BASE(n) (NXP_ETSEC_CCSRBAR + 0x24000UL + \ + ((uint32_t)(n) * 0x1000UL)) +/* The MDIO management block is in eTSEC1's window at +0x520. */ +#define ETSEC_MDIO_OFFSET 0x520UL + +/* ---- eTSEC register offsets (within a block) ------------------------ */ +#define ETSEC_IEVENT 0x010UL +#define ETSEC_IMASK 0x014UL +#define ETSEC_ECNTRL 0x020UL +#define ETSEC_MINFLR 0x024UL +#define ETSEC_DMACTRL 0x02CUL +#define ETSEC_TBIPA 0x030UL +#define ETSEC_TCTRL 0x100UL +#define ETSEC_TSTAT 0x104UL +#define ETSEC_TBPTR 0x184UL +#define ETSEC_TBASE 0x204UL +#define ETSEC_RCTRL 0x300UL +#define ETSEC_RSTAT 0x304UL +#define ETSEC_MRBLR 0x340UL +#define ETSEC_RBPTR 0x384UL +#define ETSEC_RBASE 0x404UL +#define ETSEC_MACCFG1 0x500UL +#define ETSEC_MACCFG2 0x504UL +#define ETSEC_IPGIFG 0x508UL +#define ETSEC_HAFDUP 0x50CUL +#define ETSEC_MAXFRM 0x510UL +#define ETSEC_MACSTNADDR1 0x540UL +#define ETSEC_MACSTNADDR2 0x544UL +#define ETSEC_IADDR0 0x800UL +#define ETSEC_GADDR0 0x880UL +#define ETSEC_ATTR 0xBC8UL +#define ETSEC_ATTRELI 0xBCCUL + +/* MACCFG1 */ +#define MACCFG1_SOFT_RESET 0x80000000UL +#define MACCFG1_RX_FLOW 0x00000020UL +#define MACCFG1_TX_FLOW 0x00000010UL +#define MACCFG1_RX_EN 0x00000004UL +#define MACCFG1_TX_EN 0x00000001UL + +/* MACCFG2 */ +#define MACCFG2_INIT_SETTINGS 0x00007205UL +#define MACCFG2_FULL_DUPLEX 0x00000001UL +#define MACCFG2_IF_MASK 0x00000300UL +#define MACCFG2_GMII 0x00000200UL /* byte (1000) */ +#define MACCFG2_MII 0x00000100UL /* nibble (10/100) */ + +/* ECNTRL */ +#define ECNTRL_INIT_SETTINGS 0x00001000UL +#define ECNTRL_TBI_MODE 0x00000020UL +#define ECNTRL_REDUCED_MODE 0x00000010UL /* RGMII/RMII reduced pins */ +#define ECNTRL_R100 0x00000008UL /* 100Mbps in reduced mode */ +#define ECNTRL_REDUCED_MII 0x00000004UL /* RMII */ +#define ECNTRL_SGMII_MODE 0x00000002UL + +/* DMACTRL */ +#define DMACTRL_INIT_SETTINGS 0x000000C3UL /* TDSEN|TBDSEN|WOP|... */ +#define DMACTRL_GRS 0x00000010UL +#define DMACTRL_GTS 0x00000008UL + +/* TSTAT / RSTAT (clear-on-write halt bits = ring doorbells). */ +#define TSTAT_CLEAR_THALT 0x80000000UL +#define RSTAT_CLEAR_RHALT 0x00800000UL + +/* IEVENT */ +#define IEVENT_INIT_CLEAR 0xFFFFFFFFUL +#define IEVENT_BSY 0x20000000UL +#define IEVENT_GRSC 0x00000100UL +#define IEVENT_GTSC 0x02000000UL + +/* RCTRL */ +#define RCTRL_PROM 0x00000008UL + +/* Misc init values. */ +#define MINFLR_INIT_SETTINGS 0x00000040UL +#define ATTR_INIT_SETTINGS 0x000000C0UL + +/* ---- MDIO management registers (eTSEC1 base + 0x520) ---------------- */ +#define MIIM_CFG 0x00UL +#define MIIM_COM 0x04UL +#define MIIM_ADD 0x08UL +#define MIIM_CON 0x0CUL /* write data */ +#define MIIM_STAT 0x10UL /* read data */ +#define MIIM_IND 0x14UL +#define MIIMCFG_RESET_MGMT 0x80000000UL +#define MIIMCFG_INIT_VALUE 0x00000003UL /* clock divider */ +#define MIIMCOM_READ_CYCLE 0x00000001UL +#define MIIMADD_PHY_SHIFT 8 +#define MIIMIND_BUSY 0x00000001UL +#define MIIMIND_NOTVALID 0x00000004UL + +/* ---- Buffer descriptor (big-endian, 8 bytes) ------------------------ */ +struct etsec_bd { + volatile uint16_t status; + volatile uint16_t length; + volatile uint32_t bufptr; +}; + +/* RX status bits */ +#define RXBD_EMPTY 0x8000U +#define RXBD_WRAP 0x2000U +#define RXBD_INTERRUPT 0x1000U +#define RXBD_LAST 0x0800U +#define RXBD_FIRST 0x0400U +#define RXBD_STATS 0x003FU /* error mask (clean frame == 0) */ + +/* TX status bits */ +#define TXBD_READY 0x8000U +#define TXBD_PADCRC 0x4000U +#define TXBD_WRAP 0x2000U +#define TXBD_INTERRUPT 0x1000U +#define TXBD_LAST 0x0800U +#define TXBD_CRC 0x0400U + +/* ---- Ring / buffer sizing ------------------------------------------- */ +#define ETSEC_RX_RING_SIZE 8U +#define ETSEC_TX_RING_SIZE 8U +#define ETSEC_MAX_BUF_LEN 1536U /* MRBLR, multiple of 64, >= MAXFRM */ +#define ETSEC_MAX_FRAME_LEN 1518U +#define ETSEC_BD_ALIGN 32U /* e500v2 cache line */ +#define ETSEC_BUF_ALIGN 64U +#define ETSEC_POLL_TRIES 1000000U + +/* ---- Public API ------------------------------------------------------ */ + +/* Soft-reset and bring up the selected eTSEC (MAC, BD rings, MDIO, PHY) and + * populate ll with the MAC address, interface name and poll/send callbacks. + * Pass mac=NULL for the built-in default. Returns a status word encoding the + * detected PHY id high byte, link bit and PHY address, or a negative value + * on hard failure. */ +int nxp_etsec_init(struct wolfIP_ll_dev *ll, const uint8_t *mac); + +/* Detected PHY MDIO address, or -1 (also -1 for a fixed link). */ +int nxp_etsec_phy_addr(void); + +/* MDIO clause-22 read of the detected PHY (0xFFFF on error / no PHY). */ +uint16_t nxp_etsec_phy_read(uint8_t reg); + +/* Non-zero when the link is up (always 1 for a fixed link). */ +uint32_t nxp_etsec_link_up(void); + +/* Optional debug log hook (NULL = disabled). */ +void nxp_etsec_set_log(void (*log_fn)(const char *msg)); + +#endif /* WOLFIP_NXP_ETSEC_H */ diff --git a/src/port/nxp_etsec/nxp_etsec_board.h b/src/port/nxp_etsec/nxp_etsec_board.h new file mode 100644 index 00000000..68e264a9 --- /dev/null +++ b/src/port/nxp_etsec/nxp_etsec_board.h @@ -0,0 +1,72 @@ +/* nxp_etsec_board.h + * + * Board parameters for the NXP eTSEC (Gianfar) ethernet driver + * (nxp_etsec.c). Defaults target the NXP P1021RDB (eTSEC1, RGMII). + * + * Every value here is overridable from CFLAGS or a consumer header so the + * same driver can serve other eTSEC boards (P1020/P1021/P2020, ...). + * + * 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 WOLFIP_NXP_ETSEC_BOARD_H +#define WOLFIP_NXP_ETSEC_BOARD_H + +/* CCSRBAR as seen by the booted application. wolfBoot RELOCATES CCSRBAR to + * 0xFFE00000 for the main/app stage on the P1021 (hal/nxp_ppc.h), so the + * eTSEC registers live there -- NOT at the 0xFF700000 reset default. */ +#ifndef NXP_ETSEC_CCSRBAR +#define NXP_ETSEC_CCSRBAR 0xFFE00000UL +#endif + +/* eTSEC index: 0 = eTSEC1, 1 = eTSEC2, 2 = eTSEC3. On the P1021RDB eTSEC1 + * is RGMII to the VSC7385 switch (fixed link, no external PHY); eTSEC2/3 + * are SGMII to external PHYs. */ +#ifndef NXP_ETSEC_INDEX +#define NXP_ETSEC_INDEX 0U +#endif + +/* MAC-to-PHY interface: 0 = RGMII, 1 = SGMII. */ +#ifndef NXP_ETSEC_IF_SGMII +#define NXP_ETSEC_IF_SGMII 0 +#endif + +/* Fixed link (no external PHY autoneg) -- use for the eTSEC1->switch RGMII + * uplink. When 1 the driver skips PHY detect and forces NXP_ETSEC_SPEED. */ +#ifndef NXP_ETSEC_FIXED_LINK +#define NXP_ETSEC_FIXED_LINK 1 +#endif + +/* Forced/expected link speed in Mbps (10/100/1000). */ +#ifndef NXP_ETSEC_SPEED +#define NXP_ETSEC_SPEED 1000 +#endif + +/* External PHY MDIO address (used when not a fixed link). The P1021RDB + * convention is eTSEC1/2/3 PHYs at 0x0/0x1/0x2. nxp_etsec_init() also scans + * the bus. All external MDIO goes through eTSEC1's MDIO window. */ +#ifndef NXP_ETSEC_PHY_ADDR +#define NXP_ETSEC_PHY_ADDR 0x1U +#endif + +/* TBI (internal SGMII PHY) MDIO address -- kept off the external 0..2 range. */ +#ifndef NXP_ETSEC_TBIPA +#define NXP_ETSEC_TBIPA 0x1FU +#endif + +#endif /* WOLFIP_NXP_ETSEC_BOARD_H */ diff --git a/src/port/nxp_fman/README.md b/src/port/nxp_fman/README.md new file mode 100644 index 00000000..91ed67a1 --- /dev/null +++ b/src/port/nxp_fman/README.md @@ -0,0 +1,79 @@ +# wolfIP NXP QorIQ FMan port + +Ethernet driver for the NXP QorIQ Frame Manager (FMan) multirate Ethernet MAC (mEMAC) and MDIO, shared across the big-endian PowerPC DPAA1 QorIQ parts: T2080 (e6500), T1024 and T1040 (e5500). One driver serves every board; all board-specific values come from `nxp_fman_board.h`. Lead bring-up target: Curtiss-Wright VPX3-152 (T2080, FM1@DTSEC1). + +## Model + +The board (DDR, clocks, MMU, UART) and the FMan firmware microcode are brought up by the boot stage (wolfBoot `hal_fman_init`). This driver runs in the booted application: it brings up a single mEMAC in polled independent (BMI direct) mode and exposes the two wolfIP link-device callbacks via `struct wolfIP_ll_dev` (`poll`/`send`). There is no QMan/BMan/DPAA datapath. + +It is consumed by the wolfBoot test-app (which links wolfIP core + this driver + the TFTP client). `nxp_fman.c` is the reusable driver; board parameters live in `nxp_fman_board.h`. + +## Files + +- `nxp_fman.h` - public API + mEMAC/MDIO register map (offsets from `FMAN_BASE`). +- `nxp_fman.c` - driver: big-endian register access, MDIO clause-22 read/write, PHY detect/reset/autoneg, SGMII PCS or RGMII interface select, and the wolfIP poll/send callbacks. +- `nxp_fman_board.h` - board parameters (CCSRBAR, mEMAC index, PHY address, MDIO clock divider, interface mode). All overridable from CFLAGS. +- `config.h` - wolfIP compile-time configuration for this port. + +## Board parameters + +All overridable from CFLAGS. The CCSRBAR default tracks the board define (`BOARD_CW_VPX3152` -> relocated `0xEF000000`, otherwise the QorIQ reset default `0xFE000000` for the RDB / NAII 68PPC2 / T1024 / T1040). `nxp_fman_init()` probes `NXP_FMAN_PHY_ADDR` first, then scans the MDIO bus and returns the first responder. On a multi-port board several DTSEC PHYs share one MDIO bus, so the scan can return the wrong port's PHY -- set `NXP_FMAN_PHY_ADDR` explicitly to the PHY wired to your `NXP_FMAN_MEMAC_IDX` port (read it from the board device tree). Likewise set the MAC-to-PHY interface (SGMII vs RGMII via `NXP_FMAN_IF_SGMII`) and the mEMAC/FM port per board if they differ from FM1@DTSEC1. + +| Define | VPX3-152 value | Notes | +|---|---|---| +| `NXP_FMAN_CCSRBAR` | `0xEF000000` | CW relocates CCSRBAR; RDB/NAII/T1024/T1040 default `0xFE000000` (auto by board define) | +| `NXP_FMAN_MEMAC_IDX` | `1` | FM1@DTSEC1 -> mEMAC1 | +| `NXP_FMAN_MDIO_EMI` | `1` | Dedicated 1G MDIO at `FMAN_BASE + 0xFC000` | +| `NXP_FMAN_PHY_ADDR` | `0x2` | AR8031 on FM1@DTSEC1 (probed first, then bus scan). Set explicitly on multi-port boards -- the scan returns the first responder, which may be another port's PHY (NAII DTSEC3 = addr `0x0`) | +| `NXP_FMAN_MDIO_CLKDIV` | `258` | QorIQ default | +| `NXP_FMAN_IF_SGMII` | `1` | `1` = SGMII (configures the internal PCS), `0` = RGMII | + +## Interface mode + +`NXP_FMAN_IF_SGMII=1` (default) programs the internal SGMII PCS/TBI for in-band auto-negotiation and sets the mEMAC `IF_MODE` to GMII. `NXP_FMAN_IF_SGMII=0` selects RGMII (`IF_MODE_RG`) and skips the PCS step. Both modes follow the PHY speed/duplex via in-band signalling; a board needing a fixed speed can clear `IF_MODE_EN_AUTO` and set `IF_MODE_SETSP_*` instead. + +## Status + +Hardware-verified on two T2080 boards: + +- **Curtiss-Wright VPX3-152** (FM1@DTSEC1, SGMII, AR8031 @ addr 2): MDIO clause-22 + PHY bring-up (id `0x004DD074`), SGMII PCS + mEMAC enable, and the full FMan independent-mode RX/TX datapath (MURAM parameter RAM, BMI ports, buffer-descriptor rings). The booted test-app reaches a DHCP lease, fetches a file over TFTP (checksum verified), and the TCP throughput benchmark (`WOLFIP_SPEED_TEST`) measures roughly 123 Mbps receive / 62 Mbps transmit from the polled single-loop stack (single-run, host driver via `nc`). +- **NAII 68PPC2** (FM1@DTSEC3 PRIME, **RGMII**, Marvell 88E1xxx id `0x0141` @ addr **0**): same driver, RGMII path (`NXP_FMAN_IF_SGMII=0`). Config flags `NXP_FMAN_MEMAC_IDX=3 NXP_FMAN_IF_SGMII=0 NXP_FMAN_PHY_ADDR=0` (from the board device tree `ethernet@e4000`); no driver code change. Booted test-app: `link=UP`, DHCP lease `10.0.4.247`, `WOLFIP_TEST: PASS`. This validated both interface modes (SGMII + RGMII) and the explicit-PHY-address path. + +- **T1040D4RDB** (e5500, FM1@DTSEC4 = the "ETH0" front port, **RGMII**, Realtek RTL8211 id `0x001CC915` @ addr **4**): same driver, two-stage wolfBoot boot. Config flags `NXP_FMAN_MEMAC_IDX=4 NXP_FMAN_IF_SGMII=0 NXP_FMAN_PHY_ADDR=4`; the cabled port was identified with the test app's `-DWOLFIP_PHY_SCAN` MDIO scan (only addr 4 reported link=UP). Booted test-app: DHCP lease `10.0.4.247`, `WOLFIP_TEST: PASS`. First e5500 datapath validation (the prior two boards are e6500). + +The T1024 reuses the driver unchanged (same FMan offsets, reset-default CCSRBAR); its demo is build-validated and awaits hardware. + +### Per-board bring-up matrix + +The driver is one file; each board differs only in which FMan port is cabled and how its PHY is wired. Set these from the board device tree (or run the test app's `-DWOLFIP_PHY_SCAN` once to find the port whose PHY reports `link=UP`). All values are `CFLAGS_EXTRA+=-D...`. + +| Board | `NXP_FMAN_MEMAC_IDX` | `NXP_FMAN_IF_SGMII` | `NXP_FMAN_PHY_ADDR` | PHY | Throughput RX/TX | +|---|---|---|---|---|---| +| CW VPX3-152 (T2080) | 1 (default) | 1 (default) | 2 | AR8031 | ~123 / ~62 Mbps | +| NAII 68PPC2 (T2080) | 3 | 0 | 0 | Marvell 88E1xxx | (not benchmarked) | +| T1040D4RDB | 4 | 0 | 4 | Realtek RTL8211 | (pending) | +| T1024RDB | TBD | TBD | TBD | TBD | (awaits hardware) | + +Finding the port on a new board: build the test app with `-DWOLFIP_PHY_SCAN`, boot, and read the `wolfIP: MDIO scan` lines -- the address reporting `link=UP` is the cabled port. Cross-reference the board DTB (`ethernet@eN000` `phy-handle` -> `ethernet-phy@A reg=` and `phy-connection-type`) to confirm the mEMAC index and SGMII-vs-RGMII. + +### Running the throughput benchmark + +Build with `WOLFIP_SPEED_TEST=1`; the board acquires a DHCP lease then runs a one-connection TCP server on port 9 that both sinks (RX) and sources (TX), printing `SPEED done RX (~) TX (~)` when the connection closes. Drive each direction separately from a host on the same subnet (`` = the board's DHCP address): + +``` +# RX (board receives ~73 MB, then prints): -q1 closes the socket on EOF +dd if=/dev/zero bs=1460 count=50000 | nc -q1 9 >/dev/null +# TX (board sources for ~8 s, host reads): timeout closes the socket +timeout 8 nc 9 /dev/null +``` + +`nxp_fman_init()` populates the `struct wolfIP_ll_dev` (mac/ifname/mtu/poll/send), runs the FMan common init + per-port setup + interface config + enable, then detects the PHY (`nxp_fman_phy_read()` reads PHY registers for diagnostics). `eth_poll()`/`eth_send()` move frames through the BD rings with explicit e5500/e6500 cache maintenance (`dcbf`). + +## Build + +The driver is built by its consumer (the wolfBoot test-app) with the PowerPC cross-compiler, e.g.: + +``` +powerpc-linux-gnu-gcc -mcpu=e6500 -mbig-endian -I -Isrc/port/nxp_fman \ + -c src/port/nxp_fman/nxp_fman.c +``` diff --git a/src/port/nxp_fman/config.h b/src/port/nxp_fman/config.h new file mode 100644 index 00000000..06f76af4 --- /dev/null +++ b/src/port/nxp_fman/config.h @@ -0,0 +1,68 @@ +/* config.h + * + * wolfIP configuration for the NXP QorIQ FMan port (T2080 / T1024 / T1040). + * + * 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 +#define TXBUF_SIZE LINK_MTU + +#define MAX_NEIGHBORS 8 +#define WOLFIP_ARP_PENDING_MAX 2 + +#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 1 +#endif + +/* Static IP fallback (used when DHCP is disabled or times out). */ +#define WOLFIP_IP "192.168.1.10" +#define WOLFIP_NETMASK "255.255.255.0" +#define WOLFIP_GW "192.168.1.1" +#define WOLFIP_STATIC_DNS_IP "8.8.8.8" + +#if WOLFIP_ENABLE_DHCP +#define DHCP +#endif + +#endif /* WOLF_CONFIG_H */ diff --git a/src/port/nxp_fman/nxp_fman.c b/src/port/nxp_fman/nxp_fman.c new file mode 100644 index 00000000..132eb785 --- /dev/null +++ b/src/port/nxp_fman/nxp_fman.c @@ -0,0 +1,781 @@ +/* nxp_fman.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 + * + * NXP QorIQ FMan (Frame Manager) ethernet driver for wolfIP. + * + * Common to the DPAA1 QorIQ parts (T2080/e6500, T1024 and T1040/e5500): + * a single mEMAC in polled independent (BMI-direct) mode, no QMan/BMan. + * The FMan microcode is uploaded by the boot stage (wolfBoot + * hal_fman_init); this driver runs the FMan common init (QMI/FPM/BMI), + * per-port parameter RAM, BMI ports, mEMAC + (SGMII or RGMII) PHY + * interface, and the polled RX/TX buffer-descriptor datapath. Ported + * from the QorIQ U-Boot FMan driver (drivers/net/fm/). All board-specific + * values (CCSRBAR, mEMAC index, PHY address, interface mode) come from + * nxp_fman_board.h so the one driver serves every QorIQ board. + */ + +#include +#include +#include "config.h" +#include "nxp_fman.h" + +/* Local byte-loop mem helpers. The driver is linked into bare-metal images + * (e.g. the wolfBoot test-app) whose startup performs no PLT/GOT fixup, so + * we avoid external libc calls (a variable-size memcpy would otherwise be + * routed through an uninitialized PLT stub and branch to address 0). */ +static void *fman_memcpy(void *dst, const void *src, uint32_t n) +{ + uint8_t *d = (uint8_t *)dst; + const uint8_t *s = (const uint8_t *)src; + while (n--) + *d++ = *s++; + return dst; +} +static void fman_memset(void *dst, int c, uint32_t n) +{ + uint8_t *d = (uint8_t *)dst; + while (n--) + *d++ = (uint8_t)c; +} + +/* ---- Big-endian CCSR register access (sync/twi/isync like wolfBoot) --- */ +#if defined(__powerpc__) || defined(__PPC__) || defined(__ppc__) +static inline uint32_t fman_rd32(uintptr_t addr) +{ + uint32_t ret; + __asm__ __volatile__( + "sync;\n" "lwz %0,0(%1);\n" "twi 0,%0,0;\n" "isync" + : "=r"(ret) : "r"(addr) : "memory"); + return ret; +} +static inline void fman_wr32(uintptr_t addr, uint32_t val) +{ + __asm__ __volatile__( + "sync;\n" "stw %0,0(%1)" : : "r"(val), "r"(addr) : "memory"); +} +static inline void fman_sync(void) +{ + __asm__ __volatile__("sync" ::: "memory"); +} +/* e5500/e6500 data cache line = 64 bytes. We use dcbf (flush + invalidate) + * for both directions: dcbi (invalidate-only) is privileged/unreliable on + * Book-E e5500/e6500, and for our access pattern the descriptor/buffer lines + * are clean when re-read (flushed at recycle, not re-dirtied), so dcbf acts + * as an invalidate (no stale write-back) before the FMan-DMA'd data is read. */ +static void dcache_flush(const void *p, uint32_t len) +{ + uintptr_t a = (uintptr_t)p & ~63UL; + uintptr_t end = (uintptr_t)p + len; + for (; a < end; a += 64) + __asm__ __volatile__("dcbf 0,%0" : : "r"(a) : "memory"); + fman_sync(); +} +static void dcache_inval(const void *p, uint32_t len) +{ + dcache_flush(p, len); +} +#else +/* Portable fallbacks so the file compiles for host syntax checks. */ +static inline uint32_t fman_rd32(uintptr_t addr) { return *(volatile uint32_t *)addr; } +static inline void fman_wr32(uintptr_t addr, uint32_t val) { *(volatile uint32_t *)addr = val; } +static inline void fman_sync(void) { } +static void dcache_flush(const void *p, uint32_t len) { (void)p; (void)len; } +static void dcache_inval(const void *p, uint32_t len) { (void)p; (void)len; } +#endif + +/* Optional debug log hook (set by the application). */ +static void (*g_log)(const char *) = 0; +void nxp_fman_set_log(void (*log_fn)(const char *)) { g_log = log_fn; } +#define FLOG(s) do { if (g_log) g_log(s); } while (0) + +/* MURAM is 32-bit access only; 16-bit fields need a read-modify-write of + * the containing 32-bit big-endian word. */ +static void muram_writew(uintptr_t addr, uint16_t val) +{ + uintptr_t w = addr & ~3UL; + uint32_t cur = fman_rd32(w); + if (addr & 2) + cur = (cur & 0xFFFF0000UL) | (uint32_t)val; + else + cur = (cur & 0x0000FFFFUL) | ((uint32_t)val << 16); + fman_wr32(w, cur); +} +static uint16_t muram_readw(uintptr_t addr) +{ + uint32_t cur = fman_rd32(addr & ~3UL); + return (addr & 2) ? (uint16_t)(cur & 0xFFFFU) : (uint16_t)(cur >> 16); +} + +/* ---- MURAM bump allocator -------------------------------------------- */ +static uint32_t muram_ptr; +static uint32_t muram_end; + +static void muram_init(void) +{ + muram_ptr = (uint32_t)FMAN_MURAM_BASE + (uint32_t)FM_MURAM_RES_SIZE; + muram_end = (uint32_t)FMAN_MURAM_BASE + (uint32_t)FM_MURAM_TOTAL; +} + +static uint32_t muram_alloc(uint32_t size, uint32_t align) +{ + uint32_t off, ret, a; + off = muram_ptr & (align - 1U); + if (off != 0U) + muram_ptr += (align - off); + off = size & (align - 1U); + if (off != 0U) + size += (align - off); + if ((muram_ptr + size) >= muram_end) + return 0; /* out of MURAM */ + ret = muram_ptr; + muram_ptr += size; + for (a = ret; a < ret + size; a += 4U) + fman_wr32(a, 0); + return ret; +} + +/* ---- Buffer descriptor (big-endian, 16 bytes) ------------------------ */ +struct fm_bd { + volatile uint16_t status; + volatile uint16_t len; + volatile uint32_t res0; + volatile uint16_t res1; + volatile uint16_t buf_ptr_hi; + volatile uint32_t buf_ptr_lo; +}; + +static struct fm_bd rx_bd_ring[RX_BD_RING_SIZE] __attribute__((aligned(64))); +static struct fm_bd tx_bd_ring[TX_BD_RING_SIZE] __attribute__((aligned(64))); +static uint8_t rx_buf_pool[RX_BD_RING_SIZE][MAX_RXBUF_LEN] __attribute__((aligned(64))); +static uint8_t tx_buf_pool[TX_BD_RING_SIZE][MAX_TXBUF_LEN] __attribute__((aligned(64))); + +static uint32_t rx_pram; /* MURAM address of RX parameter RAM */ +static uint32_t tx_pram; /* MURAM address of TX parameter RAM */ +static uint32_t rx_idx; +static uint32_t tx_idx; +static int32_t phy_addr = -1; + +/* ---- PHY (clause 22) register map ------------------------------------ */ +#define PHY_BMCR 0x00U +#define PHY_BMSR 0x01U +#define PHY_ID1 0x02U +#define PHY_ID2 0x03U +#define PHY_ANAR 0x04U + +#define BMCR_RESET (1U << 15) +#define BMCR_AUTONEG_EN (1U << 12) +#define BMCR_POWER_DOWN (1U << 11) +#define BMCR_ISOLATE (1U << 10) +#define BMCR_RESTART_ANEG (1U << 9) +#define BMSR_LINK (1U << 2) +#define BMSR_ANEG_COMPLETE (1U << 5) +#define ANAR_DEFAULT 0x01E1U + +#define MDIO_TIMEOUT 1000000U + +/* MDIO controller register offsets relative to a controller base. */ +#define MDIO_OFF_STAT 0x30UL +#define MDIO_OFF_CTRL 0x34UL +#define MDIO_OFF_DATA 0x38UL +#define MDIO_OFF_ADDR 0x3CUL + +#define DEDICATED_MDIO_BASE FMAN_MDIO_BASE(NXP_FMAN_MDIO_EMI) +#define INTERNAL_PCS_BASE FMAN_MEMAC_MDIO_BASE(NXP_FMAN_MEMAC_IDX) + +/* ---- MDIO clause-22 access (controller base parameterized) ----------- */ +static void mdio_setup(uintptr_t mbase) +{ + fman_wr32(mbase + MDIO_OFF_STAT, + MDIO_STAT_CLKDIV(NXP_FMAN_MDIO_CLKDIV) | MDIO_STAT_NEG); +} + +static int mdio_wait_bsy(uintptr_t mbase) +{ + uint32_t t = MDIO_TIMEOUT; + while ((fman_rd32(mbase + MDIO_OFF_STAT) & MDIO_STAT_BSY) && --t) { + } + return t ? 0 : -1; +} + +static int mdio_wait_data(uintptr_t mbase) +{ + uint32_t t = MDIO_TIMEOUT; + while ((fman_rd32(mbase + MDIO_OFF_DATA) & MDIO_DATA_BSY) && --t) { + } + return t ? 0 : -1; +} + +static uint16_t mdio_read_base(uintptr_t mbase, uint32_t phy, uint32_t reg) +{ + uint32_t cfg, ctl; + + cfg = fman_rd32(mbase + MDIO_OFF_STAT); + fman_wr32(mbase + MDIO_OFF_STAT, cfg & ~MDIO_STAT_EN_C45); + if (mdio_wait_bsy(mbase) != 0) + return 0xFFFFU; + + ctl = MDIO_CTL_PORT_ADDR(phy) | MDIO_CTL_DEV_ADDR(reg); + fman_wr32(mbase + MDIO_OFF_CTRL, ctl); + if (mdio_wait_bsy(mbase) != 0) + return 0xFFFFU; + + fman_wr32(mbase + MDIO_OFF_CTRL, ctl | MDIO_CTL_READ); + if (mdio_wait_data(mbase) != 0) + return 0xFFFFU; + if (fman_rd32(mbase + MDIO_OFF_STAT) & MDIO_STAT_RD_ER) + return 0xFFFFU; + + return (uint16_t)(fman_rd32(mbase + MDIO_OFF_DATA) & 0xFFFFU); +} + +static int mdio_write_base(uintptr_t mbase, uint32_t phy, uint32_t reg, + uint16_t value) +{ + uint32_t cfg; + + cfg = fman_rd32(mbase + MDIO_OFF_STAT); + fman_wr32(mbase + MDIO_OFF_STAT, cfg & ~MDIO_STAT_EN_C45); + if (mdio_wait_bsy(mbase) != 0) + return -1; + + fman_wr32(mbase + MDIO_OFF_CTRL, MDIO_CTL_PORT_ADDR(phy) | MDIO_CTL_DEV_ADDR(reg)); + if (mdio_wait_bsy(mbase) != 0) + return -1; + + fman_wr32(mbase + MDIO_OFF_DATA, MDIO_DATA_VAL(value)); + if (mdio_wait_data(mbase) != 0) + return -1; + return 0; +} + +/* PHY accessors use the dedicated 1G MDIO bus. */ +static uint16_t mdio_read(uint32_t phy, uint32_t reg) +{ + return mdio_read_base(DEDICATED_MDIO_BASE, phy, reg); +} +static int mdio_write(uint32_t phy, uint32_t reg, uint16_t value) +{ + return mdio_write_base(DEDICATED_MDIO_BASE, phy, reg, value); +} + +/* ---- PHY discovery / link -------------------------------------------- */ +static int32_t phy_detect(void) +{ + uint32_t addr; + uint16_t id; + + id = mdio_read(NXP_FMAN_PHY_ADDR, PHY_ID1); + if (id != 0xFFFFU && id != 0x0000U) + return (int32_t)NXP_FMAN_PHY_ADDR; + for (addr = 0; addr < 32U; addr++) { + id = mdio_read(addr, PHY_ID1); + if (id != 0xFFFFU && id != 0x0000U) + return (int32_t)addr; + } + return -1; +} + +int nxp_fman_phy_addr(void) +{ + return (int)phy_addr; +} + +uint16_t nxp_fman_phy_read(uint8_t reg) +{ + if (phy_addr < 0) + return 0xFFFFU; + return mdio_read((uint32_t)phy_addr, reg); +} + +/* Read a register from an explicit MDIO address (bus diagnostics / scan). + * Unlike nxp_fman_phy_read() this does not use the auto-detected phy_addr, + * so it can probe every address on the shared MDIO bus. Requires the FMan + * MDIO to have been set up (nxp_fman_init() called first). */ +uint16_t nxp_fman_phy_read_at(uint8_t addr, uint8_t reg) +{ + return mdio_read((uint32_t)addr, reg); +} + +uint32_t nxp_fman_link_up(void) +{ + uint16_t bsr; + if (phy_addr < 0) + return 0; + bsr = mdio_read((uint32_t)phy_addr, PHY_BMSR); + bsr = mdio_read((uint32_t)phy_addr, PHY_BMSR); + return (bsr & BMSR_LINK) ? 1U : 0U; +} + +/* Returns 0 on success, <0 on a hard failure (PHY reset never cleared or an + * MDIO write failed), or 1 if auto-negotiation did not complete in time (a + * soft condition, e.g. no cable -- the caller can still bring the datapath + * up and read the live link state). */ +static int phy_init(uint32_t phy) +{ + uint32_t timeout; + uint16_t ctrl, bsr; + + if (mdio_write(phy, PHY_BMCR, BMCR_RESET) != 0) + return -1; + timeout = MDIO_TIMEOUT; + do { + ctrl = mdio_read(phy, PHY_BMCR); + } while ((ctrl & BMCR_RESET) && --timeout); + if (timeout == 0) + return -1; /* reset never cleared: PHY absent/stuck */ + + if (mdio_write(phy, PHY_ANAR, ANAR_DEFAULT) != 0) + return -1; + + ctrl &= ~(BMCR_POWER_DOWN | BMCR_ISOLATE); + ctrl |= BMCR_AUTONEG_EN | BMCR_RESTART_ANEG; + if (mdio_write(phy, PHY_BMCR, ctrl) != 0) + return -1; + + timeout = MDIO_TIMEOUT; + do { + bsr = mdio_read(phy, PHY_BMSR); + } while (!(bsr & BMSR_ANEG_COMPLETE) && --timeout); + if (timeout == 0) + return 1; /* autoneg incomplete (link likely down) */ + return 0; +} + +/* ---- FMan common init (QMI / FPM / DMA / BMI) ------------------------- */ +static void fm_init_qmi(void) +{ + uint32_t v = fman_rd32(FMAN_QMI_FMQM_GC); + fman_wr32(FMAN_QMI_FMQM_GC, v & ~(FMQM_GC_ENQ_EN | FMQM_GC_DEQ_EN)); + fman_wr32(FMAN_QMI_FMQM_EIEN, 0); + fman_wr32(FMAN_QMI_FMQM_EIE, FMQM_EIE_CLEAR_ALL); + fman_wr32(FMAN_QMI_FMQM_IEN, 0); + fman_wr32(FMAN_QMI_FMQM_IE, FMQM_IE_CLEAR_ALL); +} + +static uint32_t fm_assign_risc(uint32_t port_id) +{ + uint32_t risc_sel, val; + risc_sel = (port_id & 0x1U) ? FMFPPRC_RISC2 : FMFPPRC_RISC1; + val = (port_id << FMFPPRC_PORTID_SHIFT) & 0x3F000000UL; + val |= ((risc_sel << FMFPPRC_ORA_SHIFT) | risc_sel); + return val; +} + +static void fm_init_fpm(uint32_t rx_pid, uint32_t tx_pid) +{ + uint32_t v; + int i; + + v = fman_rd32(FMAN_FPM_FMFPEE); + fman_wr32(FMAN_FPM_FMFPEE, + v | (FMFPEE_EHM | FMFPEE_UEC | FMFPEE_CER | FMFPEE_DER)); + + /* Assign the RISC engine for our RX and TX IM ports. */ + fman_wr32(FMAN_FPM_FPMPRC, fm_assign_risc(rx_pid)); + fman_wr32(FMAN_FPM_FPMPRC, fm_assign_risc(tx_pid)); + + fman_wr32(FMAN_FPM_FPMFLC, FMFP_FLC_DISP_LIM_NONE); + fman_wr32(FMAN_FPM_FMFPEE, FMFPEE_CLEAR_EVENT); + for (i = 0; i < 4; i++) + fman_wr32(FMAN_FPM_FPMCEV(i), 0xFFFFFFFFUL); + fman_wr32(FMAN_FPM_FPMRCR, FMFP_RCR_MDEC | FMFP_RCR_IDEC); +} + +static void fm_init_dma(void) +{ + uint32_t v; + v = fman_rd32(FMAN_DMA_FMDMSR); + fman_wr32(FMAN_DMA_FMDMSR, v | FMDMSR_CLEAR_ALL); + v = fman_rd32(FMAN_DMA_FMDMMR); + fman_wr32(FMAN_DMA_FMDMMR, v | FMDMMR_SBER); +} + +static int fm_init_bmi(uint32_t rx_pid, uint32_t tx_pid) +{ + uint32_t base, offset, val, blk; + + base = muram_alloc((uint32_t)FM_FREE_POOL_SIZE, (uint32_t)FM_FREE_POOL_ALIGN); + if (base == 0) + return -1; + offset = base - (uint32_t)FMAN_MURAM_BASE; + + val = offset / 256U; + blk = (uint32_t)FM_FREE_POOL_SIZE / 256U; + val |= ((blk - 1U) << FMBM_CFG1_FBPS_SHIFT); + fman_wr32(FMAN_BMI_FMBM_CFG1, val); + + fman_wr32(FMAN_BMI_FMBM_IER, FMBM_IER_DISABLE_ALL); + fman_wr32(FMAN_BMI_FMBM_IEVR, FMBM_IEVR_CLEAR_ALL); + + /* size the FIFO for our 1G RX and TX ports (4 tasks, 4KB FIFO) */ + fman_wr32(FMAN_BMI_FMBM_PP(rx_pid), FMBM_PP_MXT(4)); + fman_wr32(FMAN_BMI_FMBM_PFS(rx_pid), FMBM_PFS_IFSZ(0xF)); + fman_wr32(FMAN_BMI_FMBM_PP(tx_pid), FMBM_PP_MXT(4)); + fman_wr32(FMAN_BMI_FMBM_PFS(tx_pid), FMBM_PFS_IFSZ(0xF)); + + fman_wr32(FMAN_BMI_FMBM_INIT, FMBM_INIT_START); + return 0; +} + +/* ---- Per-port parameter RAM + BD rings ------------------------------- */ +static int fm_rx_param_init(uint32_t rx_pid) +{ + uint32_t page_off, qd; + uint32_t i; + + rx_pram = muram_alloc((uint32_t)FM_PRAM_SIZE, (uint32_t)FM_PRAM_ALIGN); + if (rx_pram == 0) + return -1; + page_off = rx_pram - (uint32_t)FMAN_MURAM_BASE; + + fman_wr32(rx_pram + PRAM_OFF_MODE, PRAM_MODE_GLOBAL); + fman_wr32(rx_pram + PRAM_OFF_RXQD_PTR, page_off + (uint32_t)PRAM_OFF_RXQD); + muram_writew(rx_pram + PRAM_OFF_MRBLR, MAX_RXBUF_LOG2); + + for (i = 0; i < RX_BD_RING_SIZE; i++) { + rx_bd_ring[i].status = RxBD_EMPTY; + rx_bd_ring[i].len = 0; + rx_bd_ring[i].buf_ptr_hi = 0; + rx_bd_ring[i].buf_ptr_lo = (uint32_t)(uintptr_t)&rx_buf_pool[i][0]; + } + dcache_flush(rx_bd_ring, sizeof(rx_bd_ring)); + /* Clean+invalidate the RX buffers before the FMan DMA owns them, so a + * later write-back of a dirty (e.g. BSS-zeroed) line cannot clobber a + * DMA-written frame. */ + dcache_flush(rx_buf_pool, sizeof(rx_buf_pool)); + + qd = rx_pram + (uint32_t)PRAM_OFF_RXQD; + muram_writew(qd + QD_OFF_GEN, 0); + muram_writew(qd + QD_OFF_BD_BASE_HI, 0); + fman_wr32(qd + QD_OFF_BD_BASE_LO, (uint32_t)(uintptr_t)rx_bd_ring); + muram_writew(qd + QD_OFF_BD_RING_SIZE, + (uint16_t)(sizeof(struct fm_bd) * RX_BD_RING_SIZE)); + muram_writew(qd + QD_OFF_OFFSET_IN, 0); + muram_writew(qd + QD_OFF_OFFSET_OUT, 0); + + fman_wr32(FMAN_BMI_PORT(rx_pid) + BMI_RX_RFQID, page_off); + rx_idx = 0; + return 0; +} + +static int fm_tx_param_init(uint32_t tx_pid) +{ + uint32_t page_off, qd; + uint32_t i; + + tx_pram = muram_alloc((uint32_t)FM_PRAM_SIZE, (uint32_t)FM_PRAM_ALIGN); + if (tx_pram == 0) + return -1; + page_off = tx_pram - (uint32_t)FMAN_MURAM_BASE; + + fman_wr32(tx_pram + PRAM_OFF_MODE, PRAM_MODE_GLOBAL); + fman_wr32(tx_pram + PRAM_OFF_TXQD_PTR, page_off + (uint32_t)PRAM_OFF_TXQD); + + for (i = 0; i < TX_BD_RING_SIZE; i++) { + tx_bd_ring[i].status = TxBD_LAST; + tx_bd_ring[i].len = 0; + tx_bd_ring[i].buf_ptr_hi = 0; + tx_bd_ring[i].buf_ptr_lo = 0; + } + dcache_flush(tx_bd_ring, sizeof(tx_bd_ring)); + + qd = tx_pram + (uint32_t)PRAM_OFF_TXQD; + muram_writew(qd + QD_OFF_BD_BASE_HI, 0); + fman_wr32(qd + QD_OFF_BD_BASE_LO, (uint32_t)(uintptr_t)tx_bd_ring); + muram_writew(qd + QD_OFF_BD_RING_SIZE, + (uint16_t)(sizeof(struct fm_bd) * TX_BD_RING_SIZE)); + muram_writew(qd + QD_OFF_OFFSET_IN, 0); + muram_writew(qd + QD_OFF_OFFSET_OUT, 0); + + fman_wr32(FMAN_BMI_PORT(tx_pid) + BMI_TX_TCFQID, page_off); + tx_idx = 0; + return 0; +} + +/* ---- BMI ports (independent mode) ------------------------------------ */ +static void bmi_rx_port_init(uint32_t rx_pid) +{ + uintptr_t b = FMAN_BMI_PORT(rx_pid); + uint32_t v; + + fman_wr32(b + BMI_RX_RCFG, FMBM_RCFG_IM); + fman_wr32(b + BMI_RX_RIM, 0); + fman_wr32(b + BMI_RX_RFNE, NIA_ENG_RISC | NIA_RISC_AC_IM_RX); + v = fman_rd32(b + BMI_RX_RFCA); + v &= ~(FMBM_RFCA_ORDER | FMBM_RFCA_MR_MASK); + v |= FMBM_RFCA_MR(4); + fman_wr32(b + BMI_RX_RFCA, v); + fman_wr32(b + BMI_RX_RSTC, FMBM_RSTC_EN); + fman_wr32(b + BMI_RX_RPC, 0); +} + +static void bmi_tx_port_init(uint32_t tx_pid) +{ + uintptr_t b = FMAN_BMI_PORT(tx_pid); + uint32_t v; + + fman_wr32(b + BMI_TX_TCFG, FMBM_TCFG_IM); + fman_wr32(b + BMI_TX_TFNE, NIA_ENG_RISC | NIA_RISC_AC_IM_TX); + fman_wr32(b + BMI_TX_TFENE, NIA_ENG_RISC | NIA_RISC_AC_IM_TX); + v = fman_rd32(b + BMI_TX_TFCA); + v &= ~(FMBM_TFCA_ORDER | FMBM_TFCA_MR_MASK); + v |= FMBM_TFCA_MR(4); + fman_wr32(b + BMI_TX_TFCA, v); + fman_wr32(b + BMI_TX_TSTC, FMBM_TSTC_EN); + fman_wr32(b + BMI_TX_TPC, 0); +} + +/* ---- mEMAC + SGMII PCS ----------------------------------------------- */ +static void memac_init(uint32_t idx, const uint8_t *mac) +{ + uint32_t a0, a1; + + fman_wr32(FMAN_MEMAC_IMASK(idx), IMASK_MASK_ALL); + fman_wr32(FMAN_MEMAC_IEVENT(idx), IEVENT_CLEAR_ALL); + fman_wr32(FMAN_MEMAC_MAXFRMG(idx), MAX_RXBUF_LEN & MEMAC_MAXFRM_MASK); + fman_wr32(FMAN_MEMAC_HTBLE_CTRL(idx), 0); + + /* MAC 0x..:..:CD -> ADDR_0=0x..3210, ADDR_1=0x0000..54 */ + a0 = ((uint32_t)mac[3] << 24) | ((uint32_t)mac[2] << 16) | + ((uint32_t)mac[1] << 8) | (uint32_t)mac[0]; + a1 = (((uint32_t)mac[5] << 8) | (uint32_t)mac[4]) & 0x0000FFFFUL; + fman_wr32(FMAN_MEMAC_MAC_ADDR_0(idx), a0); + fman_wr32(FMAN_MEMAC_MAC_ADDR_1(idx), a1); +} + +/* Select the mEMAC-to-PHY interface. SGMII drives the GMII pins behind the + * internal PCS; RGMII selects the RGMII pins (IF_MODE_RG). Both follow the + * link speed/duplex via in-band signalling (EN_AUTO). A board that needs a + * fixed speed can clear EN_AUTO and set IF_MODE_SETSP_* instead. */ +static void memac_set_interface(uint32_t idx) +{ + uint32_t v = fman_rd32(FMAN_MEMAC_IF_MODE(idx)); + v &= ~(IF_MODE_MASK | IF_MODE_RG | IF_MODE_RM); + v |= IF_MODE_GMII | IF_MODE_EN_AUTO; +#if !NXP_FMAN_IF_SGMII + v |= IF_MODE_RG; +#endif + fman_wr32(FMAN_MEMAC_IF_MODE(idx), v); +} + +#if NXP_FMAN_IF_SGMII +/* Program the internal SGMII PCS (TBI) for auto-negotiation. */ +static void sgmii_configure_serdes(uint32_t idx) +{ + uintptr_t pcs = FMAN_MEMAC_MDIO_BASE(idx); + mdio_setup(pcs); + mdio_write_base(pcs, 0, 0x14, + PHY_SGMII_IF_MODE_AN | PHY_SGMII_IF_MODE_SGMII); + mdio_write_base(pcs, 0, 0x04, PHY_SGMII_DEV_ABILITY_SGMII); + mdio_write_base(pcs, 0, 0x13, 0x0003); + mdio_write_base(pcs, 0, 0x12, 0x0D40); + mdio_write_base(pcs, 0, 0x00, + PHY_SGMII_CR_DEF_VAL | PHY_SGMII_CR_RESET_AN); +} +#endif /* NXP_FMAN_IF_SGMII */ + +static void fm_port_enable(uint32_t rx_pid, uint32_t tx_pid, uint32_t idx) +{ + uintptr_t brx = FMAN_BMI_PORT(rx_pid); + uintptr_t btx = FMAN_BMI_PORT(tx_pid); + uint32_t v; + + v = fman_rd32(brx + BMI_RX_RCFG); + fman_wr32(brx + BMI_RX_RCFG, v | FMBM_RCFG_EN); + + v = fman_rd32(FMAN_MEMAC_CMD_CFG(idx)); + fman_wr32(FMAN_MEMAC_CMD_CFG(idx), + v | MEMAC_CMD_CFG_RXTX_EN | MEMAC_CMD_CFG_NO_LEN_CHK); + + v = fman_rd32(btx + BMI_TX_TCFG); + fman_wr32(btx + BMI_TX_TCFG, v | FMBM_TCFG_EN); + + /* release TX from graceful stop */ + v = fman_rd32(tx_pram + PRAM_OFF_MODE); + fman_wr32(tx_pram + PRAM_OFF_MODE, v & ~PRAM_MODE_GRACEFUL_STOP); +} + +/* ---- wolfIP poll/send callbacks -------------------------------------- */ +static int eth_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + struct fm_bd *bd = &tx_bd_ring[tx_idx]; + uint32_t qd = tx_pram + (uint32_t)PRAM_OFF_TXQD; + uint16_t off_in, ring_sz; + (void)dev; + + if (len == 0 || len > MAX_TXBUF_LEN) + return -1; + + dcache_inval(bd, sizeof(*bd)); + if (bd->status & TxBD_READY) + return -2; /* ring full */ + + fman_memcpy(tx_buf_pool[tx_idx], frame, len); + if (len < 60U) { + fman_memset(tx_buf_pool[tx_idx] + len, 0, 60U - len); + len = 60U; + } + dcache_flush(tx_buf_pool[tx_idx], len); + + bd->buf_ptr_hi = 0; + bd->buf_ptr_lo = (uint32_t)(uintptr_t)tx_buf_pool[tx_idx]; + bd->len = (uint16_t)len; + fman_sync(); + bd->status = TxBD_READY | TxBD_LAST; + dcache_flush(bd, sizeof(*bd)); + + /* doorbell: advance TxQD offset_in (kicks the RISC) */ + off_in = muram_readw(qd + QD_OFF_OFFSET_IN); + ring_sz = muram_readw(qd + QD_OFF_BD_RING_SIZE); + off_in += (uint16_t)sizeof(struct fm_bd); + if (off_in >= ring_sz) + off_in = 0; + muram_writew(qd + QD_OFF_OFFSET_IN, off_in); + fman_sync(); + + tx_idx = (tx_idx + 1U) % TX_BD_RING_SIZE; + return (int)len; +} + +static int eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + struct fm_bd *bd = &rx_bd_ring[rx_idx]; + uint32_t qd = rx_pram + (uint32_t)PRAM_OFF_RXQD; + uint16_t status, flen = 0, off_out, ring_sz; + int err; + (void)dev; + + dcache_inval(bd, sizeof(*bd)); + status = bd->status; + if (status & RxBD_EMPTY) + return 0; + + err = (status & RxBD_ERROR) ? 1 : 0; + if (!err) { + flen = bd->len; + if (flen > MAX_RXBUF_LEN) + flen = MAX_RXBUF_LEN; + if (flen > len) + flen = (uint16_t)len; + if (flen > 0) { + dcache_inval(rx_buf_pool[rx_idx], flen); + fman_memcpy(frame, rx_buf_pool[rx_idx], flen); + } + } + + /* recycle the BD */ + bd->status = RxBD_EMPTY; + bd->len = 0; + dcache_flush(bd, sizeof(*bd)); + + off_out = muram_readw(qd + QD_OFF_OFFSET_OUT); + ring_sz = muram_readw(qd + QD_OFF_BD_RING_SIZE); + off_out += (uint16_t)sizeof(struct fm_bd); + if (off_out >= ring_sz) + off_out = 0; + muram_writew(qd + QD_OFF_OFFSET_OUT, off_out); + fman_sync(); + + rx_idx = (rx_idx + 1U) % RX_BD_RING_SIZE; + return err ? 0 : (int)flen; +} + +/* ---- Public init ----------------------------------------------------- */ +int nxp_fman_init(struct wolfIP_ll_dev *ll, const uint8_t *mac) +{ + static const uint8_t default_mac[6] = { + 0x02, 0x11, 0x20, 0x80, 0x00, 0x01 + }; + uint32_t rx_pid, tx_pid, idx; + uint16_t id1, bsr; + + if (ll == NULL) + return -1; + if (mac == NULL) + mac = default_mac; + + fman_memcpy(ll->mac, mac, 6); + fman_memset(ll->ifname, 0, sizeof(ll->ifname)); + fman_memcpy(ll->ifname, "eth0", 4); + ll->mtu = LINK_MTU; + ll->poll = eth_poll; + ll->send = eth_send; + + idx = NXP_FMAN_MEMAC_IDX; + rx_pid = FMAN_RX_PORT_ID(idx); + tx_pid = FMAN_TX_PORT_ID(idx); + + /* Confirm the boot stage uploaded + enabled the FMan microcode. */ + FLOG("iready"); + if (!(fman_rd32(FMAN_IRAM_IREADY) & IRAM_READY)) + return -3; + + /* FMan common init (wolfBoot does not run this). */ + FLOG("muram"); + muram_init(); + FLOG("qmi"); + fm_init_qmi(); + FLOG("fpm"); + fm_init_fpm(rx_pid, tx_pid); + FLOG("dma"); + fm_init_dma(); + FLOG("bmi-common"); + if (fm_init_bmi(rx_pid, tx_pid) != 0) + return -4; + + /* Per-port parameter RAM, BD rings, BMI ports. */ + FLOG("rx-param"); + if (fm_rx_param_init(rx_pid) != 0) + return -4; + FLOG("tx-param"); + if (fm_tx_param_init(tx_pid) != 0) + return -4; + FLOG("memac"); + memac_init(idx, mac); +#if NXP_FMAN_IF_SGMII + FLOG("sgmii"); + sgmii_configure_serdes(idx); +#endif + memac_set_interface(idx); + FLOG("bmi-ports"); + bmi_rx_port_init(rx_pid); + bmi_tx_port_init(tx_pid); + FLOG("enable"); + fm_port_enable(rx_pid, tx_pid, idx); + FLOG("enabled"); + + /* Bring up the external PHY over the dedicated MDIO. */ + mdio_setup(DEDICATED_MDIO_BASE); + phy_addr = phy_detect(); + if (phy_addr < 0) + return 0x100; /* datapath up, PHY not found */ + + /* A hard PHY-init failure (reset stuck / MDIO error) is propagated; a + * positive return (autoneg incomplete) is not fatal -- the live link + * state is encoded in the status word below. */ + if (phy_init((uint32_t)phy_addr) < 0) + return -5; + id1 = mdio_read((uint32_t)phy_addr, PHY_ID1); + bsr = mdio_read((uint32_t)phy_addr, PHY_BMSR); + + return (int)(((uint32_t)(id1 & 0xFF00U) << 8) | + ((bsr & BMSR_LINK) ? 0x100U : 0U) | + ((uint32_t)phy_addr & 0xFFU)); +} diff --git a/src/port/nxp_fman/nxp_fman.h b/src/port/nxp_fman/nxp_fman.h new file mode 100644 index 00000000..6db87a0d --- /dev/null +++ b/src/port/nxp_fman/nxp_fman.h @@ -0,0 +1,300 @@ +/* nxp_fman.h + * + * NXP QorIQ FMan (Frame Manager) ethernet MAC/PHY driver for wolfIP. + * Targets the multirate Ethernet MAC (mEMAC) and dedicated MDIO found on + * T-series QorIQ parts (T2080, T1024, T1040). Big-endian PowerPC e5500/e6500. + * + * The FMan firmware (microcode) is uploaded by the boot stage (wolfBoot + * hal_fman_init); this driver brings up a single mEMAC in polled + * independent (BMI direct) mode and exposes wolfIP poll/send callbacks. + * + * Board parameters (CCSRBAR, mEMAC index, PHY address, interface mode) + * live in nxp_fman_board.h and are overridable from CFLAGS. + * + * 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 WOLFIP_NXP_FMAN_H +#define WOLFIP_NXP_FMAN_H + +#include +#include "wolfip.h" +#include "nxp_fman_board.h" + +/* ---- FMan CCSR block layout (offsets from CCSRBAR) ------------------- */ + +#define FMAN_BASE (NXP_FMAN_CCSRBAR + 0x400000UL) +#define FMAN_MURAM_BASE (FMAN_BASE) + +/* ---- mEMAC (multirate Ethernet MAC) ---------------------------------- */ +/* mEMAC(n) base: n is 1-based. mEMAC1=0xE0000, step 0x2000. */ +#define FMAN_MEMAC_BASE(n) (FMAN_BASE + 0xE0000UL + ((((n) - 1) & 0x3U) * 0x2000UL)) +#define FMAN_MEMAC_CMD_CFG(n) (FMAN_MEMAC_BASE(n) + 0x008UL) +#define FMAN_MEMAC_MAC_ADDR_0(n) (FMAN_MEMAC_BASE(n) + 0x00CUL) +#define FMAN_MEMAC_MAC_ADDR_1(n) (FMAN_MEMAC_BASE(n) + 0x010UL) +#define FMAN_MEMAC_MAXFRMG(n) (FMAN_MEMAC_BASE(n) + 0x014UL) +#define FMAN_MEMAC_HTBLE_CTRL(n) (FMAN_MEMAC_BASE(n) + 0x02CUL) +#define FMAN_MEMAC_IEVENT(n) (FMAN_MEMAC_BASE(n) + 0x040UL) +#define FMAN_MEMAC_IMASK(n) (FMAN_MEMAC_BASE(n) + 0x04CUL) +#define FMAN_MEMAC_IF_MODE(n) (FMAN_MEMAC_BASE(n) + 0x300UL) +#define FMAN_MEMAC_IF_STATUS(n) (FMAN_MEMAC_BASE(n) + 0x304UL) + +/* CMD_CFG bits */ +#define MEMAC_CMD_CFG_RX_EN 0x00000002UL +#define MEMAC_CMD_CFG_TX_EN 0x00000001UL +#define MEMAC_CMD_CFG_NO_LEN_CHK 0x00020000UL + +/* IF_MODE bits */ +#define IF_MODE_MASK 0x00000003UL +#define IF_MODE_XGMII 0x00000000UL +#define IF_MODE_GMII 0x00000002UL +#define IF_MODE_RG 0x00000004UL +#define IF_MODE_RM 0x00000008UL +#define IF_MODE_EN_AUTO 0x00008000UL +#define IF_MODE_SETSP_10M 0x00002000UL +#define IF_MODE_SETSP_1000M 0x00004000UL +#define IF_MODE_SETSP_MASK 0x00006000UL + +/* ---- Dedicated MDIO controller --------------------------------------- */ +/* 1G MDIO at FMAN_BASE + 0xFC000; EMI index n is 1-based, step 0x1000. */ +#define FMAN_MDIO_BASE(n) (FMAN_BASE + 0xFC000UL + ((((n) - 1) & 0x1U) * 0x1000UL)) +#define FMAN_MDIO_STAT(n) (FMAN_MDIO_BASE(n) + 0x030UL) /* config/status */ +#define FMAN_MDIO_CTRL(n) (FMAN_MDIO_BASE(n) + 0x034UL) +#define FMAN_MDIO_DATA(n) (FMAN_MDIO_BASE(n) + 0x038UL) +#define FMAN_MDIO_ADDR(n) (FMAN_MDIO_BASE(n) + 0x03CUL) + +/* MDIO_STAT (config) bits */ +#define MDIO_STAT_CLKDIV(x) (((((uint32_t)(x)) >> 1) & 0xFFU) << 8) +#define MDIO_STAT_BSY (1UL << 0) +#define MDIO_STAT_RD_ER (1UL << 1) +#define MDIO_STAT_PRE (1UL << 5) +#define MDIO_STAT_EN_C45 (1UL << 6) /* clause 45 enable */ +#define MDIO_STAT_NEG (1UL << 23) + +/* MDIO_CTRL bits */ +#define MDIO_CTL_DEV_ADDR(x) (((uint32_t)(x)) & 0x1FU) +#define MDIO_CTL_PORT_ADDR(x) ((((uint32_t)(x)) & 0x1FU) << 5) +#define MDIO_CTL_READ (1UL << 15) + +#define MDIO_ADDR_VAL(x) (((uint32_t)(x)) & 0xFFFFU) +#define MDIO_DATA_VAL(x) (((uint32_t)(x)) & 0xFFFFU) +#define MDIO_DATA_BSY (1UL << 31) + +/* ---- FMan common blocks (offsets from FMAN_BASE) --------------------- */ +#define FMAN_BMI_COMMON (FMAN_BASE + 0x80000UL) +#define FMAN_QMI_COMMON (FMAN_BASE + 0x80400UL) +#define FMAN_DMA (FMAN_BASE + 0xC2000UL) +#define FMAN_FPM (FMAN_BASE + 0xC3000UL) +#define FMAN_IRAM (FMAN_BASE + 0xC4000UL) + +/* BMI per-port register block. port[pid-1] sits at +0x80000 + pid*0x1000. */ +#define FMAN_BMI_PORT(pid) (FMAN_BASE + 0x80000UL + ((uint32_t)(pid) * 0x1000UL)) +/* RX/TX BMI port-id bases for 1G mEMAC n (1-based). */ +#define FMAN_RX_PORT_ID(n) (0x08U + ((n) - 1U)) +#define FMAN_TX_PORT_ID(n) (0x28U + ((n) - 1U)) + +/* Internal per-mEMAC MDIO (SGMII PCS), one 4KB block above each mEMAC. */ +#define FMAN_MEMAC_MDIO_BASE(n) (FMAN_MEMAC_BASE(n) + 0x1000UL) + +/* ---- IRAM (microcode) ------------------------------------------------ */ +#define FMAN_IRAM_IADD (FMAN_IRAM + 0x000UL) +#define FMAN_IRAM_IDATA (FMAN_IRAM + 0x004UL) +#define FMAN_IRAM_IREADY (FMAN_IRAM + 0x00CUL) +#define IRAM_READY 0x80000000UL + +/* ---- FPM (frame processing manager) registers ------------------------ */ +#define FMAN_FPM_FPMPRC (FMAN_FPM + 0x004UL) /* port-id control */ +#define FMAN_FPM_FPMFLC (FMAN_FPM + 0x00CUL) /* flush control */ +#define FMAN_FPM_FMFPEE (FMAN_FPM + 0x0DCUL) /* event and enable */ +#define FMAN_FPM_FPMRCR (FMAN_FPM + 0x070UL) /* rams control/event */ +#define FMAN_FPM_FPMCEV(i) (FMAN_FPM + 0x0E0UL + ((i) * 4UL)) /* CPU event */ + +#define FMFPPRC_PORTID_SHIFT 24 +#define FMFPPRC_ORA_SHIFT 16 +#define FMFPPRC_RISC1 0x00000001UL +#define FMFPPRC_RISC2 0x00000002UL +#define FMFP_FLC_DISP_LIM_NONE 0x00000000UL +#define FMFPEE_EHM 0x00000008UL +#define FMFPEE_UEC 0x00000004UL +#define FMFPEE_CER 0x00000002UL +#define FMFPEE_DER 0x00000001UL +#define FMFPEE_RFM 0x00010000UL +#define FMFPEE_DECC 0x80000000UL +#define FMFPEE_STL 0x40000000UL +#define FMFPEE_SECC 0x20000000UL +#define FMFPEE_CLEAR_EVENT (FMFPEE_DECC | FMFPEE_STL | FMFPEE_SECC | \ + FMFPEE_EHM | FMFPEE_UEC | FMFPEE_CER | \ + FMFPEE_DER | FMFPEE_RFM) +#define FMFP_RCR_MDEC 0x00008000UL +#define FMFP_RCR_IDEC 0x00004000UL + +/* ---- QMI common registers -------------------------------------------- */ +#define FMAN_QMI_FMQM_GC (FMAN_QMI_COMMON + 0x000UL) +#define FMAN_QMI_FMQM_EIE (FMAN_QMI_COMMON + 0x008UL) +#define FMAN_QMI_FMQM_EIEN (FMAN_QMI_COMMON + 0x00CUL) +#define FMAN_QMI_FMQM_IE (FMAN_QMI_COMMON + 0x014UL) +#define FMAN_QMI_FMQM_IEN (FMAN_QMI_COMMON + 0x018UL) +#define FMQM_GC_ENQ_EN 0x80000000UL +#define FMQM_GC_DEQ_EN 0x40000000UL +#define FMQM_EIE_CLEAR_ALL 0xC0000000UL +#define FMQM_IE_CLEAR_ALL 0x80000000UL + +/* ---- DMA registers --------------------------------------------------- */ +#define FMAN_DMA_FMDMSR (FMAN_DMA + 0x000UL) +#define FMAN_DMA_FMDMMR (FMAN_DMA + 0x004UL) +#define FMDMSR_CLEAR_ALL 0x0FF80000UL /* all ECC/bus err status bits */ +#define FMDMMR_SBER 0x10000000UL + +/* ---- BMI common registers -------------------------------------------- */ +#define FMAN_BMI_FMBM_INIT (FMAN_BMI_COMMON + 0x000UL) +#define FMAN_BMI_FMBM_CFG1 (FMAN_BMI_COMMON + 0x004UL) +#define FMAN_BMI_FMBM_IEVR (FMAN_BMI_COMMON + 0x020UL) +#define FMAN_BMI_FMBM_IER (FMAN_BMI_COMMON + 0x024UL) +#define FMAN_BMI_FMBM_PP(pid) (FMAN_BMI_COMMON + 0x100UL + ((pid) * 4UL)) +#define FMAN_BMI_FMBM_PFS(pid) (FMAN_BMI_COMMON + 0x200UL + ((pid) * 4UL)) +#define FMBM_INIT_START 0x80000000UL +#define FMBM_CFG1_FBPS_SHIFT 16 +#define FMBM_IER_DISABLE_ALL 0x00000000UL +#define FMBM_IEVR_CLEAR_ALL 0xE0000000UL +#define FMBM_PP_MXT(x) ((((uint32_t)(x) - 1U) << 24) & 0x3F000000UL) +#define FMBM_PFS_IFSZ(x) ((uint32_t)(x) & 0x000003FFUL) + +#define FM_MURAM_RES_SIZE 0x1000UL +#define FM_FREE_POOL_SIZE 0x20000UL +#define FM_FREE_POOL_ALIGN 256UL +/* Usable MURAM cap for the bump allocator. Held below the smallest QorIQ + * FMan MURAM (T2080 is 384KB, T1024/T1040 smaller) so the same value is + * safe on every supported part. */ +#define FM_MURAM_TOTAL 0x28000UL + +/* ---- BMI RX port registers (offsets within FMAN_BMI_PORT) ------------ */ +#define BMI_RX_RCFG 0x000UL +#define BMI_RX_RST 0x004UL +#define BMI_RX_RIM 0x018UL +#define BMI_RX_RFNE 0x020UL +#define BMI_RX_RFCA 0x024UL +#define BMI_RX_RFQID 0x060UL +#define BMI_RX_RSTC 0x200UL +#define BMI_RX_RPC 0x280UL +#define FMBM_RCFG_EN 0x80000000UL +#define FMBM_RCFG_IM 0x01000000UL +#define FMBM_RST_BSY 0x80000000UL +#define FMBM_RFCA_ORDER 0x80000000UL +#define FMBM_RFCA_MR_MASK 0x003F0000UL +#define FMBM_RFCA_MR(x) (((uint32_t)(x) << 16) & FMBM_RFCA_MR_MASK) +#define FMBM_RSTC_EN 0x80000000UL + +/* ---- BMI TX port registers ------------------------------------------- */ +#define BMI_TX_TCFG 0x000UL +#define BMI_TX_TST 0x004UL +#define BMI_TX_TFNE 0x018UL +#define BMI_TX_TFCA 0x01CUL +#define BMI_TX_TCFQID 0x020UL +#define BMI_TX_TFENE 0x028UL +#define BMI_TX_TSTC 0x200UL +#define BMI_TX_TPC 0x280UL +#define FMBM_TCFG_EN 0x80000000UL +#define FMBM_TCFG_IM 0x01000000UL +#define FMBM_TST_BSY 0x80000000UL +#define FMBM_TFCA_ORDER 0x80000000UL +#define FMBM_TFCA_MR_MASK 0x003F0000UL +#define FMBM_TFCA_MR(x) (((uint32_t)(x) << 16) & FMBM_TFCA_MR_MASK) +#define FMBM_TSTC_EN 0x80000000UL + +/* Next-invoked-action (RISC engine, IM action codes) */ +#define NIA_ENG_RISC 0x00000000UL +#define NIA_RISC_AC_IM_TX 0x00000008UL +#define NIA_RISC_AC_IM_RX 0x0000000AUL + +/* ---- mEMAC additional bits ------------------------------------------- */ +#define MEMAC_CMD_CFG_RXTX_EN (MEMAC_CMD_CFG_RX_EN | MEMAC_CMD_CFG_TX_EN) +#define MEMAC_MAXFRM_MASK 0x0000FFFFUL +#define IMASK_MASK_ALL 0x00000000UL +#define IEVENT_CLEAR_ALL 0xFFFFFFFFUL + +/* SGMII PCS (TBI) auto-negotiation register values */ +#define PHY_SGMII_CR_DEF_VAL 0x1140U +#define PHY_SGMII_CR_RESET_AN 0x0200U +#define PHY_SGMII_DEV_ABILITY_SGMII 0x4001U +#define PHY_SGMII_IF_MODE_AN 0x0002U +#define PHY_SGMII_IF_MODE_SGMII 0x0001U + +/* ---- Buffer descriptor / queue descriptor (big-endian) --------------- */ +/* BD status bits */ +#define RxBD_EMPTY 0x8000U +#define RxBD_LAST 0x0800U +#define RxBD_PHYS_ERR 0x0008U +#define RxBD_SIZE_ERR 0x0004U +#define RxBD_ERROR (RxBD_PHYS_ERR | RxBD_SIZE_ERR) +#define TxBD_READY 0x8000U +#define TxBD_LAST 0x0800U + +/* PRAM */ +#define PRAM_MODE_GLOBAL 0x20000000UL +#define PRAM_MODE_GRACEFUL_STOP 0x00800000UL +#define FM_PRAM_SIZE 256UL /* sizeof(global pram), 256-aligned */ +#define FM_PRAM_ALIGN 256UL +#define RX_BD_RING_SIZE 8U +#define TX_BD_RING_SIZE 8U +#define MAX_RXBUF_LOG2 11U +#define MAX_RXBUF_LEN (1U << MAX_RXBUF_LOG2) /* 2048 */ +/* TX buffers are sized the same as RX; named separately so the TX pool and + * the eth_send() length bound do not depend on an RX-named constant. */ +#define MAX_TXBUF_LEN MAX_RXBUF_LEN + +/* QD field offsets within a PRAM (rxqd @ +0x20, txqd @ +0x40). */ +#define PRAM_OFF_MODE 0x00UL +#define PRAM_OFF_RXQD_PTR 0x04UL +#define PRAM_OFF_TXQD_PTR 0x08UL +#define PRAM_OFF_MRBLR 0x0CUL /* u16 */ +#define PRAM_OFF_RXQD 0x20UL +#define PRAM_OFF_TXQD 0x40UL +/* fm_port_qd field offsets */ +#define QD_OFF_GEN 0x00UL /* u16 */ +#define QD_OFF_BD_BASE_HI 0x02UL /* u16 */ +#define QD_OFF_BD_BASE_LO 0x04UL /* u32 */ +#define QD_OFF_BD_RING_SIZE 0x08UL /* u16 */ +#define QD_OFF_OFFSET_IN 0x0AUL /* u16 */ +#define QD_OFF_OFFSET_OUT 0x0CUL /* u16 */ + +/* ---- Public API ------------------------------------------------------ */ + +/* Initialize the mEMAC/MDIO, detect the PHY, and populate ll with the + * MAC address, interface name and poll/send callbacks. Pass mac=NULL to + * use the built-in default address. Returns a status word encoding the + * detected PHY id high byte, link bit and PHY address (see nxp_fman.c), + * or a negative value on hard failure. */ +int nxp_fman_init(struct wolfIP_ll_dev *ll, const uint8_t *mac); + +/* Detected PHY MDIO address, or -1 if the bus scan failed. */ +int nxp_fman_phy_addr(void); + +/* MDIO clause-22 read of the detected PHY (0xFFFF if no PHY / bus error). */ +uint16_t nxp_fman_phy_read(uint8_t reg); + +/* MDIO clause-22 read of an explicit address (bus scan / diagnostics). */ +uint16_t nxp_fman_phy_read_at(uint8_t addr, uint8_t reg); + +/* Non-zero when the PHY reports link up. */ +uint32_t nxp_fman_link_up(void); + +/* Optional debug log hook: the driver calls it at bring-up phase + * boundaries. Pass NULL (default) to disable. */ +void nxp_fman_set_log(void (*log_fn)(const char *msg)); + +#endif /* WOLFIP_NXP_FMAN_H */ diff --git a/src/port/nxp_fman/nxp_fman_board.h b/src/port/nxp_fman/nxp_fman_board.h new file mode 100644 index 00000000..f21e904b --- /dev/null +++ b/src/port/nxp_fman/nxp_fman_board.h @@ -0,0 +1,69 @@ +/* nxp_fman_board.h + * + * Board parameters for the NXP QorIQ FMan ethernet driver (nxp_fman.c). + * Defaults target the Curtiss-Wright VPX3-152 (T2080, FM1@DTSEC1). + * + * Every value here is overridable from CFLAGS or a consumer header so the + * same driver can serve other QorIQ boards (T1024, T1040, NAII 68PPC2). + * + * 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 WOLFIP_NXP_FMAN_BOARD_H +#define WOLFIP_NXP_FMAN_BOARD_H + +/* CCSRBAR (Configuration, Control and Status Register Base Address). + * The CW VPX3-152 relocates CCSRBAR to 0xEF000000 so it does not overlap + * the board's 256MB NOR flash window; other T2080 boards (RDB, NAII 68PPC2) + * keep the reset default 0xFE000000. Match wolfBoot's per-board CCSRBAR. */ +#ifndef NXP_FMAN_CCSRBAR +#ifdef BOARD_CW_VPX3152 +#define NXP_FMAN_CCSRBAR 0xEF000000UL +#else +#define NXP_FMAN_CCSRBAR 0xFE000000UL +#endif +#endif + +/* mEMAC index driving the PHY (1-based). FM1@DTSEC1 -> mEMAC1. */ +#ifndef NXP_FMAN_MEMAC_IDX +#define NXP_FMAN_MEMAC_IDX 1 +#endif + +/* External MDIO interface (EMI) index for the dedicated 1G MDIO bus + * (1 = first 1G MDIO at FMAN_BASE + 0xFC000). */ +#ifndef NXP_FMAN_MDIO_EMI +#define NXP_FMAN_MDIO_EMI 1 +#endif + +/* PHY MDIO bus address. The VPX3-152 board wires the AR8031 on + * FM1@DTSEC1 at address 0x2 (NOT wolfBoot's generic SGMII default 0x1). */ +#ifndef NXP_FMAN_PHY_ADDR +#define NXP_FMAN_PHY_ADDR 0x2 +#endif + +/* MDIO clock divider. ratio = (2 * CLKDIV) + 1; 258 is the QorIQ default. */ +#ifndef NXP_FMAN_MDIO_CLKDIV +#define NXP_FMAN_MDIO_CLKDIV 258 +#endif + +/* MAC-to-PHY interface mode: 1 = SGMII (VPX3-152 FM1@DTSEC1), 0 = RGMII. */ +#ifndef NXP_FMAN_IF_SGMII +#define NXP_FMAN_IF_SGMII 1 +#endif + +#endif /* WOLFIP_NXP_FMAN_BOARD_H */ diff --git a/src/port/nxp_qe_uec/README.md b/src/port/nxp_qe_uec/README.md new file mode 100644 index 00000000..2d4d004f --- /dev/null +++ b/src/port/nxp_qe_uec/README.md @@ -0,0 +1,49 @@ +# wolfIP NXP QUICC Engine UCC (UEC) port + +Ethernet driver for the NXP QUICC Engine (QE) UCC fast controller in UEC-GETH (ethernet) mode, for big-endian PowerPC parts that carry a QE (e.g. P1021/P1025, e500v2). Brings up one UCC in polled mode and exposes the wolfIP poll/send callbacks. + +## Important: which P1021 MAC? + +The **stock P1021RDB routes its copper through eTSEC (Gianfar) + a VSC7385 switch, not the QE UCC** -- the QE-UEC ethernet path is a P1025RDB-style configuration. On a stock P1021RDB the QE only drives the switch firmware. Use the **`nxp_etsec` port** for stock P1021RDB hardware. Use this `nxp_qe_uec` port for boards with custom UCC-to-PHY wiring (or P1025-style RMII). + +## Model + +The board (DDR, clocks, UART, pin-mux) and the QE microcode are brought up by the boot stage (wolfBoot `hal_qe_init`, which uploads the microcode, sets IRAM-ready, configures SDMA, resets the engine, and muxes the UCC pins). This driver runs in the booted application: it routes the UCC clocks, runs UCC-fast init (GUMR + virtual FIFOs), configures the MAC + MDIO/PHY, builds the parameter RAM in QE MURAM, sets up the BD rings in DRAM, issues the `INIT_TX_RX` command, and polls the rings. + +## Files + +- `nxp_qe_uec.h` - public API + QE engine / UCC fast / UEC MAC / MII / BD / param-RAM register map. +- `nxp_qe_uec.c` - driver: BE register access, MURAM allocator, QE command register, MDIO clause-22, PHY bring-up, UCC-fast + MAC init, parameter RAM, BD-ring datapath. +- `nxp_qe_uec_board.h` - board parameters (CCSRBAR, UCC index, PHY address, interface mode, speed, SNUMs). All overridable from CFLAGS. +- `config.h` - wolfIP compile-time configuration for this port. + +## Board parameters + +| Define | Default | Notes | +|---|---|---| +| `NXP_QE_CCSRBAR` | `0xFF700000` | P1021 reset default (kept by wolfBoot for the QE block) | +| `NXP_QE_IMMR_OFFSET` | `0x80000` | QE_IMMR = CCSRBAR + 0x80000 | +| `NXP_QE_UCC_NUM` | `0` | 0-based UCC index (UCC1=0; wolfBoot muxes UCC1=MII, UCC5=RMII) | +| `NXP_QE_PHY_ADDR` | `0x0` | external PHY (probed first, then bus scan) | +| `NXP_QE_IF_MODE` | `0` | 0=MII, 1=RMII, 2=RGMII | +| `NXP_QE_SPEED` | `100` | 10/100/1000 (selects VFIFO sizes and MAC byte/nibble mode) | +| `NXP_QE_SNUM_RX/RX2/TX` | `0x04/0x0C/0x05` | serial numbers from the P1021 28-snum pool | + +## Status + +Build-validated (compiles clean for e500 BE in MII, RMII and RGMII/gigabit modes). The polled `eth_poll`/`eth_send` ring datapath (index/wrap/ring-full and length-clamp bookkeeping) is NOT yet exercised by any host or hardware test. Hardware bring-up is pending suitable QE/UCC hardware. The bring-up follows the U-Boot QE UEC driver (drivers/qe/uec.c). Hardware-verify items, flagged inline in the source: + +- The CMXUCRn per-UCC RX/TX clock-route 4-bit field layout is not in the U-Boot sources and must be taken from the P1021 RM "QE Multiplexing" -- `qe_clock_route()` currently only programs the well-defined MII-management routing (CMXGCR). +- Whether `qe_assign_page()` is required after wolfBoot's engine reset, and its exact CECDR page encoding. +- The PHY interface mode and MDIO address (board-specific; the stock P1021RDB does not use this path at all). + +## Cache coherency + +e500v2 has no I/O cache snooping and wolfBoot maps low DDR cacheable-but-non-coherent, so the driver does explicit `dcbf` cache maintenance (32-byte lines) around the BD rings and buffers. `TSTATE`/`RSTATE` also set the QE `BMR_GLB` snoop bit. + +## Build + +``` +powerpc-linux-gnu-gcc -mcpu=e500mc -mbig-endian -I -Isrc/port/nxp_qe_uec \ + -c src/port/nxp_qe_uec/nxp_qe_uec.c +``` diff --git a/src/port/nxp_qe_uec/config.h b/src/port/nxp_qe_uec/config.h new file mode 100644 index 00000000..d86c7ef1 --- /dev/null +++ b/src/port/nxp_qe_uec/config.h @@ -0,0 +1,68 @@ +/* config.h + * + * wolfIP configuration for the NXP QUICC Engine UCC (UEC) port (P1021/P1025). + * + * 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 +#define TXBUF_SIZE LINK_MTU + +#define MAX_NEIGHBORS 8 +#define WOLFIP_ARP_PENDING_MAX 2 + +#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 1 +#endif + +/* Static IP fallback (used when DHCP is disabled or times out). */ +#define WOLFIP_IP "192.168.1.10" +#define WOLFIP_NETMASK "255.255.255.0" +#define WOLFIP_GW "192.168.1.1" +#define WOLFIP_STATIC_DNS_IP "8.8.8.8" + +#if WOLFIP_ENABLE_DHCP +#define DHCP +#endif + +#endif /* WOLF_CONFIG_H */ diff --git a/src/port/nxp_qe_uec/nxp_qe_uec.c b/src/port/nxp_qe_uec/nxp_qe_uec.c new file mode 100644 index 00000000..7722bc97 --- /dev/null +++ b/src/port/nxp_qe_uec/nxp_qe_uec.c @@ -0,0 +1,628 @@ +/* nxp_qe_uec.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 + * + * NXP QUICC Engine UCC ethernet (UEC-GETH) driver for wolfIP. + * + * Brings up one UCC fast controller in polled mode: clock routing, UCC fast + * init (GUMR/VFIFOs), MAC config + MDIO/PHY, parameter RAM in QE MURAM, BD + * rings in DRAM, the INIT_TX_RX command, and the polled poll/send datapath. + * The QE microcode is uploaded by the boot stage (wolfBoot hal_qe_init). + * Ported from the U-Boot QE UEC driver (drivers/qe/uec.c). e500v2, BE. + * + * Several items are board/silicon specific and must be verified on hardware + * (flagged inline): the CMXUCRn per-UCC clock-route field layout, the + * assign-page requirement, and the PHY interface/address. + */ + +#include +#include +#include "config.h" +#include "nxp_qe_uec.h" + +/* Local byte-loop mem helpers (bare-metal, no PLT/GOT fixup). */ +static void *qe_memcpy(void *dst, const void *src, uint32_t n) +{ + uint8_t *d = (uint8_t *)dst; + const uint8_t *s = (const uint8_t *)src; + while (n--) + *d++ = *s++; + return dst; +} +static void qe_memset(void *dst, int c, uint32_t n) +{ + uint8_t *d = (uint8_t *)dst; + while (n--) + *d++ = (uint8_t)c; +} + +/* ---- Big-endian register access (sync/twi/isync like wolfBoot) ------- */ +#if defined(__powerpc__) || defined(__PPC__) || defined(__ppc__) +static inline uint32_t qe_rd32(uintptr_t a) +{ + uint32_t r; + __asm__ __volatile__("sync;\n" "lwz %0,0(%1);\n" "twi 0,%0,0;\n" "isync" + : "=r"(r) : "r"(a) : "memory"); + return r; +} +static inline void qe_wr32(uintptr_t a, uint32_t v) +{ + __asm__ __volatile__("sync;\n" "stw %0,0(%1)" : : "r"(v), "r"(a) : "memory"); +} +static inline void qe_wr16(uintptr_t a, uint16_t v) +{ + __asm__ __volatile__("sync" ::: "memory"); + *(volatile uint16_t *)a = v; +} +static inline void qe_wr8(uintptr_t a, uint8_t v) +{ + __asm__ __volatile__("sync" ::: "memory"); + *(volatile uint8_t *)a = v; +} +static inline void qe_sync(void) +{ + __asm__ __volatile__("sync" ::: "memory"); +} +/* e500v2 data cache line = 32 bytes. dcbf (flush+invalidate) for both + * directions; BMR_GLB also enables QE snooping of the CSB. */ +static void dcache_flush(const void *p, uint32_t len) +{ + uintptr_t a = (uintptr_t)p & ~31UL; + uintptr_t end = (uintptr_t)p + len; + for (; a < end; a += 32) + __asm__ __volatile__("dcbf 0,%0" : : "r"(a) : "memory"); + qe_sync(); +} +#else +/* Portable fallbacks for host syntax checks. */ +static inline uint32_t qe_rd32(uintptr_t a) { return *(volatile uint32_t *)a; } +static inline void qe_wr32(uintptr_t a, uint32_t v) { *(volatile uint32_t *)a = v; } +static inline void qe_wr16(uintptr_t a, uint16_t v) { *(volatile uint16_t *)a = v; } +static inline void qe_wr8(uintptr_t a, uint8_t v) { *(volatile uint8_t *)a = v; } +static inline void qe_sync(void) { } +static void dcache_flush(const void *p, uint32_t len) { (void)p; (void)len; } +#endif + +#define dcache_inval(p, l) dcache_flush((p), (l)) + +/* Optional debug log hook. */ +static void (*g_log)(const char *) = 0; +void nxp_qe_uec_set_log(void (*log_fn)(const char *)) { g_log = log_fn; } +#define QLOG(s) do { if (g_log) g_log(s); } while (0) + +/* ---- Driver state ---------------------------------------------------- */ +static uintptr_t ucc_base; /* UCC fast register block */ +static uintptr_t mac_base; /* UEC MAC block (ucc_base + 0x100) */ +static uint32_t tx_glbl_off; /* MURAM offsets */ +static uint32_t tx_pram_off; +static uint32_t rx_idx; +static uint32_t tx_idx; +static int32_t phy_addr = -1; + +#define MURAM_ADDR(off) (QE_MURAM_BASE + (uintptr_t)(off)) + +static struct qe_bd rx_bd_ring[QE_RX_RING_SIZE] + __attribute__((aligned(QE_BD_RING_ALIGN))); +static struct qe_bd tx_bd_ring[QE_TX_RING_SIZE] + __attribute__((aligned(QE_BD_RING_ALIGN))); +static uint8_t rx_buf_pool[QE_RX_RING_SIZE][QE_MRBLR] + __attribute__((aligned(QE_BUF_ALIGN))); +static uint8_t tx_buf_pool[QE_TX_RING_SIZE][QE_MRBLR] + __attribute__((aligned(QE_BUF_ALIGN))); + +/* ---- MURAM bump allocator -------------------------------------------- */ +static uint32_t muram_ptr; + +static void muram_init(void) +{ + muram_ptr = (uint32_t)QE_MURAM_RES; +} + +static uint32_t muram_alloc(uint32_t size, uint32_t align) +{ + uint32_t off, a; + off = (muram_ptr + (align - 1U)) & ~(align - 1U); + if (off + size > (uint32_t)QE_MURAM_SIZE) + return 0xFFFFFFFFUL; + muram_ptr = off + size; + for (a = 0; a < size; a += 4U) + qe_wr32(MURAM_ADDR(off + a), 0); + return off; +} + +/* ---- QE command register --------------------------------------------- */ +static int qe_issue_cmd(uint32_t cmd, uint32_t sbc, uint8_t mcn, + uint32_t cmd_data) +{ + uint32_t t = QE_POLL_TRIES; + + if (cmd == QE_RESET) { + qe_wr32(QE_CECR, cmd | QE_CR_FLG); + } + else { + qe_wr32(QE_CECDR, cmd_data); + qe_wr32(QE_CECR, sbc | QE_CR_FLG | + ((uint32_t)mcn << QE_CR_PROTOCOL_SHIFT) | cmd); + } + while ((qe_rd32(QE_CECR) & QE_CR_FLG) && --t) { + } + return t ? 0 : -1; +} + +/* Assign a MURAM page to a serial number. U-Boot's qe_init does this for + * every snum; wolfBoot reset the engine but did not, so replicate it for the + * snums we use. VERIFY: exact CECDR page encoding on the target silicon. */ +static int qe_assign_page(uint32_t snum) +{ + uint32_t t = QE_POLL_TRIES; + qe_wr32(QE_CECDR, 0); + qe_wr32(QE_CECR, QE_ASSIGN_PAGE | QE_CR_FLG | + (snum << QE_CR_ASSIGN_PAGE_SNUM_SHIFT)); + while ((qe_rd32(QE_CECR) & QE_CR_FLG) && --t) { + } + return t ? 0 : -1; +} + +/* ---- MDIO clause-22 (UEC MAC MII block) ------------------------------ */ +#define MDIO_TIMEOUT 1000000U + +#define PHY_BMCR 0x00U +#define PHY_BMSR 0x01U +#define PHY_ID1 0x02U +#define PHY_ID2 0x03U +#define PHY_ANAR 0x04U +#define BMCR_RESET (1U << 15) +#define BMCR_AUTONEG_EN (1U << 12) +#define BMCR_POWER_DOWN (1U << 11) +#define BMCR_ISOLATE (1U << 10) +#define BMCR_RESTART_ANEG (1U << 9) +#define BMSR_LINK (1U << 2) +#define BMSR_ANEG_COMPLETE (1U << 5) +#define ANAR_DEFAULT 0x01E1U + +static void mii_init(void) +{ + uint32_t t = MDIO_TIMEOUT; + qe_wr32(mac_base + UEC_MIIMCFG, MIIMCFG_RESET_MGMT); + qe_wr32(mac_base + UEC_MIIMCFG, MIIMCFG_CLK_DIV10); + while ((qe_rd32(mac_base + UEC_MIIMIND) & MIIMIND_BUSY) && --t) { + } +} + +static uint16_t mdio_read(uint32_t phy, uint32_t reg) +{ + uint32_t t = MDIO_TIMEOUT; + qe_wr32(mac_base + UEC_MIIMADD, + ((phy & 0x1FU) << MIIMADD_PHY_SHIFT) | (reg & 0x1FU)); + qe_wr32(mac_base + UEC_MIIMCOM, 0); + qe_sync(); + qe_wr32(mac_base + UEC_MIIMCOM, MIIMCOM_READ_CYCLE); + while ((qe_rd32(mac_base + UEC_MIIMIND) & + (MIIMIND_NOTVALID | MIIMIND_BUSY)) && --t) { + } + qe_wr32(mac_base + UEC_MIIMCOM, 0); + if (t == 0) + return 0xFFFFU; + return (uint16_t)(qe_rd32(mac_base + UEC_MIIMSTAT) & 0xFFFFU); +} + +static int mdio_write(uint32_t phy, uint32_t reg, uint16_t val) +{ + uint32_t t = MDIO_TIMEOUT; + qe_wr32(mac_base + UEC_MIIMCOM, 0); + qe_wr32(mac_base + UEC_MIIMADD, + ((phy & 0x1FU) << MIIMADD_PHY_SHIFT) | (reg & 0x1FU)); + qe_wr32(mac_base + UEC_MIIMCON, (uint32_t)val); + qe_sync(); + while ((qe_rd32(mac_base + UEC_MIIMIND) & MIIMIND_BUSY) && --t) { + } + return t ? 0 : -1; +} + +/* ---- PHY discovery / link -------------------------------------------- */ +static int32_t phy_detect(void) +{ + uint32_t addr; + uint16_t id; + + id = mdio_read(NXP_QE_PHY_ADDR, PHY_ID1); + if (id != 0xFFFFU && id != 0x0000U) + return (int32_t)NXP_QE_PHY_ADDR; + for (addr = 0; addr < 32U; addr++) { + id = mdio_read(addr, PHY_ID1); + if (id != 0xFFFFU && id != 0x0000U) + return (int32_t)addr; + } + return -1; +} + +int nxp_qe_uec_phy_addr(void) { return (int)phy_addr; } + +uint16_t nxp_qe_uec_phy_read(uint8_t reg) +{ + if (phy_addr < 0) + return 0xFFFFU; + return mdio_read((uint32_t)phy_addr, reg); +} + +uint32_t nxp_qe_uec_link_up(void) +{ + uint16_t bsr; + if (phy_addr < 0) + return 0; + bsr = mdio_read((uint32_t)phy_addr, PHY_BMSR); + bsr = mdio_read((uint32_t)phy_addr, PHY_BMSR); + return (bsr & BMSR_LINK) ? 1U : 0U; +} + +/* 0 = success, <0 = hard failure (reset stuck / MDIO error), 1 = autoneg + * incomplete (soft; link likely down -- caller reads live link state). */ +static int phy_init(uint32_t phy) +{ + uint32_t timeout; + uint16_t ctrl, bsr; + + if (mdio_write(phy, PHY_BMCR, BMCR_RESET) != 0) + return -1; + timeout = MDIO_TIMEOUT; + do { + ctrl = mdio_read(phy, PHY_BMCR); + } while ((ctrl & BMCR_RESET) && --timeout); + if (timeout == 0) + return -1; + + if (mdio_write(phy, PHY_ANAR, ANAR_DEFAULT) != 0) + return -1; + ctrl &= ~(BMCR_POWER_DOWN | BMCR_ISOLATE); + ctrl |= BMCR_AUTONEG_EN | BMCR_RESTART_ANEG; + if (mdio_write(phy, PHY_BMCR, ctrl) != 0) + return -1; + + timeout = MDIO_TIMEOUT; + do { + bsr = mdio_read(phy, PHY_BMSR); + } while (!(bsr & BMSR_ANEG_COMPLETE) && --timeout); + if (timeout == 0) + return 1; + return 0; +} + +/* ---- Clock routing --------------------------------------------------- */ +/* Route the MII management clock to this UCC and set the UCC RX/TX clock + * source. VERIFY: the CMXUCRn per-UCC 4-bit RX/TX clock field layout is not + * in the U-Boot sources used; consult the P1021 RM "QE Multiplexing". The + * MII-management routing (CMXGCR) is well defined. */ +static void qe_clock_route(uint32_t ucc_num) +{ + uint32_t v = qe_rd32(QE_CMXGCR); + v &= ~QE_CMXGCR_MII_ENET_MNG_MASK; + v |= (ucc_num << QE_CMXGCR_MII_ENET_MNG_SHIFT) & + QE_CMXGCR_MII_ENET_MNG_MASK; + qe_wr32(QE_CMXGCR, v); +} + +/* ---- UCC fast + MAC bring-up ----------------------------------------- */ +static void uccf_init(uint32_t speed) +{ + uint32_t rx_vfifo, tx_vfifo; + uint16_t urfs, urfet, urfset, utfs, utfet, utftt; + + qe_wr8(ucc_base + UCCF_GUEMR, UCC_GUEMR_INIT); + qe_wr32(ucc_base + UCCF_GUMR, UCC_GUMR_ETH); + + if (speed >= 1000U) { + urfs = 4096; urfet = 2048; urfset = 3072; + utfs = 8192; utfet = 4096; utftt = 0x400; + } + else { + urfs = 512; urfet = 256; urfset = 384; + utfs = 512; utfet = 256; utftt = 128; + } + rx_vfifo = muram_alloc((uint32_t)urfs + 8U, 8U); + tx_vfifo = muram_alloc((uint32_t)utfs, 8U); + + qe_wr32(ucc_base + UCCF_URFB, rx_vfifo); + qe_wr16(ucc_base + UCCF_URFS, urfs); + qe_wr16(ucc_base + UCCF_URFET, urfet); + qe_wr16(ucc_base + UCCF_URFSET, urfset); + qe_wr32(ucc_base + UCCF_UTFB, tx_vfifo); + qe_wr16(ucc_base + UCCF_UTFS, utfs); + qe_wr16(ucc_base + UCCF_UTFET, utfet); + qe_wr16(ucc_base + UCCF_UTFTT, utftt); + + qe_wr32(ucc_base + UCCF_UCCM, 0); /* polled: mask all */ + qe_wr32(ucc_base + UCCF_UCCE, 0xFFFFFFFFUL); /* clear events (W1C) */ +} + +static void mac_init(const uint8_t *mac, uint32_t speed) +{ + uint32_t cfg2, upsmr; + uint32_t a1, a2; + + qe_wr32(mac_base + UEC_MACCFG1, 0); + cfg2 = MACCFG2_INIT_VALUE; + upsmr = UPSMR_INIT_VALUE; + + cfg2 &= ~MACCFG2_IF_MODE_MASK; + cfg2 |= (speed >= 1000U) ? MACCFG2_BYTE : MACCFG2_NIBBLE; + +#if (NXP_QE_IF_MODE == 2) /* RGMII */ + upsmr |= UPSMR_RPM; + if (speed < 100U) + upsmr |= UPSMR_R10M; +#elif (NXP_QE_IF_MODE == 1) /* RMII */ + upsmr |= UPSMR_RMM; + if (speed < 100U) + upsmr |= UPSMR_R10M; +#endif /* else MII: no extra UPSMR bits */ + + qe_wr32(mac_base + UEC_MACCFG2, cfg2); + qe_wr32(ucc_base + UCCF_UPSMR, upsmr); + + /* Station address: MACSTNADDR1 = mac[5..2], MACSTNADDR2 = mac[1..0]. */ + a1 = ((uint32_t)mac[5] << 24) | ((uint32_t)mac[4] << 16) | + ((uint32_t)mac[3] << 8) | (uint32_t)mac[2]; + a2 = ((uint32_t)mac[1] << 24) | ((uint32_t)mac[0] << 16); + qe_wr32(mac_base + UEC_MACSTNADDR1, a1); + qe_wr32(mac_base + UEC_MACSTNADDR2, a2); +} + +/* ---- Parameter RAM + BD rings ---------------------------------------- */ +static int build_param_ram(void) +{ + uint32_t sq, tx_data, rx_glbl, rx_data, rbdq, rx_thr, init; + uint64_t txbd = (uint64_t)(uintptr_t)tx_bd_ring; + uint64_t rxbd = (uint64_t)(uintptr_t)rx_bd_ring; + uint32_t i; + + tx_glbl_off = muram_alloc((uint32_t)TXG_SIZE, 64U); + sq = muram_alloc((uint32_t)SQQD_SIZE, 32U); + /* TX thread data: num_tx*136 (+32 pad when num_tx==1). */ + tx_data = muram_alloc((uint32_t)UEC_THREAD_DATA_TX_SIZE + 32U, 256U); + tx_pram_off = muram_alloc((uint32_t)UEC_THREAD_TX_PRAM_SIZE, 64U); + rx_glbl = muram_alloc((uint32_t)RXG_SIZE, 64U); + rx_data = muram_alloc((uint32_t)UEC_THREAD_DATA_RX_SIZE, 256U); + rbdq = muram_alloc((uint32_t)RBDQ_SIZE, 8U); + rx_thr = muram_alloc((uint32_t)UEC_THREAD_RX_PRAM_SIZE, 128U); + init = muram_alloc((uint32_t)INIT_SIZE, 4U); + + if (tx_glbl_off == 0xFFFFFFFFUL || sq == 0xFFFFFFFFUL || + tx_data == 0xFFFFFFFFUL || + tx_pram_off == 0xFFFFFFFFUL || rx_glbl == 0xFFFFFFFFUL || + rx_data == 0xFFFFFFFFUL || rbdq == 0xFFFFFFFFUL || + rx_thr == 0xFFFFFFFFUL || init == 0xFFFFFFFFUL) + return -1; + + /* TX global param RAM. */ + qe_wr16(MURAM_ADDR(tx_glbl_off) + TXG_TEMODER, TEMODER_INIT_VALUE); + qe_wr32(MURAM_ADDR(tx_glbl_off) + TXG_SQPTR, sq); + qe_wr32(MURAM_ADDR(sq) + SQQD_BD_RING_BASE, (uint32_t)(txbd & 0xFFFFFFFFUL)); + qe_wr32(MURAM_ADDR(sq) + SQQD_LAST_BD, + (uint32_t)((txbd + (uint64_t)(QE_TX_RING_SIZE - 1U) * QE_SIZEOFBD) + & 0xFFFFFFFFUL)); + qe_wr32(MURAM_ADDR(tx_glbl_off) + TXG_TSTATE, UEC_TSTATE_INIT); + qe_wr32(MURAM_ADDR(tx_glbl_off) + TXG_TQPTR, tx_data); + + /* RX global param RAM. */ + qe_wr32(MURAM_ADDR(rx_glbl) + RXG_REMODER, 0); + qe_wr32(MURAM_ADDR(rx_glbl) + RXG_RQPTR, rx_data); + qe_wr16(MURAM_ADDR(rx_glbl) + RXG_TYPEORLEN, 3072); + qe_wr8(MURAM_ADDR(rx_glbl) + RXG_RSTATE, UEC_RSTATE_INIT); + qe_wr16(MURAM_ADDR(rx_glbl) + RXG_MRBLR, QE_MRBLR); + qe_wr32(MURAM_ADDR(rx_glbl) + RXG_RBDQPTR, rbdq); + qe_wr32(MURAM_ADDR(rbdq) + RBDQ_EXT_BD_BASE, + (uint32_t)(rxbd & 0xFFFFFFFFUL)); + qe_wr16(MURAM_ADDR(rx_glbl) + RXG_MFLR, QE_MAX_FRAME_LEN); + qe_wr16(MURAM_ADDR(rx_glbl) + RXG_MINFLR, 64); + qe_wr16(MURAM_ADDR(rx_glbl) + RXG_MAXD1, 1520); + qe_wr16(MURAM_ADDR(rx_glbl) + RXG_MAXD2, 1520); + qe_wr16(MURAM_ADDR(rx_glbl) + RXG_VLANTYPE, 0x8100); + + /* init-enet command block. */ + qe_wr8(MURAM_ADDR(init) + INIT_RES0 + 0, 0x06); + qe_wr8(MURAM_ADDR(init) + INIT_RES0 + 1, 0x30); + qe_wr8(MURAM_ADDR(init) + INIT_RES0 + 2, 0xFF); + qe_wr8(MURAM_ADDR(init) + INIT_RES0 + 3, 0x00); + qe_wr16(MURAM_ADDR(init) + INIT_RES4, 0x0400); + qe_wr32(MURAM_ADDR(init) + INIT_RGF_TGF_RXG, + (1UL << ENET_INIT_RGF_SHIFT) | (1UL << ENET_INIT_TGF_SHIFT) | + rx_glbl | QE_RISC_RISC1); + /* RX thread table: num_rx+1 entries (entry 0 uses offset 0). */ + qe_wr32(MURAM_ADDR(init) + INIT_RXTHREAD + 0, + ((uint32_t)NXP_QE_SNUM_RX << ENET_INIT_SNUM_SHIFT) | QE_RISC_RISC1); + qe_wr32(MURAM_ADDR(init) + INIT_RXTHREAD + 4, + ((uint32_t)NXP_QE_SNUM_RX2 << ENET_INIT_SNUM_SHIFT) | rx_thr | + QE_RISC_RISC1); + qe_wr32(MURAM_ADDR(init) + INIT_TXGLOBAL, tx_glbl_off | QE_RISC_RISC1); + qe_wr32(MURAM_ADDR(init) + INIT_TXTHREAD + 0, + ((uint32_t)NXP_QE_SNUM_TX << ENET_INIT_SNUM_SHIFT) | tx_pram_off | + QE_RISC_RISC1); + + /* BD rings in DRAM. */ + for (i = 0; i < QE_TX_RING_SIZE; i++) { + tx_bd_ring[i].status = 0; + tx_bd_ring[i].len = 0; + tx_bd_ring[i].data = 0; + } + tx_bd_ring[QE_TX_RING_SIZE - 1].status = BD_WRAP; + for (i = 0; i < QE_RX_RING_SIZE; i++) { + rx_bd_ring[i].status = RxBD_EMPTY; + rx_bd_ring[i].len = 0; + rx_bd_ring[i].data = (uint32_t)(uintptr_t)&rx_buf_pool[i][0]; + } + rx_bd_ring[QE_RX_RING_SIZE - 1].status = BD_WRAP | RxBD_EMPTY; + dcache_flush(tx_bd_ring, sizeof(tx_bd_ring)); + dcache_flush(rx_bd_ring, sizeof(rx_bd_ring)); + /* Clean+invalidate the RX buffers before the QE DMA owns them so a later + * write-back of a dirty line cannot clobber a DMA-written frame. */ + dcache_flush(rx_buf_pool, sizeof(rx_buf_pool)); + + qe_sync(); + return qe_issue_cmd(QE_INIT_TX_RX, QE_CR_SUBBLOCK_UCCFAST(NXP_QE_UCC_NUM), + (uint8_t)QE_CR_PROTOCOL_ETHERNET, init); +} + +/* ---- wolfIP poll/send callbacks -------------------------------------- */ +static int eth_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + struct qe_bd *bd = &tx_bd_ring[tx_idx]; + uint16_t wrap; + (void)dev; + + if (len == 0 || len > QE_MRBLR) + return -1; + + dcache_inval(bd, sizeof(*bd)); + if (bd->status & TxBD_READY) + return -2; /* ring full */ + + qe_memcpy(tx_buf_pool[tx_idx], frame, len); + if (len < 60U) { + qe_memset(tx_buf_pool[tx_idx] + len, 0, 60U - len); + len = 60U; + } + dcache_flush(tx_buf_pool[tx_idx], len); + + wrap = bd->status & BD_WRAP; + bd->data = (uint32_t)(uintptr_t)tx_buf_pool[tx_idx]; + bd->len = (uint16_t)len; + qe_sync(); + bd->status = wrap | TxBD_READY | BD_LAST | TxBD_PADCRC | TxBD_TXCRC; + dcache_flush(bd, sizeof(*bd)); + + qe_wr16(ucc_base + UCCF_UTODR, UCC_FAST_TOD); /* transmit on demand */ + qe_sync(); + + tx_idx = (tx_idx + 1U) % QE_TX_RING_SIZE; + return (int)len; +} + +static int eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + struct qe_bd *bd = &rx_bd_ring[rx_idx]; + uint16_t status, wrap, flen = 0; + int err; + (void)dev; + + dcache_inval(bd, sizeof(*bd)); + status = bd->status; + if (status & RxBD_EMPTY) + return 0; + + err = (status & RxBD_ERROR) ? 1 : 0; + if (!err) { + flen = bd->len; + if (flen > QE_MRBLR) + flen = QE_MRBLR; + if (flen > len) + flen = (uint16_t)len; + if (flen > 0) { + dcache_inval(rx_buf_pool[rx_idx], flen); + qe_memcpy(frame, rx_buf_pool[rx_idx], flen); + } + } + + wrap = status & BD_WRAP; + bd->len = 0; + qe_sync(); + bd->status = wrap | RxBD_EMPTY; + dcache_flush(bd, sizeof(*bd)); + + rx_idx = (rx_idx + 1U) % QE_RX_RING_SIZE; + return err ? 0 : (int)flen; +} + +/* ---- Public init ----------------------------------------------------- */ +int nxp_qe_uec_init(struct wolfIP_ll_dev *ll, const uint8_t *mac) +{ + static const uint8_t default_mac[6] = { + 0x02, 0x11, 0x20, 0x10, 0x21, 0x01 + }; + uint16_t id1, bsr; + + if (ll == NULL) + return -1; + if (mac == NULL) + mac = default_mac; + + qe_memcpy(ll->mac, mac, 6); + qe_memset(ll->ifname, 0, sizeof(ll->ifname)); + qe_memcpy(ll->ifname, "eth0", 4); + ll->mtu = LINK_MTU; + ll->poll = eth_poll; + ll->send = eth_send; + + ucc_base = QE_UCC_BASE(NXP_QE_UCC_NUM); + mac_base = ucc_base + UEC_MAC_OFFSET; + + /* Confirm the boot stage uploaded + enabled the QE microcode. */ + QLOG("iready"); + if (!(qe_rd32(QE_IRAM_IREADY) & QE_IRAM_READY)) + return -3; + + QLOG("muram"); + muram_init(); + QLOG("snum"); + if (qe_assign_page(NXP_QE_SNUM_RX) != 0 || + qe_assign_page(NXP_QE_SNUM_RX2) != 0 || + qe_assign_page(NXP_QE_SNUM_TX) != 0) + return -5; /* QE command processor wedged */ + + QLOG("clk"); + qe_clock_route(NXP_QE_UCC_NUM); + + QLOG("uccf"); + uccf_init(NXP_QE_SPEED); + QLOG("mac"); + mac_init(mac, NXP_QE_SPEED); + + /* Allocate MURAM, fill the TX/RX global param RAM + init-enet block, + * set up the BD rings, and issue INIT_TX_RX. */ + QLOG("param"); + if (build_param_ram() != 0) + return -4; + + /* Enable MAC + UCC, then kick the RISC threads. */ + QLOG("enable"); + qe_wr32(mac_base + UEC_MACCFG1, + qe_rd32(mac_base + UEC_MACCFG1) | + MACCFG1_ENABLE_RX | MACCFG1_ENABLE_TX); + qe_wr32(ucc_base + UCCF_GUMR, + qe_rd32(ucc_base + UCCF_GUMR) | UCC_GUMR_ENT | UCC_GUMR_ENR); + if (qe_issue_cmd(QE_RESTART_TX, QE_CR_SUBBLOCK_UCCFAST(NXP_QE_UCC_NUM), + (uint8_t)QE_CR_PROTOCOL_ETHERNET, 0) != 0 || + qe_issue_cmd(QE_RESTART_RX, QE_CR_SUBBLOCK_UCCFAST(NXP_QE_UCC_NUM), + (uint8_t)QE_CR_PROTOCOL_ETHERNET, 0) != 0) + return -6; /* RESTART_TX/RX command timed out */ + + /* Bring up the external PHY. */ + QLOG("phy"); + mii_init(); + phy_addr = phy_detect(); + if (phy_addr < 0) + return 0x100; /* datapath up, PHY not found */ + if (phy_init((uint32_t)phy_addr) < 0) + return -5; /* hard PHY-init failure */ + id1 = mdio_read((uint32_t)phy_addr, PHY_ID1); + bsr = mdio_read((uint32_t)phy_addr, PHY_BMSR); + + return (int)(((uint32_t)(id1 & 0xFF00U) << 8) | + ((bsr & BMSR_LINK) ? 0x100U : 0U) | + ((uint32_t)phy_addr & 0xFFU)); +} diff --git a/src/port/nxp_qe_uec/nxp_qe_uec.h b/src/port/nxp_qe_uec/nxp_qe_uec.h new file mode 100644 index 00000000..2f355ff0 --- /dev/null +++ b/src/port/nxp_qe_uec/nxp_qe_uec.h @@ -0,0 +1,244 @@ +/* nxp_qe_uec.h + * + * NXP QUICC Engine (QE) UCC ethernet (UEC-GETH) driver for wolfIP. + * Targets the QE UCC fast controller on big-endian PowerPC parts that carry + * a QUICC Engine (e.g. P1021/P1025, e500v2). The QE microcode is uploaded + * by the boot stage (wolfBoot hal_qe_init); this driver brings up one UCC in + * polled mode and exposes the wolfIP poll/send callbacks. + * + * Board parameters (CCSRBAR, UCC index, PHY address, interface mode) live + * in nxp_qe_uec_board.h and are overridable from CFLAGS. + * + * NOTE: the stock P1021RDB routes its copper through eTSEC (see the nxp_etsec + * port), not the QE UCC. Use this port for boards with custom UCC-to-PHY + * wiring (or P1025-style RMII). + * + * 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 WOLFIP_NXP_QE_UEC_H +#define WOLFIP_NXP_QE_UEC_H + +#include +#include "wolfip.h" +#include "nxp_qe_uec_board.h" + +/* ---- QE engine block (QE_IMMR) -------------------------------------- */ +#define QE_IMMR (NXP_QE_CCSRBAR + NXP_QE_IMMR_OFFSET) + +/* I-RAM (microcode) - uploaded by wolfBoot; we only confirm ready. */ +#define QE_IRAM_IADD (QE_IMMR + 0x000UL) +#define QE_IRAM_IDATA (QE_IMMR + 0x004UL) +#define QE_IRAM_IREADY (QE_IMMR + 0x00CUL) +#define QE_IRAM_READY 0x80000000UL + +/* Command processor (CP). */ +#define QE_CECR (QE_IMMR + 0x100UL) +#define QE_CECDR (QE_IMMR + 0x108UL) +#define QE_CR_FLG 0x00010000UL +#define QE_CR_PROTOCOL_SHIFT 6 +#define QE_CR_PROTOCOL_ETHERNET 0x0CUL +#define QE_CR_ASSIGN_PAGE_SNUM_SHIFT 17 +/* Commands. */ +#define QE_RESET 0x80000000UL +#define QE_INIT_TX_RX 0x00000000UL +#define QE_ASSIGN_PAGE 0x00000012UL +#define QE_RESTART_TX 0x00000006UL +#define QE_RESTART_RX 0x0000001BUL +/* Sub-block code for UCC fast n (ucc_num is 0-based: UCC1 -> 0). */ +#define QE_CR_SUBBLOCK_UCCFAST(ucc) (0x02000000UL + (uint32_t)(ucc) * 0x00200000UL) + +/* QE multiplexing (clock routing). */ +#define QE_CMXGCR (QE_IMMR + 0x140UL) +#define QE_CMXGCR_MII_ENET_MNG_MASK 0x00007000UL +#define QE_CMXGCR_MII_ENET_MNG_SHIFT 12 +/* CMXUCR1 holds UCC1+UCC3, CMXUCR2 holds UCC5+UCC7, etc. */ +#define QE_CMXUCR1 (QE_IMMR + 0x150UL) +#define QE_CMXUCR2 (QE_IMMR + 0x154UL) +#define QE_CMXUCR3 (QE_IMMR + 0x158UL) +#define QE_CMXUCR4 (QE_IMMR + 0x15CUL) + +/* MURAM. */ +#define QE_MURAM_BASE (QE_IMMR + 0x10000UL) +#define QE_MURAM_SIZE 0x6000UL /* P1021: 24KB */ +#define QE_MURAM_RES 0x800UL /* wolfBoot SDMA scratch at off 0 */ + +/* ---- UCC fast register block ---------------------------------------- */ +/* UCC base for ucc_num (0-based): odd index -> 0x3000 page, even -> 0x2000. */ +#define QE_UCC_BASE(ucc) (QE_IMMR + (((ucc) & 1U) ? 0x3000UL : 0x2000UL) + \ + ((uint32_t)((ucc) >> 1) * 0x200UL)) + +#define UCCF_GUMR 0x00UL +#define UCCF_UPSMR 0x04UL +#define UCCF_UTODR 0x08UL /* u16 */ +#define UCCF_UCCE 0x10UL +#define UCCF_UCCM 0x14UL +#define UCCF_URFB 0x20UL +#define UCCF_URFS 0x24UL /* u16 */ +#define UCCF_URFET 0x28UL /* u16 */ +#define UCCF_URFSET 0x2AUL /* u16 */ +#define UCCF_UTFB 0x2CUL +#define UCCF_UTFS 0x30UL /* u16 */ +#define UCCF_UTFET 0x34UL /* u16 */ +#define UCCF_UTFTT 0x38UL /* u16 */ +#define UCCF_GUEMR 0x90UL /* u8 */ + +#define UCC_GUEMR_INIT 0x13U /* RESERVED3|FAST_RX|FAST_TX */ +#define UCC_GUMR_ETH 0x0000000CUL +#define UCC_GUMR_ENR 0x00000020UL +#define UCC_GUMR_ENT 0x00000010UL +#define UCC_FAST_TOD 0x8000U + +/* ---- UEC MAC register block (UCC base + 0x100) ---------------------- */ +#define UEC_MAC_OFFSET 0x100UL +#define UEC_MACCFG1 0x00UL +#define UEC_MACCFG2 0x04UL +#define UEC_IPGIFG 0x08UL +#define UEC_HAFDUP 0x0CUL +#define UEC_MIIMCFG 0x20UL +#define UEC_MIIMCOM 0x24UL +#define UEC_MIIMADD 0x28UL +#define UEC_MIIMCON 0x2CUL +#define UEC_MIIMSTAT 0x30UL +#define UEC_MIIMIND 0x34UL +#define UEC_MACSTNADDR1 0x40UL +#define UEC_MACSTNADDR2 0x44UL +#define UEC_UTBIPAR 0x54UL + +#define MACCFG1_ENABLE_RX 0x00000004UL +#define MACCFG1_ENABLE_TX 0x00000001UL +#define MACCFG2_INIT_VALUE 0x00007035UL /* PREL|RES1|LC|PAD_CRC|FDX */ +#define MACCFG2_FDX 0x00000001UL +#define MACCFG2_NIBBLE 0x00000100UL /* 10/100 */ +#define MACCFG2_BYTE 0x00000200UL /* 1000 */ +#define MACCFG2_IF_MODE_MASK 0x00000300UL +#define UPSMR_INIT_VALUE 0x02002000UL /* HSE|RES1 */ +#define UPSMR_RPM 0x00080000UL /* RGMII */ +#define UPSMR_R10M 0x00040000UL +#define UPSMR_TBIM 0x00010000UL +#define UPSMR_RMM 0x00001000UL /* RMII */ +#define UPSMR_SGMM 0x00000020UL /* SGMII */ + +/* MII management bits. */ +#define MIIMCFG_RESET_MGMT 0x80000000UL +#define MIIMCFG_CLK_DIV10 0x00000004UL +#define MIIMCOM_READ_CYCLE 0x00000001UL +#define MIIMADD_PHY_SHIFT 8 +#define MIIMIND_BUSY 0x00000001UL +#define MIIMIND_NOTVALID 0x00000004UL + +/* ---- Buffer descriptor (big-endian, 8 bytes) ------------------------ */ +struct qe_bd { + volatile uint16_t status; + volatile uint16_t len; + volatile uint32_t data; +}; +#define QE_SIZEOFBD 8U + +#define BD_WRAP 0x2000U +#define BD_INT 0x1000U +#define BD_LAST 0x0800U +#define BD_CLEAN 0x3000U /* keep WRAP|INT on recycle */ +#define TxBD_READY 0x8000U +#define TxBD_PADCRC 0x4000U +#define TxBD_TXCRC 0x0400U +#define RxBD_EMPTY 0x8000U +#define RxBD_FIRST 0x0400U +#define RxBD_ERROR 0x003EU /* LG|NO|SHORT|CRC|OVERRUN */ + +/* ---- Param RAM field offsets (in MURAM) ----------------------------- */ +/* TX global param RAM (0x80 bytes, align 64). */ +#define TXG_TEMODER 0x00UL /* u16 */ +#define TXG_SQPTR 0x38UL /* u32 */ +#define TXG_TSTATE 0x44UL /* u32 */ +#define TXG_TQPTR 0x70UL /* u32 */ +#define TXG_SIZE 0x80UL +#define TEMODER_INIT_VALUE 0xC000U +#define UEC_TSTATE_INIT 0x30000000UL /* BMR_INIT (0x30) << 24 */ +/* send-queue QD (align 32); entry 0 used. */ +#define SQQD_BD_RING_BASE 0x00UL /* u32 = TBASE */ +#define SQQD_LAST_BD 0x0CUL /* u32 */ +#define SQQD_SIZE 0x40UL + +/* RX global param RAM (0x100 bytes, align 64). */ +#define RXG_REMODER 0x00UL /* u32 */ +#define RXG_RQPTR 0x04UL /* u32 */ +#define RXG_TYPEORLEN 0x20UL /* u16 */ +#define RXG_RSTATE 0x36UL /* u8 */ +#define RXG_MRBLR 0x46UL /* u16 */ +#define RXG_RBDQPTR 0x48UL /* u32 */ +#define RXG_MFLR 0x4CUL /* u16 */ +#define RXG_MINFLR 0x4EUL /* u16 */ +#define RXG_MAXD1 0x50UL /* u16 */ +#define RXG_MAXD2 0x52UL /* u16 */ +#define RXG_VLANTYPE 0x7CUL /* u16 */ +#define RXG_SIZE 0x100UL +#define UEC_RSTATE_INIT 0x30U /* BMR_INIT byte */ +/* RX-BD-queue entry (align 8). */ +#define RBDQ_EXT_BD_BASE 0x08UL /* u32 = RBASE */ +#define RBDQ_SIZE 0x40UL + +/* init-enet command block (align 4). */ +#define INIT_RES0 0x00UL /* u8 x4: 0x06,0x30,0xff,0x00 */ +#define INIT_RES4 0x04UL /* u16: 0x0400 */ +#define INIT_RGF_TGF_RXG 0x08UL /* u32 */ +#define INIT_RXTHREAD 0x0CUL /* u32 x (num_rx+1) */ +#define INIT_TXGLOBAL 0x38UL /* u32 */ +#define INIT_TXTHREAD 0x3CUL /* u32 x num_tx */ +#define INIT_SIZE 0x60UL +#define ENET_INIT_RGF_SHIFT 28 +#define ENET_INIT_TGF_SHIFT 24 +#define ENET_INIT_SNUM_SHIFT 24 +#define QE_RISC_RISC1 0x00000001UL + +/* thread param sizes/alignments. */ +#define UEC_THREAD_RX_PRAM_SIZE 128UL +#define UEC_THREAD_TX_PRAM_SIZE 64UL +#define UEC_THREAD_DATA_RX_SIZE 40UL +#define UEC_THREAD_DATA_TX_SIZE 136UL + +/* ---- Ring / buffer sizing ------------------------------------------- */ +#define QE_RX_RING_SIZE 8U /* multiple of 4, min 8 */ +#define QE_TX_RING_SIZE 8U /* min 2 */ +#define QE_MRBLR 1536U /* multiple of 128 */ +#define QE_MAX_FRAME_LEN 1518U +#define QE_BD_RING_ALIGN 32U +#define QE_BUF_ALIGN 64U +#define QE_POLL_TRIES 1000000U + +/* ---- Public API ------------------------------------------------------ */ + +/* Bring up one UCC (param RAM, BD rings, MAC, PHY) and populate ll with the + * MAC address, interface name and poll/send callbacks. Pass mac=NULL to use + * the built-in default. Returns a status word encoding the detected PHY id + * high byte, link bit and PHY address, or a negative value on failure. */ +int nxp_qe_uec_init(struct wolfIP_ll_dev *ll, const uint8_t *mac); + +/* Detected PHY MDIO address, or -1 if the bus scan failed. */ +int nxp_qe_uec_phy_addr(void); + +/* MDIO clause-22 read of the detected PHY (0xFFFF on error / no PHY). */ +uint16_t nxp_qe_uec_phy_read(uint8_t reg); + +/* Non-zero when the PHY reports link up. */ +uint32_t nxp_qe_uec_link_up(void); + +/* Optional debug log hook (NULL = disabled). */ +void nxp_qe_uec_set_log(void (*log_fn)(const char *msg)); + +#endif /* WOLFIP_NXP_QE_UEC_H */ diff --git a/src/port/nxp_qe_uec/nxp_qe_uec_board.h b/src/port/nxp_qe_uec/nxp_qe_uec_board.h new file mode 100644 index 00000000..766cbee0 --- /dev/null +++ b/src/port/nxp_qe_uec/nxp_qe_uec_board.h @@ -0,0 +1,79 @@ +/* nxp_qe_uec_board.h + * + * Board parameters for the NXP QUICC Engine UCC ethernet driver + * (nxp_qe_uec.c). Defaults target the NXP P1021 (UCC1). + * + * Every value here is overridable from CFLAGS or a consumer header so the + * same driver can serve other QE/UCC boards. + * + * 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 WOLFIP_NXP_QE_UEC_BOARD_H +#define WOLFIP_NXP_QE_UEC_BOARD_H + +/* CCSRBAR (P1021 reset default, kept by wolfBoot). hal/nxp_ppc.h. */ +#ifndef NXP_QE_CCSRBAR +#define NXP_QE_CCSRBAR 0xFF700000UL +#endif + +/* QE engine block (QE_IMMR) = CCSRBAR + 0x80000 (wolfBoot QE_ENGINE_BASE). */ +#ifndef NXP_QE_IMMR_OFFSET +#define NXP_QE_IMMR_OFFSET 0x80000UL +#endif + +/* UCC index (0-based): UCC1=0, UCC2=1, UCC3=2, UCC4=3, UCC5=4, ... + * wolfBoot pin-muxes UCC1 as MII and UCC5 as RMII on the P1021. */ +#ifndef NXP_QE_UCC_NUM +#define NXP_QE_UCC_NUM 0U +#endif + +/* External PHY MDIO address. nxp_qe_uec_init() also scans the bus. */ +#ifndef NXP_QE_PHY_ADDR +#define NXP_QE_PHY_ADDR 0x0U +#endif + +/* MAC-to-PHY interface mode: + * 0 = MII (10/100, wolfBoot default mux for UCC1) + * 1 = RMII (10/100, wolfBoot default mux for UCC5; P1025RDB style) + * 2 = RGMII (10/100/1000, custom boards) + * The P1021RDB stock copper is on eTSEC, not the QE UCC -- pick the mode + * matching your board's actual UCC-to-PHY wiring. */ +#ifndef NXP_QE_IF_MODE +#define NXP_QE_IF_MODE 0 +#endif + +/* Link speed in Mbps used to program the MAC interface (10/100/1000). */ +#ifndef NXP_QE_SPEED +#define NXP_QE_SPEED 100 +#endif + +/* SNUMs handed to the RX and TX threads (distinct entries from the P1021 + * 28-snum pool: 0x04,0x05,0x0c,0x0d,0x14,0x15,...). The init-enet RX thread + * table carries num_rx+1 entries, so two RX SNUMs are used. */ +#ifndef NXP_QE_SNUM_RX +#define NXP_QE_SNUM_RX 0x04U +#endif +#ifndef NXP_QE_SNUM_RX2 +#define NXP_QE_SNUM_RX2 0x0CU +#endif +#ifndef NXP_QE_SNUM_TX +#define NXP_QE_SNUM_TX 0x05U +#endif + +#endif /* WOLFIP_NXP_QE_UEC_BOARD_H */ diff --git a/src/tftp/wolftftp.c b/src/tftp/wolftftp.c index aa7d9ab8..a87d285f 100644 --- a/src/tftp/wolftftp.c +++ b/src/tftp/wolftftp.c @@ -20,10 +20,13 @@ */ #include "wolftftp.h" -#include -#include #include +/* This client avoids (snprintf) and (tolower) so it + * links on bare-metal targets without a hosted libc. The small local + * helpers below cover the only uses: ASCII case-folding and unsigned + * decimal formatting of TFTP option values. */ + #define WOLFTFTP_PKT_MAX (4U + WOLFTFTP_MAX_BLKSIZE) #define WOLFTFTP_OPT_BLKSIZE 0x01U #define WOLFTFTP_OPT_TIMEOUT 0x02U @@ -59,6 +62,35 @@ static size_t wolftftp_strnlen_local(const char *s, size_t max_len) return max_len; } +/* ASCII lowercase (no /_ctype_ dependency). */ +static unsigned char wolftftp_tolower_local(unsigned char c) +{ + if (c >= 'A' && c <= 'Z') + c = (unsigned char)(c - 'A' + 'a'); + return c; +} + +/* Format an unsigned value as decimal into out[0..cap-1] with a NUL + * terminator, replacing snprintf("%u"/"%lu", ...). */ +static void wolftftp_utoa_local(char *out, size_t cap, unsigned long v) +{ + char tmp[24]; + size_t n = 0; + size_t i; + + if (cap == 0) + return; + if (v == 0) + tmp[n++] = '0'; + while (v != 0) { + tmp[n++] = (char)('0' + (v % 10UL)); + v /= 10UL; + } + for (i = 0; i < n && (i + 1U) < cap; i++) + out[i] = tmp[n - 1U - i]; + out[i] = '\0'; +} + static int wolftftp_stricmp_local(const char *a, const char *b) { unsigned char ca; @@ -67,8 +99,8 @@ static int wolftftp_stricmp_local(const char *a, const char *b) if (a == NULL || b == NULL) return -1; while (*a != '\0' || *b != '\0') { - ca = (unsigned char)tolower((unsigned char)*a); - cb = (unsigned char)tolower((unsigned char)*b); + ca = wolftftp_tolower_local((unsigned char)*a); + cb = wolftftp_tolower_local((unsigned char)*b); if (ca != cb) return (int)ca - (int)cb; if (*a != '\0') @@ -240,28 +272,28 @@ static int wolftftp_build_request(uint8_t *buf, uint16_t max_len, uint16_t opcod *requested_opts = 0; if (cfg->blksize != WOLFTFTP_DEFAULT_BLKSIZE) { - (void)snprintf(value, sizeof(value), "%u", cfg->blksize); + wolftftp_utoa_local(value, sizeof(value), (unsigned long)cfg->blksize); ret = wolftftp_append_opt(buf, &off, max_len, "blksize", value); if (ret != 0) return ret; *requested_opts |= WOLFTFTP_OPT_BLKSIZE; } if (cfg->timeout_s != WOLFTFTP_DEFAULT_TIMEOUT_S) { - (void)snprintf(value, sizeof(value), "%u", cfg->timeout_s); + wolftftp_utoa_local(value, sizeof(value), (unsigned long)cfg->timeout_s); ret = wolftftp_append_opt(buf, &off, max_len, "timeout", value); if (ret != 0) return ret; *requested_opts |= WOLFTFTP_OPT_TIMEOUT; } if (cfg->windowsize > 1U) { - (void)snprintf(value, sizeof(value), "%u", cfg->windowsize); + wolftftp_utoa_local(value, sizeof(value), (unsigned long)cfg->windowsize); ret = wolftftp_append_opt(buf, &off, max_len, "windowsize", value); if (ret != 0) return ret; *requested_opts |= WOLFTFTP_OPT_WINDOWSIZE; } if (tsize != 0U || cfg->max_image_size != 0U) { - (void)snprintf(value, sizeof(value), "%lu", (unsigned long)tsize); + wolftftp_utoa_local(value, sizeof(value), (unsigned long)tsize); ret = wolftftp_append_opt(buf, &off, max_len, "tsize", value); if (ret != 0) return ret; @@ -431,25 +463,25 @@ static int wolftftp_build_oack(uint8_t *buf, uint16_t max_len, return -WOLFIP_EINVAL; wolftftp_write_u16(buf, WOLFTFTP_OP_OACK); if ((opts & WOLFTFTP_OPT_BLKSIZE) != 0U) { - (void)snprintf(value, sizeof(value), "%u", neg->blksize); + wolftftp_utoa_local(value, sizeof(value), (unsigned long)neg->blksize); ret = wolftftp_append_opt(buf, &off, max_len, "blksize", value); if (ret != 0) return ret; } if ((opts & WOLFTFTP_OPT_TIMEOUT) != 0U) { - (void)snprintf(value, sizeof(value), "%u", neg->timeout_s); + wolftftp_utoa_local(value, sizeof(value), (unsigned long)neg->timeout_s); ret = wolftftp_append_opt(buf, &off, max_len, "timeout", value); if (ret != 0) return ret; } if ((opts & WOLFTFTP_OPT_TSIZE) != 0U) { - (void)snprintf(value, sizeof(value), "%lu", (unsigned long)neg->tsize); + wolftftp_utoa_local(value, sizeof(value), (unsigned long)neg->tsize); ret = wolftftp_append_opt(buf, &off, max_len, "tsize", value); if (ret != 0) return ret; } if ((opts & WOLFTFTP_OPT_WINDOWSIZE) != 0U) { - (void)snprintf(value, sizeof(value), "%u", neg->windowsize); + wolftftp_utoa_local(value, sizeof(value), (unsigned long)neg->windowsize); ret = wolftftp_append_opt(buf, &off, max_len, "windowsize", value); if (ret != 0) return ret; diff --git a/wolfip.h b/wolfip.h index 1a5b8e7c..8294fb3a 100644 --- a/wolfip.h +++ b/wolfip.h @@ -120,8 +120,19 @@ typedef uint32_t ip4; /* Macros, compiler specific. */ #define PACKED __attribute__((packed)) -#define ee16(x) __builtin_bswap16(x) -#define ee32(x) __builtin_bswap32(x) +/* ee16/ee32 convert between host and network (big-endian) byte order. + * On a big-endian host (e.g. PowerPC e5500/e6500) host order already + * matches network order, so these must be no-ops; only swap on a + * little-endian host. */ +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +# define ee16(x) ((uint16_t)(x)) +# define ee32(x) ((uint32_t)(x)) +#elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define ee16(x) __builtin_bswap16(x) +# define ee32(x) __builtin_bswap32(x) +#else +# error "Cannot determine byte order; define __BYTE_ORDER__" +#endif #ifndef WOLFIP_EAGAIN #ifdef EAGAIN