Implement physical CPU preservation lifecycle management, stack allocation,
and build rules with -mbranch-protection=none and -fno-stack-protector.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 kernel/cpu.c                     |   2 +
 kernel/liveupdate/Kconfig        |  35 +++
 kernel/liveupdate/Makefile       |   3 +
 kernel/liveupdate/cpu_preserve.c | 359 +++++++++++++++++++++++++++++++
 4 files changed, 399 insertions(+)

diff --git a/kernel/cpu.c b/kernel/cpu.c
index b3c8553d7bd6..8637b5e7f2de 100644
--- a/kernel/cpu.c
+++ b/kernel/cpu.c
@@ -14,6 +14,7 @@
 #include <linux/sched/smt.h>
 #include <linux/unistd.h>
 #include <linux/cpu.h>
+#include <linux/cpu_preserve.h>
 #include <linux/oom.h>
 #include <linux/rcupdate.h>
 #include <linux/delay.h>
@@ -349,6 +350,7 @@ static inline void cpuhp_ap_update_sync_state(enum 
cpuhp_sync_state state) { }
 void cpuhp_ap_report_dead(void)
 {
        cpuhp_ap_update_sync_state(SYNC_STATE_DEAD);
+       cpu_preserved_report_dead();
 }
 
 void __weak arch_cpuhp_cleanup_dead_cpu(unsigned int cpu) { }
diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig
index 2490f9a9b838..52c5fd7a620b 100644
--- a/kernel/liveupdate/Kconfig
+++ b/kernel/liveupdate/Kconfig
@@ -101,4 +101,39 @@ config LIVEUPDATE_GUEST_MEMFD
 
          If unsure, say N.
 
+config ARCH_SUPPORTS_LIVEUPDATE_CPU
+       bool
+
+config LIVEUPDATE_CPU
+       bool "Live update support for preserved CPUs"
+       depends on LIVEUPDATE
+       depends on ARCH_SUPPORTS_LIVEUPDATE_CPU
+       depends on HOTPLUG_CPU
+       default LIVEUPDATE
+       help
+         Enable live update support for physical CPU preservation.
+
+         This feature allows physical CPU cores to be preserved across
+         kexec-based live updates without undergoing a hardware reset or
+         full kernel offline/online cycle.
+
+         When a core is preserved, it is isolated from host scheduling and
+         parked in a dedicated execution loop within preserved memory pages.
+         Its execution context, state, and necessary page tables are handed
+         over across the kexec transition using the KHO
+         framework. The incoming kernel detects preserved cores during early
+         boot and avoids bringing them up during smp_init(), keeping them
+         isolated and running their assigned workloads.
+
+         This capability is a core building block for live-updating virtual
+         machine hosts (e.g. Caretaker/OrphanVM) without pausing or
+         disrupting running guest workloads, and can also speed up kernel
+         live updates by skipping CPU re-initialization.
+
+         Preservation lifecycle is managed from userspace via LUO sessions
+         associated with /sys/devices/system/cpu/cpu<N>/preserve file
+         descriptors, and monitored via sysfs.
+
+         If unsure, say N.
+
 endmenu
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index d9f469462556..486f7854bb75 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
+CFLAGS_cpu_preserve.o += $(call cc-option,-mbranch-protection=none) 
-fno-stack-protector $(call cc-option,-ftrivial-auto-var-init=uninitialized) 
$(call cc-option,-fno-jump-tables)
+
 luo-y :=                                                               \
                kho_block.o                                             \
                luo_core.o                                              \
@@ -11,3 +13,4 @@ obj-$(CONFIG_KEXEC_HANDOVER)          += kexec_handover.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS)   += kexec_handover_debugfs.o
 
 obj-$(CONFIG_LIVEUPDATE)               += luo.o
+obj-$(CONFIG_LIVEUPDATE_CPU)           += cpu_preserve.o
diff --git a/kernel/liveupdate/cpu_preserve.c b/kernel/liveupdate/cpu_preserve.c
index e19208670c3e..c5fd3a07ccb5 100644
--- a/kernel/liveupdate/cpu_preserve.c
+++ b/kernel/liveupdate/cpu_preserve.c
@@ -247,3 +247,362 @@ static phys_addr_t cpu_preserved_get_data_pa(void)
        return cpu_preserved_data_pages ? 
page_to_phys(cpu_preserved_data_pages) : 0;
 }
 
+static void cpu_preserved_sync_global_ser(void)
+{
+       struct cpu_preserved_global_ser *ser = cpu_preserved_global_ser;
+
+       if (!ser)
+               return;
+
+       bitmap_to_arr64(ser->cpu_preserved_bitmap,
+                       cpumask_bits(&cpu_preserved_mask), nr_cpu_ids);
+       if (cpu_preserved_text_pages) {
+               ser->text_runtime_pa = page_to_phys(cpu_preserved_text_pages);
+               ser->text_runtime_size =
+                       (1UL << cpu_preserved_text_order) * PAGE_SIZE;
+       }
+       if (cpu_preserved_data_pages) {
+               ser->data_runtime_pa = page_to_phys(cpu_preserved_data_pages);
+               ser->data_runtime_size =
+                       (1UL << cpu_preserved_data_order) * PAGE_SIZE;
+       }
+       KHOSER_STORE_PTR(ser->pcpus_runtime, cpu_preserved_outgoing.pcpus_ser);
+       cpu_preserved_clean_sz(ser,
+                              struct_size(ser, cpu_preserved_bitmap, 
ser->nr_cpu_words));
+}
+
+static void cpu_preserved_free_kho(void *va, bool is_incoming)
+{
+       if (!va)
+               return;
+
+       if (is_incoming)
+               kho_restore_free(va);
+       else
+               kho_unpreserve_free(va);
+}
+
+static void cpu_preserved_free_stack(phys_addr_t stack_pa, bool is_incoming)
+{
+       if (stack_pa)
+               cpu_preserved_free_kho(phys_to_virt(stack_pa), is_incoming);
+}
+
+static void cpu_preserved_state_cleanup(struct cpu_preserved_state *st,
+                                       bool is_incoming)
+{
+       if (!cpumask_empty(&st->mask))
+               return;
+
+       cpu_preserved_free_kho(st->pcpus_ser, is_incoming);
+       st->pcpus_ser = NULL;
+       kfree(st->pcpus);
+       st->pcpus = NULL;
+}
+
+/*
+ * Drop @cpu out of the preserved state, free its preserved stack, and
+ * republish the globals a parked core may still be reading.  The caller holds
+ * cpu_preserved_lock and has already made the core leave the park loop.
+ */
+static void __cpu_unpreserve_locked(unsigned int cpu)
+{
+       struct cpu_preserved_state *incoming = &cpu_preserved_incoming;
+       struct cpu_preserved_state *outgoing = &cpu_preserved_outgoing;
+       struct cpu_preserved_pcpu_ser *ser = cpu_preserved_get_pcpu_ser(cpu);
+       struct cpu_preserved_pcpu *pcpu = cpu_preserved_get_pcpu(cpu);
+       bool is_incoming = cpu_preserved_is_incoming(cpu);
+       phys_addr_t stack_pa = 0;
+
+       lockdep_assert_held(&cpu_preserved_lock);
+
+       cpumask_clear_cpu(cpu, &outgoing->mask);
+       cpumask_clear_cpu(cpu, &incoming->mask);
+       cpumask_clear_cpu(cpu, &cpu_preserved_mask);
+       cpu_preserved_clean(&cpu_preserved_mask);
+       set_cpu_present(cpu, true);
+
+       if (ser)
+               WRITE_ONCE(ser->workload, 0);
+
+       if (pcpu) {
+               stack_pa = pcpu->stack_pa;
+               memset(pcpu, 0, sizeof(*pcpu));
+       }
+
+       cpu_preserved_free_stack(stack_pa, is_incoming);
+
+       /* @pcpu and @ser point into these arrays: do not touch past this 
point. */
+       cpu_preserved_state_cleanup(outgoing, false);
+       cpu_preserved_state_cleanup(incoming, true);
+
+       if (cpumask_empty(&cpu_preserved_mask)) {
+               WRITE_ONCE(cpu_preserved_pcpus_va, NULL);
+               WRITE_ONCE(cpu_preserved_host_pcpus_va, NULL);
+               cpu_preserved_clean(&cpu_preserved_pcpus_va);
+               cpu_preserved_clean(&cpu_preserved_host_pcpus_va);
+       }
+
+       cpu_preserved_sync_global_ser();
+}
+
+/**
+ * cpu_unpreserve - Unpreserve a physical CPU and restore it to online state
+ * @cpu: Logical CPU identifier.
+ *
+ * Signals the CPU to exit the parking loop, cleans up preserved stack memory,
+ * and restores the core to host scheduling via standard add_cpu().
+ */
+static void cpu_unpreserve(unsigned int cpu)
+{
+       int ret;
+
+       scoped_guard(mutex, &cpu_preserved_lock) {
+               if (!cpu_is_preserved(cpu))
+                       return;
+
+               cpu_signal_exit(cpu);
+               arch_cpu_preserved_kick(cpu);
+       }
+
+       /*
+        * cpu_wait_dead() busy-polls for up to 20 seconds.  Do not hold
+        * cpu_preserved_lock across it: the poll only reads pcpu->state, which
+        * stays valid for as long as the CPU is preserved, and holding the lock
+        * here would stall every other preservation operation and every sysfs
+        * reader for the entire window.
+        */
+       if (cpu_wait_dead(cpu))
+               return;
+
+       scoped_guard(mutex, &cpu_preserved_lock) {
+               if (!cpu_is_preserved(cpu))
+                       return;
+
+               __cpu_unpreserve_locked(cpu);
+       }
+
+       ret = add_cpu(cpu);
+       if (ret < 0)
+               pr_err("Failed to bring unpreserved cpu %u back online: %d\n",
+                      cpu, ret);
+}
+
+/*
+ * FLB Ops for Preserved CPUs
+ */
+static int cpu_preserved_flb_preserve(struct liveupdate_flb_op_args *argp)
+{
+       unsigned int nr_words = BITS_TO_U64(nr_cpu_ids);
+       struct cpu_preserved_global_ser *ser;
+       size_t ser_sz;
+       int ret;
+
+       ret = cpu_preserved_init_runtime_buffer();
+       if (ret)
+               return ret;
+
+       ser_sz = struct_size(ser, cpu_preserved_bitmap, nr_words);
+
+       mutex_lock(&cpu_preserved_lock);
+       ser = kho_alloc_preserve(ser_sz);
+       if (IS_ERR(ser)) {
+               mutex_unlock(&cpu_preserved_lock);
+               return PTR_ERR(ser);
+       }
+
+       memset(ser, 0, ser_sz);
+       ser->nr_cpu_words = nr_words;
+       cpu_preserved_global_ser = ser;
+       cpu_preserved_sync_global_ser();
+       mutex_unlock(&cpu_preserved_lock);
+
+       argp->data = virt_to_phys(ser);
+       argp->obj = ser;
+       return 0;
+}
+
+static void cpu_preserved_flb_unpreserve(struct liveupdate_flb_op_args *argp)
+{
+       struct cpu_preserved_global_ser *ser;
+
+       if (!argp->data)
+               return;
+
+       ser = phys_to_virt(argp->data);
+       mutex_lock(&cpu_preserved_lock);
+       cpu_preserved_global_ser = NULL;
+       mutex_unlock(&cpu_preserved_lock);
+
+       cpu_preserved_unpreserve_runtime_buffer();
+       kho_unpreserve_free(ser);
+}
+
+static int cpu_preserved_flb_retrieve(struct liveupdate_flb_op_args *argp)
+{
+       struct cpu_preserved_global_ser *ser;
+       struct cpu_preserved_pcpu_ser *pcpus;
+       u64 nr_bits;
+       int cpu;
+
+       if (!argp->data)
+               return -EINVAL;
+
+       ser = phys_to_virt(argp->data);
+       arch_cpu_preserved_early_init();
+
+       /*
+        * The outgoing kernel may have been built with a larger NR_CPUS.  Any
+        * preserved CPU we cannot represent would be silently forgotten and
+        * left spinning in its park loop forever, so refuse the handover
+        * instead.
+        */
+       nr_bits = (u64)ser->nr_cpu_words * BITS_PER_TYPE(u64);
+       if (nr_bits > nr_cpu_ids &&
+           find_next_bit((const unsigned long *)ser->cpu_preserved_bitmap,
+                         nr_bits, nr_cpu_ids) < nr_bits) {
+               pr_err("preserved CPU above nr_cpu_ids=%u in handover data\n",
+                      nr_cpu_ids);
+               return -ERANGE;
+       }
+
+       mutex_lock(&cpu_preserved_lock);
+       bitmap_from_arr64(cpumask_bits(&cpu_preserved_mask),
+                         ser->cpu_preserved_bitmap, min_t(u64, nr_bits, 
nr_cpu_ids));
+       cpumask_copy(&cpu_preserved_incoming.mask, &cpu_preserved_mask);
+       pcpus = KHOSER_LOAD_PTR(ser->pcpus_runtime);
+
+       if (pcpus) {
+               cpu_preserved_incoming.pcpus_ser = pcpus;
+               WRITE_ONCE(cpu_preserved_pcpus_va, pcpus);
+               cpu_preserved_clean(&cpu_preserved_pcpus_va);
+       }
+       cpu_preserved_clean(&cpu_preserved_mask);
+       for_each_cpu(cpu, &cpu_preserved_mask)
+               set_cpu_present(cpu, false);
+       mutex_unlock(&cpu_preserved_lock);
+
+       argp->obj = ser;
+       return 0;
+}
+
+static void cpu_preserved_flb_finish(struct liveupdate_flb_op_args *argp)
+{
+       struct cpu_preserved_global_ser *ser;
+
+       if (!argp->obj)
+               return;
+
+       ser = argp->obj;
+
+       scoped_guard(mutex, &cpu_preserved_lock) {
+               if (cpu_preserved_incoming.pcpus_ser) {
+                       kho_restore_free(cpu_preserved_incoming.pcpus_ser);
+                       cpu_preserved_incoming.pcpus_ser = NULL;
+               }
+               kfree(cpu_preserved_incoming.pcpus);
+               cpu_preserved_incoming.pcpus = NULL;
+       }
+
+       if (ser->text_runtime_pa && ser->text_runtime_size) {
+               unsigned long nr_pages = ser->text_runtime_size >> PAGE_SHIFT;
+               struct page *page = kho_restore_pages(ser->text_runtime_pa, 
nr_pages);
+
+               if (page) {
+                       for (unsigned long i = 0; i < nr_pages; i++)
+                               __free_page(page + i);
+               }
+       }
+
+       if (ser->data_runtime_pa && ser->data_runtime_size) {
+               unsigned long nr_pages = ser->data_runtime_size >> PAGE_SHIFT;
+               struct page *page = kho_restore_pages(ser->data_runtime_pa, 
nr_pages);
+
+               if (page) {
+                       for (unsigned long i = 0; i < nr_pages; i++)
+                               __free_page(page + i);
+               }
+       }
+
+       kho_restore_free(ser);
+}
+
+static const struct liveupdate_flb_ops cpu_preserved_flb_ops = {
+       .preserve   = cpu_preserved_flb_preserve,
+       .unpreserve = cpu_preserved_flb_unpreserve,
+       .retrieve   = cpu_preserved_flb_retrieve,
+       .finish     = cpu_preserved_flb_finish,
+       .owner      = THIS_MODULE,
+};
+
+static struct liveupdate_flb cpu_preserved_flb = {
+       .ops        = &cpu_preserved_flb_ops,
+       .compatible = CPU_PRESERVED_LUO_FLB_COMPATIBLE,
+};
+
+static int cpu_preserve_reboot_notify(struct notifier_block *nb,
+                                     unsigned long action, void *data)
+{
+       int cpu;
+
+       scoped_guard(mutex, &cpu_preserved_lock) {
+               for_each_cpu(cpu, &cpu_preserved_mask) {
+                       /*
+                        * If this CPU is not being preserved across an outgoing
+                        * live update, signal it to exit the park loop and
+                        * offline it.
+                        */
+                       if (kexec_in_progress && liveupdate_enabled() &&
+                           !cpu_preserved_is_incoming(cpu))
+                               continue;
+
+                       cpu_signal_exit(cpu);
+                       arch_cpu_preserved_kick(cpu);
+                       if (cpu_wait_dead(cpu))
+                               continue;
+
+                       __cpu_unpreserve_locked(cpu);
+               }
+       }
+
+       return NOTIFY_OK;
+}
+
+static struct notifier_block cpu_preserve_reboot_nb = {
+       .notifier_call = cpu_preserve_reboot_notify,
+       .priority = 0,
+};
+
+/**
+ * cpu_preserve_early_init - Early boot registration & retrieval of CPUs
+ *
+ * Registers the preserved CPU file handler and FLB with LUO, retrieves 
incoming
+ * preserved CPU state prior to secondary SMP bringup, and registers the reboot
+ * notifier.
+ *
+ * Return: 0 on success, or negative error code on failure.
+ */
+static int __init cpu_preserve_early_init(void)
+{
+       void *obj;
+       int err;
+
+       if (!liveupdate_enabled())
+               cpumask_clear(&cpu_preserved_mask);
+       cpumask_clear(&cpu_preserved_outgoing.mask);
+       cpumask_clear(&cpu_preserved_incoming.mask);
+       cpu_preserved_outgoing.pcpus = NULL;
+       cpu_preserved_outgoing.pcpus_ser = NULL;
+       cpu_preserved_incoming.pcpus = NULL;
+       cpu_preserved_incoming.pcpus_ser = NULL;
+       cpu_preserved_global_ser = NULL;
+
+       /* Retrieve incoming preserved CPUs before secondary CPU bringup */
+       if (liveupdate_enabled())
+               liveupdate_flb_get_incoming(&cpu_preserved_flb, &obj);
+
+       register_reboot_notifier(&cpu_preserve_reboot_nb);
+
+       return 0;
+}
+early_initcall(cpu_preserve_early_init);
-- 
2.55.0.1082.g2b9226bbc0-goog


Reply via email to