[RFC PATCH 18/46] liveupdate: cpu_preserve: Add LUO file handler for preserved physical CPUs
From: Pasha Tatashin
Date: Sun Sep 20 2026 - 15:45:45 EST
Implement the LUO file handler (cpu_preserve_handler) and early boot
incoming CPU adoption for /sys/devices/system/cpu/cpuX/preserve fds.
Signed-off-by: Pasha Tatashin <pasha.tatashin@xxxxxxxxxx>
---
kernel/liveupdate/cpu_preserve.c | 189 ++++++++++++++++++++++++++++++-
1 file changed, 185 insertions(+), 4 deletions(-)
diff --git a/kernel/liveupdate/cpu_preserve.c b/kernel/liveupdate/cpu_preserve.c
index 2f92dfee82b1..9a039ba9e912 100644
--- a/kernel/liveupdate/cpu_preserve.c
+++ b/kernel/liveupdate/cpu_preserve.c
@@ -837,8 +837,10 @@ void __cpu_preserved_text cpu_preserved_set_dead(int cpu)
{
struct cpu_preserved_pcpu_ser *ser = cpu_preserved_get_pcpu_ser(cpu);
- if (ser)
- WRITE_ONCE(ser->workload, CPU_PRESERVED_DEAD);
+ if (ser) {
+ smp_store_release(&ser->workload, CPU_PRESERVED_DEAD);
+ cpu_preserved_clean(ser);
+ }
}
EXPORT_SYMBOL_GPL(cpu_preserved_set_dead);
@@ -1097,8 +1099,6 @@ void __cpu_preserved_text cpu_preserved_park_loop(int cpu)
switch (READ_ONCE(ser->workload)) {
case CPU_PRESERVED_EXITING:
case CPU_PRESERVED_DEAD:
- WRITE_ONCE(ser->workload, CPU_PRESERVED_DEAD);
- cpu_preserved_clean(ser);
return;
case CPU_PRESERVED_WORKLOAD:
cpu_preserved_run_workload(ser, pcpu);
@@ -1131,6 +1131,7 @@ void cpu_preserved_park(int cpu)
} else {
cpu_preserved_park_loop(cpu);
arch_cpu_preserved_park_finish(cpu);
+ cpu_preserved_set_dead(cpu);
}
}
EXPORT_SYMBOL_GPL(cpu_preserved_park);
@@ -1498,6 +1499,171 @@ static struct liveupdate_flb cpu_preserved_flb = {
.compatible = CPU_PRESERVED_LUO_FLB_COMPATIBLE,
};
+/*
+ * LUO File Handler Callbacks for /sys/devices/system/cpu/cpu<N>/preserve
+ */
+static int file_to_cpu(struct file *file, unsigned int *cpup)
+{
+ struct dentry *dentry, *parent;
+ unsigned int cpu;
+
+ if (!file || !file->f_path.dentry)
+ return -EINVAL;
+
+ if (file_inode(file)->i_sb->s_magic != SYSFS_MAGIC)
+ return -EINVAL;
+
+ dentry = file->f_path.dentry;
+ if (strcmp(dentry->d_name.name, "preserve"))
+ return -EINVAL;
+
+ parent = dentry->d_parent;
+ if (!parent || sscanf(parent->d_name.name, "cpu%u", &cpu) != 1)
+ return -EINVAL;
+
+ if (cpu >= nr_cpu_ids || !cpu_possible(cpu) ||
+ !cpu_is_hotpluggable(cpu)) {
+ return -EINVAL;
+ }
+
+ *cpup = cpu;
+ return 0;
+}
+
+static bool cpu_preserve_can_preserve(struct liveupdate_file_handler *handler,
+ struct file *file)
+{
+ unsigned int cpu;
+
+ return IS_ENABLED(CONFIG_LIVEUPDATE_ONCORE) && file_to_cpu(file, &cpu) == 0;
+}
+
+static int cpu_preserve_preserve(struct liveupdate_file_op_args *args)
+{
+ struct cpu_preserved_file_ser *fser;
+ unsigned int cpu;
+ int ret;
+
+ ret = file_to_cpu(args->file, &cpu);
+ if (ret)
+ return ret;
+
+ ret = cpu_preserve(cpu);
+ if (ret)
+ return ret;
+
+ fser = kho_alloc_preserve(sizeof(*fser));
+ if (IS_ERR(fser)) {
+ cpu_unpreserve(cpu);
+ return PTR_ERR(fser);
+ }
+
+ memset(fser, 0, sizeof(*fser));
+ fser->cpu = cpu;
+
+ scoped_guard(mutex, &cpu_preserved_lock)
+ fser->stack_pa = cpu_preserved_outgoing.pcpus[cpu].stack_pa;
+
+ args->serialized_data = virt_to_phys(fser);
+ return 0;
+}
+
+static void cpu_preserve_unpreserve(struct liveupdate_file_op_args *args)
+{
+ struct cpu_preserved_file_ser *fser;
+ unsigned int cpu;
+
+ if (!args->serialized_data)
+ return;
+
+ fser = phys_to_virt(args->serialized_data);
+ cpu = fser->cpu;
+
+ cpu_unpreserve(cpu);
+
+ kho_unpreserve_free(fser);
+}
+
+static void cpu_preserve_restore_incoming_cpu(struct liveupdate_session *session,
+ struct cpu_preserved_file_ser *fser)
+{
+ unsigned int cpu = fser->cpu;
+
+ scoped_guard(mutex, &cpu_preserved_lock) {
+ cpumask_set_cpu(cpu, &cpu_preserved_incoming.mask);
+ cpumask_set_cpu(cpu, &cpu_preserved_mask);
+
+ if (!cpu_preserved_incoming.pcpus) {
+ cpu_preserved_incoming.pcpus =
+ kcalloc(nr_cpu_ids,
+ sizeof(*cpu_preserved_incoming.pcpus),
+ GFP_KERNEL);
+ WRITE_ONCE(cpu_preserved_host_pcpus_va,
+ cpu_preserved_incoming.pcpus);
+ cpu_preserved_clean(&cpu_preserved_host_pcpus_va);
+ }
+
+ if (cpu_preserved_incoming.pcpus)
+ cpu_preserved_incoming.pcpus[cpu].stack_pa = fser->stack_pa;
+
+ cpu_preserved_clean(&cpu_preserved_mask);
+ }
+}
+
+static int cpu_preserve_retrieve(struct liveupdate_file_op_args *args)
+{
+ struct cpu_preserved_file_ser *fser;
+ struct file *file;
+ char path[64];
+
+ if (!args->serialized_data)
+ return -EINVAL;
+
+ fser = phys_to_virt(args->serialized_data);
+
+ snprintf(path, sizeof(path),
+ "/sys/devices/system/cpu/cpu%u/preserve", fser->cpu);
+ file = filp_open(path, O_RDONLY, 0);
+ if (IS_ERR(file))
+ return PTR_ERR(file);
+
+ args->file = file;
+ cpu_preserve_restore_incoming_cpu(args->session, fser);
+ cpu_preserved_detach_workload(fser->cpu);
+
+ return 0;
+}
+
+static void cpu_preserve_finish(struct liveupdate_file_op_args *args)
+{
+ struct cpu_preserved_file_ser *fser;
+
+ if (!args->serialized_data)
+ return;
+
+ fser = phys_to_virt(args->serialized_data);
+ if (args->retrieve_status <= 0)
+ cpu_preserve_restore_incoming_cpu(args->session, fser);
+
+ cpu_unpreserve(fser->cpu);
+
+ kho_restore_free(fser);
+}
+
+static const struct liveupdate_file_ops cpu_preserve_file_ops = {
+ .can_preserve = cpu_preserve_can_preserve,
+ .preserve = cpu_preserve_preserve,
+ .retrieve = cpu_preserve_retrieve,
+ .unpreserve = cpu_preserve_unpreserve,
+ .finish = cpu_preserve_finish,
+ .owner = THIS_MODULE,
+};
+
+static struct liveupdate_file_handler cpu_preserve_handler = {
+ .ops = &cpu_preserve_file_ops,
+ .compatible = CPU_PRESERVED_LUO_FH_COMPATIBLE,
+};
+
static int cpu_preserve_reboot_notify(struct notifier_block *nb,
unsigned long action, void *data)
{
@@ -1555,6 +1721,21 @@ static int __init cpu_preserve_early_init(void)
cpu_preserved_incoming.pcpus_ser = NULL;
cpu_preserved_global_ser = NULL;
+ err = liveupdate_register_file_handler(&cpu_preserve_handler);
+ if (err && err != -EOPNOTSUPP) {
+ pr_err("Could not register cpu_preserve file handler: %pe\n",
+ ERR_PTR(err));
+ return err;
+ }
+
+ err = liveupdate_register_flb(&cpu_preserve_handler,
+ &cpu_preserved_flb);
+ if (err && err != -EOPNOTSUPP) {
+ pr_err("Could not register cpu_preserved FLB: %pe\n",
+ ERR_PTR(err));
+ return err;
+ }
+
/* Retrieve incoming preserved CPUs before secondary CPU bringup */
if (liveupdate_enabled())
liveupdate_flb_get_incoming(&cpu_preserved_flb, &obj);
--
2.55.0.1082.g2b9226bbc0-goog