Add struct cpu_preserved_as and cpu_preserved_as_create/map/adopt/destroy
interfaces to manage isolated page tables for preserved physical CPUs.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 include/linux/cpu_preserve.h     |  65 +++
 include/linux/kho/abi/cpu.h      |  38 ++
 kernel/liveupdate/cpu_preserve.c | 958 +++++++++++++++++++++++++++++++
 3 files changed, 1061 insertions(+)

diff --git a/include/linux/cpu_preserve.h b/include/linux/cpu_preserve.h
index 94181952df1a..472fcf3933a2 100644
--- a/include/linux/cpu_preserve.h
+++ b/include/linux/cpu_preserve.h
@@ -269,6 +269,71 @@ 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);
+void *cpu_preserved_as_alloc_page(void *arg);
+
+/**
+ * arch_cpu_preserved_as_map - Add one range to a preserved address space
+ * @as: Address space to map into; @as->pgd is the root to populate.
+ * @pa: Physical address of the range.
+ * @va: Virtual address the range must appear at.
+ * @size: Size of the range in bytes.
+ * @prot: Protection to apply.
+ *
+ * Architecture backend for cpu_preserved_as_map().  Page table pages must be
+ * obtained from cpu_preserved_as_alloc_page() with @as as its argument, so
+ * that the core layer can preserve and later free them; the caller holds the
+ * mapping lock and takes care of cache maintenance and of the TLB.
+ *
+ * Return: 0 on success, or a negative errno on failure.
+ */
+int arch_cpu_preserved_as_map(struct cpu_preserved_as *as, phys_addr_t pa,
+                             unsigned long va, size_t size, pgprot_t prot);
+
+/**
+ * arch_cpu_preserved_as_flush_tlb - Publish preserved page table updates
+ *
+ * Called after every successful arch_cpu_preserved_as_map().  Architectures
+ * whose preserved CPUs can hold stale translations for these address spaces
+ * must invalidate them here; the others need do nothing.
+ */
+void arch_cpu_preserved_as_flush_tlb(void);
+
+/**
+ * arch_cpu_preserved_set_transition_as - Publish the default address space
+ * @as: Address space a preserved CPU parks in when its workload has none.
+ *
+ * The value has to be readable from preserved text after the kexec, which is
+ * architecture specific storage, so the core layer hands it over rather than
+ * exporting a variable.
+ */
+void arch_cpu_preserved_set_transition_as(struct cpu_preserved_as *as);
+
+int cpu_preserved_map_range(phys_addr_t pa, unsigned long va,
+                           size_t size, pgprot_t prot);
+int cpu_preserved_map_buffer(void *va, size_t size);
+
+/**
+ * arch_cpu_preserved_mpidr_to_cpu - Map an ARM64 MPIDR affinity value to a 
logical CPU ID
+ * @mpidr: Hardware MPIDR affinity value.
+ *
+ * Return: Logical CPU identifier, or -EINVAL if not found.
+ */
+int arch_cpu_preserved_mpidr_to_cpu(u64 mpidr);
+
+/**
+ * arch_cpu_preserved_is_active - Check whether any preserved CPU runtime 
mapping is active
+ *
+ * Return: %true if preserved runtime mappings are active, %false otherwise.
+ */
+bool arch_cpu_preserved_is_active(void);
+
+/**
+ * arch_cpu_preserved_switch_pgd - Switch the current preserved CPU to an 
isolated PGD
+ * @pgd_pa: Physical address of the root page table to install.
+ *
+ * This function must be placed in the __cpu_preserved_text section.
+ */
+void arch_cpu_preserved_switch_pgd(phys_addr_t pgd_pa);
 
 #else /* !CONFIG_LIVEUPDATE_CPU */
 
diff --git a/include/linux/kho/abi/cpu.h b/include/linux/kho/abi/cpu.h
index 5926be37f7a0..f75bfb4c6161 100644
--- a/include/linux/kho/abi/cpu.h
+++ b/include/linux/kho/abi/cpu.h
@@ -96,6 +96,44 @@ static_assert(offsetof(struct cpu_preserved_global_ser,
                       cpu_preserved_bitmap) % sizeof(u64) == 0,
              "cpu_preserved_bitmap must be 64-bit aligned");
 
+#define CPU_PRESERVED_AS_MAX_PGTABLE_PAGES     1024
+
+/**
+ * struct cpu_preserved_as_ser - Serialized preserved address space metadata
+ * @nr_pgtable_pages: Number of valid entries in @pgtable_pages.
+ * @reserved:         Must be zero.
+ * @pgtable_pages:    Physical addresses of all page table pages (root PGD is 
at index 0).
+ */
+struct cpu_preserved_as_ser {
+       u32 nr_pgtable_pages;
+       u32 reserved;
+       u64 pgtable_pages[CPU_PRESERVED_AS_MAX_PGTABLE_PAGES];
+} __packed;
+
+static_assert(offsetof(struct cpu_preserved_as_ser, pgtable_pages) == 8);
+
+/**
+ * struct oncore_session_ser - Serialized on-core session metadata
+ * @session_name:  LUO session name.
+ * @sess_pa:       Opaque physical address of preserved struct oncore_session
+ *                 (retained for freeing across kexec, never dereferenced).
+ * @as:            Preservation pointer to struct cpu_preserved_as_ser.
+ * @nr_cpu_words:  Number of 64-bit words in @cpus_bitmap.
+ * @reserved:      Must be zero.
+ * @cpus_bitmap:   Bitmap of physical CPUs assigned to this on-core session.
+ */
+struct oncore_session_ser {
+       char session_name[LIVEUPDATE_SESSION_NAME_LENGTH];
+       u64 sess_pa;
+       DECLARE_KHOSER_PTR(as, struct cpu_preserved_as_ser *);
+       u32 nr_cpu_words;
+       u32 reserved;
+       u64 cpus_bitmap[];
+} __packed;
+
+static_assert(offsetof(struct oncore_session_ser, cpus_bitmap) % sizeof(u64) 
== 0,
+             "cpus_bitmap must be 64-bit aligned");
+
 /**
  * struct cpu_preserved_file_ser - Per-file serialized state for preserved CPU 
fd
  * @cpu:      Logical CPU identifier.
diff --git a/kernel/liveupdate/cpu_preserve.c b/kernel/liveupdate/cpu_preserve.c
index cc27d1624d29..2f92dfee82b1 100644
--- a/kernel/liveupdate/cpu_preserve.c
+++ b/kernel/liveupdate/cpu_preserve.c
@@ -267,6 +267,8 @@ static void cpu_preserved_sync_global_ser(void)
                        (1UL << cpu_preserved_data_order) * PAGE_SIZE;
        }
        KHOSER_STORE_PTR(ser->pcpus_runtime, cpu_preserved_outgoing.pcpus_ser);
+       KHOSER_STORE_PTR(ser->transition_as,
+                        cpu_preserved_transition_as ? 
cpu_preserved_transition_as->ser : NULL);
        cpu_preserved_clean_sz(ser,
                               struct_size(ser, cpu_preserved_bitmap, 
ser->nr_cpu_words));
 }
@@ -282,6 +284,857 @@ static void cpu_preserved_free_kho(void *va, bool 
is_incoming)
                kho_unpreserve_free(va);
 }
 
+/**
+ * cpu_preserved_as_alloc_page - Allocate a page table page for @arg
+ * @arg: The struct cpu_preserved_as being populated.
+ *
+ * Page table allocator handed to the architecture page table builders.
+ *
+ * There is deliberately no alloc_page() fallback.  It would be
+ * kho_alloc_preserve() open-coded, and the only way it could differ is by
+ * ignoring the preservation error -- which would hand back an unpreserved
+ * page table page.  The orphaned core has no fault handler, so that failure
+ * is unrecoverable and must not be silent.
+ *
+ * Return: A zeroed, preserved page, or NULL.
+ */
+void *cpu_preserved_as_alloc_page(void *arg)
+{
+       struct cpu_preserved_as *as = arg;
+       void *ptr;
+
+       if (WARN_ON_ONCE(as->ser->nr_pgtable_pages >= 
ARRAY_SIZE(as->ser->pgtable_pages)))
+               return NULL;
+
+       ptr = kho_alloc_preserve(PAGE_SIZE);
+       if (IS_ERR_OR_NULL(ptr))
+               return NULL;
+
+       cpu_preserved_clean_sz(ptr, PAGE_SIZE);
+       as->ser->pgtable_pages[as->ser->nr_pgtable_pages++] = virt_to_phys(ptr);
+
+       return ptr;
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_as_alloc_page);
+
+/*
+ * Page table pages are preserved as they are allocated, but a cancelled live
+ * update unpreserves everything, so state the preservation again after every
+ * change.  Pages inherited from the previous kernel already belong to KHO.
+ */
+static int cpu_preserved_as_preserve_pgtables(struct cpu_preserved_as *as)
+{
+       unsigned int i;
+
+       if (as->is_incoming)
+               return 0;
+
+       for (i = 0; i < as->ser->nr_pgtable_pages; i++) {
+               void *p = phys_to_virt(as->ser->pgtable_pages[i]);
+               int ret;
+
+               cpu_preserved_clean_sz(p, PAGE_SIZE);
+               ret = kho_preserve_pages(virt_to_page(p), 1);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void cpu_preserved_as_unpreserve_pgtables(struct cpu_preserved_as *as)
+{
+       for (unsigned int i = 0; i < as->ser->nr_pgtable_pages; i++)
+               
kho_unpreserve_pages(virt_to_page(phys_to_virt(as->ser->pgtable_pages[i])), 1);
+}
+
+/**
+ * cpu_preserved_as_map - Map one range into one preserved address space
+ * @as: Address space to map into.
+ * @pa: Physical address of the range.
+ * @va: Virtual address the range must appear at.
+ * @size: Size of the range in bytes.
+ * @prot: Protection to apply.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int cpu_preserved_as_map(struct cpu_preserved_as *as, phys_addr_t pa,
+                        unsigned long va, size_t size, pgprot_t prot)
+{
+       int ret;
+
+       guard(mutex)(&cpu_preserved_as_map_lock);
+
+       ret = arch_cpu_preserved_as_map(as, pa, va, size, prot);
+       if (ret)
+               return ret;
+
+       ret = cpu_preserved_as_preserve_pgtables(as);
+       if (ret)
+               return ret;
+
+       arch_cpu_preserved_as_flush_tlb();
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_as_map);
+
+static int cpu_preserved_init_runtime_buffer(void);
+
+static int cpu_preserved_as_map_buf(struct cpu_preserved_as *as,
+                                   void *va, size_t size)
+{
+       if (!va || !size)
+               return 0;
+
+       return cpu_preserved_as_map(as, virt_to_phys(va), (unsigned long)va,
+                                   size, PAGE_KERNEL);
+}
+
+static int cpu_preserved_as_map_runtime(struct cpu_preserved_as *as)
+{
+       unsigned long text_start = (unsigned long)__cpu_preserved_text_start;
+       unsigned long data_start = (unsigned long)__cpu_preserved_data_start;
+       size_t text_sz = (unsigned long)__cpu_preserved_text_end - text_start;
+       size_t data_sz = (unsigned long)__cpu_preserved_data_end - data_start;
+       struct cpu_preserved_state *outgoing = &cpu_preserved_outgoing;
+       int cpu, ret;
+
+       ret = cpu_preserved_as_map(as, cpu_preserved_get_text_pa(),
+                                  text_start, text_sz, PAGE_KERNEL_ROX);
+       if (ret)
+               return ret;
+
+       ret = cpu_preserved_as_map(as, cpu_preserved_get_data_pa(),
+                                  data_start, data_sz, PAGE_KERNEL);
+       if (ret)
+               return ret;
+
+       ret = cpu_preserved_as_map_buf(as, outgoing->pcpus_ser,
+                                      sizeof(*outgoing->pcpus_ser) * 
nr_cpu_ids);
+       if (ret)
+               return ret;
+
+       ret = cpu_preserved_as_map_buf(as, outgoing->pcpus,
+                                      sizeof(*outgoing->pcpus) * nr_cpu_ids);
+       if (ret)
+               return ret;
+
+       for_each_cpu(cpu, &outgoing->mask) {
+               phys_addr_t spa = outgoing->pcpus[cpu].stack_pa;
+
+               if (!spa)
+                       continue;
+               ret = cpu_preserved_as_map_buf(as, phys_to_virt(spa),
+                                              CPU_PRESERVED_STACK_SIZE);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+/**
+ * cpu_preserved_as_create - Build a new preserved address space
+ *
+ * Allocates a root page table, maps the preserved text and data into it, and
+ * publishes it so that subsequent cpu_preserved_map_range() calls reach it.
+ *
+ * Return: The new address space, or an ERR_PTR() on failure.
+ */
+struct cpu_preserved_as *cpu_preserved_as_create(void)
+{
+       struct cpu_preserved_as *as;
+       int ret;
+
+       ret = cpu_preserved_init_runtime_buffer();
+       if (ret)
+               return ERR_PTR(ret);
+
+       as = kzalloc_obj(*as, GFP_KERNEL);
+       if (!as)
+               return ERR_PTR(-ENOMEM);
+       INIT_LIST_HEAD(&as->node);
+
+       as->ser = kho_alloc_preserve(sizeof(*as->ser));
+       if (IS_ERR(as->ser)) {
+               ret = PTR_ERR(as->ser);
+               kfree(as);
+               return ERR_PTR(ret);
+       }
+       memset(as->ser, 0, sizeof(*as->ser));
+
+       as->pgd = cpu_preserved_as_alloc_page(as);
+       if (!as->pgd) {
+               ret = -ENOMEM;
+               goto err;
+       }
+       as->pgd_pa = virt_to_phys(as->pgd);
+
+       ret = cpu_preserved_as_map_runtime(as);
+       if (ret)
+               goto err;
+
+       scoped_guard(mutex, &cpu_preserved_as_list_lock)
+               list_add_tail(&as->node, &cpu_preserved_as_list);
+
+       return as;
+
+err:
+       cpu_preserved_as_destroy(as);
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_as_create);
+
+/**
+ * cpu_preserved_as_destroy - Tear down a preserved address space
+ * @as: Address space to release.  NULL is accepted and does nothing.
+ */
+void cpu_preserved_as_destroy(struct cpu_preserved_as *as)
+{
+       if (!as)
+               return;
+
+       scoped_guard(mutex, &cpu_preserved_as_list_lock)
+               list_del_init(&as->node);
+
+       if (as->ser) {
+               scoped_guard(mutex, &cpu_preserved_as_map_lock) {
+                       for (unsigned int i = 0; i < as->ser->nr_pgtable_pages; 
i++) {
+                               void *va = 
phys_to_virt(as->ser->pgtable_pages[i]);
+
+                               cpu_preserved_free_kho(va, as->is_incoming);
+                       }
+               }
+               cpu_preserved_free_kho(as->ser, as->is_incoming);
+       }
+
+       kfree(as);
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_as_destroy);
+
+/**
+ * cpu_preserved_as_adopt - Take over an address space from the previous kernel
+ * @ser: Address space serialization descriptor recovered from preserved 
memory.
+ *
+ * The page tables are left exactly as the outgoing kernel built them --
+ * preserved CPUs are running out of them right now -- but the list linkage is
+ * stale and has to be rebuilt, and the pages now belong to KHO rather than to
+ * this kernel's allocator.
+ */
+struct cpu_preserved_as *cpu_preserved_as_adopt(struct cpu_preserved_as_ser 
*ser)
+{
+       struct cpu_preserved_as *as;
+
+       if (!ser)
+               return NULL;
+
+       as = kzalloc_obj(*as, GFP_KERNEL);
+       if (!as)
+               return NULL;
+
+       as->ser = ser;
+       as->pgd_pa = ser->nr_pgtable_pages ? ser->pgtable_pages[0] : 0;
+       as->pgd = phys_to_virt(as->pgd_pa);
+       as->is_incoming = true;
+       INIT_LIST_HEAD(&as->node);
+
+       guard(mutex)(&cpu_preserved_as_list_lock);
+       list_add_tail(&as->node, &cpu_preserved_as_list);
+
+       return as;
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_as_adopt);
+
+static void cpu_preserved_preserve_runtime_buffer(void)
+{
+       if (cpu_preserved_runtime_preserved)
+               return;
+
+       /*
+        * This is the text the orphaned core executes and the data it reads
+        * after the kexec.  If either cannot be preserved there is nothing to
+        * hand over, so do not claim the runtime is preserved.
+        */
+       if (WARN_ON_ONCE(kho_preserve_pages(cpu_preserved_text_pages,
+                                           1 << cpu_preserved_text_order)))
+               return;
+       if (WARN_ON_ONCE(kho_preserve_pages(cpu_preserved_data_pages,
+                                           1 << cpu_preserved_data_order)))
+               return;
+
+       
WARN_ON_ONCE(kho_preserve_pages(virt_to_page(cpu_preserved_transition_as->ser),
+                                       1 << 
get_order(sizeof(*cpu_preserved_transition_as->ser))));
+
+       scoped_guard(mutex, &cpu_preserved_as_map_lock)
+               
WARN_ON_ONCE(cpu_preserved_as_preserve_pgtables(cpu_preserved_transition_as));
+
+       cpu_preserved_runtime_preserved = true;
+}
+
+static void cpu_preserved_unpreserve_runtime_buffer(void)
+{
+       if (!cpu_preserved_runtime_preserved)
+               return;
+
+       kho_unpreserve_pages(cpu_preserved_text_pages,
+                            1 << cpu_preserved_text_order);
+       kho_unpreserve_pages(cpu_preserved_data_pages,
+                            1 << cpu_preserved_data_order);
+       kho_unpreserve_pages(virt_to_page(cpu_preserved_transition_as->ser),
+                            1 << 
get_order(sizeof(*cpu_preserved_transition_as->ser)));
+
+       scoped_guard(mutex, &cpu_preserved_as_map_lock)
+               
cpu_preserved_as_unpreserve_pgtables(cpu_preserved_transition_as);
+
+       cpu_preserved_runtime_preserved = false;
+}
+
+/**
+ * cpu_preserved_init_runtime_buffer - Allocate execution buffer outside 
Scratch
+ *
+ * The compiled __cpu_preserved_text and __cpu_preserved_data sections are
+ * part of the host kernel binary image. During a host kexec live update, the
+ * memory range occupied by the current kernel is designated as KHO Scratch
+ * memory to allow the incoming kernel to be placed and unpacked. By 
definition,
+ * Scratch memory must not contain preserved memory, as the incoming kernel
+ * will overwrite Scratch during boot.
+ *
+ * Preserving the compiled text and data sections in-place would create a
+ * conflict where preserved memory overlaps Scratch, triggering handover
+ * failures or memory corruption when the incoming kernel overwrites the old
+ * kernel text while preserved physical CPUs are still executing Caretaker 
loops
+ * on their cores.
+ *
+ * To avoid this, we dynamically allocate dedicated text and data buffer pages
+ * from free memory (outside Scratch) via alloc_pages(GFP_KERNEL), copy the
+ * compiled text and data into them, remap the virtual addresses in the page
+ * tables to point to these newly allocated pages, and preserve only these
+ * external pages with KHO. Preserved CPUs execute out of these external pages,
+ * allowing the incoming kernel to freely overwrite Scratch.
+ *
+ * Return: 0 on success, or negative error code on allocation/setup failure.
+ */
+static int cpu_preserved_init_runtime_buffer(void)
+{
+       size_t text_size = (unsigned long)__cpu_preserved_text_end -
+                          (unsigned long)__cpu_preserved_text_start;
+       size_t data_size = (unsigned long)__cpu_preserved_data_end -
+                          (unsigned long)__cpu_preserved_data_start;
+       unsigned int text_nr_pages = DIV_ROUND_UP(text_size, PAGE_SIZE);
+       unsigned int data_nr_pages = DIV_ROUND_UP(data_size, PAGE_SIZE);
+       int ret;
+
+       if (cpu_preserved_text_pages) {
+               if (cpu_preserved_transition_as)
+                       cpu_preserved_preserve_runtime_buffer();
+               return 0;
+       }
+
+       cpu_preserved_text_order = get_order(text_size);
+       cpu_preserved_text_pages = alloc_pages(GFP_KERNEL, 
cpu_preserved_text_order);
+       if (!cpu_preserved_text_pages)
+               return -ENOMEM;
+
+       cpu_preserved_data_order = get_order(data_size);
+       cpu_preserved_data_pages = alloc_pages(GFP_KERNEL, 
cpu_preserved_data_order);
+       if (!cpu_preserved_data_pages) {
+               __free_pages(cpu_preserved_text_pages, 
cpu_preserved_text_order);
+               cpu_preserved_text_pages = NULL;
+               return -ENOMEM;
+       }
+
+       memcpy(page_address(cpu_preserved_text_pages),
+              __cpu_preserved_text_start, text_size);
+       memcpy(page_address(cpu_preserved_data_pages),
+              __cpu_preserved_data_start, data_size);
+
+       ret = arch_cpu_preserved_setup_buffer(cpu_preserved_text_pages,
+                                             text_nr_pages,
+                                             cpu_preserved_data_pages,
+                                             data_nr_pages);
+       if (ret)
+               goto err_free;
+
+       /*
+        * The address space a preserved CPU parks in when its workload has not
+        * given it one of its own.  It has to exist before anything can be
+        * mapped for preserved CPUs, so build it here and let the architecture
+        * record it where preserved text can reach it after the kexec.
+        */
+       cpu_preserved_transition_as = cpu_preserved_as_create();
+       if (IS_ERR(cpu_preserved_transition_as)) {
+               ret = PTR_ERR(cpu_preserved_transition_as);
+               cpu_preserved_transition_as = NULL;
+               goto err_free;
+       }
+       arch_cpu_preserved_set_transition_as(cpu_preserved_transition_as);
+
+       cpu_preserved_preserve_runtime_buffer();
+       return 0;
+
+err_free:
+       __free_pages(cpu_preserved_data_pages, cpu_preserved_data_order);
+       __free_pages(cpu_preserved_text_pages, cpu_preserved_text_order);
+       cpu_preserved_data_pages = NULL;
+       cpu_preserved_text_pages = NULL;
+       return ret;
+}
+
+/**
+ * cpu_preserved_map_range - Map a physical range into every preserved address 
space
+ * @pa: Physical address
+ * @va: Virtual address
+ * @size: Size in bytes
+ * @prot: Page protection flags
+ *
+ * Anything a preserved CPU may touch has to be reachable from whichever
+ * address space it ends up running in, and which one that is depends on the
+ * workload, so map it into all of them.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int cpu_preserved_map_range(phys_addr_t pa, unsigned long va,
+                           size_t size, pgprot_t prot)
+{
+       struct cpu_preserved_as *as;
+       int ret;
+
+       guard(mutex)(&cpu_preserved_as_list_lock);
+
+       list_for_each_entry(as, &cpu_preserved_as_list, node) {
+               ret = cpu_preserved_as_map(as, pa, va, size, prot);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_map_range);
+
+/**
+ * cpu_preserved_map_buffer - Map a virtual buffer into transition page tables
+ * @va: Virtual address in kernel direct map
+ * @size: Size in bytes
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int cpu_preserved_map_buffer(void *va, size_t size)
+{
+       if (!va || !size)
+               return 0;
+       return cpu_preserved_map_range(virt_to_phys(va),
+                                      (unsigned long)va,
+                                      size, PAGE_KERNEL);
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_map_buffer);
+
+/**
+ * cpu_is_preserved - Check whether a CPU is currently preserved
+ * @cpu: Logical CPU identifier.
+ *
+ * Return: True if @cpu is currently preserved, false otherwise.
+ */
+bool __cpu_preserved_text cpu_is_preserved(int cpu)
+{
+       if ((unsigned int)cpu >= CONFIG_NR_CPUS)
+               return false;
+       cpu_preserved_inval(&cpu_preserved_mask);
+       return cpumask_test_cpu(cpu, &cpu_preserved_mask);
+}
+EXPORT_SYMBOL_GPL(cpu_is_preserved);
+
+static bool cpu_preserved_is_incoming(int cpu)
+{
+       if ((unsigned int)cpu >= CONFIG_NR_CPUS)
+               return false;
+       return cpumask_test_cpu(cpu, &cpu_preserved_incoming.mask);
+}
+
+static struct cpu_preserved_pcpu_ser * __cpu_preserved_text 
cpu_preserved_get_pcpu_ser(int cpu)
+{
+       struct cpu_preserved_pcpu_ser *pcpus;
+
+       if ((unsigned int)cpu >= CONFIG_NR_CPUS)
+               return NULL;
+
+       cpu_preserved_inval(&cpu_preserved_pcpus_va);
+       pcpus = READ_ONCE(cpu_preserved_pcpus_va);
+       return pcpus ? &pcpus[cpu] : NULL;
+}
+
+static struct cpu_preserved_pcpu * __cpu_preserved_text 
cpu_preserved_get_pcpu(int cpu)
+{
+       struct cpu_preserved_pcpu *pcpus;
+
+       if ((unsigned int)cpu >= CONFIG_NR_CPUS)
+               return NULL;
+
+       cpu_preserved_inval(&cpu_preserved_host_pcpus_va);
+       pcpus = READ_ONCE(cpu_preserved_host_pcpus_va);
+       return pcpus ? &pcpus[cpu] : NULL;
+}
+
+/*
+ * The preserved stack is handed over by physical address: the same page need
+ * not be mapped at the same virtual address by two different kernels, so each
+ * side derives its own VA rather than sharing one.
+ */
+static void * __cpu_preserved_text
+cpu_preserved_stack_va(int cpu)
+{
+       struct cpu_preserved_pcpu *pcpu = cpu_preserved_get_pcpu(cpu);
+       phys_addr_t pa;
+
+       if (!pcpu)
+               return NULL;
+
+       cpu_preserved_inval(&pcpu->stack_pa);
+       pa = READ_ONCE(pcpu->stack_pa);
+       if (!pa)
+               return NULL;
+
+       return phys_to_virt(pa);
+}
+
+/**
+ * cpu_preserved_get_pgd - Get root page table physical address for a 
preserved CPU
+ * @cpu: Logical CPU identifier.
+ *
+ * Return: Root PGD physical address assigned to @cpu, or 0 if not set.
+ */
+phys_addr_t __cpu_preserved_text cpu_preserved_get_pgd(int cpu)
+{
+       struct cpu_preserved_pcpu *pcpu = cpu_preserved_get_pcpu(cpu);
+
+       if (!pcpu)
+               return 0;
+
+       cpu_preserved_inval(&pcpu->pgd_pa);
+       return READ_ONCE(pcpu->pgd_pa);
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_get_pgd);
+
+/**
+ * cpu_get_preserved_mask - Get the mask of all currently preserved CPUs
+ *
+ * Return: Read-only pointer to the cpumask of preserved CPUs.
+ */
+const struct cpumask *cpu_get_preserved_mask(void)
+{
+       return &cpu_preserved_mask;
+}
+EXPORT_SYMBOL_GPL(cpu_get_preserved_mask);
+
+/**
+ * cpu_preserved_set_dead - Mark a preserved CPU as fully dead/stopped
+ * @cpu: Logical CPU identifier.
+ *
+ * Publishes %CPU_PRESERVED_DEAD in the KHO-preserved per-CPU state block when
+ * @cpu finishes exiting the preserved parking loop.
+ */
+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);
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_set_dead);
+
+static void cpu_signal_exit(int cpu)
+{
+       struct cpu_preserved_pcpu_ser *ser = cpu_preserved_get_pcpu_ser(cpu);
+       struct cpu_preserved_pcpu *pcpu = cpu_preserved_get_pcpu(cpu);
+
+       if (ser) {
+               WRITE_ONCE(ser->workload, CPU_PRESERVED_EXITING);
+               cpu_preserved_clean(ser);
+       }
+       if (pcpu) {
+               WRITE_ONCE(pcpu->entry_fn, NULL);
+               WRITE_ONCE(pcpu->entry_data, NULL);
+               cpu_preserved_clean(pcpu);
+       }
+}
+
+/**
+ * cpu_preserved_should_exit - Check if a running preserved workload should 
exit
+ * @cpu: Logical CPU identifier.
+ *
+ * Polled by workloads executing on preserved physical CPUs to detect when the
+ * host kernel has requested workload detachment or CPU reclamation.
+ *
+ * Return: %true if the workload on @cpu must exit back to the park loop,
+ *         %false otherwise.
+ */
+bool __cpu_preserved_text cpu_preserved_should_exit(int cpu)
+{
+       struct cpu_preserved_pcpu_ser *ser = cpu_preserved_get_pcpu_ser(cpu);
+
+       if (!ser)
+               return false;
+
+       cpu_preserved_inval(ser);
+       return READ_ONCE(ser->workload) != CPU_PRESERVED_WORKLOAD;
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_should_exit);
+
+/**
+ * cpu_preserved_attach_workload - Attach & start workload execution on core
+ * @cpu: Logical CPU identifier.
+ * @entry_fn: Workload callback to execute repeatedly on the physical core.
+ * @data: Opaque argument passed to @entry_fn.
+ *
+ * Transitions @cpu from idle parking to executing @entry_fn(@data) on the
+ * physical core, and kicks the CPU to begin execution immediately.
+ *
+ * Return: 0 on success, -EINVAL if @cpu is invalid, -ENODEV if not preserved,
+ * or -EBUSY if a workload is already attached.
+ */
+int cpu_preserved_attach_workload(int cpu,
+                                 void (*entry_fn)(void *data), void *data)
+{
+       struct cpu_preserved_pcpu_ser *ser;
+       struct cpu_preserved_pcpu *pcpu;
+
+       if ((unsigned int)cpu >= nr_cpu_ids)
+               return -EINVAL;
+
+       mutex_lock(&cpu_preserved_lock);
+       if (!cpumask_test_cpu(cpu, &cpu_preserved_outgoing.mask)) {
+               mutex_unlock(&cpu_preserved_lock);
+               return -ENODEV;
+       }
+
+       ser = &cpu_preserved_outgoing.pcpus_ser[cpu];
+       pcpu = &cpu_preserved_outgoing.pcpus[cpu];
+       if (ser->workload != CPU_PRESERVED_PARKED || pcpu->entry_fn) {
+               mutex_unlock(&cpu_preserved_lock);
+               return -EBUSY;
+       }
+
+       WRITE_ONCE(pcpu->entry_data, data);
+       WRITE_ONCE(pcpu->entry_fn, entry_fn);
+       WRITE_ONCE(ser->workload, CPU_PRESERVED_WORKLOAD);
+
+       cpu_preserved_clean(pcpu);
+       cpu_preserved_clean(ser);
+
+       arch_cpu_preserved_kick(cpu);
+       mutex_unlock(&cpu_preserved_lock);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_attach_workload);
+
+/**
+ * cpu_preserved_detach_workload - Detach workload and return core to idle park
+ * @cpu: Logical CPU identifier.
+ *
+ * Clears any attached workload on @cpu, returning the core to the default
+ * idle parking loop.
+ *
+ * Return: 0 on success, -EINVAL if @cpu is invalid, or -ENODEV if
+ * not preserved.
+ */
+int cpu_preserved_detach_workload(int cpu)
+{
+       struct cpu_preserved_pcpu_ser *ser;
+       struct cpu_preserved_pcpu *pcpu;
+
+       if ((unsigned int)cpu >= nr_cpu_ids)
+               return -EINVAL;
+
+       mutex_lock(&cpu_preserved_lock);
+       if (!cpumask_test_cpu(cpu, &cpu_preserved_mask)) {
+               mutex_unlock(&cpu_preserved_lock);
+               return -ENODEV;
+       }
+
+       ser = cpu_preserved_get_pcpu_ser(cpu);
+       pcpu = cpu_preserved_get_pcpu(cpu);
+       if (!ser) {
+               mutex_unlock(&cpu_preserved_lock);
+               return -ENODEV;
+       }
+
+       if (READ_ONCE(ser->workload) == CPU_PRESERVED_WORKLOAD) {
+               WRITE_ONCE(ser->workload, CPU_PRESERVED_PARKED);
+               cpu_preserved_clean(ser);
+       }
+       if (pcpu) {
+               WRITE_ONCE(pcpu->entry_fn, NULL);
+               WRITE_ONCE(pcpu->entry_data, NULL);
+               cpu_preserved_clean(pcpu);
+       }
+
+       arch_cpu_preserved_kick(cpu);
+       mutex_unlock(&cpu_preserved_lock);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_detach_workload);
+
+/**
+ * cpu_preserved_set_workload_context - Set workload context and root page 
table
+ * @cpu: Logical CPU identifier.
+ * @ctx: Opaque owning workload context pointer.
+ * @pgd_pa: Physical address of workload root page table (or 0 for default).
+ */
+void cpu_preserved_set_workload_context(int cpu, void *ctx, phys_addr_t pgd_pa)
+{
+       struct cpu_preserved_stack_context *sctx;
+       struct cpu_preserved_pcpu *pcpu;
+
+       if (cpu < 0 || cpu >= nr_cpu_ids)
+               return;
+
+       mutex_lock(&cpu_preserved_lock);
+       pcpu = cpu_preserved_get_pcpu(cpu);
+       sctx = cpu_preserved_stack_va(cpu);
+       /*
+        * Validate the signature before writing through it.  The read side
+        * (cpu_preserved_get_stack_context()) has always done this; this path
+        * did not, so a stale or not-yet-initialised stack_pa would have been
+        * scribbled over.
+        */
+       if (sctx && sctx->magic == CPU_PRESERVED_STACK_MAGIC) {
+               sctx->workload_context = (u64)(uintptr_t)ctx;
+               sctx->session_pgd_pa = pgd_pa;
+               pcpu->pgd_pa = pgd_pa;
+       }
+       mutex_unlock(&cpu_preserved_lock);
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_set_workload_context);
+
+#define CPU_WAIT_DEAD_TIMEOUT_US       20000000
+#define CPU_WAIT_DEAD_STEP_US          100
+#define CPU_WAIT_DEAD_KICK_STEPS       50
+
+/**
+ * cpu_wait_dead - Wait for a preserved CPU to exit the park loop and power 
down
+ * @cpu: Logical CPU identifier.
+ *
+ * Polls the KHO-preserved per-CPU state block until @cpu observes
+ * %CPU_PRESERVED_EXITING, leaves cpu_preserved_park_loop(), and publishes
+ * %CPU_PRESERVED_DEAD, periodically sending an IPI kick to wake it from any
+ * low-power wait state.  Once %CPU_PRESERVED_DEAD is observed, invokes
+ * arch_cpu_preserved_wait_dead() to wait for final hardware teardown.
+ *
+ * Return: 0 on success, -ENODEV if @cpu has no preserved state block, or
+ *         -ETIMEDOUT if @cpu did not reach %CPU_PRESERVED_DEAD within
+ *         %CPU_WAIT_DEAD_TIMEOUT_US microseconds.
+ */
+static int cpu_wait_dead(int cpu)
+{
+       struct cpu_preserved_pcpu_ser *ser = cpu_preserved_get_pcpu_ser(cpu);
+       int i;
+
+       if (!ser)
+               return -ENODEV;
+
+       for (i = 0; i < CPU_WAIT_DEAD_TIMEOUT_US / CPU_WAIT_DEAD_STEP_US; i++) {
+               cpu_preserved_inval(ser);
+               if (READ_ONCE(ser->workload) == CPU_PRESERVED_DEAD) {
+                       arch_cpu_preserved_wait_dead(cpu);
+                       return 0;
+               }
+               if (i % CPU_WAIT_DEAD_KICK_STEPS == 0)
+                       arch_cpu_preserved_kick(cpu);
+               udelay(CPU_WAIT_DEAD_STEP_US);
+       }
+
+       pr_err("Timed out waiting for preserved cpu %d to stop (workload=%u)\n",
+              cpu, READ_ONCE(ser->workload));
+       return -ETIMEDOUT;
+}
+
+static void __cpu_preserved_text
+cpu_preserved_run_workload(struct cpu_preserved_pcpu_ser *ser,
+                          struct cpu_preserved_pcpu *pcpu)
+{
+       void (*fn)(void *data);
+       void *arg;
+
+       if (!pcpu)
+               return;
+
+       cpu_preserved_inval(pcpu);
+       fn = READ_ONCE(pcpu->entry_fn);
+       arg = READ_ONCE(pcpu->entry_data);
+       if (fn)
+               fn(arg);
+
+       cpu_preserved_inval(ser);
+       if (cmpxchg(&ser->workload, CPU_PRESERVED_WORKLOAD,
+                   CPU_PRESERVED_PARKED) == CPU_PRESERVED_WORKLOAD)
+               cpu_preserved_clean(ser);
+}
+STACK_FRAME_NON_STANDARD(cpu_preserved_run_workload);
+
+/**
+ * cpu_preserved_park_loop - Generic execution loop for a parked preserved CPU
+ * @cpu: Logical CPU identifier.
+ *
+ * Core execution loop executed on the dedicated preserved stack in
+ * __cpu_preserved_text.  Waits in low-power park state, dispatches attached
+ * workload callbacks, and exits when the CPU is unpreserved and reclaimed.
+ */
+void __cpu_preserved_text cpu_preserved_park_loop(int cpu)
+{
+       struct cpu_preserved_pcpu_ser *ser = cpu_preserved_get_pcpu_ser(cpu);
+       struct cpu_preserved_pcpu *pcpu = cpu_preserved_get_pcpu(cpu);
+
+       if (!ser)
+               return;
+
+       WRITE_ONCE(ser->workload, CPU_PRESERVED_PARKED);
+       cpu_preserved_clean(ser);
+
+       arch_cpu_preserved_park_init(cpu);
+
+       for (;;) {
+               cpu_preserved_inval(ser);
+               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);
+                       break;
+               default:
+                       arch_cpu_preserved_park_wait();
+                       break;
+               }
+       }
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_park_loop);
+STACK_FRAME_NON_STANDARD(cpu_preserved_park_loop);
+
+/**
+ * cpu_preserved_park - Main execution and parking loop for a preserved CPU
+ * @cpu: Logical CPU identifier of the calling core.
+ *
+ * Called on the physical CPU being offlined/preserved. Enters a dedicated
+ * low-power parking loop in preserved memory, repeatedly executing any
+ * attached workload callback, until signaled to exit upon unpreservation.
+ */
+void cpu_preserved_park(int cpu)
+{
+       void *stack = cpu_preserved_stack_va(cpu);
+
+       if (stack) {
+               unsigned long top_of_stack = (unsigned long)stack +
+                       CPU_PRESERVED_STACK_SIZE - CPU_PRESERVED_STACK_HEADROOM;
+               arch_cpu_preserved_park_on_stack(cpu, top_of_stack);
+       } else {
+               cpu_preserved_park_loop(cpu);
+               arch_cpu_preserved_park_finish(cpu);
+       }
+}
+EXPORT_SYMBOL_GPL(cpu_preserved_park);
+
 static void cpu_preserved_free_stack(phys_addr_t stack_pa, bool is_incoming)
 {
        if (stack_pa)
@@ -346,6 +1199,106 @@ static void __cpu_unpreserve_locked(unsigned int cpu)
        cpu_preserved_sync_global_ser();
 }
 
+static int cpu_preserved_init_outgoing(void)
+{
+       struct cpu_preserved_state *outgoing = &cpu_preserved_outgoing;
+       size_t ser_sz = sizeof(*outgoing->pcpus_ser) * nr_cpu_ids;
+       int ret;
+
+       if (outgoing->pcpus_ser)
+               return 0;
+
+       ret = cpu_preserved_init_runtime_buffer();
+       if (ret)
+               return ret;
+
+       outgoing->pcpus = kcalloc(nr_cpu_ids, sizeof(*outgoing->pcpus),
+                                 GFP_KERNEL);
+       if (!outgoing->pcpus)
+               return -ENOMEM;
+
+       outgoing->pcpus_ser = kho_alloc_preserve(ser_sz);
+       if (IS_ERR(outgoing->pcpus_ser)) {
+               ret = PTR_ERR(outgoing->pcpus_ser);
+               kfree(outgoing->pcpus);
+               outgoing->pcpus = NULL;
+               outgoing->pcpus_ser = NULL;
+               return ret;
+       }
+       memset(outgoing->pcpus_ser, 0, ser_sz);
+
+       WRITE_ONCE(cpu_preserved_pcpus_va, outgoing->pcpus_ser);
+       WRITE_ONCE(cpu_preserved_host_pcpus_va, outgoing->pcpus);
+
+       cpu_preserved_map_buffer(outgoing->pcpus_ser, ser_sz);
+       cpu_preserved_map_buffer(outgoing->pcpus,
+                                sizeof(*outgoing->pcpus) * nr_cpu_ids);
+
+       cpu_preserved_clean(&cpu_preserved_pcpus_va);
+       cpu_preserved_clean(&cpu_preserved_host_pcpus_va);
+
+       return 0;
+}
+
+static int cpu_preserve(unsigned int cpu)
+{
+       struct cpu_preserved_state *outgoing = &cpu_preserved_outgoing;
+       struct cpu_preserved_stack_context *sctx;
+       struct cpu_preserved_pcpu_ser *ser;
+       struct cpu_preserved_pcpu *pcpu;
+       void *stack;
+       int ret;
+
+       stack = kho_alloc_preserve(CPU_PRESERVED_STACK_SIZE);
+       if (IS_ERR(stack))
+               return PTR_ERR(stack);
+
+       sctx = stack;
+       sctx->magic = CPU_PRESERVED_STACK_MAGIC;
+       sctx->cpu = cpu;
+
+       scoped_guard(mutex, &cpu_preserved_lock) {
+               if (cpu_is_preserved(cpu)) {
+                       kho_unpreserve_free(stack);
+                       return -EBUSY;
+               }
+
+               ret = cpu_preserved_init_outgoing();
+               if (ret) {
+                       kho_unpreserve_free(stack);
+                       return ret;
+               }
+
+               cpumask_set_cpu(cpu, &outgoing->mask);
+               cpumask_set_cpu(cpu, &cpu_preserved_mask);
+               cpu_preserved_clean(&cpu_preserved_mask);
+
+               ser = &outgoing->pcpus_ser[cpu];
+               pcpu = &outgoing->pcpus[cpu];
+               WRITE_ONCE(ser->workload, CPU_PRESERVED_PARKED);
+               pcpu->stack_pa = virt_to_phys(stack);
+               cpu_preserved_map_buffer(stack, CPU_PRESERVED_STACK_SIZE);
+               pcpu->pgd_pa = cpu_preserved_transition_as->pgd_pa;
+               WRITE_ONCE(pcpu->entry_fn, NULL);
+               WRITE_ONCE(pcpu->entry_data, NULL);
+               cpu_preserved_sync_global_ser();
+       }
+
+       if (cpu_online(cpu)) {
+               ret = remove_cpu(cpu);
+               if (ret < 0) {
+                       pr_err("Failed to offline preserved cpu %u: %d\n",
+                              cpu, ret);
+                       scoped_guard(mutex, &cpu_preserved_lock)
+                               __cpu_unpreserve_locked(cpu);
+                       return ret;
+               }
+       }
+
+       set_cpu_present(cpu, false);
+       return 0;
+}
+
 /**
  * cpu_unpreserve - Unpreserve a physical CPU and restore it to online state
  * @cpu: Logical CPU identifier.
@@ -488,6 +1441,7 @@ static int cpu_preserved_flb_retrieve(struct 
liveupdate_flb_op_args *argp)
 
 static void cpu_preserved_flb_finish(struct liveupdate_flb_op_args *argp)
 {
+       struct cpu_preserved_as_ser *trans_as;
        struct cpu_preserved_global_ser *ser;
 
        if (!argp->obj)
@@ -495,6 +1449,10 @@ static void cpu_preserved_flb_finish(struct 
liveupdate_flb_op_args *argp)
 
        ser = argp->obj;
 
+       trans_as = KHOSER_LOAD_PTR(ser->transition_as);
+       if (trans_as)
+               cpu_preserved_as_destroy(cpu_preserved_as_adopt(trans_as));
+
        scoped_guard(mutex, &cpu_preserved_lock) {
                if (cpu_preserved_incoming.pcpus_ser) {
                        kho_restore_free(cpu_preserved_incoming.pcpus_ser);
-- 
2.55.0.1082.g2b9226bbc0-goog


Reply via email to