Skip to content

Commit 78c405b

Browse files
committed
zynqmp: add non-cacheable DMA window and hal_dma_set_noncached()
1 parent 8eafe1e commit 78c405b

11 files changed

Lines changed: 303 additions & 2 deletions

File tree

‎Makefile‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ $(LSCRIPT): $(LSCRIPT_IN) FORCE
727727
sed -e "s/@WOLFBOOT_STAGE1_FLASH_ADDR@/$(WOLFBOOT_STAGE1_FLASH_ADDR)/g" | \
728728
sed -e "s/@WOLFBOOT_STAGE1_BASE_ADDR@/$(WOLFBOOT_STAGE1_BASE_ADDR)/g" | \
729729
sed -e "s/@WOLFBOOT_LOAD_BASE@/$(WOLFBOOT_LOAD_BASE)/g" | \
730+
sed -e "s/@WOLFBOOT_DMA_BUFFER_ADDRESS@/$(WOLFBOOT_DMA_BUFFER_ADDRESS)/g" | \
730731
sed -e "s/@BOOTLOADER_START@/$(BOOTLOADER_START)/g" | \
731732
sed -e "s/@IMAGE_HEADER_SIZE@/$(IMAGE_HEADER_SIZE)/g" | \
732733
sed -e "s/@WOLFBOOT_LOAD_ADDRESS@/$(WOLFBOOT_LOAD_ADDRESS)/g" | \

‎docs/HAL.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,22 @@ implementation in new ports must return immediately without performing any actio
146146
if the content of the bootloader partition in the two banks already match.
147147

148148

149+
### Optional support for non-coherent DMA
150+
151+
`int hal_dma_set_noncached(uintptr_t start, uintptr_t end)`
152+
153+
Re-attribute `[start, end)` as non-cacheable. Only needed by ports that run with the MMU and D-cache enabled and that hand memory to a bus master which is not coherent with the CPU caches, such as an Ethernet MAC driven from a hook. Returns 0 on success, or negative if the port cannot satisfy the request.
154+
155+
A weak default in `src/libwolfboot.c` returns an error, so the symbol always links. It deliberately does **not** succeed silently: a no-op would leave the caller sharing write-back memory with a non-coherent master, which is the failure this function exists to prevent.
156+
157+
A port implementing it must:
158+
159+
- Round the range **outward** to whatever granule its translation tables can express. On ZynqMP (`hal/zynq.c`) that is a 2MB block, so the caller has to give the region an aligned block of its own rather than placing it next to other data.
160+
- Clean and invalidate the affected range **before** changing the attribute, so a line still dirty at the switch cannot later be written back over what the bus master has since put there.
161+
- Push the modified table entries out and invalidate the TLB at the exception level that owns the translation.
162+
163+
Callers place their buffers with a dedicated linker section. The ZynqMP port provides `.dma_buffers` in `hal/zynq.ld`, based at `WOLFBOOT_DMA_BUFFER_ADDRESS` (default `0x8200000`, clear of the kernel, the FIT staging area and the DTS) and exporting `_dma_buffers_start` / `_dma_buffers_end`.
164+
149165
### wolfHSM HAL extensions
150166

151167
Refer to [wolfHSM.md](wolfHSM.md) for the wolfHSM-specific HAL functions and an overview of wolfHSM compatibility.

‎docs/Targets.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4184,6 +4184,10 @@ Key configuration options:
41844184
41854185
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.
41864186
4187+
### Non-cacheable DMA window
4188+
4189+
The ZynqMP GEM is not coherent with the CPU caches, so a hook that drives it needs non-cacheable memory. `hal/zynq.ld` reserves a 2MB-aligned `.dma_buffers` region at `WOLFBOOT_DMA_BUFFER_ADDRESS` (default `0x8200000`) and `hal_dma_set_noncached()` re-attributes it at runtime; see [HAL.md](./HAL.md).
4190+
41874191
### Building with Xilinx tools (Vitis IDE)
41884192
41894193
See [IDE/XilinxSDK/README.md](/IDE/XilinxSDK/README.md) for using Xilinx IDE

‎hal/zynq.c‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3063,4 +3063,93 @@ void sdhci_platform_dma_complete(void *buf, uint32_t sz, int is_write)
30633063
#endif /* DISK_SDCARD || DISK_EMMC */
30643064

30653065

3066+
#if defined(MMU) && defined(__WOLFBOOT)
3067+
/* wolfBoot maps DDR write-back in 2MB blocks from the static MMUTableL2. A
3068+
* non-coherent DMA master cannot share 8-byte descriptors through that:
3069+
* several fall in one cache line, so cleaning one clobbers its neighbours'
3070+
* ownership bits. Re-attribute the whole block Normal-NC instead.
3071+
*
3072+
* Valid block, AttrIndx=0 (MAIR[0] is Normal-NC), AF set, never executable.
3073+
* Shareability is ignored for Non-Cacheable memory. */
3074+
#define ZYNQMP_L2_BLOCK_NORMAL_NC \
3075+
(0x401ULL | (1ULL << 53) | (1ULL << 54))
3076+
3077+
3078+
/* From src/boot_aarch64_start.S: four contiguous 512-entry tables off
3079+
* L1[0..3] mapping 0x0-0xFFFFFFFF, so the index is addr >> 21. */
3080+
extern uint64_t MMUTableL2[];
3081+
3082+
/* Mark every 2MB block overlapping [start,end) Normal-NC. Cleans first: a
3083+
* line still dirty at the change could land on what the master since wrote. */
3084+
/* The walker may not snoop, so push entries out before invalidating. */
3085+
static void zynqmp_mmu_publish(uint64_t first, uint64_t last)
3086+
{
3087+
uint64_t i;
3088+
3089+
__asm__ volatile("dsb ishst" : : : "memory");
3090+
for (i = first; i <= last; i++) {
3091+
__asm__ volatile("dc civac, %0"
3092+
: : "r"((uintptr_t)&MMUTableL2[i]) : "memory");
3093+
}
3094+
__asm__ volatile("dsb sy" : : : "memory");
3095+
}
3096+
3097+
/* wolfBoot runs at EL3 as an FSBL replacement, at EL2 under BL31. */
3098+
static void zynqmp_mmu_tlbi(void)
3099+
{
3100+
switch (current_el()) {
3101+
case 3:
3102+
__asm__ volatile("tlbi alle3" : : : "memory");
3103+
break;
3104+
case 2:
3105+
__asm__ volatile("tlbi alle2" : : : "memory");
3106+
break;
3107+
default:
3108+
__asm__ volatile("tlbi vmalle1" : : : "memory");
3109+
break;
3110+
}
3111+
__asm__ volatile("dsb sy" : : : "memory");
3112+
__asm__ volatile("isb" : : : "memory");
3113+
}
3114+
3115+
int hal_dma_set_noncached(uintptr_t start, uintptr_t end)
3116+
{
3117+
uintptr_t addr;
3118+
uint64_t first, last, i;
3119+
3120+
if (zynqmp_l2_block_range((uint64_t)start, (uint64_t)end, &first, &last)
3121+
!= 0) {
3122+
return -1;
3123+
}
3124+
3125+
/* Whole blocks: the attribute applies per block. */
3126+
for (addr = (uintptr_t)(first << ZYNQMP_L2_BLOCK_SHIFT);
3127+
addr < (uintptr_t)((last + 1) << ZYNQMP_L2_BLOCK_SHIFT);
3128+
addr += CACHE_LINE_SIZE) {
3129+
__asm__ volatile("dc civac, %0" : : "r"(addr) : "memory");
3130+
}
3131+
__asm__ volatile("dsb sy" : : : "memory");
3132+
3133+
/* Break-before-make: valid -> valid memory-type changes are CONSTRAINED
3134+
* UNPREDICTABLE on ARMv8-A. Nothing may touch the range while it is
3135+
* unmapped; the caller owns a dedicated region and wolfBoot's own code
3136+
* and data are in a different block. */
3137+
for (i = first; i <= last; i++) {
3138+
MMUTableL2[i] = 0;
3139+
}
3140+
zynqmp_mmu_publish(first, last);
3141+
zynqmp_mmu_tlbi();
3142+
3143+
for (i = first; i <= last; i++) {
3144+
MMUTableL2[i] = (i << ZYNQMP_L2_BLOCK_SHIFT)
3145+
| ZYNQMP_L2_BLOCK_NORMAL_NC;
3146+
}
3147+
zynqmp_mmu_publish(first, last);
3148+
zynqmp_mmu_tlbi();
3149+
3150+
return 0;
3151+
}
3152+
#endif /* MMU && __WOLFBOOT */
3153+
3154+
30663155
#endif /* TARGET_zynq */

‎hal/zynq.h‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,31 @@
689689
#define CRL_APB_DBG_LPD_CTRL (CRL_APB_BASE + 0x00B0U)
690690
#define CRL_APB_RST_LPD_DBG (CRL_APB_BASE + 0x0240U)
691691

692-
692+
#ifndef __ASSEMBLER__
693+
#include <stdint.h>
694+
#include <stddef.h>
695+
696+
/* 2MB: the smallest granule this translation table can re-attribute. */
697+
#define ZYNQMP_L2_BLOCK_SHIFT 21
698+
/* Four contiguous 512-entry tables covering 0x0-0xFFFFFFFF. */
699+
#define ZYNQMP_L2_ENTRIES 2048
700+
701+
/* [start,end) to the inclusive 2MB block indices covering it. Split out so
702+
* it can be unit tested: these indices decide which physical blocks get
703+
* re-attributed. Returns 0, or -1 for an empty range or one past 4GB. */
704+
static inline int zynqmp_l2_block_range(uint64_t start, uint64_t end,
705+
uint64_t* first, uint64_t* last)
706+
{
707+
if (end <= start || first == NULL || last == NULL) {
708+
return -1;
709+
}
710+
*first = start >> ZYNQMP_L2_BLOCK_SHIFT;
711+
*last = (end - 1) >> ZYNQMP_L2_BLOCK_SHIFT;
712+
if (*last >= ZYNQMP_L2_ENTRIES) {
713+
return -1;
714+
}
715+
return 0;
716+
}
717+
#endif /* !__ASSEMBLER__ */
693718

694719
#endif /* _ZYNQMP_H_ */

‎hal/zynq.ld‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ MEMORY
1919
* Must match WOLFBOOT_ORIGIN in the target .config.
2020
*/
2121
psu_ddr_0_MEM_0 : ORIGIN = 0x8000000, LENGTH = 0x200000
22+
/* Non-cacheable DDR for bus-master DMA. Its own 2MB-aligned block: 2MB
23+
* is the smallest granule the translation table can re-attribute, and it
24+
* must stay clear of .text so the image hash is not run uncached.
25+
* NOLOAD, so an unused region costs nothing. */
26+
psu_ddr_dma_MEM_0 : ORIGIN = @WOLFBOOT_DMA_BUFFER_ADDRESS@, LENGTH = 0x200000
2227
psu_ddr_1_MEM_0 : ORIGIN = 0x800000000, LENGTH = 0x80000000
2328
psu_ocm_ram_0_MEM_0 : ORIGIN = 0xFFFC0000, LENGTH = 0x40000
2429
psu_qspi_linear_0_MEM_0 : ORIGIN = 0xC0000000, LENGTH = 0x20000000
@@ -293,6 +298,17 @@ _SDA_BASE_ = __sdata_start + ((__sbss_end - __sdata_start) / 2 );
293298

294299
_SDA2_BASE_ = __sdata2_start + ((__sbss2_end - __sdata2_start) / 2 );
295300

301+
/* Its own MEMORY region so hal_dma_set_noncached() can mark the enclosing
302+
* 2MB block Normal-NC without touching wolfBoot's code or data. */
303+
.dma_buffers (NOLOAD) : {
304+
. = ALIGN(64);
305+
_dma_buffers_start = .;
306+
*(.dma_buffers)
307+
*(.dma_buffers.*)
308+
. = ALIGN(64);
309+
_dma_buffers_end = .;
310+
} > psu_ddr_dma_MEM_0
311+
296312
/* Generate Stack and Heap definitions */
297313

298314
.heap (NOLOAD) : {

‎include/hal.h‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ void hal_deinit();
6060
void hal_init(void);
6161

6262
/* Timer functions (platform-specific, used for benchmarking) */
63-
#if defined(WOLFBOOT_UPDATE_DISK) || defined(BOOT_BENCHMARK)
63+
#if defined(WOLFBOOT_UPDATE_DISK) || defined(BOOT_BENCHMARK) || \
64+
defined(PREBOOT_NETCHECK)
6465
uint64_t hal_get_timer_us(void);
6566
#endif
6667

@@ -113,6 +114,12 @@ void hal_cache_invalidate(void);
113114
int hal_flash_protect(haladdr_t address, int len);
114115
void hal_prepare_boot(void);
115116

117+
/* Re-attribute [start,end) non-cacheable, for memory shared with a
118+
* non-coherent bus master. Returns 0, or negative if the port cannot; see
119+
* docs/HAL.md. The weak default fails rather than doing nothing, so a caller
120+
* never silently runs DMA through write-back memory. */
121+
int hal_dma_set_noncached(uintptr_t start, uintptr_t end);
122+
116123
#ifdef DUALBANK_SWAP
117124
void hal_flash_dualbank_swap(void);
118125
#endif

‎options.mk‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,11 @@ ifeq ($(WOLFHSM_SERVER),1)
16831683

16841684
endif
16851685

1686+
# Non-cacheable DDR carve-out for bus-master DMA (hal/zynq.ld). Substituted
1687+
# into the linker script only; code uses the _dma_buffers_start/_end symbols
1688+
# the script exports rather than this address.
1689+
WOLFBOOT_DMA_BUFFER_ADDRESS?=0x8200000
1690+
16861691
# wolfBoot hooks framework
16871692
# WOLFBOOT_HOOKS_FILE: path to a single .c file containing hook definitions
16881693
WOLFBOOT_HOOKS_ENABLED :=

‎src/libwolfboot.c‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ void WEAKFUNCTION hal_cache_invalidate(void)
252252
/* if cache flushing is required implement in hal */
253253
}
254254

255+
/* Weak default; a port with the MMU and D-cache on overrides it. Fails
256+
* rather than succeeding: a silent no-op would leave the caller sharing
257+
* write-back memory with a non-coherent master. */
258+
int WEAKFUNCTION hal_dma_set_noncached(uintptr_t start, uintptr_t end)
259+
{
260+
(void)start;
261+
(void)end;
262+
return -1;
263+
}
264+
255265
#ifdef NVM_FLASH_WRITEONCE
256266
/* Some internal FLASH memory models don't allow
257267
* multiple writes after erase in the same page/area.

‎tools/unit-tests/Makefile‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ TESTS+=unit-rp2350-flash-write
125125
TESTS+=unit-fwtpm-rsp-overrun
126126
TESTS+=unit-fwtpm-cmd-toctou
127127
TESTS+=unit-fdt-memrsv-wrap
128+
TESTS+=unit-zynq-dma-range
128129
TESTS+=unit-pkcs11_store-stalecache
129130
TESTS+=unit-aurix-erased-fill
130131
TESTS+=unit-aurix-erased-fill-invert
@@ -473,6 +474,10 @@ unit-fwtpm-cmd-toctou: ../../include/target.h unit-fwtpm-cmd-toctou.c
473474
gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) \
474475
-DWOLFTPM_USER_SETTINGS $(LDFLAGS)
475476

477+
# unit-zynq-dma-range: block-index math behind hal_dma_set_noncached()
478+
unit-zynq-dma-range: unit-zynq-dma-range.c
479+
gcc -o $@ $^ $(CFLAGS) $(LDFLAGS)
480+
476481
# unit-fdt-memrsv-wrap: layout validation in front of fdt_add_mem_rsv()
477482
# (F-11045). Links the real parser rather than extracting one function.
478483
unit-fdt-memrsv-wrap:CFLAGS+=-DWOLFBOOT_FDT

0 commit comments

Comments
 (0)