Skip to contentSkip to conversation

perf: Check if HT is supported and enabled

PATCH In review Lin Ming 6 participants 15 messages Dec 27, 2010 – Jan 5, 2011 1 message not in the archive

Subsystems (2)
lore
Share & tools
lore

Lin Ming

author
#0 lore raw

PATCH 4/7 perf: Check if HT is supported and enabled

Avoid the percore allocations if HT is not supported or disabled.

---
 arch/x86/kernel/cpu/perf_event_intel.c |   44 +++++++++++++++++++++++++++----
 1 files changed, 38 insertions(+), 6 deletions(-)
arch/x86/kernel/cpu/perf_event_intel.c +38 -6
index bc4afb1..354d1de 100644
@@ -1053,9 +1053,38 @@ static __initconst const struct x86_pmu core_pmu = {
 	.event_constraints	= intel_core_event_constraints,
 };
 
+/*
+ * Check if HT is capable and enabled
+ * TBD: move it to generic place, so it can be used by others
+ */
+
+static bool ht_enabled(int cpu)
+{
+	int total_logical_processors, total_cores;
+	unsigned int eax, ebx, ecx, edx;
+
+	cpuid(1, &eax, &ebx, &ecx, &edx);
+
+	/* Bit 28 in EDX indicates if it's HT capable */
+	if (!(edx & 0x10000000))
+		return false;
+
+	total_logical_processors = (ebx >> 16) & 0xff;
+
+	ecx = 0;
+	cpuid(4, &eax, &ebx, &ecx, &edx);
+	total_cores = ((eax >> 26) & 0x3f) + 1;
+
+	/* Thread nums per core */
+	return (total_logical_processors / total_cores) > 1;
+}
+
 static int intel_pmu_cpu_prepare(int cpu)
 {
 	struct cpu_hw_events *cpuc = &per_cpu(cpu_hw_events, cpu);
+
+	if (!ht_enabled(cpu))
+		return NOTIFY_OK;
 	
 	cpuc->per_core = kzalloc_node(sizeof(struct intel_percore), 
 				      GFP_KERNEL, cpu_to_node(cpu));
@@ -1073,6 +1102,15 @@ static void intel_pmu_cpu_starting(int cpu)
 	int core_id = topology_core_id(cpu);
 	int i;
 
+	init_debug_store_on_cpu(cpu);
+	/*
+	 * Deal with CPUs that don't clear their LBRs on power-up.
+	 */
+	intel_pmu_lbr_reset();
+
+	if (!ht_enabled(cpu))
+		return;
+
 	for_each_online_cpu(i) {
 		struct intel_percore *pc = per_cpu(cpu_hw_events, i).per_core;
 
@@ -1085,12 +1123,6 @@ static void intel_pmu_cpu_starting(int cpu)
 
 	cpuc->per_core->core_id = core_id;
 	cpuc->per_core->refcnt++;
-
-	init_debug_store_on_cpu(cpu);
-	/*
-	 * Deal with CPUs that don't clear their LBRs on power-up.
-	 */
-	intel_pmu_lbr_reset();
 }
 
 static void intel_pmu_cpu_dying(int cpu)
Signature · 1 line
1.7.3
author
#2 lore raw
In reply to a message not in the archive
Quoted 33 lines · Lin Ming

Avoid the percore allocations if HT is not supported or disabled.

---
 arch/x86/kernel/cpu/perf_event_intel.c |   44 +++++++++++++++++++++++++++----
 1 files changed, 38 insertions(+), 6 deletions(-)
arch/x86/kernel/cpu/perf_event_intel.c +16 -0
index bc4afb1..354d1de 100644
@@ -1053,9 +1053,38 @@ static __initconst const struct x86_pmu core_pmu = {
       .event_constraints      = intel_core_event_constraints,
 };
 
+/*
+ * Check if HT is capable and enabled
+ * TBD: move it to generic place, so it can be used by others
+ */
+
+static bool ht_enabled(int cpu)
+{
+       int total_logical_processors, total_cores;
+       unsigned int eax, ebx, ecx, edx;
+
+       cpuid(1, &eax, &ebx, &ecx, &edx);
+
+       /* Bit 28 in EDX indicates if it's HT capable */
+       if (!(edx & 0x10000000))
+               return false;
+

arch/x86/kernel/cpu/perf_event_intel.c static __initconst const struct x86_pmu core_pmu = { · line 1081

Quoted 10 lines
arch/x86/kernel/cpu/perf_event_intel.c +10 -0
+       total_logical_processors = (ebx >> 16) & 0xff;
+
+       ecx = 0;
+       cpuid(4, &eax, &ebx, &ecx, &edx);
+       total_cores = ((eax >> 26) & 0x3f) + 1;
+
+       /* Thread nums per core */
+       return (total_logical_processors / total_cores) > 1;
+}
+

This function can be simplified as below,

static bool ht_enabled()

{
        if (!cpu_has(&boot_cpu_data, X86_FEATURE_HT))
                return false;

        return smp_num_siblings > 1;
}

But this still can't detect if HT is on or off. smp_num_siblings is always 2 even if HT is disabled in BIOS.

Any idea how to detect if HT is on or not?

Thanks,
Lin Ming

Quoted 50 lines
arch/x86/kernel/cpu/perf_event_intel.c +12 -7
 static int intel_pmu_cpu_prepare(int cpu)
 {
       struct cpu_hw_events *cpuc = &per_cpu(cpu_hw_events, cpu);
+
+       if (!ht_enabled(cpu))
+               return NOTIFY_OK;
 
       cpuc->per_core = kzalloc_node(sizeof(struct intel_percore),
                                     GFP_KERNEL, cpu_to_node(cpu));
@@ -1073,6 +1102,15 @@ static void intel_pmu_cpu_starting(int cpu)
       int core_id = topology_core_id(cpu);
       int i;
 
+       init_debug_store_on_cpu(cpu);
+       /*
+        * Deal with CPUs that don't clear their LBRs on power-up.
+        */
+       intel_pmu_lbr_reset();
+
+       if (!ht_enabled(cpu))
+               return;
+
       for_each_online_cpu(i) {
               struct intel_percore *pc = per_cpu(cpu_hw_events, i).per_core;
 
@@ -1085,12 +1123,6 @@ static void intel_pmu_cpu_starting(int cpu)
 
       cpuc->per_core->core_id = core_id;
       cpuc->per_core->refcnt++;
-
-       init_debug_store_on_cpu(cpu);
-       /*
-        * Deal with CPUs that don't clear their LBRs on power-up.
-        */
-       intel_pmu_lbr_reset();
 }
 
 static void intel_pmu_cpu_dying(int cpu)
--

1.7.3

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

#3 lore raw
Quoted 32 lines · Lin Ming
Quoted 11 lines
arch/x86/kernel/cpu/perf_event_intel.c +11 -0
+static bool ht_enabled(int cpu)
+{
+       int total_logical_processors, total_cores;
+       unsigned int eax, ebx, ecx, edx;
+
+       cpuid(1, &eax, &ebx, &ecx, &edx);
+
+       /* Bit 28 in EDX indicates if it's HT capable */
+       if (!(edx & 0x10000000))
+               return false;
+

arch/x86/kernel/cpu/perf_event_intel.c static __initconst const struct x86_pmu core_pmu = { · line 1081

Quoted 10 lines
arch/x86/kernel/cpu/perf_event_intel.c +10 -0
+       total_logical_processors = (ebx >> 16) & 0xff;
+
+       ecx = 0;
+       cpuid(4, &eax, &ebx, &ecx, &edx);
+       total_cores = ((eax >> 26) & 0x3f) + 1;
+
+       /* Thread nums per core */
+       return (total_logical_processors / total_cores) > 1;
+}
+

This function can be simplified as below,

static bool ht_enabled()

{
        if (!cpu_has(&boot_cpu_data, X86_FEATURE_HT))
                return false;

        return smp_num_siblings > 1;
}
Quoted 4 lines

But this still can't detect if HT is on or off. smp_num_siblings is always 2 even if HT is disabled in BIOS.

Any idea how to detect if HT is on or not?

Not quite sure, the intel docs aren't really clear on how the HW supports HT, has 2 siblings but BIOS disabled it thing works. I just tried reading the arch/x86 code but that only got me more confused.

hpa, could you comment?

#4 lore raw
Quoted 3 lines

Not quite sure, the intel docs aren't really clear on how the HW supports HT, has 2 siblings but BIOS disabled it thing works. I just tried reading the arch/x86 code but that only got me more confused.

... or you just keep the original code which works fine in any case without this.

-Andi

#5 lore raw
In reply to Peter Zijlstra
Quoted 14 lines · Peter Zijlstra
Quoted 14 lines

This function can be simplified as below,

static bool ht_enabled()

{
        if (!cpu_has(&boot_cpu_data, X86_FEATURE_HT))
                return false;

        return smp_num_siblings > 1;
}

But this still can't detect if HT is on or off. smp_num_siblings is always 2 even if HT is disabled in BIOS.

Quoted 8 lines
Quoted 1 line

Any idea how to detect if HT is on or not?

Not quite sure, the intel docs aren't really clear on how the HW supports HT, has 2 siblings but BIOS disabled it thing works. I just tried reading the arch/x86 code but that only got me more confused.

hpa, could you comment?

Zeroeth of all: anyone who writes () in a function prototype in C needs to get severely napalmed, maimed, hung and then really hurt. It is (void) in C, () means (...) which is literally NEVER what you want.

Now, *first* of all: smp_num_siblings as it sits is obviously broken; the whole notion of a global variable for what is inherently a per-cpu property is just braindead. At least theoretically there could be cores with and without HT or with different thread count in the same system.

Second, perf should get this from /proc/cpuinfo, not by grotting around cpuid by itself.

Third, the code in the kernel is indeed pretty confusing, and it also has the global variable braindamage... but either it works (in which case getting the data from /proc/cpuinfo works) or it needs fixing in addition to the global variable issue.

-hpa

#6 lore raw
Quoted 28 lines · H. Peter Anvin
Quoted 14 lines · Peter Zijlstra
Quoted 14 lines

This function can be simplified as below,

static bool ht_enabled()

{
        if (!cpu_has(&boot_cpu_data, X86_FEATURE_HT))
                return false;

        return smp_num_siblings > 1;
}

But this still can't detect if HT is on or off. smp_num_siblings is always 2 even if HT is disabled in BIOS.

Quoted 8 lines
Quoted 1 line

Any idea how to detect if HT is on or not?

Not quite sure, the intel docs aren't really clear on how the HW supports HT, has 2 siblings but BIOS disabled it thing works. I just tried reading the arch/x86 code but that only got me more confused.

hpa, could you comment?

Zeroeth of all: anyone who writes () in a function prototype in C needs to get severely napalmed, maimed, hung and then really hurt. It is (void) in C, () means (...) which is literally NEVER what you want.

Quoted 7 lines

Now, *first* of all: smp_num_siblings as it sits is obviously broken; the whole notion of a global variable for what is inherently a per-cpu property is just braindead. At least theoretically there could be cores with and without HT or with different thread count in the same system.

Second, perf should get this from /proc/cpuinfo, not by grotting around cpuid by itself.

Its the kernel space bit that wants to know if HT is enabled on CPU bringup, /proc/cpuinfo is kinda useless in that context.

Quoted 4 lines

Third, the code in the kernel is indeed pretty confusing, and it also has the global variable braindamage... but either it works (in which case getting the data from /proc/cpuinfo works) or it needs fixing in addition to the global variable issue.

So you failed to address the primary question, how do we tell if HT is enabled for a particular CPU?

X86_FEATURE_HT tells us the CPU supports telling us about HT, smp_num_siblings > 1 tells us the CPU is capable of HT. But Lin found that if you disable HT in the BIOS both those are true but we still don't have HT enabled.

The problem we have to address is that Intel has some MSRs shared between threads and if HT is enabled we need to allocate some memory to manage this shared resource.

The simple check outlined above X86_FEATURE_HT && smp_num_siblings > 1, will get us most of the way and will I think be good enough (false positives but no false negatives). But it did get us wondering about how all this works.

So could you, irrespective of Linux implementation details tell us how to detect if HT is available and enabled from a HW and BIOS perspective?

#7 lore raw

Hi,

I had to deal with this issue with perfmon back in 2.6.30 and earlier kernels. I remember that I was surprised not to find any easy helper function to figure this out back then. Being HT capable is different from having HT enabled. Seems like the situation has not improved since then.

My solution at the time (2.6.30) was to do:

        ht_enabled = cpumask_weight(__get_cpu_var(cpu_sibling_map)) > 1;

Not too convinced the per-cpu variable is necessary because I don't know of a BIOS that would allow you to turn on HT on only some of the cores (AFAIK, this is an all or nothing feature).

Quoted 65 lines · Peter Zijlstra
Quoted 28 lines · H. Peter Anvin
Quoted 14 lines · Peter Zijlstra
Quoted 14 lines

This function can be simplified as below,

static bool ht_enabled()
{
        if (!cpu_has(&boot_cpu_data, X86_FEATURE_HT)) return false;

        return smp_num_siblings > 1;
}

But this still can't detect if HT is on or off. smp_num_siblings is always 2 even if HT is disabled in BIOS.

Quoted 8 lines
Quoted 1 line

Any idea how to detect if HT is on or not?

Not quite sure, the intel docs aren't really clear on how the HW supports HT, has 2 siblings but BIOS disabled it thing works. I just tried reading the arch/x86 code but that only got me more confused.

hpa, could you comment?

Zeroeth of all: anyone who writes () in a function prototype in C needs to get severely napalmed, maimed, hung and then really hurt.  It is (void) in C, () means (...) which is literally NEVER what you want.

Quoted 7 lines

Now, *first* of all: smp_num_siblings as it sits is obviously broken; the whole notion of a global variable for what is inherently a per-cpu property is just braindead.  At least theoretically there could be cores with and without HT or with different thread count in the same system.

Second, perf should get this from /proc/cpuinfo, not by grotting around cpuid by itself.

Its the kernel space bit that wants to know if HT is enabled on CPU bringup, /proc/cpuinfo is kinda useless in that context.

Quoted 4 lines

Third, the code in the kernel is indeed pretty confusing, and it also has the global variable braindamage... but either it works (in which case getting the data from /proc/cpuinfo works) or it needs fixing in addition to the global variable issue.

So you failed to address the primary question, how do we tell if HT is enabled for a particular CPU?

X86_FEATURE_HT tells us the CPU supports telling us about HT, smp_num_siblings > 1 tells us the CPU is capable of HT. But Lin found that if you disable HT in the BIOS both those are true but we still don't have HT enabled.

The problem we have to address is that Intel has some MSRs shared between threads and if HT is enabled we need to allocate some memory to manage this shared resource.

The simple check outlined above X86_FEATURE_HT && smp_num_siblings > 1, will get us most of the way and will I think be good enough (false positives but no false negatives). But it did get us wondering about how all this works.

So could you, irrespective of Linux implementation details tell us how to detect if HT is available and enabled from a HW and BIOS perspective?

#8 lore raw
Quoted 2 lines · Stephane Eranian

My solution at the time (2.6.30) was to do:

        ht_enabled = cpumask_weight(__get_cpu_var(cpu_sibling_map)) > 1;

Won't that report a machine a HT disabled when you offline a sibling? Which kinda defeats the purpose of our usage here, since we need to know it before either sibling comes online.

#9 lore raw
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Stephane Eranian

My solution at the time (2.6.30) was to do:
        ht_enabled = cpumask_weight(__get_cpu_var(cpu_sibling_map)) > 1;

Won't that report a machine a HT disabled when you offline a sibling?

I think you're right. I was not dealing with hotplug CPU.

Quoted 2 lines

Which kinda defeats the purpose of our usage here, since we need to know it before either sibling comes online.

Then, it seems the only hope is to peek at a MSR that reports the BIOS setting. But I don't know which one it is.

Couldn't you simply over-provision, and then when the CPU is online, use my ht_enabled statement to figure out whether or not you need to handle the sharing issue?

#10 lore raw
Quoted 4 lines · Stephane Eranian

Couldn't you simply over-provision, and then when the CPU is online, use my ht_enabled statement to figure out whether or not you need to handle the sharing issue?

That's pretty much what Lin's latest does:

X86_FEATURE_HT && smp_num_siblings > 1

shows the CPU is capable of HT and will allocate the needed resources.

#11 lore raw
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines · Stephane Eranian

Couldn't you simply over-provision, and then when the CPU is online, use my ht_enabled statement to figure out whether or not you need to handle the sharing issue?

Quoted 6 lines

That's pretty much what Lin's latest does:

 X86_FEATURE_HT && smp_num_siblings > 1

shows the CPU is capable of HT and will allocate the needed resources.

Ok, but once the CPU is online, you can still check whether or not HT is enabled. If not, then you don't need to bother with the MSR sharing code. So you may allocate a data structure which you won't use and I think that's fine given we don't have a good way of figuring out whether HT is enabled or not.

#15 lore raw
In reply to Peter Zijlstra
Quoted 2 lines · Peter Zijlstra

X86_FEATURE_HT tells us the CPU supports telling us about HT, smp_num_siblings > 1 tells us the CPU is capable of HT.

A subtle but important distinction. Several laptops ago, I had a P4-M chip that had FEATURE_HT, but num_siblings == 1. ISTR trying to boot an SMP kernel on it and it dying gloriously trying to bring up the non-existent other sibling.

#12 lore raw
In reply to Peter Zijlstra
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines · Stephane Eranian

Couldn't you simply over-provision, and then when the CPU is online, use my ht_enabled statement to figure out whether or not you need to handle the sharing issue?

Quoted 8 lines

That's pretty much what Lin's latest does:

X86_FEATURE_HT && smp_num_siblings > 1

shows the CPU is capable of HT and will allocate the needed resources.

From the sound of it, it doesn't seem like it's worth optimizing out the

fairly small memory allocation.

-hpa

author
#13 lore raw
In reply to Stephane Eranian
Quoted 12 lines · Stephane Eranian
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Stephane Eranian

My solution at the time (2.6.30) was to do:

        ht_enabled = cpumask_weight(__get_cpu_var(cpu_sibling_map)) > 1;

Won't that report a machine a HT disabled when you offline a sibling?

I think you're right. I was not dealing with hotplug CPU.

Quoted 2 lines

Which kinda defeats the purpose of our usage here, since we need to know it before either sibling comes online.

Quoted 6 lines

Then, it seems the only hope is to peek at a MSR that reports the BIOS setting. But I don't know which one it is.

Couldn't you simply over-provision, and then when the CPU is online, use my ht_enabled statement to figure out whether or not you need to handle the sharing issue?

I got an idea from Tony,

Quoted 5 lines

If this is worth doing, then perhaps you could write a routine to check if another HT thread on this same core has already been brought online ... i.e. defer the allocation to when you see the second thread. If HT is off, then you'll never see a second thread so you'll avoid the allocation

We can't defer the allocation to CPU_UP_PREPARE notifier(intel_pmu_cpu_prepare), because the thread cpumask for that booting cpu has not been setup yet.

So below code defers the allocation to CPU_STARTING notifier(intel_pmu_cpu_starting).

static bool ht_enabled(int cpu)

{
        struct cpumask *siblings = topology_thread_cpumask(cpu);

        if (!cpu_has(&boot_cpu_data, X86_FEATURE_HT))
                return false;

        return cpus_weight(*siblings) > 1;
}

static void intel_pmu_cpu_starting(int cpu)

{
        struct cpu_hw_events *cpuc = &per_cpu(cpu_hw_events, cpu);
        struct intel_percore *pc;
        int core_id = topology_core_id(cpu);
        int i;

        init_debug_store_on_cpu(cpu);
        /*
         * Deal with CPUs that don't clear their LBRs on power-up.
         */
        intel_pmu_lbr_reset();

        if (!ht_enabled(cpu))
                return;

        cpuc->per_core = kzalloc_node(sizeof(struct intel_percore),
                                      GFP_KERNEL, cpu_to_node(cpu));
        if (!cpuc->per_core)
                return;

        raw_spin_lock_init(&cpuc->per_core->lock);
        pc = cpuc->per_core;

        for_each_cpu(i, topology_thread_cpumask(cpu)) {
                cpuc = &per_cpu(cpu_hw_events, cpu);

                cpuc->per_core = pc;
                cpuc->per_core->core_id = core_id;
                cpuc->per_core->refcnt++;
        }
}

But when unplug/plug 2 HT threads, I got a BUG.

BUG: sleeping function called from invalid context at /opt/linux-2.6/mm/slub.c:793
in_atomic(): 0, irqs_disabled(): 1, pid: 0, name: kworker/0:1
INFO: lockdep is turned off.
irq event stamp: 0
hardirqs last  enabled at (0): [<          (null)>]           (null)
hardirqs last disabled at (0): [<ffffffff8106b4f0>] copy_process+0x59d/0x11a7
softirqs last  enabled at (0): [<ffffffff8106b4f0>] copy_process+0x59d/0x11a7
softirqs last disabled at (0): [<          (null)>]           (null)
Pid: 0, comm: kworker/0:1 Tainted: G        W   2.6.37-rc7-tip-mlin+ #267
Call Trace:
 [<ffffffff81060144>] ? __might_sleep+0xe9/0xee
 [<ffffffff81113e14>] ? kmem_cache_alloc_node_notrace+0x49/0xdd
 [<ffffffff8103f24d>] ? intel_pmu_cpu_starting+0xa8/0x18b
 [<ffffffff8103f24d>] ? intel_pmu_cpu_starting+0xa8/0x18b
 [<ffffffff8104669c>] ? generic_set_all+0x25f/0x294
 [<ffffffff81735d76>] ? x86_pmu_notifier+0x4b/0x52
 [<ffffffff81742e23>] ? notifier_call_chain+0x5e/0x92
 [<ffffffff8108cc55>] ? __raw_notifier_call_chain+0x9/0xb
 [<ffffffff8106e834>] ? __cpu_notify+0x1b/0x2d
 [<ffffffff8106e854>] ? cpu_notify+0xe/0x10
 [<ffffffff81739d43>] ? notify_cpu_starting+0x24/0x26
 [<ffffffff817381b4>] ? start_secondary+0x122/0x193
#14 lore raw
Quoted 10 lines · Lin Ming

static void intel_pmu_cpu_starting(int cpu)

{
        struct cpu_hw_events *cpuc = &per_cpu(cpu_hw_events, cpu);
        struct intel_percore *pc;
        int core_id = topology_core_id(cpu);
        int i;

        init_debug_store_on_cpu(cpu);
        /*
         * Deal with CPUs that don't clear their LBRs on power-up.
Quoted 8 lines
         */
        intel_pmu_lbr_reset();

        if (!ht_enabled(cpu))
                return;

        cpuc->per_core = kzalloc_node(sizeof(struct intel_percore),
                                      GFP_KERNEL, cpu_to_node(cpu));

#define CPU_STARTING 0x000A /* CPU (unsigned)v soon running.
* Called on the new cpu, just before
* enabling interrupts. Must not sleep,
* must not fail */

Quoted 6 lines
        if (!cpuc->per_core)
                return;

        raw_spin_lock_init(&cpuc->per_core->lock);
        pc = cpuc->per_core;
Quoted 8 lines
        for_each_cpu(i, topology_thread_cpumask(cpu)) {
                cpuc = &per_cpu(cpu_hw_events, cpu);

                cpuc->per_core = pc;
                cpuc->per_core->core_id = core_id;
                cpuc->per_core->refcnt++;
        }
} 

Anyway, I think something like:

static bool ht_capable(void)

{
	return boot_cpu_has(X86_FEATURE_HT) && smp_num_siblings > 1;
}

is sufficient, the whole hotplug issues makes relying on enumeration results impractical.