Define the KHO ABI structures for physical CPU preservation in include/linux/kho/abi/cpu.h and the core kernel interfaces in include/linux/cpu_preserve.h.
Signed-off-by: Pasha Tatashin <[email protected]> --- include/linux/cpu_preserve.h | 352 +++++++++++++++++++++++++++++++++++ include/linux/kho/abi/cpu.h | 112 +++++++++++ 2 files changed, 464 insertions(+) create mode 100644 include/linux/kho/abi/cpu.h diff --git a/include/linux/cpu_preserve.h b/include/linux/cpu_preserve.h index f653838c383d..94181952df1a 100644 --- a/include/linux/cpu_preserve.h +++ b/include/linux/cpu_preserve.h @@ -9,9 +9,75 @@ #define _LINUX_CPU_PRESERVE_H #include <linux/compiler.h> +#include <linux/cpumask.h> +#include <linux/errno.h> +#include <linux/kho/abi/cpu.h> +#include <linux/list.h> +#include <linux/smp.h> +#include <linux/types.h> #ifdef CONFIG_LIVEUPDATE_CPU +#include <asm/cpu_preserve.h> +#include <asm/page.h> + +#define CPU_PRESERVED_STACK_ORDER ARCH_CPU_PRESERVED_STACK_ORDER +#define CPU_PRESERVED_STACK_SIZE ((size_t)PAGE_SIZE << CPU_PRESERVED_STACK_ORDER) +#define CPU_PRESERVED_STACK_HEADROOM 256 +#define CPU_PRESERVED_STACK_MAGIC 0x435055505354414bULL /* "CPUPSTAK" */ + +/** + * struct cpu_preserved_stack_context - Context header at base of preserved CPU stack + * @magic: Validation signature (%CPU_PRESERVED_STACK_MAGIC). + * @cpu: Logical CPU identifier of the preserved physical core. + * @reserved: Must be zero. + * @workload_context: Opaque owning workload or session context. + * @session_pgd_pa: Session root page table physical address, or 0. + * + * This structure lives at the base of a preserved CPU's dedicated stack and is + * accessed by the preserved CPU during parking and workload execution. It is + * private to the preserved CPU execution context of the kernel that allocated it. + */ +struct cpu_preserved_stack_context { + u64 magic; + u32 cpu; + u32 reserved; + u64 workload_context; + u64 session_pgd_pa; +}; + +static_assert(sizeof(struct cpu_preserved_stack_context) == 32); +static_assert(offsetof(struct cpu_preserved_stack_context, magic) == 0, + "magic must lead: the struct is found by masking the stack pointer"); + +/** + * cpu_preserved_get_stack_context - Return the context block at the base of the preserved stack + * + * Masks the current stack pointer to %CPU_PRESERVED_STACK_SIZE alignment and + * validates %CPU_PRESERVED_STACK_MAGIC. + * + * Return: Pointer to &struct cpu_preserved_stack_context if executing on a + * preserved CPU stack, or %NULL otherwise. + */ +static inline struct cpu_preserved_stack_context * +cpu_preserved_get_stack_context(void) +{ + struct cpu_preserved_stack_context *sctx; + unsigned long sp; + +#if defined(CONFIG_X86_64) + asm volatile("mov %%rsp, %0" : "=r"(sp)); +#elif defined(CONFIG_ARM64) + asm volatile("mov %0, sp" : "=r"(sp)); +#else + return NULL; +#endif + sctx = (struct cpu_preserved_stack_context *)(sp & ~(CPU_PRESERVED_STACK_SIZE - 1)); + if (sctx && sctx->magic == CPU_PRESERVED_STACK_MAGIC) + return sctx; + return NULL; +} + /* * __cpu_preserved_text: Code executed by preserved physical CPUs during live * update kexec handover in orphan mode. @@ -36,12 +102,298 @@ extern char __cpu_preserved_text_start[], __cpu_preserved_text_end[]; extern char __cpu_preserved_data_start[], __cpu_preserved_data_end[]; +bool cpu_is_preserved(int cpu); +bool cpu_preserved_should_exit(int cpu); +void cpu_preserved_set_dead(int cpu); +void cpu_preserved_park(int cpu); +void cpu_preserved_park_loop(int cpu); +const struct cpumask *cpu_get_preserved_mask(void); +phys_addr_t cpu_preserved_get_pgd(int cpu); +int cpu_preserved_attach_workload(int cpu, + void (*entry_fn)(void *data), void *data); +int cpu_preserved_detach_workload(int cpu); +void cpu_preserved_set_workload_context(int cpu, void *ctx, phys_addr_t pgd_pa); + +/** + * cpu_preserved_report_dead - Park preserved CPU when reporting dead in hotplug + * + * Invoked by cpuhp_ap_report_dead() after CPU hotplug offline synchronization + * is complete. If the calling CPU is marked for preservation across live update, + * transition it into the preserved parking loop instead of powering down. + */ +static inline void cpu_preserved_report_dead(void) +{ + if (cpu_is_preserved(raw_smp_processor_id())) + cpu_preserved_park(raw_smp_processor_id()); +} + +/* + * Architecture-specific hooks for CPU preservation. + */ + +/** + * arch_cpu_preserved_kick - Signal or wake up a preserved physical CPU + * @cpu: Logical CPU identifier. + * + * Architecture backend hook to wake up the specified preserved CPU from its + * low-power parking state (e.g. via IPI, NMI, or SGI). + * + * This function must be placed in the __cpu_preserved_text section. + */ +void arch_cpu_preserved_kick(int cpu); + +/** + * arch_cpu_preserved_park_wait - Architecture low-power wait in parking loop + * + * Architecture backend hook to execute a low-power wait instruction + * (e.g., cpu_relax/pause, wfe) while parked. + * + * This function must be placed in the __cpu_preserved_text section. + */ +void arch_cpu_preserved_park_wait(void); + +/** + * arch_cpu_preserved_park_init - Architecture setup upon entering park loop + * @cpu: Logical CPU identifier. + * + * Architecture backend hook to configure the physical core (e.g., disable + * or mask local interrupts) upon entering the park loop. + * + * This function must be placed in the __cpu_preserved_text section. + */ +void arch_cpu_preserved_park_init(int cpu); + +/** + * arch_cpu_preserved_early_init - Arch early-boot init for incoming preserved CPUs + * + * Called during early boot in the incoming kernel when preserved physical CPUs + * are adopted from KHO metadata. + */ +void arch_cpu_preserved_early_init(void); + +/** + * arch_cpu_preserved_park_finish - Architecture cleanup on park loop exit + * @cpu: Logical CPU identifier. + * + * Architecture backend hook to execute cleanup or CPU powerdown sequence + * when the park loop exits. + * + * This function must be placed in the __cpu_preserved_text section. + */ +void arch_cpu_preserved_park_finish(int cpu); + +/** + * arch_cpu_preserved_park_on_stack - Switch stack and enter park loop + * @cpu: Logical CPU identifier. + * @stack_top: Top address of the preserved stack. + * + * Architecture backend hook to switch to the preserved execution stack + * and invoke cpu_preserved_park_loop(). + * + * This function must be placed in the __cpu_preserved_text section. + */ +void arch_cpu_preserved_park_on_stack(int cpu, unsigned long stack_top); + +/** + * arch_cpu_preserved_dcache_clean - Clean data cache for address range + * @start: Starting virtual address. + * @end: Ending virtual address. + * + * Architecture backend hook to flush/clean data caches to PoC for memory + * preservation across live update. + * + * This function must be placed in the __cpu_preserved_text section. + */ +void arch_cpu_preserved_dcache_clean(unsigned long start, unsigned long end); + +/** + * arch_cpu_preserved_dcache_inval - Invalidate/clean data cache for range + * @start: Starting virtual address. + * @end: Ending virtual address. + * + * Architecture backend hook to clean/invalidate data caches across live + * update transitions. + * + * This function must be placed in the __cpu_preserved_text section. + */ +void arch_cpu_preserved_dcache_inval(unsigned long start, unsigned long end); + +/** + * arch_cpu_preserved_wait_dead - Wait for CPU to reach dead state + * @cpu: Logical CPU identifier. + * + * Architecture backend hook to wait for a CPU to be fully stopped. + * + * Executed in normal text context during CPU teardown. + */ +void arch_cpu_preserved_wait_dead(int cpu); + +struct page; + +/** + * arch_cpu_preserved_setup_buffer - Map preserved execution buffer outside Scratch + * @text_page: Head page of allocated preserved text memory. + * @text_nr_pages: Number of pages in the preserved text buffer. + * @data_page: Head page of allocated preserved data memory. + * @data_nr_pages: Number of pages in the preserved data buffer. + * + * Architecture backend hook to remap kernel page table entries for + * __cpu_preserved_text and __cpu_preserved_data to the newly allocated + * pages outside Scratch memory. + * + * Return: 0 on success, or a negative errno on failure. + */ +int arch_cpu_preserved_setup_buffer(struct page *text_page, + unsigned int text_nr_pages, + struct page *data_page, + unsigned int data_nr_pages); + +/** + * struct cpu_preserved_as - In-memory address space handle for a preserved CPU + * @node: Entry on the global list of preserved address spaces. + * @pgd: Root page table virtual address in host direct map. + * @pgd_pa: Physical address of @pgd, as loaded into CR3 / TTBR1. + * @is_incoming: This address space was built by the previous kernel. + * @ser: Preserved serialization descriptor holding page table physical addresses. + */ +struct cpu_preserved_as { + struct list_head node; + void *pgd; + phys_addr_t pgd_pa; + bool is_incoming; + struct cpu_preserved_as_ser *ser; +}; + +struct cpu_preserved_as *cpu_preserved_as_create(void); +void cpu_preserved_as_destroy(struct cpu_preserved_as *as); +struct cpu_preserved_as *cpu_preserved_as_adopt(struct cpu_preserved_as_ser *ser); +int cpu_preserved_as_map(struct cpu_preserved_as *as, phys_addr_t pa, + unsigned long va, size_t size, pgprot_t prot); #else /* !CONFIG_LIVEUPDATE_CPU */ #define __cpu_preserved_text #define __cpu_preserved_data +static inline bool cpu_is_preserved(int cpu) { return false; } +static inline bool cpu_preserved_should_exit(int cpu) { return true; } +static inline void cpu_preserved_park(int cpu) {} +static inline void cpu_preserved_set_dead(int cpu) {} +static inline void cpu_preserved_report_dead(void) {} +static inline const struct cpumask *cpu_get_preserved_mask(void) +{ + return cpu_none_mask; +} + +static inline int cpu_preserved_attach_workload(int cpu, + void (*entry_fn)(void *data), + void *data) +{ + return -EOPNOTSUPP; +} + +static inline int cpu_preserved_detach_workload(int cpu) +{ + return -EOPNOTSUPP; +} + +static inline void cpu_preserved_set_workload_context(int cpu, void *ctx, + phys_addr_t pgd_pa) {} +static inline void arch_cpu_preserved_kick(int cpu) {} +static inline void arch_cpu_preserved_park_wait(void) {} +static inline void arch_cpu_preserved_park_init(int cpu) {} +static inline void arch_cpu_preserved_early_init(void) {} +static inline void arch_cpu_preserved_park_finish(int cpu) {} +static inline void arch_cpu_preserved_dcache_clean(unsigned long start, + unsigned long end) {} +static inline void arch_cpu_preserved_dcache_inval(unsigned long start, + unsigned long end) {} +static inline void arch_cpu_preserved_wait_dead(int cpu) {} +static inline phys_addr_t cpu_preserved_get_pgd(int cpu) { return 0; } +static inline int arch_cpu_preserved_setup_buffer(struct page *text_page, + unsigned int text_nr_pages, + struct page *data_page, + unsigned int data_nr_pages) +{ + return 0; +} + +static inline int cpu_preserved_map_range(phys_addr_t pa, unsigned long va, + size_t size, pgprot_t prot) { return 0; } +static inline int cpu_preserved_map_buffer(void *va, size_t size) { return 0; } +static inline int arch_cpu_preserved_mpidr_to_cpu(u64 mpidr) { return -EINVAL; } +static inline bool arch_cpu_preserved_is_active(void) { return false; } +static inline void arch_cpu_preserved_switch_pgd(phys_addr_t pgd_pa) {} +static inline struct cpu_preserved_stack_context * +cpu_preserved_get_stack_context(void) +{ + return NULL; +} + #endif /* CONFIG_LIVEUPDATE_CPU */ +/* + * Object-granular wrappers around the arch dcache hooks. + * + * Every preserved-memory handshake flushes or invalidates a whole object, so + * spell that out once instead of open-coding (addr, addr + size) at each call + * site: the size can then never drift from the object it is supposed to cover. + * + * @p is a pointer to the object. For a statically sized array, pass &array so + * that sizeof(*(p)) is the size of the whole array rather than of one element. + * Use the _sz() forms for flexible-array structures and for raw page buffers, + * where the length is not derivable from the type. + */ +#define cpu_preserved_clean_sz(p, sz) \ + arch_cpu_preserved_dcache_clean((unsigned long)(p), \ + (unsigned long)(p) + (sz)) +#define cpu_preserved_inval_sz(p, sz) \ + arch_cpu_preserved_dcache_inval((unsigned long)(p), \ + (unsigned long)(p) + (sz)) +#define cpu_preserved_clean(p) cpu_preserved_clean_sz(p, sizeof(*(p))) +#define cpu_preserved_inval(p) cpu_preserved_inval_sz(p, sizeof(*(p))) + +/** + * cpu_preserved_memset - Compiler-barrier-safe byte fill for preserved runtime code + * @s: Destination buffer. + * @c: Byte value to fill. + * @n: Number of bytes to fill. + * + * Uses WRITE_ONCE() per byte so the compiler's loop-idiom pass cannot replace + * the loop with a call to the unmapped host kernel memset() inside + * __cpu_preserved_text. + * + * Return: @s. + */ +static inline void *cpu_preserved_memset(void *s, int c, size_t n) +{ + unsigned char *p = s; + + while (n--) + WRITE_ONCE(*p++, (unsigned char)c); + return s; +} + +/** + * cpu_preserved_memcpy - Compiler-barrier-safe byte copy for preserved runtime code + * @dest: Destination buffer. + * @src: Source buffer. + * @n: Number of bytes to copy. + * + * Uses READ_ONCE()/WRITE_ONCE() per byte so the compiler's loop-idiom pass + * cannot replace the loop with a call to the unmapped host kernel memcpy() + * inside __cpu_preserved_text. + * + * Return: @dest. + */ +static inline void *cpu_preserved_memcpy(void *dest, const void *src, size_t n) +{ + const unsigned char *s = src; + unsigned char *d = dest; + + while (n--) + WRITE_ONCE(*d++, READ_ONCE(*s++)); + return dest; +} + #endif /* _LINUX_CPU_PRESERVE_H */ diff --git a/include/linux/kho/abi/cpu.h b/include/linux/kho/abi/cpu.h new file mode 100644 index 000000000000..5926be37f7a0 --- /dev/null +++ b/include/linux/kho/abi/cpu.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026, Google LLC. + * Pasha Tatashin <[email protected]> + */ + +#ifndef _LINUX_KHO_ABI_CPU_H +#define _LINUX_KHO_ABI_CPU_H + +#include <linux/build_bug.h> +#include <linux/kho/abi/kexec_handover.h> +#include <linux/stddef.h> +#include <linux/types.h> +#include <uapi/linux/liveupdate.h> + +/** + * DOC: CPU Preservation Live Update ABI + * + * Physical CPU preservation uses the ABI defined below to serialize + * and restore the state of preserved CPUs across a live update kexec + * reboot using the LUO. + * + * Preserved CPUs are isolated from host scheduling and remain active + * in a parking loop or running workload across the live update reboot. + * This ABI provides the contract for communicating preserved core metadata + * and per-file descriptor state to the incoming kernel. + * + * The state is serialized into packed structures + * (struct cpu_preserved_global_ser and struct cpu_preserved_file_ser) which + * are handed over to the next kernel via the KHO mechanism. + * + * This interface is a contract. Any modification to the structure + * fields, compatible strings, or the layout of the `__packed` + * serialization structures defined here constitutes a breaking change. + * Such changes require incrementing the version number in the + * CPU_PRESERVED_LUO_FLB_COMPATIBLE or CPU_PRESERVED_LUO_FH_COMPATIBLE + * compatibility strings to prevent a new kernel from misinterpreting + * data from an old kernel. + * + * Changes are allowed provided the compatibility version is + * incremented; however, backward/forward compatibility is only + * guaranteed for kernels supporting the same ABI version. + */ + +/* The compatibility string for preserved CPU FLB */ +#define CPU_PRESERVED_LUO_FLB_COMPATIBLE "cpu_flb_v1" + +/* The compatibility string for preserved CPU file handler */ +#define CPU_PRESERVED_LUO_FH_COMPATIBLE "cpu_fh_v1" + +enum cpu_preserved_workload { + CPU_PRESERVED_PARKED = 1, + CPU_PRESERVED_WORKLOAD = 2, + CPU_PRESERVED_EXITING = 3, + CPU_PRESERVED_DEAD = 4, +}; + +/** + * struct cpu_preserved_pcpu_ser - Per-CPU mailbox in preserved memory + * @workload: Preserved workload state (enum cpu_preserved_workload). + */ +struct cpu_preserved_pcpu_ser { + u32 workload; +} __packed; + +/** + * struct cpu_preserved_global_ser - Global FLB serialization header + * @nr_cpu_words: Number of u64 words in @cpu_preserved_bitmap. + * @reserved: Must be zero. + * @text_runtime_pa: Physical address of preserved CPU runtime text. + * @text_runtime_size: Size of preserved CPU runtime text in bytes. + * @data_runtime_pa: Physical address of preserved CPU runtime data. + * @data_runtime_size: Size of preserved CPU runtime data in bytes. + * @pcpus_runtime: Preservation pointer to struct cpu_preserved_pcpu_ser array. + * @transition_as: Preservation pointer to transition struct cpu_preserved_as_ser. + * @cpu_preserved_bitmap: Bitmap of physical CPUs preserved across live update. + * + * The preserved-CPU bitmap is serialized as an explicitly sized array of u64 + * rather than as a cpumask_t: sizeof(cpumask_t) depends on NR_CPUS, so + * embedding one would make the offset of every subsequent field depend on the + * .config of the kernel that wrote it. + */ +struct cpu_preserved_global_ser { + u32 nr_cpu_words; + u32 reserved; + u64 text_runtime_pa; + u64 text_runtime_size; + u64 data_runtime_pa; + u64 data_runtime_size; + DECLARE_KHOSER_PTR(pcpus_runtime, struct cpu_preserved_pcpu_ser *); + DECLARE_KHOSER_PTR(transition_as, struct cpu_preserved_as_ser *); + u64 cpu_preserved_bitmap[]; +} __packed; + +static_assert(offsetof(struct cpu_preserved_global_ser, + cpu_preserved_bitmap) % sizeof(u64) == 0, + "cpu_preserved_bitmap must be 64-bit aligned"); + +/** + * struct cpu_preserved_file_ser - Per-file serialized state for preserved CPU fd + * @cpu: Logical CPU identifier. + * @reserved: Must be zero. + * @stack_pa: Physical address of this CPU's preserved stack. + * @oncore: Preservation pointer to on-core session serialized metadata. + */ +struct cpu_preserved_file_ser { + u32 cpu; + u32 reserved; + u64 stack_pa; +} __packed; + +#endif /* _LINUX_KHO_ABI_CPU_H */ -- 2.55.0.1082.g2b9226bbc0-goog

