Skip to content

Commit 46cce85

Browse files
committed
Add bare-metal wolfIP ports for UltraScale+ MPSoC (ZCU102), Versal VMK180 and Zynq-7000 (ZC702)
1 parent 364f95f commit 46cce85

74 files changed

Lines changed: 7319 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/port/amd/README.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# wolfIP AMD/Xilinx bare-metal ports
2+
3+
Bare-metal wolfIP ports for AMD/Xilinx PS-GEM SoCs, sharing one tree:
4+
5+
- **ZCU102** - ZynqMP, Cortex-A53, AArch64, EL3
6+
- **Versal Gen 1 / VMK180** - Cortex-A72, AArch64, EL3
7+
- **Zynq-7000 / ZC702** - Cortex-A9, ARMv7-A, SVC
8+
9+
All three are brought up on real hardware (DHCP, ICMP ping, UDP echo).
10+
11+
## Layout
12+
13+
Shared code lives once; each board's Makefile selects which components to
14+
compile (build-selected files, not `#ifdef` forks).
15+
16+
```
17+
common/ arch- and SoC-independent
18+
app.c app.h shared UDP-echo + DHCP demo (board hooks: board.c)
19+
gem_core.c gem.h shared Cadence GEM core (init, MDIO, polled TX, diag)
20+
gem_regs.h gem_port.h GEM register map / internal hook interface
21+
uart_util.c shared UART helpers (puts/puthex/putdec/putip4)
22+
entropy.c memuse-pattern RNG (counter via arch_counter64)
23+
wolfip_config.h shared wolfIP profile (board config.h includes it)
24+
gic.h uart.h mmu.h driver API headers
25+
26+
arch/aarch64/ cache.h timer.h mmu_aarch64.c startup_aarch64.S exception_aarch64.c
27+
arch/armv7/ cache.h timer.h mmu_armv7.c startup_armv7.S
28+
29+
ip/ per-IP-block drivers (build-selected)
30+
uart_cadence.c uart_pl011.c UART
31+
gic_gicv2.c gic_gicv3.c GIC
32+
gem_swq.c gem_rx_swq_poll.c gem_rx_poll.c RX delivery model (all boards poll)
33+
gem_rx_irq.c reference IRQ-driven RX (not built; see file)
34+
phy_dp83867.c phy_marvell.c PHY drivers
35+
phy_dispatch_dp83867.c phy_dispatch_multi.c PHY vendor dispatch
36+
37+
boards/<board>/ the build root for each board (keeps app.elf + JTAG in place)
38+
board.h board.c board_gem.c config.h Makefile target*.ld jtag/ [bootgen/]
39+
```
40+
41+
## Component selection per board
42+
43+
| Component | ZCU102 | Versal | Zynq-7000 |
44+
|-----------|--------|--------|-----------|
45+
| arch | aarch64 | aarch64 | armv7 |
46+
| UART | cadence | pl011 | cadence |
47+
| GIC | gicv2 | gicv3 | gicv2 |
48+
| GEM RX | gem_rx_swq_poll + gem_swq | gem_rx_swq_poll + gem_swq | gem_rx_poll |
49+
| PHY | dp83867 | dp83867 | dp83867 + marvell (multi) |
50+
| GEM inst | GEM3 | GEM0 | GEM0 |
51+
52+
## Build
53+
54+
```
55+
cd boards/zcu102 && make CROSS_COMPILE=aarch64-none-elf-
56+
cd boards/versal && make CROSS_COMPILE=aarch64-none-elf-
57+
cd boards/zynq7000 && make CROSS_COMPILE=arm-none-eabi-
58+
```
59+
60+
Output is `app.elf` in the board directory. See each board's `README.md`
61+
for the JTAG / BOOT.BIN flow and bring-up notes.
62+
63+
## Throughput test (SPEED_TEST)
64+
65+
The default build runs the UDP echo + DHCP demo. Building with
66+
`CFLAGS_EXTRA=-DSPEED_TEST` instead brings up a TCP throughput server on
67+
**port 9** (a discard/chargen-style sink + source, in the spirit of iperf but
68+
without iperf3's JSON control channel, which is impractical on bare metal). On
69+
each accepted connection the board sinks everything the host sends (RX) and, in
70+
the same window, sources chargen data whenever the socket is writable (TX); on
71+
close it prints the byte totals and an average rate over the UART:
72+
73+
```
74+
cd boards/zcu102 && make CROSS_COMPILE=aarch64-none-elf- CFLAGS_EXTRA=-DSPEED_TEST
75+
```
76+
77+
Measure from a host on the same subnet as the board (replace `<ip>` with the
78+
leased address printed at DHCP bind):
79+
80+
```
81+
# RX (host -> board): how fast the board sinks
82+
dd if=/dev/zero bs=1460 count=20000 | nc -q1 <ip> 9
83+
84+
# TX (board -> host): how fast the board sources
85+
nc <ip> 9 </dev/null | pv -r >/dev/null
86+
```
87+
88+
The board's own `SPEED done ... RX/TX bytes (~B/s)` UART line is the
89+
authoritative figure (it times the connection with the hardware clock). Note
90+
the RX and TX counters cover the same connection window, so during the RX run
91+
the board is also back-sourcing; the printed RX B/s is the host->board goodput
92+
under that concurrent load. iperf3 host-to-host on the same link is a useful
93+
*link* reference, but the board is not an iperf3 endpoint.
94+
95+
The `SPEED_TEST` build also widens the TCP window (`RXBUF_SIZE`/`TXBUF_SIZE` to
96+
`LINK_MTU * 6` in `config.h`) and trims the UDP socket count to keep the larger
97+
per-socket buffers inside the 256 KB OCM budget.
98+
99+
### Results
100+
101+
Single Cortex core, 1 Gbps RGMII link, MTU 1500, host on the same switch.
102+
RX is the board's UART `~B/s` line (host -> board); TX is host-measured
103+
(board -> host). Bytes x8 for Mbps.
104+
105+
| Board (SoC, core) | Layout / boot | RX Mbps | TX Mbps |
106+
|------------------------------|-----------------|--------:|--------:|
107+
| VMK180 (Versal, A72 @ EL3) | DDR (JTAG) | ~300 | ~334 |
108+
| ZCU102 (ZynqMP, A53 @ EL3) | DDR (SD boot) | ~126 | ~194 |
109+
| ZC702 (Zynq-7000, A9 @ SVC) | OCM (JTAG) | ~22 | ~19 |
110+
| ZCU102 (ZynqMP, A53 @ EL3) | OCM (JTAG) | ~10 | ~9 |
111+
112+
The single dominant factor is the **memory layout**: the OCM layout runs *all*
113+
code (and the rings) from Normal non-cacheable OCM, so every instruction fetch
114+
and frame copy is uncached. The DDR layout keeps code+data in cacheable DDR and
115+
maps only the GEM DMA region non-cacheable - ~13-30x faster, as the two ZCU102
116+
rows show directly (same SoC/core, OCM ~10/9 vs DDR ~126/194 Mbps). The faster
117+
A72 (Versal) reaches ~300/334 on DDR.
118+
119+
How each DDR number was loaded: Versal's PLM trains DDR from a boot PDI, so the
120+
DDR app loads cleanly over JTAG. On ZynqMP, JTAG writes into DDR after a bare
121+
`psu_init` are unreliable (the load goes through the A53 with a cache flush and
122+
either errors or lands corrupt - DDR itself is fine, a direct DAP memtest passes),
123+
so the ZCU102 DDR figure is from an **SD boot**: `FSBL_ELF=.../zynqmp_fsbl.elf
124+
make bootbin` produces a DDR-layout `BOOT.BIN` that the FSBL trains DDR for and
125+
DMA-loads (no JTAG memory writes). Copy it to the SD card's FAT boot partition
126+
and set SW6 = SD. The same applies to ZC702 (its OCM-only port has no DDR layout
127+
yet; a DDR profile is future work).
128+
129+
What it took to get here:
130+
131+
1. **NC-map the DMA rings in the DDR layout (correctness, not just speed).**
132+
The DDR layout had mapped the GEM BD rings cacheable with per-BD
133+
`cache_clean`. Because the 8-byte BDs share 64-byte cache lines, cleaning one
134+
BD wrote stale neighbours back over MAC-set OWN bits and wedged the RX ring
135+
under sustained (TCP-rate) load - the UDP-only profile never had two BDs live
136+
in a line at once. The DMA region is now Normal-NC in both layouts, with
137+
`.dma_buffers` in its own 2 MB block so `.text` stays cacheable.
138+
2. **Main-loop poll cadence.** The original loop called `wolfIP_poll()` then
139+
`delay_ms(1)`, capping the stack at ~1 poll/ms (~12 Mbps) and feeding wolfIP
140+
a `tick++` counter that only approximated real milliseconds. It now
141+
busy-polls with a real-millisecond clock from the hardware timer
142+
(`timer_now()/timer_freq()`), which also de-skews every DHCP/TCP/ARP timeout.
143+
3. **Drain RX fully, bounded TX per event.** Reading one chunk per READABLE
144+
left the advertised TCP window stuck (~2 KB) and deadlocked; the SPEED server
145+
now drains the rx buffer each event and does a bounded tx fill.
146+
4. **Word-wise `memcpy`/`memset`.** Frame-staging copies are 8 bytes at a time
147+
(bytewise tail), which matters for the non-cacheable DMA buffers.
148+
149+
Notes / remaining levers: ZCU102 uses the same poll-driven RX as the other two
150+
boards - its original IRQ-driven RX storms the CPU under sustained RX load.
151+
A DDR/BOOT.BIN profile for the OCM boards (cached code) and draining more than
152+
one frame per poll are the next levers.

src/port/amd/arch/aarch64/cache.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* cache.h
2+
*
3+
* Copyright (C) 2026 wolfSSL Inc.
4+
*
5+
* This file is part of wolfIP TCP/IP stack.
6+
*
7+
* AArch64 (Cortex-A53/A72) cache maintenance for GEM DMA coherency. The
8+
* cache line is 64 bytes. With D-cache enabled and BD/buffers in normal
9+
* cacheable memory, CPU writes may sit in L1 and not be visible to the
10+
* MAC's DMA path. cache_clean() writes back dirty lines before DMA
11+
* reads; cache_inval() invalidates lines so CPU reads pull fresh
12+
* DMA-written data.
13+
*/
14+
#ifndef AMD_CACHE_H
15+
#define AMD_CACHE_H
16+
17+
#include <stdint.h>
18+
19+
#define CACHE_LINE 64u
20+
21+
static inline void cache_clean(const void *p, uint32_t sz)
22+
{
23+
uintptr_t start = (uintptr_t)p & ~(CACHE_LINE - 1u);
24+
uintptr_t end = ((uintptr_t)p + sz + CACHE_LINE - 1u) & ~(CACHE_LINE - 1u);
25+
uintptr_t a;
26+
for (a = start; a < end; a += CACHE_LINE)
27+
__asm__ volatile ("dc cvac, %0" :: "r"(a) : "memory");
28+
__asm__ volatile ("dsb sy" ::: "memory");
29+
}
30+
31+
static inline void cache_inval(const void *p, uint32_t sz)
32+
{
33+
uintptr_t start = (uintptr_t)p & ~(CACHE_LINE - 1u);
34+
uintptr_t end = ((uintptr_t)p + sz + CACHE_LINE - 1u) & ~(CACHE_LINE - 1u);
35+
uintptr_t a;
36+
for (a = start; a < end; a += CACHE_LINE)
37+
__asm__ volatile ("dc ivac, %0" :: "r"(a) : "memory");
38+
__asm__ volatile ("dsb sy" ::: "memory");
39+
}
40+
41+
#endif /* AMD_CACHE_H */
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/* exception_aarch64.c
2+
*
3+
* Copyright (C) 2026 wolfSSL Inc.
4+
*
5+
* This file is part of wolfIP TCP/IP stack.
6+
*
7+
* wolfIP is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation; either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* wolfIP is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
20+
*
21+
* AArch64 EL3 fault reporters, called from the startup.S vector
22+
* trampolines on a synchronous exception or SError/FIQ. ARMv7 ports
23+
* just hang on faults (no C reporter), so this file is AArch64-only.
24+
*/
25+
#include <stdint.h>
26+
#include "uart.h"
27+
28+
/* Called from startup.S vector trampoline on synchronous/SError fault. */
29+
void exception_report(uint64_t esr, uint64_t elr, uint64_t far, uint64_t spsr)
30+
{
31+
uart_puts("\n\n*** EL3 SYNC EXCEPTION ***\n");
32+
uart_puts(" ESR_EL3 : "); uart_puthex((uint32_t)(esr >> 32));
33+
uart_puthex((uint32_t)esr); uart_puts("\n");
34+
uart_puts(" EC = "); uart_puthex((uint32_t)((esr >> 26) & 0x3F));
35+
uart_puts(" (0x21=instr abort, 0x25=data abort, 0x24=alignment)\n");
36+
uart_puts(" ELR_EL3 : "); uart_puthex((uint32_t)(elr >> 32));
37+
uart_puthex((uint32_t)elr); uart_puts("\n");
38+
uart_puts(" FAR_EL3 : "); uart_puthex((uint32_t)(far >> 32));
39+
uart_puthex((uint32_t)far); uart_puts("\n");
40+
uart_puts(" SPSR_EL3: "); uart_puthex((uint32_t)spsr); uart_puts("\n");
41+
}
42+
43+
void exception_report_serror(uint64_t esr, uint64_t elr, uint64_t far,
44+
uint64_t spsr, uint64_t kind)
45+
{
46+
(void)kind;
47+
uart_puts("\n\n*** EL3 SError / FIQ ***\n");
48+
uart_puts(" ESR_EL3 : "); uart_puthex((uint32_t)(esr >> 32));
49+
uart_puthex((uint32_t)esr); uart_puts("\n");
50+
uart_puts(" ELR_EL3 : "); uart_puthex((uint32_t)(elr >> 32));
51+
uart_puthex((uint32_t)elr); uart_puts("\n");
52+
uart_puts(" FAR_EL3 : "); uart_puthex((uint32_t)(far >> 32));
53+
uart_puthex((uint32_t)far); uart_puts("\n");
54+
uart_puts(" SPSR_EL3: "); uart_puthex((uint32_t)spsr); uart_puts("\n");
55+
}

0 commit comments

Comments
 (0)