1*e45ca16eSNicolas Pitre /* 2*e45ca16eSNicolas Pitre * Copyright (c) 2026, BayLibre SAS 3*e45ca16eSNicolas Pitre * 4*e45ca16eSNicolas Pitre * SPDX-License-Identifier: BSD-3-Clause 5*e45ca16eSNicolas Pitre */ 6*e45ca16eSNicolas Pitre 7*e45ca16eSNicolas Pitre #ifndef PLAT_HOLD_PEN_H 8*e45ca16eSNicolas Pitre #define PLAT_HOLD_PEN_H 9*e45ca16eSNicolas Pitre 10*e45ca16eSNicolas Pitre #ifndef __ASSEMBLER__ 11*e45ca16eSNicolas Pitre #include <cdefs.h> 12*e45ca16eSNicolas Pitre #include <stddef.h> 13*e45ca16eSNicolas Pitre #include <stdint.h> 14*e45ca16eSNicolas Pitre 15*e45ca16eSNicolas Pitre #include <lib/cassert.h> 16*e45ca16eSNicolas Pitre #endif 17*e45ca16eSNicolas Pitre 18*e45ca16eSNicolas Pitre #include <lib/cpus/cpu_ops.h> 19*e45ca16eSNicolas Pitre #include <lib/utils_def.h> 20*e45ca16eSNicolas Pitre #include <platform_def.h> 21*e45ca16eSNicolas Pitre 22*e45ca16eSNicolas Pitre /* 23*e45ca16eSNicolas Pitre * Generic hold pen for SMP secondary CPU bring-up. 24*e45ca16eSNicolas Pitre * 25*e45ca16eSNicolas Pitre * Platforms call plat_hold_pen_init() once at boot to write 26*e45ca16eSNicolas Pitre * HOLD_STATE_WAIT followed by magic tags into every slot, then flush 27*e45ca16eSNicolas Pitre * to main memory. 28*e45ca16eSNicolas Pitre * 29*e45ca16eSNicolas Pitre * The primary CPU signals a secondary by calling plat_hold_pen_signal(), 30*e45ca16eSNicolas Pitre * which writes the warm boot entrypoint into the target slot, 31*e45ca16eSNicolas Pitre * flushes it, and issues SEV. 32*e45ca16eSNicolas Pitre * 33*e45ca16eSNicolas Pitre * Secondary CPUs may reach their polling loop before plat_hold_pen_init() 34*e45ca16eSNicolas Pitre * has run (they diverge in el3_entrypoint_common before C runtime 35*e45ca16eSNicolas Pitre * init). To guard against branching to stale SRAM content, each 36*e45ca16eSNicolas Pitre * slot carries two magic tags that must both match before the entry 37*e45ca16eSNicolas Pitre * field is considered. Random memory is unlikely to contain both 38*e45ca16eSNicolas Pitre * magic values in sequence. The entry field is written before the 39*e45ca16eSNicolas Pitre * magic tags during init so that a valid entry value is always in 40*e45ca16eSNicolas Pitre * place before the tags make the slot "live". 41*e45ca16eSNicolas Pitre * 42*e45ca16eSNicolas Pitre * Before branching, the secondary writes HOLD_STATE_WAIT back into 43*e45ca16eSNicolas Pitre * its entry field so that a subsequent warm boot re-entering the 44*e45ca16eSNicolas Pitre * polling loop does not see a stale entrypoint. 45*e45ca16eSNicolas Pitre * 46*e45ca16eSNicolas Pitre * Each slot is cache-line aligned so that flushing one core's slot 47*e45ca16eSNicolas Pitre * cannot accidentally evict or corrupt another's. 48*e45ca16eSNicolas Pitre */ 49*e45ca16eSNicolas Pitre 50*e45ca16eSNicolas Pitre #define HOLD_MAGIC1 UL(0xCAFECAFE) 51*e45ca16eSNicolas Pitre #define HOLD_MAGIC2 UL(0xBEEFBEEF) 52*e45ca16eSNicolas Pitre 53*e45ca16eSNicolas Pitre /* 54*e45ca16eSNicolas Pitre * All-ones sentinel: no valid entrypoint can live here. 55*e45ca16eSNicolas Pitre * Evaluates to 0xffffffff on AArch32 and 0xffffffffffffffff on AArch64. 56*e45ca16eSNicolas Pitre */ 57*e45ca16eSNicolas Pitre #define HOLD_STATE_WAIT (~UL(0)) 58*e45ca16eSNicolas Pitre 59*e45ca16eSNicolas Pitre #define HOLD_SLOT_SIZE CACHE_WRITEBACK_GRANULE 60*e45ca16eSNicolas Pitre 61*e45ca16eSNicolas Pitre /* 62*e45ca16eSNicolas Pitre * Field sizes and offsets within struct hold_slot, following the 63*e45ca16eSNicolas Pitre * pattern established in include/lib/cpus/cpu_ops.h. 64*e45ca16eSNicolas Pitre */ 65*e45ca16eSNicolas Pitre #define HOLD_SLOT_ENTRY_SIZE CPU_WORD_SIZE 66*e45ca16eSNicolas Pitre #define HOLD_SLOT_MAGIC1_SIZE CPU_WORD_SIZE 67*e45ca16eSNicolas Pitre #define HOLD_SLOT_MAGIC2_SIZE CPU_WORD_SIZE 68*e45ca16eSNicolas Pitre 69*e45ca16eSNicolas Pitre #define HOLD_SLOT_ENTRY 0 70*e45ca16eSNicolas Pitre #define HOLD_SLOT_MAGIC1 HOLD_SLOT_ENTRY + HOLD_SLOT_ENTRY_SIZE 71*e45ca16eSNicolas Pitre #define HOLD_SLOT_MAGIC2 HOLD_SLOT_MAGIC1 + HOLD_SLOT_MAGIC1_SIZE 72*e45ca16eSNicolas Pitre 73*e45ca16eSNicolas Pitre #ifndef __ASSEMBLER__ 74*e45ca16eSNicolas Pitre 75*e45ca16eSNicolas Pitre /* 76*e45ca16eSNicolas Pitre * Per-core hold pen slot, cache-line aligned. 77*e45ca16eSNicolas Pitre * The 'entry' field holds HOLD_STATE_WAIT while the core should keep 78*e45ca16eSNicolas Pitre * polling, or the warm boot entrypoint address when it should go. 79*e45ca16eSNicolas Pitre */ 80*e45ca16eSNicolas Pitre struct hold_slot { 81*e45ca16eSNicolas Pitre uintptr_t entry; 82*e45ca16eSNicolas Pitre uintptr_t magic1; 83*e45ca16eSNicolas Pitre uintptr_t magic2; 84*e45ca16eSNicolas Pitre /* Padded to cache line boundary by __aligned. */ 85*e45ca16eSNicolas Pitre } __aligned(HOLD_SLOT_SIZE); 86*e45ca16eSNicolas Pitre 87*e45ca16eSNicolas Pitre CASSERT(sizeof(struct hold_slot) == HOLD_SLOT_SIZE, 88*e45ca16eSNicolas Pitre hold_slot_not_cacheline_sized); 89*e45ca16eSNicolas Pitre CASSERT(offsetof(struct hold_slot, entry) == HOLD_SLOT_ENTRY, 90*e45ca16eSNicolas Pitre hold_slot_entry_off_mismatch); 91*e45ca16eSNicolas Pitre CASSERT(offsetof(struct hold_slot, magic1) == HOLD_SLOT_MAGIC1, 92*e45ca16eSNicolas Pitre hold_slot_magic1_off_mismatch); 93*e45ca16eSNicolas Pitre CASSERT(offsetof(struct hold_slot, magic2) == HOLD_SLOT_MAGIC2, 94*e45ca16eSNicolas Pitre hold_slot_magic2_off_mismatch); 95*e45ca16eSNicolas Pitre 96*e45ca16eSNicolas Pitre void plat_hold_pen_init(struct hold_slot *hold_pen, unsigned int core_count); 97*e45ca16eSNicolas Pitre void plat_hold_pen_signal(struct hold_slot *hold_pen, unsigned int core_idx, 98*e45ca16eSNicolas Pitre uintptr_t entry_point); 99*e45ca16eSNicolas Pitre 100*e45ca16eSNicolas Pitre #endif /* __ASSEMBLER__ */ 101*e45ca16eSNicolas Pitre 102*e45ca16eSNicolas Pitre #endif /* PLAT_HOLD_PEN_H */ 103