Skip to content

Add NXP LPC54S018M-EVK port#96

Open
aidangarske wants to merge 3 commits intomasterfrom
nxp-LPC54S-port
Open

Add NXP LPC54S018M-EVK port#96
aidangarske wants to merge 3 commits intomasterfrom
nxp-LPC54S-port

Conversation

@aidangarske
Copy link
Copy Markdown
Member

Description

  • Add bare-metal wolfIP port for the NXP LPCXpresso54S018M-EVK (Cortex-M4F, LPC54S018J4M)
  • Add shared NXP LPC Ethernet driver src/port/lpc_enet/ for Synopsys DW Ethernet QoS MAC
  • DHCP, ICMP ping, TCP echo server on port 7 over 100 Mbps RMII Ethernet (LAN8720A PHY)
  • Sub-millisecond ping latency at 96 MHz (FRO HF)
  • Build CI .github/workflows/lpc54s018.yml

  First NXP platform support for wolfIP. Adds a bare-metal port for the
  LPCXpresso54S018M development board (Cortex-M4F, 96 MHz) with DHCP,
  ICMP ping, and TCP echo server over 100 Mbps Ethernet (LAN8720A PHY).

  The Ethernet driver is split into a shared lpc_enet/ component (Synopsys
  DesignWare Ethernet QoS with enhanced descriptors) and board-specific
  code in lpc54s018/, matching the existing stm32/stm32h563 structure for
  easy reuse across other NXP LPC boards.

  Tested: DHCP lease acquisition, ICMP ping (<1 ms), TCP echo on port 7.
@aidangarske aidangarske self-assigned this Apr 2, 2026
Copilot AI review requested due to automatic review settings April 2, 2026 18:56

This comment was marked as resolved.

wolfSSL-Fenrir-bot

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings April 2, 2026 20:49
@aidangarske aidangarske review requested due to automatic review settings April 2, 2026 20:50
@aidangarske aidangarske requested a review from danielinux April 2, 2026 20:50
@aidangarske aidangarske marked this pull request as ready for review April 2, 2026 20:51
Copilot AI review requested due to automatic review settings April 2, 2026 20:51

This comment was marked as resolved.

  - Makefile: Add cppcheck comparePointers suppressions for lpc54s018 startup.c/syscalls.c
  - main.c: Fix non-atomic 64-bit tick_ms reads with get_tick_ms() (disable/enable interrupts)
  - startup.c: Restore __libc_init_array() call (matches other ports)
  - lpc_enet.c: Add RX_BUF_SIZE bounds check in eth_poll to prevent buffer over-read
  - fix_checksum.py: Auto-detect vector table offset (0x0 for RAM build, 0x200 for flash)
  - target_ram.ld: Clarify SRAM2 is 32KB on J4M package
  - flash.sh: Add flash helper script (matches N6 port's flash.sh)
Copy link
Copy Markdown

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #96

Scan targets checked: wolfip-bugs, wolfip-compliance, wolfip-defaults, wolfip-mutation, wolfip-proptest, wolfip-src

Findings: 6
6 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Findings are non-blocking.

Comment on lines +263 to +273

static int hw_reset(void)
{
uint32_t t = 1000000U;
ETH_DMAMR |= DMAMR_SWR;
while ((ETH_DMAMR & DMAMR_SWR) && --t) { }
return t ? 0 : -1;
}

static void config_mac(const uint8_t mac[6])
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔶 [Medium] config_speed_duplex uses PHY capability bits instead of negotiated link parameters
Category: Logic errors

config_speed_duplex() reads the PHY BSR register and uses bits 11-14 (BSR_10_HALF, BSR_10_FULL, BSR_100_HALF, BSR_100_FULL) to configure the MAC speed and duplex mode. Per IEEE 802.3, BSR bits 11-14 indicate the PHY's capability (what speeds/modes it can support), not the actual negotiated link parameters. This means the MAC will always be configured for the highest mode the PHY supports (typically 100 Mbps full-duplex for the LAN8720A), regardless of what was actually negotiated with the link partner. If the link partner only supports 10 Mbps or half-duplex, the MAC will be misconfigured, causing communication failure. The correct approach is to read the Auto-Negotiation Link Partner Ability Register (ANLPAR, reg 5), AND it with the local ANAR (reg 4), and derive the highest common mode — or read a PHY vendor-specific status register (e.g., LAN8720A reg 0x1F, Special Modes register, or reg 0x1E for PHY Special Control/Status).

static void config_speed_duplex(void)
{
    uint32_t maccr;
    uint16_t bsr;
    if (phy_addr < 0) return;
    maccr = ETH_MACCR & ~(MACCR_FES | MACCR_DM);
    bsr = mdio_read((uint32_t)phy_addr, PHY_BSR);
    bsr |= mdio_read((uint32_t)phy_addr, PHY_BSR);
    if (bsr & BSR_100_FULL)       maccr |= MACCR_FES | MACCR_DM;
    else if (bsr & BSR_100_HALF)  maccr |= MACCR_FES;
    else if (bsr & BSR_10_FULL)   maccr |= MACCR_DM;
    ETH_MACCR = maccr;
}

Recommendation: Read ANLPAR (register 5) and AND with ANAR (register 4) to determine the highest common capability, then configure the MAC accordingly. For example:

uint16_t anar = mdio_read(phy_addr, PHY_ANAR);
uint16_t anlpar = mdio_read(phy_addr, 0x05); // ANLPAR
uint16_t common = anar & anlpar;
if (common & 0x0100)      maccr |= MACCR_FES | MACCR_DM; // 100F
else if (common & 0x0080) maccr |= MACCR_FES;             // 100H
else if (common & 0x0040) maccr |= MACCR_DM;              // 10F

Comment on lines +280 to +281
ETH_MACWTR = 0;
ETH_MACQ0TXFCR = (1U << 7);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔹 [Low] DMACRXCR buffer size configured using RX descriptor packet-length mask
Category: Incorrect sizeof/type usage

The DMA RX control register buffer size field is masked with RDES3_PL_MASK (a mask defined for the packet length field in RX descriptors). While both fields happen to be 14 bits wide (0x3FFF), using a descriptor-specific mask for a DMA control register field is semantically incorrect and fragile — if RDES3_PL_MASK were ever changed to match a different descriptor revision, this DMA configuration would silently break. The Synopsys DW Ethernet QoS RBSZ field in CH0_RX_CONTROL uses bits [14:1], and should use its own named mask constant.

ETH_DMACRXCR = ((RX_BUF_SIZE & RDES3_PL_MASK) << DMACRXCR_RBSZ_SHIFT) |
               DMACRXCR_RPBL(DMA_PBL);

Recommendation: Define a dedicated mask for the RBSZ field, e.g.:

#define DMACRXCR_RBSZ_MASK 0x3FFFU

and use it:

ETH_DMACRXCR = ((RX_BUF_SIZE & DMACRXCR_RBSZ_MASK) << DMACRXCR_RBSZ_SHIFT) | ...

Comment on lines +109 to +121
lfsr ^= lfsr >> 17;
lfsr ^= lfsr << 5;
return lfsr;
}

static void delay_ms(uint32_t ms)
{
uint64_t target = get_tick_ms() + ms;
while (get_tick_ms() < target) { }
}

static void clock_init(void)
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [High] Weak PRNG: LFSR seeded from predictable millisecond tick counter
Category: Unsafe protocol defaults

wolfIP_getrandom() uses a 32-bit xorshift LFSR seeded from get_tick_ms(). This function is called by the wolfIP stack for generating TCP initial sequence numbers (ISNs) and other protocol randomness. The seed is the system uptime in milliseconds at the time of the first call, which is highly predictable — an attacker who can estimate device boot time (or simply observe a few packets) can predict the LFSR state and thus predict future TCP ISNs. The fallback seed 0x1A2B3C4DU when tick_ms == 0 is a hardcoded constant, making the sequence fully deterministic if the first call happens before SysTick fires. Predictable TCP ISNs enable TCP session hijacking, RST injection, and data injection attacks.

uint32_t wolfIP_getrandom(void)
{
    static uint32_t lfsr;
    static int seeded = 0;
    if (!seeded) {
        lfsr = (uint32_t)get_tick_ms();
        if (lfsr == 0U) lfsr = 0x1A2B3C4DU;
        seeded = 1;
    }
    lfsr ^= lfsr << 13;
    lfsr ^= lfsr >> 17;
    lfsr ^= lfsr << 5;
    return lfsr;
}

Recommendation: Use a hardware entropy source if available on the LPC54S018 (e.g., the on-chip True Random Number Generator peripheral, or combine multiple entropy sources such as ADC LSBs, uninitialized SRAM values at boot, and timing jitter). At minimum, mix in additional entropy beyond the tick counter (e.g., PHY register values, MAC address, free-running timer LSBs) to make the seed less predictable. Document the limitation if no hardware RNG is available.

Comment on lines +282 to +292

/* TCP echo server on port 7 */
{
struct wolfIP_sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = ee16(ECHO_PORT);
addr.sin_addr.s_addr = 0;
listen_fd = wolfIP_sock_socket(IPStack, AF_INET, IPSTACK_SOCK_STREAM, 0);
wolfIP_register_callback(IPStack, listen_fd, echo_cb, IPStack);
(void)wolfIP_sock_bind(IPStack, listen_fd,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔶 [Medium] Echo server ignores socket and bind/listen return values
Category: Silent error swallowing

The TCP echo server setup calls wolfIP_sock_socket(), wolfIP_sock_bind(), and wolfIP_sock_listen() but casts the return values of bind and listen to (void) and does not check the return value of wolfIP_sock_socket() before passing listen_fd to wolfIP_register_callback() and wolfIP_sock_bind(). If socket creation fails (returns a negative value), listen_fd will be negative, and subsequent calls to wolfIP_register_callback, wolfIP_sock_bind, and wolfIP_sock_listen with an invalid fd could cause undefined behavior in the wolfIP stack, or silently fail leaving the echo server callback registered on an invalid descriptor. Similarly, if bind fails, listen proceeds anyway, potentially leaving a half-configured socket.

listen_fd = wolfIP_sock_socket(IPStack, AF_INET, IPSTACK_SOCK_STREAM, 0);
wolfIP_register_callback(IPStack, listen_fd, echo_cb, IPStack);
(void)wolfIP_sock_bind(IPStack, listen_fd,
                       (struct wolfIP_sockaddr *)&addr, sizeof(addr));
(void)wolfIP_sock_listen(IPStack, listen_fd, 1);

Recommendation: Check the return value of wolfIP_sock_socket(). If it returns a negative value, do not proceed with bind/listen/register. Also check wolfIP_sock_bind() and wolfIP_sock_listen() return values and handle errors (at minimum, log the failure).

Comment on lines +468 to +473
uint16_t id1, bsr;

if (!ll) return -1;

if (!mac) {
local_mac[0] = 0x02; local_mac[1] = 0x11;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔹 [Low] Hardcoded fallback MAC address shared across all devices
Category: Dangerous default configurations

When mac is NULL (as called from main.c line 261: lpc54s018_eth_init(ll, NULL)), lpc_enet_init uses a hardcoded locally-administered MAC address 02:11:54:18:00:01. Every device flashed with this firmware will share the same MAC address. On a shared network, duplicate MAC addresses cause ARP table conflicts, intermittent connectivity, and can enable an attacker to impersonate or intercept traffic from another device running the same firmware. While the 0x02 prefix correctly marks it as locally administered, the static nature means no uniqueness guarantee.

if (!mac) {
    local_mac[0] = 0x02; local_mac[1] = 0x11;
    local_mac[2] = 0x54; local_mac[3] = 0x18;
    local_mac[4] = 0x00; local_mac[5] = 0x01;
    mac = local_mac;
}

Recommendation: Document prominently that the default MAC is for single-device development only. Consider deriving a unique MAC from a device-unique identifier (e.g., LPC54S018 UID registers at 0x40000100) or require the caller to always provide a MAC address.

Comment on lines +248 to +260
systick_init();
led_init();

printf("\r\n=== wolfIP LPC54S018M-EVK ===\r\n");

eth_gpio_init();
phy_reset();
eth_clk_init();

wolfIP_init_static(&IPStack);

ll = wolfIP_getdev(IPStack);
ret = lpc54s018_eth_init(ll, NULL);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔹 [Low] Echo server does not close socket on receive error
Category: Resource leaks

In echo_cb(), when wolfIP_sock_recvfrom() returns a negative value (error), the function neither closes the socket nor resets client_fd. The ret > 0 branch echoes data, the ret == 0 branch closes the socket (clean close), but ret < 0 falls through with no action. While the CB_EVENT_CLOSED handler below may eventually fire, if the error condition persists without triggering a close event, client_fd remains set to the errored socket, preventing new connections from being accepted (since accept is gated on client_fd == -1). On a single-connection echo server, this effectively becomes a denial of service for the echo service until the device is rebooted.

if ((fd == client_fd) && (event & CB_EVENT_READABLE)) {
        ret = wolfIP_sock_recvfrom(s, client_fd, rx_buf, sizeof(rx_buf),
                                   0, NULL, NULL);
        if (ret > 0) {
            (void)wolfIP_sock_sendto(s, client_fd, rx_buf, (uint32_t)ret,
                                     0, NULL, 0);
        } else if (ret == 0) {
            wolfIP_sock_close(s, client_fd);
            client_fd = -1;
        }
    }

Recommendation: Handle ret < 0 by closing the client socket and resetting client_fd: add else { wolfIP_sock_close(s, client_fd); client_fd = -1; } after the ret == 0 block.

@dgarske dgarske self-assigned this Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants