Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ $(LSCRIPT): $(LSCRIPT_IN) FORCE
sed -e "s/@WOLFBOOT_STAGE1_FLASH_ADDR@/$(WOLFBOOT_STAGE1_FLASH_ADDR)/g" | \
sed -e "s/@WOLFBOOT_STAGE1_BASE_ADDR@/$(WOLFBOOT_STAGE1_BASE_ADDR)/g" | \
sed -e "s/@WOLFBOOT_LOAD_BASE@/$(WOLFBOOT_LOAD_BASE)/g" | \
sed -e "s/@WOLFBOOT_DMA_BUFFER_ADDRESS@/$(WOLFBOOT_DMA_BUFFER_ADDRESS)/g" | \
sed -e "s/@BOOTLOADER_START@/$(BOOTLOADER_START)/g" | \
sed -e "s/@IMAGE_HEADER_SIZE@/$(IMAGE_HEADER_SIZE)/g" | \
sed -e "s/@WOLFBOOT_LOAD_ADDRESS@/$(WOLFBOOT_LOAD_ADDRESS)/g" | \
Expand Down
16 changes: 16 additions & 0 deletions docs/HAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,22 @@ implementation in new ports must return immediately without performing any actio
if the content of the bootloader partition in the two banks already match.


### Optional support for non-coherent DMA

`int hal_dma_set_noncached(uintptr_t start, uintptr_t end)`

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.

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.

A port implementing it must:

- 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.
- 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.
- Push the modified table entries out and invalidate the TLB at the exception level that owns the translation.

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`.

### wolfHSM HAL extensions

Refer to [wolfHSM.md](wolfHSM.md) for the wolfHSM-specific HAL functions and an overview of wolfHSM compatibility.
4 changes: 4 additions & 0 deletions docs/Targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -4189,6 +4189,10 @@ Key configuration options:

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.

### Non-cacheable DMA window

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).

### Building with Xilinx tools (Vitis IDE)

See [IDE/XilinxSDK/README.md](/IDE/XilinxSDK/README.md) for using Xilinx IDE
Expand Down
91 changes: 91 additions & 0 deletions hal/zynq.c
Original file line number Diff line number Diff line change
Expand Up @@ -3063,4 +3063,95 @@ void sdhci_platform_dma_complete(void *buf, uint32_t sz, int is_write)
#endif /* DISK_SDCARD || DISK_EMMC */


#if defined(MMU) && defined(__WOLFBOOT)
/* wolfBoot maps DDR write-back in 2MB blocks from the static MMUTableL2. A
* non-coherent DMA master cannot share 8-byte descriptors through that:
* several fall in one cache line, so cleaning one clobbers its neighbours'
* ownership bits. Re-attribute the whole block Normal-NC instead.
*
* Valid block, AttrIndx=0 (MAIR[0] is Normal-NC), AF set, never executable.
* Shareability is ignored for Non-Cacheable memory. */
#define ZYNQMP_L2_BLOCK_NORMAL_NC \
(0x401ULL | (1ULL << 53) | (1ULL << 54))


/* From src/boot_aarch64_start.S: four contiguous 512-entry tables off
* L1[0..3] mapping 0x0-0xFFFFFFFF, so the index is addr >> 21. */
extern uint64_t MMUTableL2[];

/* Make the table entries [first,last] visible to the table walker, which may
* not snoop the caches: push them out before anything relies on them. */
static void zynqmp_mmu_publish(uint64_t first, uint64_t last)
{
uint64_t i;

__asm__ volatile("dsb ishst" : : : "memory");
for (i = first; i <= last; i++) {
__asm__ volatile("dc civac, %0"
: : "r"((uintptr_t)&MMUTableL2[i]) : "memory");
Comment on lines +3086 to +3091
}
__asm__ volatile("dsb sy" : : : "memory");
}

/* wolfBoot runs at EL3 as an FSBL replacement, at EL2 under BL31. */
static void zynqmp_mmu_tlbi(void)
{
switch (current_el()) {
case 3:
__asm__ volatile("tlbi alle3" : : : "memory");
break;
case 2:
__asm__ volatile("tlbi alle2" : : : "memory");
break;
default:
__asm__ volatile("tlbi vmalle1" : : : "memory");
break;
}
__asm__ volatile("dsb sy" : : : "memory");
__asm__ volatile("isb" : : : "memory");
}

/* Mark every 2MB block overlapping [start,end) Normal-NC. Cleans the range
* first: a line still dirty at the moment of the change could otherwise land
* on top of what the bus master has since written. */
int hal_dma_set_noncached(uintptr_t start, uintptr_t end)
{
uintptr_t addr;
uint64_t first, last, i;

if (zynqmp_l2_block_range((uint64_t)start, (uint64_t)end, &first, &last)
!= 0) {
return -1;
}

/* Whole blocks: the attribute applies per block. */
for (addr = (uintptr_t)(first << ZYNQMP_L2_BLOCK_SHIFT);
addr < (uintptr_t)((last + 1) << ZYNQMP_L2_BLOCK_SHIFT);
addr += CACHE_LINE_SIZE) {
__asm__ volatile("dc civac, %0" : : "r"(addr) : "memory");
}
__asm__ volatile("dsb sy" : : : "memory");

/* Break-before-make: valid -> valid memory-type changes are CONSTRAINED
* UNPREDICTABLE on ARMv8-A. Nothing may touch the range while it is
* unmapped; the caller owns a dedicated region and wolfBoot's own code
* and data are in a different block. */
for (i = first; i <= last; i++) {
MMUTableL2[i] = 0;
}
zynqmp_mmu_publish(first, last);
zynqmp_mmu_tlbi();

for (i = first; i <= last; i++) {
MMUTableL2[i] = (i << ZYNQMP_L2_BLOCK_SHIFT)
| ZYNQMP_L2_BLOCK_NORMAL_NC;
}
zynqmp_mmu_publish(first, last);
zynqmp_mmu_tlbi();

return 0;
}
#endif /* MMU && __WOLFBOOT */


#endif /* TARGET_zynq */
33 changes: 32 additions & 1 deletion hal/zynq.h
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,37 @@
#define CRL_APB_DBG_LPD_CTRL (CRL_APB_BASE + 0x00B0U)
#define CRL_APB_RST_LPD_DBG (CRL_APB_BASE + 0x0240U)


#ifndef __ASSEMBLER__
#include <stdint.h>
#include <stddef.h>

/* 2MB: the smallest granule this translation table can re-attribute. */
#define ZYNQMP_L2_BLOCK_SHIFT 21
/* Four contiguous 512-entry tables covering 0x0-0xFFFFFFFF. */
#define ZYNQMP_L2_ENTRIES 2048

/* [start,end) to the inclusive 2MB block indices covering it. Split out so
* it can be unit tested: these indices decide which physical blocks get
* re-attributed. Returns 0, or -1 for an empty range or one past 4GB. */
static inline int zynqmp_l2_block_range(uint64_t start, uint64_t end,
uint64_t* first, uint64_t* last)
{
uint64_t f, l;

if (end <= start || first == NULL || last == NULL) {
return -1;
}
f = start >> ZYNQMP_L2_BLOCK_SHIFT;
l = (end - 1) >> ZYNQMP_L2_BLOCK_SHIFT;
if (l >= ZYNQMP_L2_ENTRIES) {
return -1;
}
/* Only on success, so a caller that ignores the return does not act on
* half-written indices. */
*first = f;
*last = l;
return 0;
}
#endif /* !__ASSEMBLER__ */

#endif /* _ZYNQMP_H_ */
23 changes: 23 additions & 0 deletions hal/zynq.ld
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ MEMORY
* Must match WOLFBOOT_ORIGIN in the target .config.
*/
psu_ddr_0_MEM_0 : ORIGIN = 0x8000000, LENGTH = 0x200000
/* Non-cacheable DDR for bus-master DMA. Its own 2MB-aligned block: 2MB
* is the smallest granule the translation table can re-attribute, and it
* must stay clear of .text so the image hash is not run uncached.
* NOLOAD, so an unused region costs nothing. */
psu_ddr_dma_MEM_0 : ORIGIN = @WOLFBOOT_DMA_BUFFER_ADDRESS@, LENGTH = 0x200000
psu_ddr_1_MEM_0 : ORIGIN = 0x800000000, LENGTH = 0x80000000
psu_ocm_ram_0_MEM_0 : ORIGIN = 0xFFFC0000, LENGTH = 0x40000
psu_qspi_linear_0_MEM_0 : ORIGIN = 0xC0000000, LENGTH = 0x20000000
Expand Down Expand Up @@ -293,6 +298,24 @@ _SDA_BASE_ = __sdata_start + ((__sbss_end - __sdata_start) / 2 );

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

/* Its own MEMORY region so hal_dma_set_noncached() can mark the enclosing
* 2MB block Normal-NC without touching wolfBoot's code or data. */
.dma_buffers (NOLOAD) : {
. = ALIGN(64);
_dma_buffers_start = .;
*(.dma_buffers)
*(.dma_buffers.*)
. = ALIGN(64);
_dma_buffers_end = .;
} > psu_ddr_dma_MEM_0

/* hal_dma_set_noncached() rounds the range outward to the enclosing 2MB
* block and re-attributes the whole block. A misaligned override would put
* the region in wolfBoot's own block, and the break-before-make step would
* unmap live code. Fail the build instead. */
ASSERT((_dma_buffers_start & 0x1FFFFF) == 0,
"WOLFBOOT_DMA_BUFFER_ADDRESS must be 2MB aligned")

/* Generate Stack and Heap definitions */

.heap (NOLOAD) : {
Expand Down
9 changes: 8 additions & 1 deletion include/hal.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ void hal_deinit();
void hal_init(void);

/* Timer functions (platform-specific, used for benchmarking) */
#if defined(WOLFBOOT_UPDATE_DISK) || defined(BOOT_BENCHMARK)
#if defined(WOLFBOOT_UPDATE_DISK) || defined(BOOT_BENCHMARK) || \
defined(PREBOOT_NETCHECK)
uint64_t hal_get_timer_us(void);
#endif

Expand Down Expand Up @@ -113,6 +114,12 @@ void hal_cache_invalidate(void);
int hal_flash_protect(haladdr_t address, int len);
void hal_prepare_boot(void);

/* Re-attribute [start,end) non-cacheable, for memory shared with a
* non-coherent bus master. Returns 0, or negative if the port cannot; see
* docs/HAL.md. The weak default fails rather than doing nothing, so a caller
* never silently runs DMA through write-back memory. */
int hal_dma_set_noncached(uintptr_t start, uintptr_t end);

#ifdef DUALBANK_SWAP
void hal_flash_dualbank_swap(void);
#endif
Expand Down
5 changes: 5 additions & 0 deletions options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1689,6 +1689,11 @@ ifeq ($(WOLFHSM_SERVER),1)

endif

# Non-cacheable DDR carve-out for bus-master DMA (hal/zynq.ld). Substituted
# into the linker script only; code uses the _dma_buffers_start/_end symbols
# the script exports rather than this address.
WOLFBOOT_DMA_BUFFER_ADDRESS?=0x8200000

# wolfBoot hooks framework
# WOLFBOOT_HOOKS_FILE: path to a single .c file containing hook definitions
WOLFBOOT_HOOKS_ENABLED :=
Expand Down
10 changes: 10 additions & 0 deletions src/libwolfboot.c
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,16 @@ void WEAKFUNCTION hal_cache_invalidate(void)
/* if cache flushing is required implement in hal */
}

/* Weak default; a port with the MMU and D-cache on overrides it. Fails
* rather than succeeding: a silent no-op would leave the caller sharing
* write-back memory with a non-coherent master. */
int WEAKFUNCTION hal_dma_set_noncached(uintptr_t start, uintptr_t end)
{
(void)start;
(void)end;
return -1;
}

#ifdef NVM_FLASH_WRITEONCE
/* Some internal FLASH memory models don't allow
* multiple writes after erase in the same page/area.
Expand Down
5 changes: 5 additions & 0 deletions tools/unit-tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ TESTS+=unit-rp2350-flash-write
TESTS+=unit-fwtpm-rsp-overrun
TESTS+=unit-fwtpm-cmd-toctou
TESTS+=unit-fdt-memrsv-wrap
TESTS+=unit-zynq-dma-range
TESTS+=unit-pkcs11_store-stalecache
TESTS+=unit-aurix-erased-fill
TESTS+=unit-aurix-erased-fill-invert
Expand Down Expand Up @@ -508,6 +509,10 @@ unit-fwtpm-cmd-toctou: ../../include/target.h unit-fwtpm-cmd-toctou.c
gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) \
-DWOLFTPM_USER_SETTINGS $(LDFLAGS)

# unit-zynq-dma-range: block-index math behind hal_dma_set_noncached()
unit-zynq-dma-range: unit-zynq-dma-range.c
gcc -o $@ $^ $(CFLAGS) $(LDFLAGS)

# unit-fdt-memrsv-wrap: layout validation in front of fdt_add_mem_rsv()
# (F-11045). Links the real parser rather than extracting one function.
unit-fdt-memrsv-wrap:CFLAGS+=-DWOLFBOOT_FDT
Expand Down
Loading
Loading