From 4ac2d88f4929ec3ac0a7628aead297ace1df2ae8 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 5 Aug 2026 09:19:51 -0700 Subject: [PATCH] zynq: add optional ZynqMP GEM Ethernet datapath bring-up --- .github/workflows/test-build-aarch64.yml | 7 +++ .github/workflows/test-configs.yml | 7 +++ config/examples/zynqmp.config | 19 +++++++ config/examples/zynqmp_sdcard.config | 19 +++++++ docs/Targets.md | 17 ++++++- hal/zynq.c | 56 +++++++++++++++++++++ hal/zynq.h | 63 ++++++++++++++++++++++++ 7 files changed, 186 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-build-aarch64.yml b/.github/workflows/test-build-aarch64.yml index 4865295430..230cbd65e4 100644 --- a/.github/workflows/test-build-aarch64.yml +++ b/.github/workflows/test-build-aarch64.yml @@ -13,6 +13,11 @@ on: make-args: required: false type: string + extra-cflags: + # Extra flags appended to CFLAGS_EXTRA in .config (quoted, so spaces and + # multiple -D survive). Used for opt-in features such as GEM init. + required: false + type: string psu-init-stub: # Generate a build-only stub for the board psu_init_gpl.c (ZynqMP FSBL). required: false @@ -93,6 +98,8 @@ jobs: make distclean - name: Select config + env: + EXTRA_CFLAGS: ${{inputs.extra-cflags}} run: | cp ${{inputs.config-file}} .config if [ -n "$EXTRA_CFLAGS" ]; then diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index b43138f428..af7c739efc 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -781,6 +781,13 @@ jobs: config-file: ./config/examples/zynqmp.config make-args: CFLAGS_EXTRA=-DWOLFBOOT_ZYNQMP_PHY_INIT + zynqmp_gem_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + config-file: ./config/examples/zynqmp.config + extra-cflags: '-DWOLFBOOT_ZYNQMP_GEM_INIT -DZYNQMP_GEM_INIT_LIST={0xFF0B0000UL,1000},{0xFF0C0000UL,100}' + zynq7000_test: uses: ./.github/workflows/test-build.yml with: diff --git a/config/examples/zynqmp.config b/config/examples/zynqmp.config index 920d1d0ef5..8ff7924fec 100644 --- a/config/examples/zynqmp.config +++ b/config/examples/zynqmp.config @@ -124,3 +124,22 @@ CFLAGS_EXTRA+=-DWOLFBOOT_SHA_BLOCK_SIZE=4096 #CFLAGS_EXTRA+=-DZYNQMP_GEM_BASE=0xFF0B0000UL # A PHY behind the PL only responds once the FPGA bitstream is loaded, so the # boot image (BOOT.BIN) must include it (bootgen [destination_device=pl]). + +# Optional: GEM Ethernet datapath bring-up (fixed-link SGMII). +# Complements the PHY init above: use THAT for an external MDIO PHY, use THIS +# for a fixed link with no PHY (it configures the GEM MAC datapath itself). The +# two must target different GEMs. +# For ports on a fixed link (backplane, or SGMII with no PHY the OS driver +# auto-configures) that rely on the bootloader initializing the GEM. Replays +# U-Boot's zynq_gem datapath setup: NWCFG (SGMII/PCS/speed) + PCS control +# (fixed link). RX/TX are left for the OS to enable. +# List the GEMs as {base, speed_mbps} rows, directly: +#CFLAGS_EXTRA+=-DWOLFBOOT_ZYNQMP_GEM_INIT +#CFLAGS_EXTRA+=-DZYNQMP_GEM_INIT_LIST={0xFF0B0000UL,1000},{0xFF0C0000UL,1000} +# or from a board header: +#CFLAGS_EXTRA+=-DZYNQMP_GEM_INIT_HEADER='"myboard_gem.h"' +# where myboard_gem.h has, for example: +# #define ZYNQMP_GEM_INIT_LIST { 0xFF0B0000UL, 1000 }, { 0xFF0C0000UL, 1000 } +# (GEM0 0xFF0B0000 .. GEM3 0xFF0E0000; speed 1000/100/10; an unexpected speed is +# skipped, logged only when DEBUG_UART=1). Requires the GEM already clocked/out +# of reset by the FSBL psu_init. diff --git a/config/examples/zynqmp_sdcard.config b/config/examples/zynqmp_sdcard.config index b13ad03cf7..8bbde8e8d9 100644 --- a/config/examples/zynqmp_sdcard.config +++ b/config/examples/zynqmp_sdcard.config @@ -181,3 +181,22 @@ WOLFBOOT_SECTOR_SIZE=0x1000 #CFLAGS_EXTRA+=-DZYNQMP_GEM_BASE=0xFF0B0000UL # A PHY behind the PL only responds once the FPGA bitstream is loaded, so the # boot image (BOOT.BIN) must include it (bootgen [destination_device=pl]). + +# Optional: GEM Ethernet datapath bring-up (fixed-link SGMII). +# Complements the PHY init above: use THAT for an external MDIO PHY, use THIS +# for a fixed link with no PHY (it configures the GEM MAC datapath itself). The +# two must target different GEMs. +# For ports on a fixed link (backplane, or SGMII with no PHY the OS driver +# auto-configures) that rely on the bootloader initializing the GEM. Replays +# U-Boot's zynq_gem datapath setup: NWCFG (SGMII/PCS/speed) + PCS control +# (fixed link). RX/TX are left for the OS to enable. +# List the GEMs as {base, speed_mbps} rows, directly: +#CFLAGS_EXTRA+=-DWOLFBOOT_ZYNQMP_GEM_INIT +#CFLAGS_EXTRA+=-DZYNQMP_GEM_INIT_LIST={0xFF0B0000UL,1000},{0xFF0C0000UL,1000} +# or from a board header: +#CFLAGS_EXTRA+=-DZYNQMP_GEM_INIT_HEADER='"myboard_gem.h"' +# where myboard_gem.h has, for example: +# #define ZYNQMP_GEM_INIT_LIST { 0xFF0B0000UL, 1000 }, { 0xFF0C0000UL, 1000 } +# (GEM0 0xFF0B0000 .. GEM3 0xFF0E0000; speed 1000/100/10; an unexpected speed is +# skipped, logged only when DEBUG_UART=1). Requires the GEM already clocked/out +# of reset by the FSBL psu_init. diff --git a/docs/Targets.md b/docs/Targets.md index 09498e6314..7d8b6b962e 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -3795,9 +3795,22 @@ Key configuration options: - `HASH=SHA3` - SHA3-384 hashing - `ELF=1` - ELF loading support -### Ethernet PHY init (optional) +### ZynqMP Ethernet bring-up (optional) -Opt-in (off by default), wolfBoot can replay a board's U-Boot Ethernet PHY register sequence over the GEM MDIO management plane so the PHY is ready before the OS runs. Enable with `CFLAGS_EXTRA+=-DWOLFBOOT_ZYNQMP_PHY_INIT`. The default targets the ZCU102 on-board PHY (TI DP83867 at MDIO `0x0C` on GEM3, `0xFF0E0000`) and just reads the PHY ID as a diagnostic (printed with `DEBUG_UART=1`). A board supplies its own sequence by keeping its values in a small header selected with one line, `CFLAGS_EXTRA+=-DZYNQMP_PHY_INIT_HEADER='"myboard_phy.h"'`, where that header `#define`s any of `ZYNQMP_GEM_BASE`, `ZYNQMP_PHY_ADDR`, `ZYNQMP_PHY_GPIO_ADDR`, `ZYNQMP_GEM_MDC_DIV`, and the `{op, arg0, arg1}` step array `ZYNQMP_PHY_INIT_STEPS`; scalars can also be set directly with `-D`, and anything omitted falls back to the ZCU102 defaults (see `hal/zynq.h` and the commented example in `config/examples/zynqmp.config`). Where the PHY is behind the PL, the boot image must include the FPGA bitstream (bootgen `[destination_device=pl] system.bit`) or the transactions are no-ops. +wolfBoot has no network stack, but on ZynqMP it can leave the Ethernet hardware in the state the OS expects when it replaces U-Boot. There are two independent, opt-in helpers; choose by what the port actually needs: + +- A port with a real external MDIO PHY that needs register setup before the OS (auto-negotiation, errata, LEDs) uses the **PHY init** helper (`WOLFBOOT_ZYNQMP_PHY_INIT`) - it drives the MDIO management plane to read/write the PHY chip. +- A port on a fixed link with no PHY to configure (a backplane, or SGMII where the OS driver does not bring the MAC up) uses the **GEM datapath bring-up** helper (`WOLFBOOT_ZYNQMP_GEM_INIT`) - it configures the GEM MAC's own SGMII/PCS (the OS then enables RX/TX). Tell-tale: the port has no MDIO PHY (a scan reads back `0xFFFF`) yet works under U-Boot, because U-Boot's driver initialized the MAC datapath, not a PHY. + +The two are independent and can both be enabled on the same board, but for different ports - a given GEM is either a PHY port or a fixed-link port, not both. + +#### Ethernet PHY init + +Opt-in (off by default), wolfBoot can replay a board's U-Boot Ethernet PHY register sequence over the GEM MDIO management plane so the PHY is ready before the OS runs. Enable with `CFLAGS_EXTRA+=-DWOLFBOOT_ZYNQMP_PHY_INIT`. The default targets the ZCU102 on-board PHY (TI DP83867 at MDIO `0x0C` on GEM3, `0xFF0E0000`) and just reads the PHY ID as a diagnostic (printed with `DEBUG_UART=1`). A board supplies its own sequence by keeping its values in a small header selected with one line, `CFLAGS_EXTRA+=-DZYNQMP_PHY_INIT_HEADER='"myboard_phy.h"'`, where that header `#define`s any of `ZYNQMP_GEM_BASE`, `ZYNQMP_PHY_ADDR`, `ZYNQMP_PHY_GPIO_ADDR`, `ZYNQMP_GEM_MDC_DIV`, and the `{op, arg0, arg1}` step array `ZYNQMP_PHY_INIT_STEPS`; scalars can also be set directly with `-D`, and anything omitted falls back to the ZCU102 defaults (see `hal/zynq.h` and the commented example in `config/examples/zynqmp.config`). Where the PHY is behind the PL, the boot image must include the FPGA bitstream (bootgen `[destination_device=pl] system.bit`) or the transactions are no-ops. For a fixed-link port with no PHY to configure, use the GEM datapath bring-up below instead of this. + +#### GEM Ethernet datapath bring-up + +Some ZynqMP ports are on a fixed link (a backplane, or an SGMII connection with no PHY that the OS driver auto-configures) and rely on the bootloader having brought the GEM datapath up. U-Boot's `zynq_gem` driver does this the first time it uses an interface (for example a `tftpboot`); when wolfBoot replaces U-Boot that step is lost and those ports stay down even though the OS enumerates them. Enable `CFLAGS_EXTRA+=-DWOLFBOOT_ZYNQMP_GEM_INIT` and list the affected GEMs as `{gem_base, speed_mbps}` rows in `ZYNQMP_GEM_INIT_LIST`, either directly (`CFLAGS_EXTRA+=-DZYNQMP_GEM_INIT_LIST={0xFF0B0000UL,1000},{0xFF0C0000UL,1000}`, no spaces) or from a header (`CFLAGS_EXTRA+=-DZYNQMP_GEM_INIT_HEADER='"myboard_gem.h"'` with `#define ZYNQMP_GEM_INIT_LIST { 0xFF0B0000UL, 1000 }, { 0xFF0C0000UL, 1000 }`) - GEM0 `0xFF0B0000` .. GEM3 `0xFF0E0000`, speed 1000/100/10. For each GEM wolfBoot writes `NWCFG` (SGMII + PCS select + speed) and the PCS control register (fixed link, auto-negotiation disabled) - the same registers U-Boot's driver sets. RX/TX are deliberately left off: the OS driver enables them after it programs its DMA descriptor rings (wolfBoot has none), so enabling RX here could DMA a received frame to an arbitrary address during image verify. The GEM must already be clocked and out of reset by the FSBL `psu_init`. This is distinct from the PHY init above (which configures an external MDIO PHY); the two helpers must target different GEMs, and if both name the same base this full `NWCFG` write wins - override `GEMI_CFG_MDCDIV` to reconcile the MDC divisor. ### Building with Xilinx tools (Vitis IDE) diff --git a/hal/zynq.c b/hal/zynq.c index 7717e866d9..aa030ddcd5 100644 --- a/hal/zynq.c +++ b/hal/zynq.c @@ -2176,6 +2176,58 @@ static void zynq_phy_init(void) } #endif /* WOLFBOOT_ZYNQMP_PHY_INIT */ +#ifdef WOLFBOOT_ZYNQMP_GEM_INIT +/* Bring up the listed GEM datapaths for a fixed-link SGMII connection, the way + * U-Boot's zynq_gem driver does: program NWCFG (SGMII + PCS + speed) and the + * PCS control register (fixed link, auto-neg off). RX/TX are left disabled - + * the OS driver enables them after programming its DMA descriptor rings; with + * no rings, enabling RX here could DMA a received frame to an arbitrary address + * during image verify. The GEM must already be clocked and out of reset (FSBL + * psu_init). See hal/zynq.h. */ +static void zynqmp_gem_init(void) +{ + static const struct { + uintptr_t base; + uint16_t speed; + } gems[] = { ZYNQMP_GEM_INIT_LIST }; + uint32_t i, nwcfg, pcs, rb; + + for (i = 0; i < (sizeof(gems) / sizeof(gems[0])); i++) { + nwcfg = GEMI_CFG_SGMII_BASE; + if (gems[i].speed == 1000) { + nwcfg |= GEMI_CFG_GIGE; + pcs = GEMI_PCS_1000; + } + else if (gems[i].speed == 100) { + nwcfg |= GEMI_CFG_SPEED100; + pcs = GEMI_PCS_100; + } + else if (gems[i].speed == 10) { + pcs = GEMI_PCS_10; + } + else { + wolfBoot_printf("GEM init @0x%08x: bad speed %d, skipped\n", + (unsigned int)gems[i].base, (int)gems[i].speed); + continue; + } + /* NWCFG (with PCS_SEL) must be written before the PCS control write. */ + *((volatile uint32_t*)(gems[i].base + GEMI_NWCFG)) = nwcfg; + *((volatile uint32_t*)(gems[i].base + GEMI_PCS)) = pcs; + /* Read NWCFG back: a clock-gated / in-reset GEM will not latch it. */ + rb = *((volatile uint32_t*)(gems[i].base + GEMI_NWCFG)); + if (rb != nwcfg) { + wolfBoot_printf("GEM init @0x%08x NWCFG 0x%08x != 0x%08x " + "(gated/in reset?)\n", (unsigned int)gems[i].base, + (unsigned int)rb, (unsigned int)nwcfg); + } + else { + wolfBoot_printf("GEM init @0x%08x %dMbps\n", + (unsigned int)gems[i].base, (int)gems[i].speed); + } + } +} +#endif /* WOLFBOOT_ZYNQMP_GEM_INIT */ + /* public HAL functions */ void hal_init(void) { @@ -2214,6 +2266,10 @@ void hal_init(void) zynq_phy_init(); #endif +#ifdef WOLFBOOT_ZYNQMP_GEM_INIT + zynqmp_gem_init(); +#endif + #if defined(EXT_FLASH) && (EXT_FLASH == 1) qspi_init(); #endif diff --git a/hal/zynq.h b/hal/zynq.h index 9e6661e965..c1a21efb30 100644 --- a/hal/zynq.h +++ b/hal/zynq.h @@ -515,6 +515,69 @@ #endif /* WOLFBOOT_ZYNQMP_PHY_INIT */ +#ifdef WOLFBOOT_ZYNQMP_GEM_INIT +/* GEM (Ethernet MAC) datapath bring-up for fixed-link SGMII ports. + * + * Some ZynqMP boards have GEM ports on a fixed link (a backplane, or an SGMII + * connection with no PHY that the OS driver auto-configures) that rely on the + * bootloader having brought the GEM datapath up. U-Boot's zynq_gem driver does + * this the first time it uses an interface; wolfBoot has no network stack, so + * this option replays the equivalent register writes: NWCFG (SGMII + PCS + + * speed) and the PCS control register (fixed link, auto-negotiation disabled). + * The values mirror the Cadence GEM driver's zynq_gem_init(). RX/TX are left + * disabled: the OS driver enables them after it programs its DMA descriptor + * rings (wolfBoot has none), so enabling RX here would be unsafe. The GEM must + * already be clocked and out of reset by the FSBL psu_init. + * + * This is separate from WOLFBOOT_ZYNQMP_PHY_INIT: a GEM is either a PHY port + * (that helper) or a fixed-link port (this one) - the two must not name the + * same GEM base. If they do, this full NWCFG write wins and overrides the PHY + * init MDC divisor; override GEMI_CFG_MDCDIV to reconcile. + * + * The board supplies ZYNQMP_GEM_INIT_LIST as {gem_base, speed_mbps} rows, + * directly or from a header selected with one line: + * CFLAGS_EXTRA+=-DZYNQMP_GEM_INIT_HEADER='"myboard_gem.h"' + * one row per GEM base (GEM0 0xFF0B0000 .. GEM3 0xFF0E0000) at 1000/100/10, e.g. + * #define ZYNQMP_GEM_INIT_LIST \ + * { 0xFF0B0000UL, 1000 }, { 0xFF0C0000UL, 1000 } + */ +#ifdef ZYNQMP_GEM_INIT_HEADER +#include ZYNQMP_GEM_INIT_HEADER +#endif +#ifndef ZYNQMP_GEM_INIT_LIST +#error "WOLFBOOT_ZYNQMP_GEM_INIT set but ZYNQMP_GEM_INIT_LIST not defined (see hal/zynq.h)" +#endif + +/* GEM register offsets from a GEM base. */ +#define GEMI_NWCFG 0x004 +#define GEMI_PCS 0x200 + +/* NWCFG bits. */ +#define GEMI_CFG_SPEED100 (1UL << 0) +#define GEMI_CFG_FDEN (1UL << 1) +#define GEMI_CFG_GIGE (1UL << 10) +#define GEMI_CFG_PCSSEL (1UL << 11) +#define GEMI_CFG_FCSREM (1UL << 17) +#define GEMI_CFG_DBUS64 (1UL << 21) +#define GEMI_CFG_SGMIIEN (1UL << 27) +/* MDC divisor (NWCFG[20:18]); default pclk/64 as U-Boot uses. Overridable for + * a board whose pclk needs a different divisor to stay under the 2.5MHz MDC + * max (only relevant if MDIO is later used on the same GEM). */ +#ifndef GEMI_CFG_MDCDIV +#define GEMI_CFG_MDCDIV (0x4UL << 18) /* MDC = pclk/64 */ +#endif +/* Common config for all speeds (U-Boot NWCFG_INIT plus SGMII/PCS select). */ +#define GEMI_CFG_SGMII_BASE \ + (GEMI_CFG_DBUS64 | GEMI_CFG_FDEN | GEMI_CFG_FCSREM | GEMI_CFG_MDCDIV | \ + GEMI_CFG_SGMIIEN | GEMI_CFG_PCSSEL) + +/* PCS control (clause-22 BMCR layout), fixed full-duplex link, auto-neg off. */ +#define GEMI_PCS_1000 0x0140UL /* 1Gbps, full duplex */ +#define GEMI_PCS_100 0x2100UL /* 100Mbps, full duplex */ +#define GEMI_PCS_10 0x0100UL /* 10Mbps, full duplex */ +#endif /* WOLFBOOT_ZYNQMP_GEM_INIT */ + + /* Configuration Security Unit (CSU) */ /* Triple-Dedundant MicroBlaze processor */ /* 128 KB CSU ROM (immutable) */