Skip to contentSkip to conversation

perf: Implement Nehalem uncore pmu

RFC PATCH In review Lin Ming 6 participants 26 messages Dec 2, 2010 – Jan 17, 2011

Subsystems (2)
lore
Share & tools
lore

Lin Ming

author
#0 lore raw

RFC PATCH v3 2/3 perf: Implement Nehalem uncore pmu

Changelogs of v3:

- Allocate uncore data with kmalloc_node, like AMD NB stuff. (Peter Zijlstra)

- per-task uncore event is not allowed. Simply set pmu::task_ctx_nr = perf_invalid_context. (Peter Zijlstra)

- Route interrupts to the first core that accesses uncore pmu. (Stephane Eranian)

- Check CPUID signatures with boot_cpu_data. (Andi Kleen)

- Remove unneeded include files. (Andi Kleen)

For the background of Nehalem uncore pmu, see Intel SDM Volume 3B "30.6.2 Performance Monitoring Facility in the Uncore"

1. data structure

struct intel_uncore {
int id; /* uncore id */
int refcnt; /* reference count */

struct perf_event *events[X86_PMC_IDX_MAX]; /* in counter order */ unsigned long active_mask[BITS_TO_LONGS(X86_PMC_IDX_MAX)]; int n_events;
int nmi_core; /* the core to handle NMI */
struct spinlock lock;
};

struct intel_uncore is the per socket structure, which is allocated like AMD NB stuff.
"lock" protects add/delete events to uncore pmu.

2. Uncore pmu NMI handling

As suggested by Stephane and Peter, all interrupts are routed to the first core that accesses the uncore pmu.

---
 arch/x86/include/asm/msr-index.h              |    1 +
 arch/x86/include/asm/perf_event.h             |    5 +
 arch/x86/kernel/cpu/Makefile                  |    1 +
 arch/x86/kernel/cpu/perf_event.c              |    6 +-
 arch/x86/kernel/cpu/perf_event_intel_uncore.c |  543 +++++++++++++++++++++++++
 arch/x86/kernel/cpu/perf_event_intel_uncore.h |   69 ++++
 include/linux/perf_event.h                    |    1 +
 7 files changed, 621 insertions(+), 5 deletions(-)
 create mode 100644 arch/x86/kernel/cpu/perf_event_intel_uncore.c
 create mode 100644 arch/x86/kernel/cpu/perf_event_intel_uncore.h
arch/x86/include/asm/msr-index.h +1 -0
index 6b89f5e..a1cc40b 100644
@@ -81,6 +81,7 @@
 #define DEBUGCTLMSR_BTS_OFF_OS		(1UL <<  9)
 #define DEBUGCTLMSR_BTS_OFF_USR		(1UL << 10)
 #define DEBUGCTLMSR_FREEZE_LBRS_ON_PMI	(1UL << 11)
+#define DEBUGCTLMSR_ENABLE_UNCORE_PMI	(1UL << 13)
 
 #define MSR_IA32_MC0_CTL		0x00000400
 #define MSR_IA32_MC0_STATUS		0x00000401
arch/x86/include/asm/perf_event.h +5 -0
index d9d4dae..ab5d0bb 100644
@@ -75,6 +75,10 @@ union cpuid10_edx {
 	unsigned int full;
 };
 
+struct pmu_nmi_state {
+	unsigned int	marked;
+	int		handled;
+};
 
 /*
  * Fixed-purpose performance events:
@@ -126,6 +130,7 @@ union cpuid10_edx {
 
 #ifdef CONFIG_PERF_EVENTS
 extern void perf_events_lapic_init(void);
+extern void init_uncore_pmu(void);
 
 #define PERF_EVENT_INDEX_OFFSET			0
 
arch/x86/kernel/cpu/Makefile +1 -0
index 3f0ebe4..db4bf99 100644
@@ -27,6 +27,7 @@ obj-$(CONFIG_CPU_SUP_TRANSMETA_32)	+= transmeta.o
 obj-$(CONFIG_CPU_SUP_UMC_32)		+= umc.o
 
 obj-$(CONFIG_PERF_EVENTS)		+= perf_event.o
+obj-$(CONFIG_PERF_EVENTS)		+= perf_event_intel_uncore.o
 
 obj-$(CONFIG_X86_MCE)			+= mcheck/
 obj-$(CONFIG_MTRR)			+= mtrr/
arch/x86/kernel/cpu/perf_event.c +1 -5
index 7202762..243805e 100644
@@ -1210,11 +1210,6 @@ void perf_events_lapic_init(void)
 	apic_write(APIC_LVTPC, APIC_DM_NMI);
 }
 
-struct pmu_nmi_state {
-	unsigned int	marked;
-	int		handled;
-};
-
 static DEFINE_PER_CPU(struct pmu_nmi_state, pmu_nmi);
 
 static int __kprobes
@@ -1362,6 +1357,7 @@ int __init init_hw_perf_events(void)
 
 	switch (boot_cpu_data.x86_vendor) {
 	case X86_VENDOR_INTEL:
+		init_uncore_pmu();
 		err = intel_pmu_init();
 		break;
 	case X86_VENDOR_AMD:
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +543 -0

543 lines not on this page · raw message

arch/x86/kernel/cpu/perf_event_intel_uncore.h new file +69 -0

69 lines not on this page · raw message

include/linux/perf_event.h +1 -0
index adf6d99..cf2cb49 100644
@@ -32,6 +32,7 @@ enum perf_type_id {
 	PERF_TYPE_HW_CACHE			= 3,
 	PERF_TYPE_RAW				= 4,
 	PERF_TYPE_BREAKPOINT			= 5,
+	PERF_TYPE_UNCORE			= 6,
 
 	PERF_TYPE_MAX,				/* non-ABI */
 };
Signature · 1 line
1.5.3
author
#1 lore raw
Quoted 7 lines · Lin Ming

Changelogs of v3:

- Allocate uncore data with kmalloc_node, like AMD NB stuff. (Peter Zijlstra)

- per-task uncore event is not allowed. Simply set pmu::task_ctx_nr = perf_invalid_context. (Peter Zijlstra)

<snip>

Review

Quoted 9 lines
(quoted diff) +9 -0
+
+static struct pmu uncore_pmu = {
+	.event_init	= uncore_pmu_event_init,
+	.add		= uncore_pmu_add,
+	.del		= uncore_pmu_del,
+	.start		= uncore_pmu_start,
+	.stop		= uncore_pmu_stop,
+	.read		= uncore_pmu_read,
+};

Sorry, I send out an old version, need below additional code to disallow per-task uncore event.

arch/x86/kernel/cpu/perf_event_intel_uncore.c +2 -0
index d2c10d8..d2a22ba 100644
@@ -252,6 +252,8 @@ static void uncore_pmu_read(struct perf_event *event)
 }
 
 static struct pmu uncore_pmu = {
+	.task_ctx_nr	= perf_invalid_context, /* per-task uncore event is not allowed */
+
 	.event_init	= uncore_pmu_event_init,
 	.add		= uncore_pmu_add,
 	.del		= uncore_pmu_del,
author
#2 lore raw
In reply to Lin Ming
Quoted 25 lines · Lin Ming
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +25 -0
+
+static int
+uncore_perf_event_set_period(struct perf_event *event)
+{
+	struct hw_perf_event *hwc = &event->hw;
+	s64 left = local64_read(&hwc->period_left);
+	s64 period = hwc->sample_period;
+	u64 max_period = (1ULL << UNCORE_CNTVAL_BITS) - 1;
+	int ret = 0, idx = hwc->idx;
+
+	/*
+	 * If we are way outside a reasonable range then just skip forward:
+	 */
+	if (unlikely(left <= -period)) {
+		left = period;
+		local64_set(&hwc->period_left, left);
+		hwc->last_period = period;
+		ret = 1;
+	}
+
+	if (unlikely(left <= 0)) {
+		left += period;
+		local64_set(&hwc->period_left, left);
+		hwc->last_period = period;
+		ret = 1;

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 143

Quoted 10 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +10 -0
+	}
+
+	if (left > max_period)
+		left = max_period;
+
+	/*
+	 * The hw event starts counting from this event offset,
+	 * mark it to be able to extra future deltas:
+	 */
+	local64_set(&hwc->prev_count, (u64)-left);

All uncore pmu interrupts from a socket are routed to one of the four cores, so local64_set seems not correct here.

But hwc->prev_count is defined as local64_t, any idea how to set it correctly?

Or is it OK if local64_set is always executed in the same cpu?

#3 lore raw
Quoted 40 lines · Lin Ming
Quoted 25 lines · Lin Ming
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +25 -0
+
+static int
+uncore_perf_event_set_period(struct perf_event *event)
+{
+	struct hw_perf_event *hwc = &event->hw;
+	s64 left = local64_read(&hwc->period_left);
+	s64 period = hwc->sample_period;
+	u64 max_period = (1ULL << UNCORE_CNTVAL_BITS) - 1;
+	int ret = 0, idx = hwc->idx;
+
+	/*
+	 * If we are way outside a reasonable range then just skip forward:
+	 */
+	if (unlikely(left <= -period)) {
+		left = period;
+		local64_set(&hwc->period_left, left);
+		hwc->last_period = period;
+		ret = 1;
+	}
+
+	if (unlikely(left <= 0)) {
+		left += period;
+		local64_set(&hwc->period_left, left);
+		hwc->last_period = period;
+		ret = 1;

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 143

Quoted 10 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +10 -0
+	}
+
+	if (left > max_period)
+		left = max_period;
+
+	/*
+	 * The hw event starts counting from this event offset,
+	 * mark it to be able to extra future deltas:
+	 */
+	local64_set(&hwc->prev_count, (u64)-left);

All uncore pmu interrupts from a socket are routed to one of the four cores, so local64_set seems not correct here.

Quoted 4 lines

But hwc->prev_count is defined as local64_t, any idea how to set it correctly?

Or is it OK if local64_set is always executed in the same cpu?

Yes, the local_t bits work as expected when its always accessed by the same cpu.

#4 lore raw
In reply to Lin Ming

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 534

Quoted 5 lines · Lin Ming
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +5 -0
+       /* Check CPUID signatures: 06_1AH, 06_1EH, 06_1FH */
+       family = boot_cpu_data.x86;
+       model = boot_cpu_data.x86_model;
+       if (family != 6 || (model != 0x1A && model != 0x1E && model != 0x1F))
+               return; 

So that's 26, 30 and 31? Curiously arch/x86/kernel/cpu/perf_event_intel.c does have 31.

Also, could you match the style from perf_event_intel.c?

#5 lore raw
Quoted 10 lines · Peter Zijlstra
Quoted 5 lines · Lin Ming

+       /* Check CPUID signatures: 06_1AH, 06_1EH, 06_1FH */
+       family = boot_cpu_data.x86;
+       model = boot_cpu_data.x86_model;
+       if (family != 6 || (model != 0x1A && model != 0x1E && model != 0x1F))
+               return;

So that's 26, 30 and 31? Curiously arch/x86/kernel/cpu/perf_event_intel.c does have 31.

It is also missing model 44 (0x2c).

#6 lore raw
Quoted 10 lines · Stephane Eranian
Quoted 9 lines · Peter Zijlstra

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 534

Quoted 5 lines · Lin Ming
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +5 -0
+       /* Check CPUID signatures: 06_1AH, 06_1EH, 06_1FH */
+       family = boot_cpu_data.x86;
+       model = boot_cpu_data.x86_model;
+       if (family != 6 || (model != 0x1A && model != 0x1E && model != 0x1F))
+               return;

So that's 26, 30 and 31? Curiously arch/x86/kernel/cpu/perf_event_intel.c does have 31.

That was clearly meant to say: doesn't.. Does Intel have an exhaustive model list somewhere?

Quoted 1 line

It is also missing model 44 (0x2c).

Right.. but if the westmere uncore is the same, then its also missing 37.

The -EX chips have a different uncore, right?

#7 lore raw
Quoted 9 lines · Peter Zijlstra
Quoted 8 lines · Stephane Eranian
Quoted 7 lines · Peter Zijlstra
Quoted 5 lines · Lin Ming

+       /* Check CPUID signatures: 06_1AH, 06_1EH, 06_1FH */
+       family = boot_cpu_data.x86;
+       model = boot_cpu_data.x86_model;
+       if (family != 6 || (model != 0x1A && model != 0x1E && model != 0x1F))
+               return;

Quoted 8 lines
Quoted 2 lines
Quoted 2 lines

So that's 26, 30 and 31? Curiously arch/x86/kernel/cpu/perf_event_intel.c does have 31.

That was clearly meant to say: doesn't.. Does Intel have an exhaustive model list somewhere?

Quoted 1 line

It is also missing model 44 (0x2c).

Uncore logic in the same on NHM and WSM.
Events may be different but I have not see a
public document that describes that.

Quoted 3 lines

Right.. but if the westmere uncore is the same, then its also missing 37.

yes, 37 is also missing.

Quoted 2 lines

The -EX chips have a different uncore, right?

True. I don't know if the programming is completely different but it would not surprise me.
Documentation is at:
http://www.intel.com/assets/en_US/pdf/designguide/323535.pdf

#8 lore raw
In reply to Peter Zijlstra

Hi,

So I have tested this patch a bit on WSM and as I expected there are issues with sampling.

When HT is on, both siblings CPUs get the interrupt. The HW does not allow you to only point interrupts to a single HT thread (CPU).

I did verify that indeed both threads get the interrupt and that you have a race condition. Both sibling CPUs stop uncore, get the status. They may get the same overflow status. Both will pass the uncore->active_mask because it's shared among siblings cores. Thus, you have a race for the whole interrupt handler execution.

You need some serialization in there. But the patch does not address this. The problem is different from the back-to-back interrupt issue that Don worked on.
The per-cpu marked/handled trick cannot work to avoid this problem.

You cannot simply say "the lowest indexed" CPU of a sibling pair handles the interrupt
because you don't know if this in an uncore intr, core interrupt or something else. You
need to check. That means each HT thread needs to check uncore ovfl_status. IF the
status is zero, then return. Otherwise, you need to do a 2nd level check before you can
execute the handler. You need to know if the sibling CPU has already "consumed" that
interrupt.

I think you need some sort of generation counter per physical core and per HT thread.
On interrupt, you could do something along the line of:

      if (mycpu->intr_count == mysibling->intr_count) {
          then mycpu->intr_count++
          execute intr_handler()
      } else {
          mycpu->intr_count++
          return;
      }

Of course, the above needs some atomicity and ad locking (but I don't think you can
use locks in NMI context).

This makes me wonder if vectoring uncore to NMI is really needed, given you cannot
correlated to an IP, incl. a kernel IP. If we were to vector to a dedicated (lower prio)
vector, then we could use the trick of saying the lowest indexed CPU in a pair handles the interrupt (because we would already know this is an uncore interrupt).
This would be much simpler. Price: not samples in kernel's critical section. But those
are useless anyway with uncore events.

- uncore_get_status().
PERF_GLOBAL_STATUS contains more than overflow
status, bit 61-63 need to be masked off.

- uncore_pmu_cpu_prepare()
I don't understand the x86_max_cores < 2 test. If you run your NHM/WSM is single core mode, you still have uncore to deal with thus, you need cpuc->intel_uncore initialized, don't you?

- I assume that the reason the uncore->refcnt management is not using atomic ops because the whole CPU hotplug is serialized, right?

author
#9 lore raw

Thanks for your great comments.

Let me read it carefully, and then reply back.

Lin Ming

Quoted 67 lines · Stephane Eranian

Hi,

So I have tested this patch a bit on WSM and as I expected there are issues with sampling.

When HT is on, both siblings CPUs get the interrupt. The HW does not allow you to only point interrupts to a single HT thread (CPU).

I did verify that indeed both threads get the interrupt and that you have a race condition. Both sibling CPUs stop uncore, get the status. They may get the same overflow status. Both will pass the uncore->active_mask because it's shared among siblings cores. Thus, you have a race for the whole interrupt handler execution.

You need some serialization in there. But the patch does not address this. The problem is different from the back-to-back interrupt issue that Don worked on.
The per-cpu marked/handled trick cannot work to avoid this problem.

You cannot simply say "the lowest indexed" CPU of a sibling pair handles the interrupt
because you don't know if this in an uncore intr, core interrupt or something else. You
need to check. That means each HT thread needs to check uncore ovfl_status. IF the
status is zero, then return. Otherwise, you need to do a 2nd level check before you can
execute the handler. You need to know if the sibling CPU has already "consumed" that
interrupt.

I think you need some sort of generation counter per physical core and per HT thread.
On interrupt, you could do something along the line of:

      if (mycpu->intr_count == mysibling->intr_count) {
          then mycpu->intr_count++
          execute intr_handler()
      } else {
          mycpu->intr_count++
          return;
      }

Of course, the above needs some atomicity and ad locking (but I don't think you can
use locks in NMI context).

This makes me wonder if vectoring uncore to NMI is really needed, given you cannot
correlated to an IP, incl. a kernel IP. If we were to vector to a dedicated (lower prio)
vector, then we could use the trick of saying the lowest indexed CPU in a pair handles the interrupt (because we would already know this is an uncore interrupt).
This would be much simpler. Price: not samples in kernel's critical section. But those
are useless anyway with uncore events.

- uncore_get_status().
PERF_GLOBAL_STATUS contains more than overflow
status, bit 61-63 need to be masked off.

- uncore_pmu_cpu_prepare()
I don't understand the x86_max_cores < 2 test. If you run your NHM/WSM is single core mode, you still have uncore to deal with thus, you need cpuc->intel_uncore initialized, don't you?

- I assume that the reason the uncore->refcnt management is not using atomic ops because the whole CPU hotplug is serialized, right?

#10 lore raw
In reply to Stephane Eranian
Quoted 7 lines · Stephane Eranian

Hi,

So I have tested this patch a bit on WSM and as I expected there are issues with sampling.

When HT is on, both siblings CPUs get the interrupt. The HW does not allow you to only point interrupts to a single HT thread (CPU).

Egads, how ugly :/

Quoted 26 lines

I did verify that indeed both threads get the interrupt and that you have a race condition. Both sibling CPUs stop uncore, get the status. They may get the same overflow status. Both will pass the uncore->active_mask because it's shared among siblings cores. Thus, you have a race for the whole interrupt handler execution.

You need some serialization in there. But the patch does not address this. The problem is different from the back-to-back interrupt issue that Don worked on.
The per-cpu marked/handled trick cannot work to avoid this problem.

You cannot simply say "the lowest indexed" CPU of a sibling pair handles the interrupt
because you don't know if this in an uncore intr, core interrupt or something else. You
need to check. That means each HT thread needs to check uncore ovfl_status. IF the
status is zero, then return. Otherwise, you need to do a 2nd level check before you can
execute the handler. You need to know if the sibling CPU has already "consumed" that
interrupt.

I think you need some sort of generation counter per physical core and per HT thread.
On interrupt, you could do something along the line of:

Quoted 8 lines
      if (mycpu->intr_count == mysibling->intr_count) {
          then mycpu->intr_count++
          execute intr_handler()
      } else {
          mycpu->intr_count++
          return;
      }

Of course, the above needs some atomicity and ad locking

Does that guarantee that the same sibling handles all interrupts? Since a lot of the infrastructure uses local*_t we're not good with cross-cpu stuff.

Damn what a mess.. we need to serialize enough for both cpus to at least see the overflow bit.. maybe something like:

struct intel_percore {
...
atomic_t uncore_barrier;
};

void uncore_barrier(void)

{
	struct intel_percore *percore = this_cpu_ptr(cpu_hw_events)->percore;
	int armed;

	armed = atomic_cmpxchg(&percore->uncore_barrier, 0, 1) == 0;
	if (armed) {
		/* we armed, it, now wait for completion */
		while (atomic_read(&percore->uncore_barrier))
			cpu_relax();
	} else {
		/* our sibling must have, decrement it */
		if (atomic_cmpxchg(&percore->uncore_barrier, 1, 0) != 1)
			BUG();
	}
}

Then have something like:

handle_uncore_interrupt()

{
	u64 overflow = rdmsrl(MSR_UNCORE_PERF_GLOBAL_OVF_STATUS);
	int cpu;

	if (!overflow)
		return 0; /* not our interrupt to handle */

	uncore_barrier(); /* wait so our sibling will also observe the overflow */

	cpu = smp_processor_id();
	if (cpu != cpumask_first(topology_thread_cpumask(cpu)))
		return 1; /* our sibling will handle it, eat the NMI */

	/* OK, we've got an overflow and we're the first CPU in the thread mask */

	... do fancy stuff ...

	return 1; /* we handled it, eat the NMI */
}
Quoted 1 line

(but I don't think you can use locks in NMI context).

You can, as long as they're never used from !NMI, its icky, but it works.

Quoted 10 lines

This makes me wonder if vectoring uncore to NMI is really needed, given you cannot
correlated to an IP, incl. a kernel IP. If we were to vector to a dedicated (lower prio)
vector, then we could use the trick of saying the lowest indexed CPU in a pair handles the interrupt (because we would already know this is an uncore interrupt).
This would be much simpler. Price: not samples in kernel's critical section. But those
are useless anyway with uncore events.

But the uncore uses the same PMI line, right? You cannot point the uncore to another vector. /me goes find the docs -- ok, its too early in the morning to clearly parse that...

Besides, people asked for the sampling thing didn't they (also we need it to fold the count to avoid overflow on 48bit). Also the PAPI people even want per-task uncore counters because that's the only thing PAPI can do.

maintainer
#11 lore raw
Quoted 90 lines · Peter Zijlstra
Quoted 7 lines · Stephane Eranian

Hi,

So I have tested this patch a bit on WSM and as I expected there are issues with sampling.

When HT is on, both siblings CPUs get the interrupt. The HW does not allow you to only point interrupts to a single HT thread (CPU).

Egads, how ugly :/

Quoted 26 lines

I did verify that indeed both threads get the interrupt and that you have a race condition. Both sibling CPUs stop uncore, get the status. They may get the same overflow status. Both will pass the uncore->active_mask because it's shared among siblings cores. Thus, you have a race for the whole interrupt handler execution.

You need some serialization in there. But the patch does not address this. The problem is different from the back-to-back interrupt issue that Don worked on.
The per-cpu marked/handled trick cannot work to avoid this problem.

You cannot simply say "the lowest indexed" CPU of a sibling pair handles the interrupt
because you don't know if this in an uncore intr, core interrupt or something else. You
need to check. That means each HT thread needs to check uncore ovfl_status. IF the
status is zero, then return. Otherwise, you need to do a 2nd level check before you can
execute the handler. You need to know if the sibling CPU has already "consumed" that
interrupt.

I think you need some sort of generation counter per physical core and per HT thread.
On interrupt, you could do something along the line of:

Quoted 8 lines
      if (mycpu->intr_count == mysibling->intr_count) {
          then mycpu->intr_count++
          execute intr_handler()
      } else {
          mycpu->intr_count++
          return;
      }

Of course, the above needs some atomicity and ad locking

Does that guarantee that the same sibling handles all interrupts? Since a lot of the infrastructure uses local*_t we're not good with cross-cpu stuff.

Damn what a mess.. we need to serialize enough for both cpus to at least see the overflow bit.. maybe something like:

struct intel_percore {
   ...
   atomic_t uncore_barrier;
};

void uncore_barrier(void)

{
	struct intel_percore *percore = this_cpu_ptr(cpu_hw_events)->percore;
	int armed;

	armed = atomic_cmpxchg(&percore->uncore_barrier, 0, 1) == 0;
	if (armed) {
		/* we armed, it, now wait for completion */
		while (atomic_read(&percore->uncore_barrier))
			cpu_relax();
	} else {
		/* our sibling must have, decrement it */
		if (atomic_cmpxchg(&percore->uncore_barrier, 1, 0) != 1)
			BUG();
	}
}

Then have something like:

handle_uncore_interrupt()

{
	u64 overflow = rdmsrl(MSR_UNCORE_PERF_GLOBAL_OVF_STATUS);
	int cpu;

	if (!overflow)
		return 0; /* not our interrupt to handle */

	uncore_barrier(); /* wait so our sibling will also observe the overflow */

	cpu = smp_processor_id();
	if (cpu != cpumask_first(topology_thread_cpumask(cpu)))
Quoted 8 lines
		return 1; /* our sibling will handle it, eat the NMI */

	/* OK, we've got an overflow and we're the first CPU in the thread mask */

	... do fancy stuff ...

	return 1; /* we handled it, eat the NMI */
}

That would of course need to also grow some smarts to detect if there is only 1 sibling online.

CC'ed Cyrill as P4 might have something similar.

#12 lore raw
Quoted 4 lines · Peter Zijlstra
Quoted 3 lines · Peter Zijlstra
Quoted 2 lines · Stephane Eranian

Hi,

Quoted 8 lines
Quoted 8 lines
Quoted 5 lines

So I have tested this patch a bit on WSM and as I expected there are issues with sampling.

When HT is on, both siblings CPUs get the interrupt. The HW does not allow you to only point interrupts to a single HT thread (CPU).

Egads, how ugly :/

crap...
...

Quoted 6 lines

That would of course need to also grow some smarts to detect if there is only 1 sibling online.

CC'ed Cyrill as P4 might have something similar.

Yes, Don was testing p4 stuff for me and have reported this issue as well. I'll check what I can do. Thanks for CC'ing me.

Cyrill

#13 lore raw
In reply to Peter Zijlstra

Hi,

Ok, so I have an explanation for what we are seeing. In fact, what bothered me in all
of this is that I did not recall ever running into this problem of double-interrupt with HT
when I implemented the perfmon support for uncore sampling. The reason is in fact
real simple.

You are getting interrupts on both threads because you have enabled uncore PMU on all CPUs, in uncore_cpu_starting():
+ rdmsrl(MSR_IA32_DEBUGCTLMSR, val);
+ wrmsrl(MSR_IA32_DEBUGCTLMSR, val | DEBUGCTLMSR_ENABLE_UNCORE_PMI);

You need to do that only on ONE of the two siblings. In fact, you want that only ONCE
per socket. You can do this on the first CPU to use the uncore to add something a bit
more dynamic and to make sure you have some control over where the overhead is applied.
Once you do that, only one CPU/socket will get the interrupt and all will be fine.

Quoted 125 lines · Peter Zijlstra
Quoted 7 lines · Stephane Eranian

Hi,

So I have tested this patch a bit on WSM and as I expected there are issues with sampling.

When HT is on, both siblings CPUs get the interrupt. The HW does not allow you to only point interrupts to a single HT thread (CPU).

Egads, how ugly :/

Quoted 23 lines

I did verify that indeed both threads get the interrupt and that you have a race condition. Both sibling CPUs stop uncore, get the status. They may get the same overflow status. Both will pass the uncore->active_mask because it's shared among siblings cores. Thus,  you have a race for the whole interrupt handler execution.

You need some serialization in there. But the patch does not address this. The problem is different from the back-to-back interrupt issue that Don worked on.
The per-cpu marked/handled trick cannot work to avoid this problem.

You cannot simply say "the lowest indexed" CPU of a sibling pair handles the interrupt
because you don't know if this in an uncore intr, core interrupt or something else. You
need to check. That means each HT thread needs to check uncore ovfl_status. IF the
status is zero, then return. Otherwise, you need to do a 2nd level check before you can
execute the handler. You need to know if the sibling CPU has already "consumed" that
interrupt.

Quoted 11 lines

I think you need some sort of generation counter per physical core and per HT thread.
On interrupt, you could do something along the line of:
      if (mycpu->intr_count == mysibling->intr_count) { then mycpu->intr_count++
          execute intr_handler()
      } else {
          mycpu->intr_count++
          return;
      }
Of course, the above needs some atomicity and ad locking

Does that guarantee that the same sibling handles all interrupts? Since a lot of the infrastructure uses local*_t we're not good with cross-cpu stuff.

Damn what a mess.. we need to serialize enough for both cpus to at least see the overflow bit.. maybe something like:

struct intel_percore {
  ...
  atomic_t uncore_barrier;
};

void uncore_barrier(void)
{
       struct intel_percore *percore = this_cpu_ptr(cpu_hw_events)->percore; int armed;

       armed = atomic_cmpxchg(&percore->uncore_barrier, 0, 1) == 0; if (armed) {
               /* we armed, it, now wait for completion */ while (atomic_read(&percore->uncore_barrier)) cpu_relax();
       } else {
               /* our sibling must have, decrement it */
               if (atomic_cmpxchg(&percore->uncore_barrier, 1, 0) != 1) BUG();
       }
}

Then have something like:

handle_uncore_interrupt()
{
       u64 overflow = rdmsrl(MSR_UNCORE_PERF_GLOBAL_OVF_STATUS); int cpu;

       if (!overflow)
               return 0; /* not our interrupt to handle */

       uncore_barrier(); /* wait so our sibling will also observe the overflow */

       cpu = smp_processor_id();
       if (cpu != cpumask_first(topology_thread_cpumask(cpu))) return 1; /* our sibling will handle it, eat the NMI */

       /* OK, we've got an overflow and we're the first CPU in the thread mask */

       ... do fancy stuff ...

       return 1; /* we handled it, eat the NMI */
}

Quoted 1 line

(but I don't think you can use locks in NMI context).

You can, as long as they're never used from !NMI, its icky, but it works.

Quoted 10 lines

This makes me wonder if vectoring uncore to NMI is really needed, given you cannot
correlated to an IP, incl. a kernel IP. If we were to vector to a dedicated (lower prio)
vector, then we could use the trick of saying the lowest indexed CPU in a pair handles the interrupt (because we would already know this is an uncore interrupt).
This would be much simpler. Price: not samples in kernel's critical section. But those
are useless anyway with uncore events.

But the uncore uses the same PMI line, right? You cannot point the uncore to another vector. /me goes find the docs -- ok, its too early in the morning to clearly parse that...

Besides, people asked for the sampling thing didn't they (also we need it to fold the count to avoid overflow on 48bit). Also the PAPI people even want per-task uncore counters because that's the only thing PAPI can do.

author
#14 lore raw
Quoted 12 lines · Stephane Eranian

Hi,

Ok, so I have an explanation for what we are seeing. In fact, what bothered me in all
of this is that I did not recall ever running into this problem of double-interrupt with HT
when I implemented the perfmon support for uncore sampling. The reason is in fact
real simple.

You are getting interrupts on both threads because you have enabled uncore PMU on all CPUs, in uncore_cpu_starting():

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 448

Quoted 2 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +2 -0
+       rdmsrl(MSR_IA32_DEBUGCTLMSR, val);
+       wrmsrl(MSR_IA32_DEBUGCTLMSR, val | DEBUGCTLMSR_ENABLE_UNCORE_PMI);

Yeah! Thanks for the catch.

Quoted 9 lines

You need to do that only on ONE of the two siblings. In fact, you want that only ONCE
per socket. You can do this on the first CPU to use the uncore to add something a bit
more dynamic and to make sure you have some control over where the overhead is applied.
Once you do that, only one CPU/socket will get the interrupt and all will be fine.

I'll update the patches.

Thanks,
Lin Ming

Quoted 129 lines · Peter Zijlstra
Quoted 125 lines · Stephane Eranian
Quoted 7 lines

Hi,

So I have tested this patch a bit on WSM and as I expected there are issues with sampling.

When HT is on, both siblings CPUs get the interrupt. The HW does not allow you to only point interrupts to a single HT thread (CPU).

Egads, how ugly :/

Quoted 26 lines

I did verify that indeed both threads get the interrupt and that you have a race condition. Both sibling CPUs stop uncore, get the status. They may get the same overflow status. Both will pass the uncore->active_mask because it's shared among siblings cores. Thus, you have a race for the whole interrupt handler execution.

You need some serialization in there. But the patch does not address this. The problem is different from the back-to-back interrupt issue that Don worked on.
The per-cpu marked/handled trick cannot work to avoid this problem.

You cannot simply say "the lowest indexed" CPU of a sibling pair handles the interrupt
because you don't know if this in an uncore intr, core interrupt or something else. You
need to check. That means each HT thread needs to check uncore ovfl_status. IF the
status is zero, then return. Otherwise, you need to do a 2nd level check before you can
execute the handler. You need to know if the sibling CPU has already "consumed" that
interrupt.

I think you need some sort of generation counter per physical core and per HT thread.
On interrupt, you could do something along the line of:

Quoted 8 lines
      if (mycpu->intr_count == mysibling->intr_count) {
          then mycpu->intr_count++
          execute intr_handler()
      } else {
          mycpu->intr_count++
          return;
      }

Of course, the above needs some atomicity and ad locking

Does that guarantee that the same sibling handles all interrupts? Since a lot of the infrastructure uses local*_t we're not good with cross-cpu stuff.

Damn what a mess.. we need to serialize enough for both cpus to at least see the overflow bit.. maybe something like:

struct intel_percore {
...
atomic_t uncore_barrier;
};

void uncore_barrier(void)

{
       struct intel_percore *percore = this_cpu_ptr(cpu_hw_events)->percore;
       int armed;

       armed = atomic_cmpxchg(&percore->uncore_barrier, 0, 1) == 0;
       if (armed) {
               /* we armed, it, now wait for completion */
               while (atomic_read(&percore->uncore_barrier))
                       cpu_relax();
       } else {
               /* our sibling must have, decrement it */
               if (atomic_cmpxchg(&percore->uncore_barrier, 1, 0) != 1)
                       BUG();
       }
}

Then have something like:

handle_uncore_interrupt()

{
       u64 overflow = rdmsrl(MSR_UNCORE_PERF_GLOBAL_OVF_STATUS);
       int cpu;

       if (!overflow)
               return 0; /* not our interrupt to handle */

       uncore_barrier(); /* wait so our sibling will also observe the overflow */

       cpu = smp_processor_id();
       if (cpu != cpumask_first(topology_thread_cpumask(cpu)))
               return 1; /* our sibling will handle it, eat the NMI */

       /* OK, we've got an overflow and we're the first CPU in the thread mask */

       ... do fancy stuff ...

       return 1; /* we handled it, eat the NMI */
}
Quoted 1 line

(but I don't think you can use locks in NMI context).

You can, as long as they're never used from !NMI, its icky, but it works.

Quoted 10 lines

This makes me wonder if vectoring uncore to NMI is really needed, given you cannot
correlated to an IP, incl. a kernel IP. If we were to vector to a dedicated (lower prio)
vector, then we could use the trick of saying the lowest indexed CPU in a pair handles the interrupt (because we would already know this is an uncore interrupt).
This would be much simpler. Price: not samples in kernel's critical section. But those
are useless anyway with uncore events.

But the uncore uses the same PMI line, right? You cannot point the uncore to another vector. /me goes find the docs -- ok, its too early in the morning to clearly parse that...

Besides, people asked for the sampling thing didn't they (also we need it to fold the count to avoid overflow on 48bit). Also the PAPI people even want per-task uncore counters because that's the only thing PAPI can do.

#15 lore raw
Quoted 11 lines · Lin Ming
Quoted 10 lines · Stephane Eranian

Hi,

Ok, so I have an explanation for what we are seeing. In fact, what bothered me in all
of this is that I did not recall ever running into this problem of double-interrupt with HT
when I implemented the perfmon support for uncore sampling. The reason is in fact
real simple.

Quoted 7 lines
Quoted 4 lines

You are getting interrupts on both threads because you have enabled uncore PMU on all CPUs, in uncore_cpu_starting():
+       rdmsrl(MSR_IA32_DEBUGCTLMSR, val);
+       wrmsrl(MSR_IA32_DEBUGCTLMSR, val | DEBUGCTLMSR_ENABLE_UNCORE_PMI);

Yeah! Thanks for the catch.

If think there you want something like:

if (cpu == cpumask_first(topology_core_siblings(cpu)) { rdmsrl(MSR_IA32_DEBUGCTLMSR, val);
    wrmsrl(MSR_IA32_DEBUGCTLMSR, val | DEBUGCTLMSR_ENABLE_UNCORE_PMI);
}
I have verified that with something like that in place, you get only one interrupt.
But it seems there may be some initialization of counters missing somewhere because running perf stat, I got bogus counts back with the uncore fixed counter.

Quoted 147 lines
Quoted 9 lines

You need to do that only on ONE of the two siblings. In fact, you want that only ONCE
per socket. You can do this on the first CPU to  use the uncore to add something a bit
more dynamic and to make sure you have some control over where the overhead is applied.
Once you do that, only one CPU/socket will get the interrupt and all will be fine.

I'll update the patches.

Thanks,
Lin Ming

Quoted 129 lines · Peter Zijlstra
Quoted 125 lines · Stephane Eranian
Quoted 7 lines

Hi,

So I have tested this patch a bit on WSM and as I expected there are issues with sampling.

When HT is on, both siblings CPUs get the interrupt. The HW does not allow you to only point interrupts to a single HT thread (CPU).

Egads, how ugly :/

Quoted 23 lines

I did verify that indeed both threads get the interrupt and that you have a race condition. Both sibling CPUs stop uncore, get the status. They may get the same overflow status. Both will pass the uncore->active_mask because it's shared among siblings cores. Thus,  you have a race for the whole interrupt handler execution.

You need some serialization in there. But the patch does not address this. The problem is different from the back-to-back interrupt issue that Don worked on.
The per-cpu marked/handled trick cannot work to avoid this problem.

You cannot simply say "the lowest indexed" CPU of a sibling pair handles the interrupt
because you don't know if this in an uncore intr, core interrupt or something else. You
need to check. That means each HT thread needs to check uncore ovfl_status. IF the
status is zero, then return. Otherwise, you need to do a 2nd level check before you can
execute the handler. You need to know if the sibling CPU has already "consumed" that
interrupt.

Quoted 11 lines

I think you need some sort of generation counter per physical core and per HT thread.
On interrupt, you could do something along the line of:
      if (mycpu->intr_count == mysibling->intr_count) { then mycpu->intr_count++
          execute intr_handler()
      } else {
          mycpu->intr_count++
          return;
      }
Of course, the above needs some atomicity and ad locking

Does that guarantee that the same sibling handles all interrupts? Since a lot of the infrastructure uses local*_t we're not good with cross-cpu stuff.

Damn what a mess.. we need to serialize enough for both cpus to at least see the overflow bit.. maybe something like:

struct intel_percore {
  ...
  atomic_t uncore_barrier;
};

void uncore_barrier(void)
{
       struct intel_percore *percore = this_cpu_ptr(cpu_hw_events)->percore; int armed;

       armed = atomic_cmpxchg(&percore->uncore_barrier, 0, 1) == 0; if (armed) {
               /* we armed, it, now wait for completion */ while (atomic_read(&percore->uncore_barrier)) cpu_relax();
       } else {
               /* our sibling must have, decrement it */
               if (atomic_cmpxchg(&percore->uncore_barrier, 1, 0) != 1) BUG();
       }
}

Then have something like:

handle_uncore_interrupt()
{
       u64 overflow = rdmsrl(MSR_UNCORE_PERF_GLOBAL_OVF_STATUS); int cpu;

       if (!overflow)
               return 0; /* not our interrupt to handle */

       uncore_barrier(); /* wait so our sibling will also observe the overflow */

       cpu = smp_processor_id();
       if (cpu != cpumask_first(topology_thread_cpumask(cpu))) return 1; /* our sibling will handle it, eat the NMI */

       /* OK, we've got an overflow and we're the first CPU in the thread mask */

       ... do fancy stuff ...

       return 1; /* we handled it, eat the NMI */
}

Quoted 1 line

(but I don't think you can use locks in NMI context).

You can, as long as they're never used from !NMI, its icky, but it works.

Quoted 10 lines

This makes me wonder if vectoring uncore to NMI is really needed, given you cannot
correlated to an IP, incl. a kernel IP. If we were to vector to a dedicated (lower prio)
vector, then we could use the trick of saying the lowest indexed CPU in a pair handles the interrupt (because we would already know this is an uncore interrupt).
This would be much simpler. Price: not samples in kernel's critical section. But those
are useless anyway with uncore events.

But the uncore uses the same PMI line, right? You cannot point the uncore to another vector. /me goes find the docs -- ok, its too early in the morning to clearly parse that...

Besides, people asked for the sampling thing didn't they (also we need it to fold the count to avoid overflow on 48bit). Also the PAPI people even want per-task uncore counters because that's the only thing PAPI can do.

#16 lore raw
Quoted 6 lines

If think there you want something like:

if (cpu == cpumask_first(topology_core_siblings(cpu)) { rdmsrl(MSR_IA32_DEBUGCTLMSR, val);
    wrmsrl(MSR_IA32_DEBUGCTLMSR, val | DEBUGCTLMSR_ENABLE_UNCORE_PMI);
}

You have to be careful with hotplug. The code would need to be rerun on hotplug. Otherwise the interrupt may end up on a downed CPU. -Andi

#17 lore raw
Quoted 9 lines · Andi Kleen
Quoted 6 lines

If think there you want something like:

if (cpu == cpumask_first(topology_core_siblings(cpu)) { rdmsrl(MSR_IA32_DEBUGCTLMSR, val); wrmsrl(MSR_IA32_DEBUGCTLMSR, val | DEBUGCTLMSR_ENABLE_UNCORE_PMI); }

You have to be careful with hotplug. The code would need to be rerun on hotplug. Otherwise the interrupt may end up on a downed CPU.

That is true.

author
#18 lore raw
In reply to Peter Zijlstra
Quoted 11 lines · Peter Zijlstra

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 534

Quoted 5 lines · Lin Ming
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +5 -0
+       /* Check CPUID signatures: 06_1AH, 06_1EH, 06_1FH */
+       family = boot_cpu_data.x86;
+       model = boot_cpu_data.x86_model;
+       if (family != 6 || (model != 0x1A && model != 0x1E && model != 0x1F))
+               return; 

So that's 26, 30 and 31? Curiously arch/x86/kernel/cpu/perf_event_intel.c does have 31.

Also, could you match the style from perf_event_intel.c?

OK.

#19 lore raw
In reply to Lin Ming

arch/x86/kernel/cpu/perf_event.c int __init init_hw_perf_events(void) · line 1363

Quoted 10 lines · Lin Ming

+++ b/arch/x86/kernel/cpu/perf_event.c

arch/x86/kernel/cpu/perf_event.c +1 -0
@@ -1362,6 +1357,7 @@ int __init init_hw_perf_events(void)
 
        switch (boot_cpu_data.x86_vendor) {
        case X86_VENDOR_INTEL:
+               init_uncore_pmu();
                err = intel_pmu_init();
                break;
        case X86_VENDOR_AMD: 

Since you're otherwise fully separated from the regular cpu driver, could you also grow your own early_initcall() ?

author
#20 lore raw
Quoted 5 lines · Peter Zijlstra
Quoted 4 lines · Lin Ming

+++ b/arch/x86/kernel/cpu/perf_event.c

(quoted diff) +0 -0
@@ -1362,6 +1357,7 @@ int __init init_hw_perf_events(void)
 
Quoted 9 lines

arch/x86/kernel/cpu/perf_event.c int __init init_hw_perf_events(void) · line 1363

Quoted 6 lines
arch/x86/kernel/cpu/perf_event.c +1 -0
        switch (boot_cpu_data.x86_vendor) {
        case X86_VENDOR_INTEL:
+               init_uncore_pmu();
                err = intel_pmu_init();
                break;
        case X86_VENDOR_AMD: 

Since you're otherwise fully separated from the regular cpu driver, could you also grow your own early_initcall() ?

OK, will use early_initcall().

#21 lore raw
In reply to Lin Ming

Lin,

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 53

Quoted 11 lines · Lin Ming
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +11 -0
+static void uncore_pmu_enable_all(int nmi_core)
+{
+       u64 ctrl;
+
+       ctrl = ((1 << UNCORE_NUM_GENERAL_COUNTERS) - 1) | MSR_UNCORE_PERF_GLOBAL_CTRL_EN_FC0;
+
+       /* Route all interrupts to the first core that accesses uncore */
+       ctrl |= 1ULL << (48 + nmi_core);
+
+       wrmsrl(MSR_UNCORE_PERF_GLOBAL_CTRL, ctrl);
+}

Are you sure nmi_core is always between 0-3 on a 4-core system and 0-5 on a 6-core system?
In other words, is that what topology_core_id(raw_smp_processor_id()) returns?

Note that, unfortunately, I have not seen documentation that says on 6-core system
UNC_GLOBAL_CTRL has 6 interrupt target bits, but it would make sense.

Otherwise, you will get a kernel panic when you wrmsr UNC_GLOBAL_CTRL.

Quoted 6 lines

+
+       if (uncore->n_events == 1) {
+               nmi_core = topology_core_id(raw_smp_processor_id());
+               uncore->nmi_core = nmi_core;
+               uncore_pmu_enable_all(nmi_core);
+       }

author
#22 lore raw
Quoted 1 line · Stephane Eranian

Lin,

Hi, Stephane,

Sorry for late response, I'm just back from vacation.

Quoted 9 lines · Lin Ming
Quoted 7 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +7 -0
+static void uncore_pmu_enable_all(int nmi_core)
+{
+       u64 ctrl;
+
+       ctrl = ((1 << UNCORE_NUM_GENERAL_COUNTERS) - 1) | MSR_UNCORE_PERF_GLOBAL_CTRL_EN_FC0;
+
+       /* Route all interrupts to the first core that accesses uncore */
Quoted 8 lines

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 53

Quoted 4 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +4 -0
+       ctrl |= 1ULL << (48 + nmi_core);
+
+       wrmsrl(MSR_UNCORE_PERF_GLOBAL_CTRL, ctrl);
+}

Are you sure nmi_core is always between 0-3 on a 4-core system and 0-5 on a 6-core system?
In other words, is that what topology_core_id(raw_smp_processor_id()) returns?

I just have a look at a 6-core system, the core id is not 0-5

$ cat /proc/cpuinfo |grep "core id"
core id		: 0
core id		: 1
core id		: 2
core id		: 8
core id		: 9
core id		: 10

So we'd better route all the interrupts to the first core of the socket.

Thanks for the catch.
Lin Ming

Quoted 14 lines

Note that, unfortunately, I have not seen documentation that says on 6-core system
UNC_GLOBAL_CTRL has 6 interrupt target bits, but it would make sense.

Otherwise, you will get a kernel panic when you wrmsr UNC_GLOBAL_CTRL.

Quoted 6 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +6 -0
+
+       if (uncore->n_events == 1) {
+               nmi_core = topology_core_id(raw_smp_processor_id());
+               uncore->nmi_core = nmi_core;
+               uncore_pmu_enable_all(nmi_core);
+       }
#23 lore raw
Quoted 35 lines · Lin Ming
Quoted 1 line · Stephane Eranian

Lin,

Hi, Stephane,

Sorry for late response, I'm just back from vacation.

Quoted 9 lines · Lin Ming
Quoted 7 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +7 -0
+static void uncore_pmu_enable_all(int nmi_core)
+{
+       u64 ctrl;
+
+       ctrl = ((1 << UNCORE_NUM_GENERAL_COUNTERS) - 1) | MSR_UNCORE_PERF_GLOBAL_CTRL_EN_FC0;
+
+       /* Route all interrupts to the first core that accesses uncore */
Quoted 8 lines

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 53

Quoted 4 lines

+       ctrl |= 1ULL << (48 + nmi_core);

arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +3 -0
+
+       wrmsrl(MSR_UNCORE_PERF_GLOBAL_CTRL, ctrl);
+}

Are you sure nmi_core is always between 0-3 on a 4-core system and 0-5 on a 6-core system?
In other words, is that what topology_core_id(raw_smp_processor_id()) returns?

I just have a look at a 6-core system, the core id is not 0-5

$ cat /proc/cpuinfo |grep "core id"
core id         : 0
core id         : 1
core id         : 2
core id         : 8
core id         : 9
core id         : 10
Quoted 2 lines

So we'd better route all the interrupts to the first core of the socket.

I recently realized the issue with 0,1,2,8,9,10. At the time I wrote the perfmon support for uncore, those systems did not exist. Sparse APIC id is a major pain for uncore PMU interrupt routine given the way UNC_GLOBAL_CTRL works.

Unfortunately, routing to core 0 (core_cpu_id=0) won't be enough in the presence of HOTPLUG CPU. Imagine I disable the first three 3 cores. Now you the cpu you have to play with are 8,9,10. You need to remap to a number between 0-5.

Quoted 20 lines

Thanks for the catch.
Lin Ming

Quoted 14 lines

Note that, unfortunately, I have not seen documentation that says on 6-core system
UNC_GLOBAL_CTRL has 6 interrupt target bits, but it would make sense.

Otherwise, you will get a kernel panic when you wrmsr UNC_GLOBAL_CTRL.

Quoted 6 lines

+
+       if (uncore->n_events == 1) {
+               nmi_core = topology_core_id(raw_smp_processor_id());
+               uncore->nmi_core = nmi_core;
+               uncore_pmu_enable_all(nmi_core);
+       }

author
#24 lore raw
Quoted 38 lines · Stephane Eranian
Quoted 35 lines · Lin Ming
Quoted 1 line · Stephane Eranian

Lin,

Hi, Stephane,

Sorry for late response, I'm just back from vacation.

Quoted 9 lines · Lin Ming
Quoted 7 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +7 -0
+static void uncore_pmu_enable_all(int nmi_core)
+{
+       u64 ctrl;
+
+       ctrl = ((1 << UNCORE_NUM_GENERAL_COUNTERS) - 1) | MSR_UNCORE_PERF_GLOBAL_CTRL_EN_FC0;
+
+       /* Route all interrupts to the first core that accesses uncore */
Quoted 8 lines

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 53

Quoted 4 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +4 -0
+       ctrl |= 1ULL << (48 + nmi_core);
+
+       wrmsrl(MSR_UNCORE_PERF_GLOBAL_CTRL, ctrl);
+}

Are you sure nmi_core is always between 0-3 on a 4-core system and 0-5 on a 6-core system?
In other words, is that what topology_core_id(raw_smp_processor_id()) returns?

I just have a look at a 6-core system, the core id is not 0-5

$ cat /proc/cpuinfo |grep "core id"
core id         : 0
core id         : 1
core id         : 2
core id         : 8
core id         : 9
core id         : 10
Quoted 2 lines

So we'd better route all the interrupts to the first core of the socket.

Quoted 8 lines

I recently realized the issue with 0,1,2,8,9,10. At the time I wrote the perfmon support for uncore, those systems did not exist. Sparse APIC id is a major pain for uncore PMU interrupt routine given the way UNC_GLOBAL_CTRL works.

Unfortunately, routing to core 0 (core_cpu_id=0) won't be enough in the presence of HOTPLUG CPU. Imagine I disable the first three 3 cores. Now you the cpu you have to play with are 8,9,10. You need to remap to a number between 0-5.

Good idea.

And I have confirmed that there are 6 interrupt target bits on my 6-core Westmere machine, although documentation does not say this.

u64 val=0x3FULL << 48;
wrmsrl(MSR_UNCORE_PERF_GLOBAL_CTRL, val);

Thanks,
Lin Ming

Quoted 22 lines
Quoted 22 lines

Thanks for the catch.
Lin Ming

Quoted 14 lines

Note that, unfortunately, I have not seen documentation that says on 6-core system
UNC_GLOBAL_CTRL has 6 interrupt target bits, but it would make sense.

Otherwise, you will get a kernel panic when you wrmsr UNC_GLOBAL_CTRL.

Quoted 6 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +6 -0
+
+       if (uncore->n_events == 1) {
+               nmi_core = topology_core_id(raw_smp_processor_id());
+               uncore->nmi_core = nmi_core;
+               uncore_pmu_enable_all(nmi_core);
+       }
#25 lore raw
Quoted 48 lines · Lin Ming
Quoted 38 lines · Stephane Eranian
Quoted 35 lines · Lin Ming
Quoted 1 line · Stephane Eranian

Lin,

Hi, Stephane,

Sorry for late response, I'm just back from vacation.

Quoted 9 lines · Lin Ming
Quoted 7 lines
arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +7 -0
+static void uncore_pmu_enable_all(int nmi_core)
+{
+       u64 ctrl;
+
+       ctrl = ((1 << UNCORE_NUM_GENERAL_COUNTERS) - 1) | MSR_UNCORE_PERF_GLOBAL_CTRL_EN_FC0;
+
+       /* Route all interrupts to the first core that accesses uncore */
Quoted 8 lines

arch/x86/kernel/cpu/perf_event_intel_uncore.c line 53

Quoted 4 lines

+       ctrl |= 1ULL << (48 + nmi_core);

arch/x86/kernel/cpu/perf_event_intel_uncore.c new file +3 -0
+
+       wrmsrl(MSR_UNCORE_PERF_GLOBAL_CTRL, ctrl);
+}

Are you sure nmi_core is always between 0-3 on a 4-core system and 0-5 on a 6-core system?
In other words, is that what topology_core_id(raw_smp_processor_id()) returns?

I just have a look at a 6-core system, the core id is not 0-5

$ cat /proc/cpuinfo |grep "core id"
core id         : 0
core id         : 1
core id         : 2
core id         : 8
core id         : 9
core id         : 10
Quoted 2 lines

So we'd better route all the interrupts to the first core of the socket.

Quoted 8 lines

I recently realized the issue with 0,1,2,8,9,10. At the time I wrote the perfmon support for uncore, those systems did not exist. Sparse APIC id is a major pain for uncore PMU interrupt routine given the way UNC_GLOBAL_CTRL works.

Unfortunately, routing to core 0 (core_cpu_id=0) won't be enough in the presence of HOTPLUG CPU. Imagine I disable the first three 3 cores. Now you the cpu you have to play with are 8,9,10. You need to remap to a number between 0-5.

Quoted 8 lines

Good idea.

And I have confirmed that there are 6 interrupt target bits on my 6-core Westmere machine, although documentation does not say this.

u64 val=0x3FULL << 48;
wrmsrl(MSR_UNCORE_PERF_GLOBAL_CTRL, val);

Yes, at least, this part looks good ;->

Quoted 28 lines

Thanks,
Lin Ming

Quoted 22 lines
Quoted 22 lines

Thanks for the catch.
Lin Ming

Quoted 14 lines

Note that, unfortunately, I have not seen documentation that says on 6-core system
UNC_GLOBAL_CTRL has 6 interrupt target bits, but it would make sense.

Otherwise, you will get a kernel panic when you wrmsr UNC_GLOBAL_CTRL.

Quoted 6 lines

+
+       if (uncore->n_events == 1) {
+               nmi_core = topology_core_id(raw_smp_processor_id());
+               uncore->nmi_core = nmi_core;
+               uncore_pmu_enable_all(nmi_core);
+       }