Merge tag 'arm64-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux

Pull arm64 fixes from Will Deacon:

 - Fix crash when using SMT hotplug on ACPI systems in conjunction with
   maxcpus=

 - Fix 30% kswapd performance regression introduced by C1-Pro SME
   erratum workaround

 - Fix TLB over-invalidation regression during memory hotplug

 - Fix incorrect encoding of FEAT_BWE2 value in ID_AA64DFR2_EL1.BWE

 - Typo fixes in the arm64 selftests

* tag 'arm64-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux:
  selftests/arm64: fix spelling errors in comments
  arm64/sysreg: Fix BWE field encoding in ID_AA64DFR2_EL1
  arm64/mm: Optimize TLB flush in unmap_hotplug_[pmd|pud]_range()
  arm64: Avoid eager DVMSync reclaim batches with C1-Pro SME erratum
  cpu/hotplug: Fix NULL kobject warning in cpuhp_smt_enable()
  arm64: smp: Fix hot-unplug tearing by forcing unregistration
This commit is contained in:
Linus Torvalds
2026-07-10 09:59:55 -07:00
11 changed files with 61 additions and 118 deletions

View File

@@ -47,11 +47,12 @@ ever have can be described at boot. There are no power-domain considerations
as such devices are emulated.
CPU Hotplug on virtual systems is supported. It is distinct from physical
CPU Hotplug as all resources are described as ``present``, but CPUs may be
marked as disabled by firmware. Only the CPU's online/offline behaviour is
influenced by firmware. An example is where a virtual machine boots with a
single CPU, and additional CPUs are added once a cloud orchestrator deploys
the workload.
CPU Hotplug as all vCPU resources are statically described in the firmware
configuration tables (e.g. MADT), meaning their maximum possible count is
known at boot. However, vCPUs that are not enabled at boot are not marked
as ``present`` by the kernel until they are hotplugged. An example is where
a virtual machine boots with a single CPU, and additional CPUs are added
once a cloud orchestrator deploys the workload.
For a virtual machine, the VMM (e.g. Qemu) plays the part of firmware.
@@ -60,16 +61,19 @@ brought online. Firmware can enforce its policy via PSCI's return codes. e.g.
``DENIED``.
The ACPI tables must describe all the resources of the virtual machine. CPUs
that firmware wishes to disable either from boot (or later) should not be
``enabled`` in the MADT GICC structures, but should have the ``online capable``
bit set, to indicate they can be enabled later. The boot CPU must be marked as
``enabled``. The 'always on' GICR structure must be used to describe the
redistributors.
that are hot-pluggable must have the ``online capable`` bit set and the
``enabled`` bit cleared in the MADT GICC structures to indicate they can be
enabled later. The boot CPU must be marked as ``enabled`` with its
``online capable`` bit cleared. The 'always on' GICR structure must be used
to describe the redistributors.
CPUs described as ``online capable`` but not ``enabled`` can be set to enabled
by the DSDT's Processor object's _STA method. On virtual systems the _STA method
must always report the CPU as ``present``. Changes to the firmware policy can
be notified to the OS via device-check or eject-request.
must always set the ``ACPI_STA_DEVICE_PRESENT`` bit, while toggling the
``ACPI_STA_DEVICE_ENABLED`` bit to reflect its plug status. The kernel will
then dynamically mark the vCPU as ``present`` within the OS when the
``ACPI_STA_DEVICE_ENABLED`` bit becomes set during hot-add. Changes to the
firmware policy can be notified to the OS via device-check or eject-request.
CPUs described as ``enabled`` in the static table, should not have their _STA
modified dynamically by firmware. Soft-restart features such as kexec will

View File

@@ -2,17 +2,11 @@
#ifndef _ARCH_ARM64_TLBBATCH_H
#define _ARCH_ARM64_TLBBATCH_H
#include <linux/cpumask.h>
struct arch_tlbflush_unmap_batch {
#ifdef CONFIG_ARM64_ERRATUM_4193714
/*
* Track CPUs that need SME DVMSync on completion of this batch.
* Otherwise, the arm64 HW can do tlb shootdown, so we don't need to
* record cpumask for sending IPI
* For arm64, HW can do TLB shootdown, so we don't need to record a
* cpumask for sending IPIs.
*/
cpumask_var_t cpumask;
#endif
};
#endif /* _ARCH_ARM64_TLBBATCH_H */

View File

@@ -82,6 +82,8 @@ static inline unsigned long get_trans_granule(void)
#ifdef CONFIG_ARM64_ERRATUM_4193714
extern cpumask_t sme_active_cpus;
void sme_do_dvmsync(const struct cpumask *mask);
static inline void sme_dvmsync(struct mm_struct *mm)
@@ -92,42 +94,12 @@ static inline void sme_dvmsync(struct mm_struct *mm)
sme_do_dvmsync(mm_cpumask(mm));
}
static inline void sme_dvmsync_add_pending(struct arch_tlbflush_unmap_batch *batch,
struct mm_struct *mm)
static inline void sme_dvmsync_batch(void)
{
if (!alternative_has_cap_unlikely(ARM64_WORKAROUND_4193714))
return;
/*
* Order the mm_cpumask() read after the hardware DVMSync.
*/
dsb(ish);
if (cpumask_empty(mm_cpumask(mm)))
return;
/*
* Allocate the batch cpumask on first use. Fall back to an immediate
* IPI for this mm in case of failure.
*/
if (!cpumask_available(batch->cpumask) &&
!zalloc_cpumask_var(&batch->cpumask, GFP_ATOMIC)) {
sme_do_dvmsync(mm_cpumask(mm));
return;
}
cpumask_or(batch->cpumask, batch->cpumask, mm_cpumask(mm));
}
static inline void sme_dvmsync_batch(struct arch_tlbflush_unmap_batch *batch)
{
if (!alternative_has_cap_unlikely(ARM64_WORKAROUND_4193714))
return;
if (!cpumask_available(batch->cpumask))
return;
sme_do_dvmsync(batch->cpumask);
cpumask_clear(batch->cpumask);
sme_do_dvmsync(&sme_active_cpus);
}
#else
@@ -135,11 +107,7 @@ static inline void sme_dvmsync_batch(struct arch_tlbflush_unmap_batch *batch)
static inline void sme_dvmsync(struct mm_struct *mm)
{
}
static inline void sme_dvmsync_add_pending(struct arch_tlbflush_unmap_batch *batch,
struct mm_struct *mm)
{
}
static inline void sme_dvmsync_batch(struct arch_tlbflush_unmap_batch *batch)
static inline void sme_dvmsync_batch(void)
{
}
@@ -285,11 +253,11 @@ static inline void __tlbi_sync_s1ish(struct mm_struct *mm)
sme_dvmsync(mm);
}
static inline void __tlbi_sync_s1ish_batch(struct arch_tlbflush_unmap_batch *batch)
static inline void __tlbi_sync_s1ish_batch(void)
{
dsb(ish);
__repeat_tlbi_sync(vale1is, 0);
sme_dvmsync_batch(batch);
sme_dvmsync_batch();
}
static inline void __tlbi_sync_s1ish_kernel(void)
@@ -434,7 +402,7 @@ static inline bool arch_tlbbatch_should_defer(struct mm_struct *mm)
*/
static inline void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch)
{
__tlbi_sync_s1ish_batch(batch);
__tlbi_sync_s1ish_batch();
}
/*
@@ -722,7 +690,6 @@ static inline void arch_tlbbatch_add_pending(struct arch_tlbflush_unmap_batch *b
__flush_tlb_range(&vma, start, end, PAGE_SIZE, 3,
TLBF_NOWALKCACHE | TLBF_NOSYNC);
sme_dvmsync_add_pending(batch, mm);
}
static inline bool __pte_flags_need_flush(ptval_t oldval, ptval_t newval)

View File

@@ -448,12 +448,14 @@ int acpi_map_cpu(acpi_handle handle, phys_cpuid_t physid, u32 apci_id,
return *pcpu;
}
set_cpu_present(*pcpu, true);
return 0;
}
EXPORT_SYMBOL(acpi_map_cpu);
int acpi_unmap_cpu(int cpu)
{
set_cpu_present(cpu, false);
return 0;
}
EXPORT_SYMBOL(acpi_unmap_cpu);

View File

@@ -1355,6 +1355,7 @@ void do_sve_acc(unsigned long esr, struct pt_regs *regs)
* SME/CME erratum handling.
*/
static cpumask_t sme_dvmsync_cpus;
cpumask_t sme_active_cpus;
/*
* These helpers are only called from non-preemptible contexts, so
@@ -1368,13 +1369,15 @@ void sme_set_active(void)
return;
cpumask_set_cpu(cpu, mm_cpumask(current->mm));
cpumask_set_cpu(cpu, &sme_active_cpus);
/*
* A subsequent (post ERET) SME access may use a stale address
* translation. On C1-Pro, a TLBI+DSB on a different CPU will wait for
* the completion of cpumask_set_cpu() above as it appears in program
* order before the SME access. The post-TLBI+DSB read of mm_cpumask()
* will lead to the IPI being issued.
* the completion of the cpumask_set_cpu() operations above as they
* appear in program order before the SME access. The post-TLBI+DSB
* read of mm_cpumask() or sme_active_cpus will lead to the IPI being
* issued.
*
* https://lore.kernel.org/r/ablEXwhfKyJW1i7l@J2N7QTR9R3
*/
@@ -1392,6 +1395,7 @@ void sme_clear_active(void)
* completed on entering EL1.
*/
cpumask_clear_cpu(cpu, mm_cpumask(current->mm));
cpumask_clear_cpu(cpu, &sme_active_cpus);
}
static void sme_dvmsync_ipi(void *unused)

View File

@@ -341,41 +341,8 @@ void flush_thread(void)
flush_gcs();
}
#ifdef CONFIG_ARM64_ERRATUM_4193714
static void arch_dup_tlbbatch_mask(struct task_struct *dst)
{
/*
* Clear the inherited cpumask with memset() to cover both cases where
* cpumask_var_t is a pointer or an array. It will be allocated lazily
* in sme_dvmsync_add_pending() if CPUMASK_OFFSTACK=y.
*/
if (alternative_has_cap_unlikely(ARM64_WORKAROUND_4193714))
memset(&dst->tlb_ubc.arch.cpumask, 0,
sizeof(dst->tlb_ubc.arch.cpumask));
}
static void arch_release_tlbbatch_mask(struct task_struct *tsk)
{
if (alternative_has_cap_unlikely(ARM64_WORKAROUND_4193714))
free_cpumask_var(tsk->tlb_ubc.arch.cpumask);
}
#else
static void arch_dup_tlbbatch_mask(struct task_struct *dst)
{
}
static void arch_release_tlbbatch_mask(struct task_struct *tsk)
{
}
#endif /* CONFIG_ARM64_ERRATUM_4193714 */
void arch_release_task_struct(struct task_struct *tsk)
{
arch_release_tlbbatch_mask(tsk);
fpsimd_release_task(tsk);
}
@@ -391,8 +358,6 @@ int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
*dst = *src;
arch_dup_tlbbatch_mask(dst);
/*
* Drop stale reference to src's sve_state and convert dst to
* non-streaming FPSIMD mode.

View File

@@ -535,23 +535,13 @@ void arch_unregister_cpu(int cpu)
{
acpi_handle acpi_handle = acpi_get_processor_handle(cpu);
struct cpu *c = &per_cpu(cpu_devices, cpu);
acpi_status status;
unsigned long long sta;
if (!acpi_handle) {
pr_err_once("Removing a CPU without associated ACPI handle\n");
return;
}
acpi_status status;
status = acpi_evaluate_integer(acpi_handle, "_STA", NULL, &sta);
if (ACPI_FAILURE(status))
return;
/* For now do not allow anything that looks like physical CPU HP */
if (cpu_present(cpu) && !(sta & ACPI_STA_DEVICE_PRESENT)) {
if (!ACPI_FAILURE(status) &&
cpu_present(cpu) && !(sta & ACPI_STA_DEVICE_PRESENT))
pr_err_once("Changing CPU present bit is not supported\n");
return;
}
unregister_cpu(c);
}
@@ -566,6 +556,11 @@ struct acpi_madt_generic_interrupt *acpi_cpu_get_madt_gicc(int cpu)
}
EXPORT_SYMBOL_GPL(acpi_cpu_get_madt_gicc);
static bool acpi_cpu_is_present(int cpu)
{
return acpi_cpu_get_madt_gicc(cpu)->flags & ACPI_MADT_ENABLED;
}
/*
* acpi_map_gic_cpu_interface - parse processor MADT entry
*
@@ -670,6 +665,10 @@ static void __init acpi_parse_and_init_cpus(void)
early_map_cpu_to_node(i, acpi_numa_get_nid(i));
}
#else
static bool acpi_cpu_is_present(int cpu)
{
return false;
}
#define acpi_parse_and_init_cpus(...) do { } while (0)
#endif
@@ -814,7 +813,8 @@ void __init smp_prepare_cpus(unsigned int max_cpus)
if (err)
continue;
set_cpu_present(cpu, true);
if (acpi_disabled || acpi_cpu_is_present(cpu))
set_cpu_present(cpu, true);
numa_store_cpu_info(cpu);
}
}

View File

@@ -1515,7 +1515,13 @@ static void unmap_hotplug_pmd_range(pud_t *pudp, unsigned long addr,
if (free_mapped) {
/* CONT blocks are not supported in the vmemmap */
WARN_ON(pmd_cont(pmd));
flush_tlb_kernel_range(addr, addr + PMD_SIZE);
/*
* Invalidating a block entry requires just
* a single overlapping TLB invalidation,
* so limit the range of the flush to a single
* page.
*/
flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
free_hotplug_page_range(pmd_page(pmd),
PMD_SIZE, altmap);
}
@@ -1545,7 +1551,8 @@ static void unmap_hotplug_pud_range(p4d_t *p4dp, unsigned long addr,
if (pud_leaf(pud)) {
pud_clear(pudp);
if (free_mapped) {
flush_tlb_kernel_range(addr, addr + PUD_SIZE);
/* See comment in unmap_hotplug_pmd_range(). */
flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
free_hotplug_page_range(pud_page(pud),
PUD_SIZE, altmap);
}

View File

@@ -1806,7 +1806,7 @@ Res0 15:8
UnsignedEnum 7:4 BWE
0b0000 NI
0b0001 FEAT_BWE
0b0002 FEAT_BWE2
0b0010 FEAT_BWE2
EndEnum
UnsignedEnum 3:0 STEP
0b0000 NI

View File

@@ -130,7 +130,7 @@ TEST(gcs_find_terminator)
* We can access a GCS via ptrace
*
* This could usefully have a fixture but note that each test is
* fork()ed into a new child whcih causes issues. Might be better to
* fork()ed into a new child which causes issues. Might be better to
* lift at least some of this out into a separate, non-harness, test
* program.
*/

View File

@@ -292,7 +292,7 @@ TEST(single_thread_different_keys)
/*
* fork() does not change keys. Only exec() does so call a worker program.
* Its only job is to sign a value and report back the resutls
* Its only job is to sign a value and report back the results
*/
TEST(exec_changed_keys)
{