Re: [PATCH] arch_topology: Support SMT control on arm64

From: Yicong Yang
Date: Thu Sep 21 2023 - 14:46:54 EST


On 2023/9/21 1:08, Dietmar Eggemann wrote:
> On 19/09/2023 14:33, Yicong Yang wrote:
>> From: Yicong Yang <yangyicong@xxxxxxxxxxxxx>
>>
>> The core CPU control framework supports runtime SMT control which
>> is not yet supported on arm64. Besides the general vulnerabilities
>> concerns we want this runtime control on our arm64 server for:
>>
>> - better single CPU performance in some cases
>> - saving overall power consumption
>>
>> This patch implements it in the following aspects:
>>
>> - implement the callbacks of the core
>
> I see only 1 function here: topology_is_primary_thread() ?

Yes. Before 6.6-rc1 there's also one function topology_smt_supported() required,
I forgot to update the comment after rebase on 6.6-rc1.

>
>> - update the SMT status after the topology enumerated on arm64
>
> That's the call init_cpu_topology()
> topology_smt_set_num_threads()
> cpu_smt_set_num_threads()
>

Yes.

>> - select HOTPLUG_SMT for arm64
>>
>> For disabling SMT we'll offline all the secondary threads and
>
> `disabling SMT` means here setting cpu_smt_control=CPU_SMT_DISABLED ?
>

Yes. The SMT control provides user interface like
`/sys/devices/system/cpu/smt/control` or cmdline option `nosmt=[force]`,
which will update cpu_smt_control = CPU_SMT_DISABLED.

>> only leave the primary thread. Since we don't have restriction
>> for primary thread selection, the first thread is chosen as the
>> primary thread in this implementation.
>>
>> Tests has been done on our ACPI based arm64 server and on
>> ACPI/OF based QEMU VMs.
>>
>> Signed-off-by: Yicong Yang <yangyicong@xxxxxxxxxxxxx>
>> ---
>> arch/arm64/Kconfig | 1 +
>> drivers/base/arch_topology.c | 63 +++++++++++++++++++++++++++++++++++
>> include/linux/arch_topology.h | 11 ++++++
>> 3 files changed, 75 insertions(+)
>>
>> diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
>> index b10515c0200b..531a71c7f499 100644
>> --- a/arch/arm64/Kconfig
>> +++ b/arch/arm64/Kconfig
>> @@ -233,6 +233,7 @@ config ARM64
>> select HAVE_KRETPROBES
>> select HAVE_GENERIC_VDSO
>> select HOTPLUG_CORE_SYNC_DEAD if HOTPLUG_CPU
>> + select HOTPLUG_SMT if SMP
>> select IRQ_DOMAIN
>> select IRQ_FORCED_THREADING
>> select KASAN_VMALLOC if KASAN
>> diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c
>> index b741b5ba82bd..75a693834fff 100644
>> --- a/drivers/base/arch_topology.c
>> +++ b/drivers/base/arch_topology.c
>> @@ -729,6 +729,63 @@ const struct cpumask *cpu_clustergroup_mask(int cpu)
>> return &cpu_topology[cpu].cluster_sibling;
>> }
>>
>> +#ifdef CONFIG_HOTPLUG_SMT
>> +static int topology_smt_num_threads = 1;
>> +
>> +void __init topology_smt_set_num_threads(void)
>> +{
>> + int cpu, sibling, threads;
>> +
>> + /*
>> + * Walk all the CPUs to find the largest thread number, in case we're
>> + * on a heterogeneous platform with only part of the CPU cores support
>> + * SMT.
>> + *
>> + * Get the thread number by checking the CPUs with same core id
>> + * rather than checking the topology_sibling_cpumask(), since the
>> + * sibling mask will not cover all the CPUs if there's CPU offline.
>> + */
>> + for_each_possible_cpu(cpu) {
>> + threads = 1;
>> +
>> + /* Invalid thread id, this CPU is not in a SMT core */
>> + if (cpu_topology[cpu].thread_id == -1)
>> + continue;
>> +
>> + for_each_possible_cpu(sibling) {
>> + if (sibling == cpu || cpu_topology[sibling].thread_id == -1)
>> + continue;
>> +
>> + if (cpu_topology[cpu].core_id == cpu_topology[sibling].core_id)
>> + threads++;
>> + }
>> +
>> + if (threads > topology_smt_num_threads)
>> + topology_smt_num_threads = threads;
>> + }
>> +
>> + /*
>> + * We don't support CONFIG_SMT_NUM_THREADS_DYNAMIC so make the
>> + * max_threads == num_threads.
>> + */
>> + cpu_smt_set_num_threads(topology_smt_num_threads, topology_smt_num_threads);
>> +}
>> +
>> +/*
>> + * On SMT Hotplug the primary thread of the SMT won't be disabled. For x86 they
>> + * seem to have a primary thread for special purpose. For other arthitectures
>> + * like arm64 there's no such restriction for a primary thread, so make the
>> + * first thread in the SMT as the primary thread.
>> + */
>> +bool topology_is_primary_thread(unsigned int cpu)
>> +{
>> + if (cpu == cpumask_first(topology_sibling_cpumask(cpu)))
>> + return true;
>> +
>> + return false;
>> +}
>> +#endif
>> +
>> void update_siblings_masks(unsigned int cpuid)
>> {
>> struct cpu_topology *cpu_topo, *cpuid_topo = &cpu_topology[cpuid];
>> @@ -841,6 +898,12 @@ void __init init_cpu_topology(void)
>> reset_cpu_topology();
>> }
>>
>> + /*
>> + * By this stage we get to know whether we support SMT or not, update
>> + * the information for the core.
>> + */
>> + topology_smt_set_num_threads();
>> +
>
> So this would be the diff between x86 and arm64:
>
> start_kernel() [init/main.c]
>
> arch_cpu_finalize_init() [arch/x86/kernel/cpu/common.c] <- x86
>
> identify_boot_cpu() [arch/x86/kernel/cpu/common.c]
>
> detect_ht() [arch/x86/kernel/cpu/common.c]
>
> detect_ht_early() [arch/x86/kernel/cpu/common.c]
>
> cpu_smt_set_num_threads(smp_num_siblings, smp_num_siblings) <- (1)
>
>
> arch_call_rest_init() [init/main.c] <- arm64
>
> rest_init() [init/main.c]
>
> kernel_init() [init/main.c]
>
> kernel_init_freeable() [init/main.c]
>
> smp_prepare_cpus() [arch/arm64/kernel/smp.c]
>
> init_cpu_topology() [drivers/base/arch_topology.c]
>
> topology_smt_set_num_threads()
>
> cpu_smt_set_num_threads(topology_smt_num_threads, topology_smt_num_threads) <- (1)
>
> [...]
>

Yes we have some differences. On arm64 the SMT information is retrieved from firmware
(ACPI/OF, parse_{acpi, dt}_topology()). So we need to update the SMT information
to the core after parsing the ACPI/OF in init_cpu_topology().

> Did some rough testing with your patch on an SMT4 Arm64 server with 256
> CPUs:
>
> (1) CPU hp out all secondaries from the thread_siblings masks
>
> for i in {32..255}; do echo 0 > /sys/devices/system/cpu/cpu$i/online; done
>
> (2) Check thread_siblings cpumasks
>
> cat /sys/devices/system/cpu/cpu*/topology/thread_siblings
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000002
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000400
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000800
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00001000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00002000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00004000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00008000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00010000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00020000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00040000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00080000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000004
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00100000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00200000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00400000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00800000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,01000000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,02000000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,04000000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,08000000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,10000000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,20000000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000008
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,40000000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,80000000
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000010
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000020
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000040
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000080
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000100
> 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000200
>
> (3) CPU hp out and out CPU31
>
> echo 0 > /sys/devices/system/cpu/cpu31/online
> echo 1 > /sys/devices/system/cpu/cpu31/online
>
> cpu_smt_control is still CPU_SMT_ENABLED in cpu_smt_allowed() so
> topology_is_primary_thread() isn't called?
>

If you manually disable SMT by offline each CPUs the cpu_smt_control will
not be updated. It'll updated when using the interface like
`/sys/devices/system/cpu/smt/control` or cmdline. By these means,
the framework will use topology_is_primary_thread() to decide which CPU
in the SMT will keep online:

// e.g. echo off > /sys/devices/system/cpu/smt/control
[ kernel/cpu.c ]
control_store()
__store_smt_control()
cpuhp_smt_disable()
for_each_online_cpu(cpu)
if (topology_is_primary_thread(cpu))
continue; <---------- will skip the primary thread
[...]
cpu_smt_control = CPU_SMT_DISABLED;

topology_is_primary_thread() checking only applies to the SMT control but
not to the CPU offline.

Thanks,
Yicong